forked from Minki/linux
drm/sun4i: Add LVDS support
The TCON supports the LVDS interface to output to a panel or a bridge. Let's add support for it. Reviewed-by: Chen-Yu Tsai <wens@csie.org> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Link: https://patchwork.freedesktop.org/patch/msgid/7fbb85f33ee1d5009fde4f0d7d236e11ca58b114.1513854122.git-series.maxime.ripard@free-electrons.com
This commit is contained in:
parent
ec08d5966b
commit
a0c1214e47
@ -15,6 +15,7 @@ sun8i-mixer-y += sun8i_mixer.o sun8i_ui_layer.o \
|
||||
|
||||
sun4i-tcon-y += sun4i_crtc.o
|
||||
sun4i-tcon-y += sun4i_dotclock.o
|
||||
sun4i-tcon-y += sun4i_lvds.o
|
||||
sun4i-tcon-y += sun4i_tcon.o
|
||||
sun4i-tcon-y += sun4i_rgb.o
|
||||
|
||||
|
177
drivers/gpu/drm/sun4i/sun4i_lvds.c
Normal file
177
drivers/gpu/drm/sun4i/sun4i_lvds.c
Normal file
@ -0,0 +1,177 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2017 Free Electrons
|
||||
* Maxime Ripard <maxime.ripard@free-electrons.com>
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include "sun4i_crtc.h"
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sun4i_lvds.h"
|
||||
|
||||
struct sun4i_lvds {
|
||||
struct drm_connector connector;
|
||||
struct drm_encoder encoder;
|
||||
|
||||
struct sun4i_tcon *tcon;
|
||||
};
|
||||
|
||||
static inline struct sun4i_lvds *
|
||||
drm_connector_to_sun4i_lvds(struct drm_connector *connector)
|
||||
{
|
||||
return container_of(connector, struct sun4i_lvds,
|
||||
connector);
|
||||
}
|
||||
|
||||
static inline struct sun4i_lvds *
|
||||
drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder)
|
||||
{
|
||||
return container_of(encoder, struct sun4i_lvds,
|
||||
encoder);
|
||||
}
|
||||
|
||||
static int sun4i_lvds_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct sun4i_lvds *lvds =
|
||||
drm_connector_to_sun4i_lvds(connector);
|
||||
struct sun4i_tcon *tcon = lvds->tcon;
|
||||
|
||||
return drm_panel_get_modes(tcon->panel);
|
||||
}
|
||||
|
||||
static struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = {
|
||||
.get_modes = sun4i_lvds_get_modes,
|
||||
};
|
||||
|
||||
static void
|
||||
sun4i_lvds_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct sun4i_lvds *lvds = drm_connector_to_sun4i_lvds(connector);
|
||||
struct sun4i_tcon *tcon = lvds->tcon;
|
||||
|
||||
drm_panel_detach(tcon->panel);
|
||||
drm_connector_cleanup(connector);
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs sun4i_lvds_con_funcs = {
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = sun4i_lvds_connector_destroy,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder);
|
||||
struct sun4i_tcon *tcon = lvds->tcon;
|
||||
|
||||
DRM_DEBUG_DRIVER("Enabling LVDS output\n");
|
||||
|
||||
if (!IS_ERR(tcon->panel)) {
|
||||
drm_panel_prepare(tcon->panel);
|
||||
drm_panel_enable(tcon->panel);
|
||||
}
|
||||
}
|
||||
|
||||
static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder);
|
||||
struct sun4i_tcon *tcon = lvds->tcon;
|
||||
|
||||
DRM_DEBUG_DRIVER("Disabling LVDS output\n");
|
||||
|
||||
if (!IS_ERR(tcon->panel)) {
|
||||
drm_panel_disable(tcon->panel);
|
||||
drm_panel_unprepare(tcon->panel);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = {
|
||||
.disable = sun4i_lvds_encoder_disable,
|
||||
.enable = sun4i_lvds_encoder_enable,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = {
|
||||
.destroy = drm_encoder_cleanup,
|
||||
};
|
||||
|
||||
int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon)
|
||||
{
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_bridge *bridge;
|
||||
struct sun4i_lvds *lvds;
|
||||
int ret;
|
||||
|
||||
lvds = devm_kzalloc(drm->dev, sizeof(*lvds), GFP_KERNEL);
|
||||
if (!lvds)
|
||||
return -ENOMEM;
|
||||
lvds->tcon = tcon;
|
||||
encoder = &lvds->encoder;
|
||||
|
||||
ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0,
|
||||
&tcon->panel, &bridge);
|
||||
if (ret) {
|
||||
dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
drm_encoder_helper_add(&lvds->encoder,
|
||||
&sun4i_lvds_enc_helper_funcs);
|
||||
ret = drm_encoder_init(drm,
|
||||
&lvds->encoder,
|
||||
&sun4i_lvds_enc_funcs,
|
||||
DRM_MODE_ENCODER_LVDS,
|
||||
NULL);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "Couldn't initialise the lvds encoder\n");
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
/* The LVDS encoder can only work with the TCON channel 0 */
|
||||
lvds->encoder.possible_crtcs = BIT(drm_crtc_index(&tcon->crtc->crtc));
|
||||
|
||||
if (tcon->panel) {
|
||||
drm_connector_helper_add(&lvds->connector,
|
||||
&sun4i_lvds_con_helper_funcs);
|
||||
ret = drm_connector_init(drm, &lvds->connector,
|
||||
&sun4i_lvds_con_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "Couldn't initialise the lvds connector\n");
|
||||
goto err_cleanup_connector;
|
||||
}
|
||||
|
||||
drm_mode_connector_attach_encoder(&lvds->connector,
|
||||
&lvds->encoder);
|
||||
|
||||
ret = drm_panel_attach(tcon->panel, &lvds->connector);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "Couldn't attach our panel\n");
|
||||
goto err_cleanup_connector;
|
||||
}
|
||||
}
|
||||
|
||||
if (bridge) {
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "Couldn't attach our bridge\n");
|
||||
goto err_cleanup_connector;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_cleanup_connector:
|
||||
drm_encoder_cleanup(&lvds->encoder);
|
||||
err_out:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_lvds_init);
|
12
drivers/gpu/drm/sun4i/sun4i_lvds.h
Normal file
12
drivers/gpu/drm/sun4i/sun4i_lvds.h
Normal file
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2017 Free Electrons
|
||||
* Maxime Ripard <maxime.ripard@free-electrons.com>
|
||||
*/
|
||||
|
||||
#ifndef _SUN4I_LVDS_H_
|
||||
#define _SUN4I_LVDS_H_
|
||||
|
||||
int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon);
|
||||
|
||||
#endif /* _SUN4I_LVDS_H_ */
|
@ -31,10 +31,52 @@
|
||||
#include "sun4i_crtc.h"
|
||||
#include "sun4i_dotclock.h"
|
||||
#include "sun4i_drv.h"
|
||||
#include "sun4i_lvds.h"
|
||||
#include "sun4i_rgb.h"
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sunxi_engine.h"
|
||||
|
||||
static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder)
|
||||
{
|
||||
struct drm_connector *connector;
|
||||
struct drm_connector_list_iter iter;
|
||||
|
||||
drm_connector_list_iter_begin(encoder->dev, &iter);
|
||||
drm_for_each_connector_iter(connector, &iter)
|
||||
if (connector->encoder == encoder) {
|
||||
drm_connector_list_iter_end(&iter);
|
||||
return connector;
|
||||
}
|
||||
drm_connector_list_iter_end(&iter);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int sun4i_tcon_get_pixel_depth(const struct drm_encoder *encoder)
|
||||
{
|
||||
struct drm_connector *connector;
|
||||
struct drm_display_info *info;
|
||||
|
||||
connector = sun4i_tcon_get_connector(encoder);
|
||||
if (!connector)
|
||||
return -EINVAL;
|
||||
|
||||
info = &connector->display_info;
|
||||
if (info->num_bus_formats != 1)
|
||||
return -EINVAL;
|
||||
|
||||
switch (info->bus_formats[0]) {
|
||||
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
|
||||
return 18;
|
||||
|
||||
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
|
||||
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
|
||||
return 24;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel,
|
||||
bool enabled)
|
||||
{
|
||||
@ -65,13 +107,63 @@ static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel,
|
||||
clk_disable_unprepare(clk);
|
||||
}
|
||||
|
||||
static void sun4i_tcon_lvds_set_status(struct sun4i_tcon *tcon,
|
||||
const struct drm_encoder *encoder,
|
||||
bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
u8 val;
|
||||
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG,
|
||||
SUN4I_TCON0_LVDS_IF_EN,
|
||||
SUN4I_TCON0_LVDS_IF_EN);
|
||||
|
||||
/*
|
||||
* As their name suggest, these values only apply to the A31
|
||||
* and later SoCs. We'll have to rework this when merging
|
||||
* support for the older SoCs.
|
||||
*/
|
||||
regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
|
||||
SUN6I_TCON0_LVDS_ANA0_C(2) |
|
||||
SUN6I_TCON0_LVDS_ANA0_V(3) |
|
||||
SUN6I_TCON0_LVDS_ANA0_PD(2) |
|
||||
SUN6I_TCON0_LVDS_ANA0_EN_LDO);
|
||||
udelay(2);
|
||||
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
|
||||
SUN6I_TCON0_LVDS_ANA0_EN_MB,
|
||||
SUN6I_TCON0_LVDS_ANA0_EN_MB);
|
||||
udelay(2);
|
||||
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
|
||||
SUN6I_TCON0_LVDS_ANA0_EN_DRVC,
|
||||
SUN6I_TCON0_LVDS_ANA0_EN_DRVC);
|
||||
|
||||
if (sun4i_tcon_get_pixel_depth(encoder) == 18)
|
||||
val = 7;
|
||||
else
|
||||
val = 0xf;
|
||||
|
||||
regmap_write_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
|
||||
SUN6I_TCON0_LVDS_ANA0_EN_DRVD(0xf),
|
||||
SUN6I_TCON0_LVDS_ANA0_EN_DRVD(val));
|
||||
} else {
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG,
|
||||
SUN4I_TCON0_LVDS_IF_EN, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
|
||||
const struct drm_encoder *encoder,
|
||||
bool enabled)
|
||||
{
|
||||
bool is_lvds = false;
|
||||
int channel;
|
||||
|
||||
switch (encoder->encoder_type) {
|
||||
case DRM_MODE_ENCODER_LVDS:
|
||||
is_lvds = true;
|
||||
/* Fallthrough */
|
||||
case DRM_MODE_ENCODER_NONE:
|
||||
channel = 0;
|
||||
break;
|
||||
@ -84,10 +176,16 @@ void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_lvds && !enabled)
|
||||
sun4i_tcon_lvds_set_status(tcon, encoder, false);
|
||||
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
|
||||
SUN4I_TCON_GCTL_TCON_ENABLE,
|
||||
enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0);
|
||||
|
||||
if (is_lvds && enabled)
|
||||
sun4i_tcon_lvds_set_status(tcon, encoder, true);
|
||||
|
||||
sun4i_tcon_channel_set_status(tcon, channel, enabled);
|
||||
}
|
||||
|
||||
@ -170,6 +268,75 @@ static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon,
|
||||
SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay));
|
||||
}
|
||||
|
||||
static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon,
|
||||
const struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
unsigned int bp;
|
||||
u8 clk_delay;
|
||||
u32 reg, val = 0;
|
||||
|
||||
tcon->dclk_min_div = 7;
|
||||
tcon->dclk_max_div = 7;
|
||||
sun4i_tcon0_mode_set_common(tcon, mode);
|
||||
|
||||
/* Adjust clock delay */
|
||||
clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
|
||||
SUN4I_TCON0_CTL_CLK_DELAY_MASK,
|
||||
SUN4I_TCON0_CTL_CLK_DELAY(clk_delay));
|
||||
|
||||
/*
|
||||
* This is called a backporch in the register documentation,
|
||||
* but it really is the back porch + hsync
|
||||
*/
|
||||
bp = mode->crtc_htotal - mode->crtc_hsync_start;
|
||||
DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
|
||||
mode->crtc_htotal, bp);
|
||||
|
||||
/* Set horizontal display timings */
|
||||
regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG,
|
||||
SUN4I_TCON0_BASIC1_H_TOTAL(mode->htotal) |
|
||||
SUN4I_TCON0_BASIC1_H_BACKPORCH(bp));
|
||||
|
||||
/*
|
||||
* This is called a backporch in the register documentation,
|
||||
* but it really is the back porch + hsync
|
||||
*/
|
||||
bp = mode->crtc_vtotal - mode->crtc_vsync_start;
|
||||
DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
|
||||
mode->crtc_vtotal, bp);
|
||||
|
||||
/* Set vertical display timings */
|
||||
regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG,
|
||||
SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) |
|
||||
SUN4I_TCON0_BASIC2_V_BACKPORCH(bp));
|
||||
|
||||
reg = SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0 |
|
||||
SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL |
|
||||
SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL;
|
||||
if (sun4i_tcon_get_pixel_depth(encoder) == 24)
|
||||
reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS;
|
||||
else
|
||||
reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS;
|
||||
|
||||
regmap_write(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, reg);
|
||||
|
||||
/* Setup the polarity of the various signals */
|
||||
if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
|
||||
val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE;
|
||||
|
||||
if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
|
||||
val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE;
|
||||
|
||||
regmap_write(tcon->regs, SUN4I_TCON0_IO_POL_REG, val);
|
||||
|
||||
/* Map output pins to channel 0 */
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
|
||||
SUN4I_TCON_GCTL_IOMAP_MASK,
|
||||
SUN4I_TCON_GCTL_IOMAP_TCON0);
|
||||
}
|
||||
|
||||
static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
@ -336,6 +503,9 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
switch (encoder->encoder_type) {
|
||||
case DRM_MODE_ENCODER_LVDS:
|
||||
sun4i_tcon0_mode_set_lvds(tcon, encoder, mode);
|
||||
break;
|
||||
case DRM_MODE_ENCODER_NONE:
|
||||
sun4i_tcon0_mode_set_rgb(tcon, mode);
|
||||
sun4i_tcon_set_mux(tcon, 0, encoder);
|
||||
@ -667,7 +837,9 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
|
||||
struct drm_device *drm = data;
|
||||
struct sun4i_drv *drv = drm->dev_private;
|
||||
struct sunxi_engine *engine;
|
||||
struct device_node *remote;
|
||||
struct sun4i_tcon *tcon;
|
||||
bool has_lvds_rst, has_lvds_alt, can_lvds;
|
||||
int ret;
|
||||
|
||||
engine = sun4i_tcon_find_engine(drv, dev->of_node);
|
||||
@ -698,6 +870,54 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This can only be made optional since we've had DT nodes
|
||||
* without the LVDS reset properties.
|
||||
*
|
||||
* If the property is missing, just disable LVDS, and print a
|
||||
* warning.
|
||||
*/
|
||||
tcon->lvds_rst = devm_reset_control_get_optional(dev, "lvds");
|
||||
if (IS_ERR(tcon->lvds_rst)) {
|
||||
dev_err(dev, "Couldn't get our reset line\n");
|
||||
return PTR_ERR(tcon->lvds_rst);
|
||||
} else if (tcon->lvds_rst) {
|
||||
has_lvds_rst = true;
|
||||
reset_control_reset(tcon->lvds_rst);
|
||||
} else {
|
||||
has_lvds_rst = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This can only be made optional since we've had DT nodes
|
||||
* without the LVDS reset properties.
|
||||
*
|
||||
* If the property is missing, just disable LVDS, and print a
|
||||
* warning.
|
||||
*/
|
||||
if (tcon->quirks->has_lvds_alt) {
|
||||
tcon->lvds_pll = devm_clk_get(dev, "lvds-alt");
|
||||
if (IS_ERR(tcon->lvds_pll)) {
|
||||
if (PTR_ERR(tcon->lvds_pll) == -ENOENT) {
|
||||
has_lvds_alt = false;
|
||||
} else {
|
||||
dev_err(dev, "Couldn't get the LVDS PLL\n");
|
||||
return PTR_ERR(tcon->lvds_rst);
|
||||
}
|
||||
} else {
|
||||
has_lvds_alt = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_lvds_rst || (tcon->quirks->has_lvds_alt && !has_lvds_alt)) {
|
||||
dev_warn(dev,
|
||||
"Missing LVDS properties, Please upgrade your DT\n");
|
||||
dev_warn(dev, "LVDS output disabled\n");
|
||||
can_lvds = false;
|
||||
} else {
|
||||
can_lvds = true;
|
||||
}
|
||||
|
||||
ret = sun4i_tcon_init_clocks(dev, tcon);
|
||||
if (ret) {
|
||||
dev_err(dev, "Couldn't init our TCON clocks\n");
|
||||
@ -729,7 +949,21 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
|
||||
goto err_free_clocks;
|
||||
}
|
||||
|
||||
ret = sun4i_rgb_init(drm, tcon);
|
||||
/*
|
||||
* If we have an LVDS panel connected to the TCON, we should
|
||||
* just probe the LVDS connector. Otherwise, just probe RGB as
|
||||
* we used to.
|
||||
*/
|
||||
remote = of_graph_get_remote_node(dev->of_node, 1, 0);
|
||||
if (of_device_is_compatible(remote, "panel-lvds"))
|
||||
if (can_lvds)
|
||||
ret = sun4i_lvds_init(drm, tcon);
|
||||
else
|
||||
ret = -EINVAL;
|
||||
else
|
||||
ret = sun4i_rgb_init(drm, tcon);
|
||||
of_node_put(remote);
|
||||
|
||||
if (ret < 0)
|
||||
goto err_free_clocks;
|
||||
|
||||
@ -879,6 +1113,7 @@ static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
|
||||
|
||||
static const struct sun4i_tcon_quirks sun6i_a31_quirks = {
|
||||
.has_channel_1 = true,
|
||||
.has_lvds_alt = true,
|
||||
.needs_de_be_mux = true,
|
||||
.set_mux = sun6i_tcon_set_mux,
|
||||
};
|
||||
@ -895,7 +1130,7 @@ static const struct sun4i_tcon_quirks sun7i_a20_quirks = {
|
||||
};
|
||||
|
||||
static const struct sun4i_tcon_quirks sun8i_a33_quirks = {
|
||||
/* nothing is supported */
|
||||
.has_lvds_alt = true,
|
||||
};
|
||||
|
||||
static const struct sun4i_tcon_quirks sun8i_v3s_quirks = {
|
||||
|
@ -70,7 +70,21 @@
|
||||
#define SUN4I_TCON0_TTL2_REG 0x78
|
||||
#define SUN4I_TCON0_TTL3_REG 0x7c
|
||||
#define SUN4I_TCON0_TTL4_REG 0x80
|
||||
|
||||
#define SUN4I_TCON0_LVDS_IF_REG 0x84
|
||||
#define SUN4I_TCON0_LVDS_IF_EN BIT(31)
|
||||
#define SUN4I_TCON0_LVDS_IF_BITWIDTH_MASK BIT(26)
|
||||
#define SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS (1 << 26)
|
||||
#define SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS (0 << 26)
|
||||
#define SUN4I_TCON0_LVDS_IF_CLK_SEL_MASK BIT(20)
|
||||
#define SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0 (1 << 20)
|
||||
#define SUN4I_TCON0_LVDS_IF_CLK_POL_MASK BIT(4)
|
||||
#define SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL (1 << 4)
|
||||
#define SUN4I_TCON0_LVDS_IF_CLK_POL_INV (0 << 4)
|
||||
#define SUN4I_TCON0_LVDS_IF_DATA_POL_MASK GENMASK(3, 0)
|
||||
#define SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL (0xf)
|
||||
#define SUN4I_TCON0_LVDS_IF_DATA_POL_INV (0)
|
||||
|
||||
#define SUN4I_TCON0_IO_POL_REG 0x88
|
||||
#define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase) ((phase & 3) << 28)
|
||||
#define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE BIT(25)
|
||||
@ -131,6 +145,16 @@
|
||||
#define SUN4I_TCON_CEU_RANGE_G_REG 0x144
|
||||
#define SUN4I_TCON_CEU_RANGE_B_REG 0x148
|
||||
#define SUN4I_TCON_MUX_CTRL_REG 0x200
|
||||
|
||||
#define SUN4I_TCON0_LVDS_ANA0_REG 0x220
|
||||
#define SUN6I_TCON0_LVDS_ANA0_EN_MB BIT(31)
|
||||
#define SUN6I_TCON0_LVDS_ANA0_EN_LDO BIT(30)
|
||||
#define SUN6I_TCON0_LVDS_ANA0_EN_DRVC BIT(24)
|
||||
#define SUN6I_TCON0_LVDS_ANA0_EN_DRVD(x) (((x) & 0xf) << 20)
|
||||
#define SUN6I_TCON0_LVDS_ANA0_C(x) (((x) & 3) << 17)
|
||||
#define SUN6I_TCON0_LVDS_ANA0_V(x) (((x) & 3) << 8)
|
||||
#define SUN6I_TCON0_LVDS_ANA0_PD(x) (((x) & 3) << 4)
|
||||
|
||||
#define SUN4I_TCON1_FILL_CTL_REG 0x300
|
||||
#define SUN4I_TCON1_FILL_BEG0_REG 0x304
|
||||
#define SUN4I_TCON1_FILL_END0_REG 0x308
|
||||
@ -149,6 +173,7 @@ struct sun4i_tcon;
|
||||
|
||||
struct sun4i_tcon_quirks {
|
||||
bool has_channel_1; /* a33 does not have channel 1 */
|
||||
bool has_lvds_alt; /* Does the LVDS clock have a parent other than the TCON clock? */
|
||||
bool needs_de_be_mux; /* sun6i needs mux to select backend */
|
||||
|
||||
/* callback to handle tcon muxing options */
|
||||
@ -167,6 +192,9 @@ struct sun4i_tcon {
|
||||
struct clk *sclk0;
|
||||
struct clk *sclk1;
|
||||
|
||||
/* Possible mux for the LVDS clock */
|
||||
struct clk *lvds_pll;
|
||||
|
||||
/* Pixel clock */
|
||||
struct clk *dclk;
|
||||
u8 dclk_max_div;
|
||||
@ -174,6 +202,7 @@ struct sun4i_tcon {
|
||||
|
||||
/* Reset control */
|
||||
struct reset_control *lcd_rst;
|
||||
struct reset_control *lvds_rst;
|
||||
|
||||
struct drm_panel *panel;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user