1
0
Fork 0

MLK-15001-21 drm/imx: ldb: Add i.MX8qm LDB support

This patch adds i.MX8qm LDB support.
Logics are added to make i.MX8qm LDB cope with Mixel LVDS PHY.
Also, logics are added to handle pixel link padding quirks for i.MX8qm LDB.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
pull/10/head
Liu Ying 2017-06-02 17:46:12 +08:00 committed by Jason Liu
parent 37f8685843
commit 62bf92e109
3 changed files with 372 additions and 66 deletions

View File

@ -9,10 +9,14 @@ nodes describing each of the two LVDS encoder channels of the bridge.
Required properties:
- #address-cells : should be <1>
- #size-cells : should be <0>
- compatible : should be "fsl,imx53-ldb" or "fsl,imx6q-ldb".
Both LDB versions are similar, but i.MX6 has an additional
multiplexer in the front to select any of the four IPU display
interfaces as input for each LVDS channel.
- compatible : should be "fsl,imx53-ldb" or "fsl,imx6q-ldb" or
"fsl,imx8qm-ldb".
All LDB versions are similar.
i.MX6q/dl has an additional multiplexer in the front to select
any of the two or four IPU display interfaces as input for each
LVDS channel.
i.MX8qm LDB supports 10bit RGB input and needs an additional
phy.
- gpr : should be <&gpr> on i.MX53 and i.MX6q.
The phandle points to the iomuxc-gpr region containing the LVDS
control register.
@ -29,14 +33,18 @@ Required properties:
On i.MX6q the following additional clocks are needed:
"di2_sel" - IPU2 DI0 mux
"di3_sel" - IPU2 DI1 mux
The following clocks are expected on i.MX8qm:
"pixel" - pixel clock
"bypass" - bypass clock
The needed clock numbers for each are documented in
Documentation/devicetree/bindings/clock/imx5-clock.txt, and in
Documentation/devicetree/bindings/clock/imx6q-clock.txt.
- power-domains : phandle pointing to power domain, only required by i.MX8qm.
Optional properties:
- pinctrl-names : should be "default" on i.MX53, not used on i.MX6q
- pinctrl-names : should be "default" on i.MX53, not used on i.MX6q and i.MX8qm
- pinctrl-0 : a phandle pointing to LVDS pin settings on i.MX53,
not used on i.MX6q
not used on i.MX6q and i.MX8qm
- fsl,dual-channel : boolean. if it exists, only LVDS channel 0 should
be configured - one input will be distributed on both outputs in dual
channel mode
@ -57,9 +65,13 @@ Required properties:
(lvds-channel@[0,1], respectively).
On i.MX6, there should be four input ports (port@[0-3]) that correspond
to the four LVDS multiplexer inputs.
A single output port (port@2 on i.MX5, port@4 on i.MX6) must be connected
to a panel input port. Optionally, the output port can be left out if
display-timings are used instead.
On i.MX8qm, the two channels of LDB connect to one display interface of DPU.
A single output port (port@2 on i.MX5, port@4 on i.MX6, port@1 on i.MX8qm)
must be connected to a panel input port or a bridge input port.
Optionally, the output port can be left out if display-timings are used
instead.
- phys: the phandle for the LVDS PHY device. Valid only on i.MX8qm.
- phy-names: should be "ldb_phy". Valid only on i.MX8qm.
Optional properties (required if display-timings are used):
- ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
@ -69,6 +81,7 @@ Optional properties (required if display-timings are used):
This describes how the color bits are laid out in the
serialized LVDS signal.
- fsl,data-width : should be <18> or <24>
Additionally, <30> for i.MX8qm.
example:

View File

@ -29,7 +29,7 @@ config DRM_IMX_LDB
select DRM_PANEL
help
Choose this to enable the internal LVDS Display Bridge (LDB)
found on i.MX53 and i.MX6 processors.
found on i.MX53, i.MX6 and i.MX8 processors.
config DRM_IMX_IPUV3
tristate

View File

@ -29,6 +29,8 @@
#include <linux/of_graph.h>
#include <video/of_display_timing.h>
#include <video/of_videomode.h>
#include <linux/phy/phy.h>
#include <linux/phy/phy-mixel-lvds.h>
#include <linux/regmap.h>
#include <linux/videodev2.h>
@ -50,6 +52,12 @@
#define LDB_DI0_VS_POL_ACT_LOW (1 << 9)
#define LDB_DI1_VS_POL_ACT_LOW (1 << 10)
#define LDB_BGREF_RMODE_INT (1 << 15)
#define LDB_CH0_10BIT_EN (1 << 22)
#define LDB_CH1_10BIT_EN (1 << 23)
#define LDB_CH0_DATA_WIDTH_24BIT (1 << 24)
#define LDB_CH1_DATA_WIDTH_24BIT (1 << 26)
#define LDB_CH0_DATA_WIDTH_30BIT (2 << 24)
#define LDB_CH1_DATA_WIDTH_30BIT (2 << 26)
struct imx_ldb;
@ -62,6 +70,9 @@ struct imx_ldb_channel {
struct drm_panel *panel;
struct drm_bridge *bridge;
struct phy *phy;
bool phy_is_on;
struct device_node *child;
struct i2c_adapter *ddc;
int chno;
@ -89,6 +100,21 @@ struct bus_mux {
int mask;
};
struct devtype {
int ctrl_reg;
struct bus_mux *bus_mux;
bool capable_10bit;
bool visible_phy;
bool has_mux;
bool is_imx8;
bool use_mixel_phy;
bool padding_quirks;
/* pixel rate in KHz */
unsigned int max_prate_single_mode;
unsigned int max_prate_dual_mode;
};
struct imx_ldb {
struct regmap *regmap;
struct device *dev;
@ -97,8 +123,21 @@ struct imx_ldb {
struct clk *clk_sel[4]; /* parent of display clock */
struct clk *clk_parent[4]; /* original parent of clk_sel */
struct clk *clk_pll[2]; /* upstream clock we can adjust */
struct clk *clk_pixel;
struct clk *clk_bypass;
u32 ldb_ctrl_reg;
u32 ldb_ctrl;
const struct bus_mux *lvds_mux;
bool capable_10bit;
bool visible_phy;
bool has_mux;
bool is_imx8;
bool use_mixel_phy;
bool padding_quirks;
/* pixel rate in KHz */
unsigned int max_prate_single_mode;
unsigned int max_prate_dual_mode;
};
static void imx_ldb_ch_set_bus_format(struct imx_ldb_channel *imx_ldb_ch,
@ -112,16 +151,38 @@ static void imx_ldb_ch_set_bus_format(struct imx_ldb_channel *imx_ldb_ch,
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
if (imx_ldb_ch->chno == 0 || dual)
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24;
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 |
LDB_CH0_DATA_WIDTH_24BIT;
if (imx_ldb_ch->chno == 1 || dual)
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24;
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 |
LDB_CH1_DATA_WIDTH_24BIT;
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
if (imx_ldb_ch->chno == 0 || dual)
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 |
LDB_CH0_DATA_WIDTH_24BIT |
LDB_BIT_MAP_CH0_JEIDA;
if (imx_ldb_ch->chno == 1 || dual)
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 |
LDB_CH1_DATA_WIDTH_24BIT |
LDB_BIT_MAP_CH1_JEIDA;
break;
case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG:
if (imx_ldb_ch->chno == 0 || dual)
ldb->ldb_ctrl |= LDB_CH0_10BIT_EN |
LDB_CH0_DATA_WIDTH_30BIT;
if (imx_ldb_ch->chno == 1 || dual)
ldb->ldb_ctrl |= LDB_CH1_10BIT_EN |
LDB_CH1_DATA_WIDTH_30BIT;
break;
case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA:
if (imx_ldb_ch->chno == 0 || dual)
ldb->ldb_ctrl |= LDB_CH0_10BIT_EN |
LDB_CH0_DATA_WIDTH_30BIT |
LDB_BIT_MAP_CH0_JEIDA;
if (imx_ldb_ch->chno == 1 || dual)
ldb->ldb_ctrl |= LDB_CH1_10BIT_EN |
LDB_CH1_DATA_WIDTH_30BIT |
LDB_BIT_MAP_CH1_JEIDA;
break;
}
@ -176,6 +237,12 @@ static void imx_ldb_set_clock(struct imx_ldb *ldb, int mux, int chno,
{
int ret;
if (ldb->is_imx8) {
clk_set_rate(ldb->clk_bypass, di_clk);
clk_set_rate(ldb->clk_pixel, di_clk);
return;
}
dev_dbg(ldb->dev, "%s: now: %ld want: %ld\n", __func__,
clk_get_rate(ldb->clk_pll[chno]), serial_clk);
clk_set_rate(ldb->clk_pll[chno], serial_clk);
@ -208,14 +275,22 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder)
drm_panel_prepare(imx_ldb_ch->panel);
if (dual) {
clk_set_parent(ldb->clk_sel[mux], ldb->clk[0]);
clk_set_parent(ldb->clk_sel[mux], ldb->clk[1]);
if (ldb->is_imx8) {
clk_prepare_enable(ldb->clk_pixel);
clk_prepare_enable(ldb->clk_bypass);
}
clk_prepare_enable(ldb->clk[0]);
clk_prepare_enable(ldb->clk[1]);
} else {
clk_set_parent(ldb->clk_sel[mux], ldb->clk[imx_ldb_ch->chno]);
if (ldb->has_mux) {
if (dual) {
clk_set_parent(ldb->clk_sel[mux], ldb->clk[0]);
clk_set_parent(ldb->clk_sel[mux], ldb->clk[1]);
clk_prepare_enable(ldb->clk[0]);
clk_prepare_enable(ldb->clk[1]);
} else {
clk_set_parent(ldb->clk_sel[mux],
ldb->clk[imx_ldb_ch->chno]);
}
}
if (imx_ldb_ch == &ldb->channel[0] || dual) {
@ -245,7 +320,19 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder)
mux << lvds_mux->shift);
}
regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl);
regmap_write(ldb->regmap, ldb->ldb_ctrl_reg, ldb->ldb_ctrl);
if (dual) {
phy_power_on(ldb->channel[0].phy);
phy_power_on(ldb->channel[1].phy);
ldb->channel[0].phy_is_on = true;
ldb->channel[1].phy_is_on = true;
} else {
phy_power_on(imx_ldb_ch->phy);
imx_ldb_ch->phy_is_on = true;
}
drm_panel_enable(imx_ldb_ch->panel);
}
@ -264,23 +351,35 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder,
int mux = drm_of_encoder_active_port_id(imx_ldb_ch->child, encoder);
u32 bus_format = imx_ldb_ch->bus_format;
if (mode->clock > 170000) {
if (mode->clock > ldb->max_prate_dual_mode) {
dev_warn(ldb->dev,
"%s: mode exceeds 170 MHz pixel clock\n", __func__);
"%s: mode exceeds %u MHz pixel clock\n", __func__,
ldb->max_prate_dual_mode / 1000);
}
if (mode->clock > 85000 && !dual) {
if (mode->clock > ldb->max_prate_single_mode && !dual) {
dev_warn(ldb->dev,
"%s: mode exceeds 85 MHz pixel clock\n", __func__);
"%s: mode exceeds %u MHz pixel clock\n", __func__,
ldb->max_prate_single_mode / 1000);
}
if (dual) {
serial_clk = 3500UL * mode->clock;
imx_ldb_set_clock(ldb, mux, 0, serial_clk, di_clk);
imx_ldb_set_clock(ldb, mux, 1, serial_clk, di_clk);
if (ldb->use_mixel_phy) {
mixel_phy_lvds_set_phy_speed(ldb->channel[0].phy,
di_clk / 2);
mixel_phy_lvds_set_phy_speed(ldb->channel[1].phy,
di_clk / 2);
}
} else {
serial_clk = 7000UL * mode->clock;
imx_ldb_set_clock(ldb, mux, imx_ldb_ch->chno, serial_clk,
di_clk);
if (ldb->use_mixel_phy)
mixel_phy_lvds_set_phy_speed(imx_ldb_ch->phy, di_clk);
}
/* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */
@ -297,6 +396,52 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder,
ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW;
}
if (dual) {
if (ldb->use_mixel_phy) {
/* VSYNC */
if (mode->flags & DRM_MODE_FLAG_NVSYNC) {
mixel_phy_lvds_set_vsync_pol(
ldb->channel[0].phy, false);
mixel_phy_lvds_set_vsync_pol(
ldb->channel[1].phy, false);
} else if (mode->flags & DRM_MODE_FLAG_PVSYNC) {
mixel_phy_lvds_set_vsync_pol(
ldb->channel[0].phy, true);
mixel_phy_lvds_set_vsync_pol(
ldb->channel[1].phy, true);
}
/* HSYNC */
if (mode->flags & DRM_MODE_FLAG_NHSYNC) {
mixel_phy_lvds_set_hsync_pol(
ldb->channel[0].phy, false);
mixel_phy_lvds_set_hsync_pol(
ldb->channel[1].phy, false);
} else if (mode->flags & DRM_MODE_FLAG_PHSYNC) {
mixel_phy_lvds_set_hsync_pol(
ldb->channel[0].phy, true);
mixel_phy_lvds_set_hsync_pol(
ldb->channel[1].phy, true);
}
}
} else {
if (ldb->use_mixel_phy) {
/* VSYNC */
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
mixel_phy_lvds_set_vsync_pol(imx_ldb_ch->phy,
false);
else if (mode->flags & DRM_MODE_FLAG_PVSYNC)
mixel_phy_lvds_set_vsync_pol(imx_ldb_ch->phy,
true);
/* HSYNC */
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
mixel_phy_lvds_set_hsync_pol(imx_ldb_ch->phy,
false);
else if (mode->flags & DRM_MODE_FLAG_PHSYNC)
mixel_phy_lvds_set_hsync_pol(imx_ldb_ch->phy,
true);
}
}
if (!bus_format) {
struct drm_connector *connector = connector_state->connector;
struct drm_display_info *di = &connector->display_info;
@ -311,22 +456,43 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder)
{
struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder);
struct imx_ldb *ldb = imx_ldb_ch->ldb;
int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN;
int mux, ret;
drm_panel_disable(imx_ldb_ch->panel);
if (dual) {
phy_power_off(ldb->channel[0].phy);
phy_power_off(ldb->channel[1].phy);
ldb->channel[0].phy_is_on = false;
ldb->channel[1].phy_is_on = false;
} else {
phy_power_off(imx_ldb_ch->phy);
imx_ldb_ch->phy_is_on = false;
}
if (imx_ldb_ch == &ldb->channel[0])
ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
else if (imx_ldb_ch == &ldb->channel[1])
ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;
regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl);
regmap_write(ldb->regmap, ldb->ldb_ctrl_reg, ldb->ldb_ctrl);
if (ldb->ldb_ctrl & LDB_SPLIT_MODE_EN) {
clk_disable_unprepare(ldb->clk[0]);
clk_disable_unprepare(ldb->clk[1]);
if (ldb->is_imx8) {
clk_disable_unprepare(ldb->clk_bypass);
clk_disable_unprepare(ldb->clk_pixel);
} else {
if (ldb->ldb_ctrl & LDB_SPLIT_MODE_EN) {
clk_disable_unprepare(ldb->clk[0]);
clk_disable_unprepare(ldb->clk[1]);
}
}
if (!ldb->has_mux)
goto unprepare_panel;
if (ldb->lvds_mux) {
const struct bus_mux *lvds_mux = NULL;
@ -349,6 +515,7 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder)
"unable to set di%d parent clock to original parent\n",
mux);
unprepare_panel:
drm_panel_unprepare(imx_ldb_ch->panel);
}
@ -358,6 +525,7 @@ static int imx_ldb_encoder_atomic_check(struct drm_encoder *encoder,
{
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder);
struct imx_ldb *ldb = imx_ldb_ch->ldb;
struct drm_display_info *di = &conn_state->connector->display_info;
u32 bus_format = imx_ldb_ch->bus_format;
@ -371,11 +539,23 @@ static int imx_ldb_encoder_atomic_check(struct drm_encoder *encoder,
}
switch (bus_format) {
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
if (ldb->padding_quirks)
imx_crtc_state->bus_format =
MEDIA_BUS_FMT_RGB666_1X30_PADLO;
else
imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
if (ldb->padding_quirks)
imx_crtc_state->bus_format =
MEDIA_BUS_FMT_RGB888_1X30_PADLO;
else
imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
break;
case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG:
case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA:
imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
break;
default:
return -EINVAL;
@ -416,6 +596,9 @@ static int imx_ldb_get_clk(struct imx_ldb *ldb, int chno)
{
char clkname[16];
if (ldb->is_imx8)
return 0;
snprintf(clkname, sizeof(clkname), "di%d", chno);
ldb->clk[chno] = devm_clk_get(ldb->dev, clkname);
if (IS_ERR(ldb->clk[chno]))
@ -497,12 +680,15 @@ struct imx_ldb_bit_mapping {
};
static const struct imx_ldb_bit_mapping imx_ldb_bit_mappings[] = {
{ MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, 18, "spwg" },
{ MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, 24, "spwg" },
{ MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, 24, "jeida" },
{ MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, 18, "spwg" },
{ MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, 24, "spwg" },
{ MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, 24, "jeida" },
{ MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG, 30, "spwg" },
{ MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA, 30, "jeida" },
};
static u32 of_get_bus_format(struct device *dev, struct device_node *np)
static u32 of_get_bus_format(struct device *dev, struct imx_ldb *ldb,
struct device_node *np)
{
const char *bm;
u32 datawidth = 0;
@ -514,6 +700,11 @@ static u32 of_get_bus_format(struct device *dev, struct device_node *np)
of_property_read_u32(np, "fsl,data-width", &datawidth);
if (!ldb->capable_10bit && datawidth == 30) {
dev_err(dev, "invalid data width: %d-bit\n", datawidth);
return -ENOENT;
}
for (i = 0; i < ARRAY_SIZE(imx_ldb_bit_mappings); i++) {
if (!strcasecmp(bm, imx_ldb_bit_mappings[i].mapping) &&
datawidth == imx_ldb_bit_mappings[i].datawidth)
@ -525,6 +716,16 @@ static u32 of_get_bus_format(struct device *dev, struct device_node *np)
return -ENOENT;
}
static struct devtype imx53_ldb_devtype = {
.ctrl_reg = IOMUXC_GPR2,
.bus_mux = NULL,
.capable_10bit = false,
.visible_phy = false,
.has_mux = true,
.max_prate_single_mode = 85000,
.max_prate_dual_mode = 150000,
};
static struct bus_mux imx6q_lvds_mux[2] = {
{
.reg = IOMUXC_GPR3,
@ -537,15 +738,39 @@ static struct bus_mux imx6q_lvds_mux[2] = {
}
};
static struct devtype imx6q_ldb_devtype = {
.ctrl_reg = IOMUXC_GPR2,
.bus_mux = imx6q_lvds_mux,
.capable_10bit = false,
.visible_phy = false,
.has_mux = true,
.max_prate_single_mode = 85000,
.max_prate_dual_mode = 170000,
};
static struct devtype imx8qm_ldb_devtype = {
.ctrl_reg = 0x10e0,
.bus_mux = NULL,
.capable_10bit = true,
.visible_phy = true,
.is_imx8 = true,
.use_mixel_phy = true,
.padding_quirks = true,
.max_prate_single_mode = 150000,
.max_prate_dual_mode = 300000,
};
/*
* For a device declaring compatible = "fsl,imx6q-ldb", "fsl,imx53-ldb",
* of_match_device will walk through this list and take the first entry
* matching any of its compatible values. Therefore, the more generic
* entries (in this case fsl,imx53-ldb) need to be ordered last.
* For a device declaring compatible = "fsl,imx8qm-ldb", "fsl,imx6q-ldb",
* "fsl,imx53-ldb", of_match_device will walk through this list and take the
* first entry matching any of its compatible values.
* Therefore, the more generic entries (in this case fsl,imx53-ldb) need
* to be ordered last.
*/
static const struct of_device_id imx_ldb_dt_ids[] = {
{ .compatible = "fsl,imx6q-ldb", .data = imx6q_lvds_mux, },
{ .compatible = "fsl,imx53-ldb", .data = NULL, },
{ .compatible = "fsl,imx8qm-ldb", .data = &imx8qm_ldb_devtype, },
{ .compatible = "fsl,imx6q-ldb", .data = &imx6q_ldb_devtype, },
{ .compatible = "fsl,imx53-ldb", .data = &imx53_ldb_devtype, },
{ }
};
MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids);
@ -596,6 +821,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
struct device_node *np = dev->of_node;
const struct of_device_id *of_id =
of_match_device(imx_ldb_dt_ids, dev);
const struct devtype *devtype = of_id->data;
struct device_node *child;
struct imx_ldb *imx_ldb;
int dual;
@ -613,45 +839,75 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
}
imx_ldb->dev = dev;
if (of_id)
imx_ldb->lvds_mux = of_id->data;
imx_ldb->ldb_ctrl_reg = devtype->ctrl_reg;
imx_ldb->lvds_mux = devtype->bus_mux;
imx_ldb->capable_10bit = devtype->capable_10bit;
imx_ldb->visible_phy = devtype->visible_phy;
imx_ldb->has_mux = devtype->has_mux;
imx_ldb->is_imx8 = devtype->is_imx8;
imx_ldb->use_mixel_phy = devtype->use_mixel_phy;
imx_ldb->padding_quirks = devtype->padding_quirks;
imx_ldb->max_prate_single_mode = devtype->max_prate_single_mode;
imx_ldb->max_prate_dual_mode = devtype->max_prate_dual_mode;
dual = of_property_read_bool(np, "fsl,dual-channel");
if (dual)
imx_ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN;
/*
* There are three different possible clock mux configurations:
* i.MX53: ipu1_di0_sel, ipu1_di1_sel
* i.MX6q: ipu1_di0_sel, ipu1_di1_sel, ipu2_di0_sel, ipu2_di1_sel
* i.MX6dl: ipu1_di0_sel, ipu1_di1_sel, lcdif_sel
* Map them all to di0_sel...di3_sel.
*/
for (i = 0; i < 4; i++) {
char clkname[16];
if (imx_ldb->is_imx8) {
imx_ldb->clk_pixel = devm_clk_get(imx_ldb->dev, "pixel");
if (IS_ERR(imx_ldb->clk_pixel))
return PTR_ERR(imx_ldb->clk_pixel);
sprintf(clkname, "di%d_sel", i);
imx_ldb->clk_sel[i] = devm_clk_get(imx_ldb->dev, clkname);
if (IS_ERR(imx_ldb->clk_sel[i])) {
ret = PTR_ERR(imx_ldb->clk_sel[i]);
imx_ldb->clk_sel[i] = NULL;
break;
}
imx_ldb->clk_parent[i] = clk_get_parent(imx_ldb->clk_sel[i]);
imx_ldb->clk_bypass = devm_clk_get(imx_ldb->dev, "bypass");
if (IS_ERR(imx_ldb->clk_bypass))
return PTR_ERR(imx_ldb->clk_bypass);
}
if (imx_ldb->has_mux) {
/*
* There are three different possible clock mux configurations:
* i.MX53: ipu1_di0_sel, ipu1_di1_sel
* i.MX6q: ipu1_di0_sel, ipu1_di1_sel, ipu2_di0_sel,
* ipu2_di1_sel
* i.MX6dl: ipu1_di0_sel, ipu1_di1_sel, lcdif_sel
* Map them all to di0_sel...di3_sel.
*/
for (i = 0; i < 4; i++) {
char clkname[16];
sprintf(clkname, "di%d_sel", i);
imx_ldb->clk_sel[i] = devm_clk_get(imx_ldb->dev,
clkname);
if (IS_ERR(imx_ldb->clk_sel[i])) {
ret = PTR_ERR(imx_ldb->clk_sel[i]);
imx_ldb->clk_sel[i] = NULL;
break;
}
imx_ldb->clk_parent[i] =
clk_get_parent(imx_ldb->clk_sel[i]);
}
if (i == 0)
return ret;
}
if (i == 0)
return ret;
for_each_child_of_node(np, child) {
struct imx_ldb_channel *channel;
int bus_format;
int port_reg;
bool auxiliary_ch = false;
ret = of_property_read_u32(child, "reg", &i);
if (ret || i < 0 || i > 1)
return -EINVAL;
if (dual && imx_ldb->use_mixel_phy && i > 0) {
auxiliary_ch = true;
channel = &imx_ldb->channel[i];
goto get_phy;
}
if (!of_device_is_available(child))
continue;
@ -667,10 +923,15 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
/*
* The output port is port@4 with an external 4-port mux or
* port@2 with the internal 2-port mux.
* port@2 with the internal 2-port mux or port@1 without mux.
*/
if (imx_ldb->has_mux)
port_reg = imx_ldb->lvds_mux ? 4 : 2;
else
port_reg = 1;
ret = drm_of_find_panel_or_bridge(child,
imx_ldb->lvds_mux ? 4 : 2, 0,
port_reg, 0,
&channel->panel, &channel->bridge);
if (ret && ret != -ENODEV)
return ret;
@ -682,7 +943,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
return ret;
}
bus_format = of_get_bus_format(dev, child);
bus_format = of_get_bus_format(dev, imx_ldb, child);
if (bus_format == -EINVAL) {
/*
* If no bus format was specified in the device tree,
@ -699,6 +960,33 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
}
channel->bus_format = bus_format;
get_phy:
if (imx_ldb->visible_phy) {
channel->phy = devm_of_phy_get(dev, child, "ldb_phy");
if (IS_ERR(channel->phy)) {
ret = PTR_ERR(channel->phy);
if (ret == -EPROBE_DEFER) {
return ret;
} else {
dev_err(dev,
"can't get channel%d phy: %d\n",
channel->chno, ret);
return ret;
}
}
ret = phy_init(channel->phy);
if (ret < 0) {
dev_err(dev,
"failed to initialize channel%d phy: %d\n",
channel->chno, ret);
return ret;
}
if (auxiliary_ch)
return 0;
}
ret = imx_ldb_register(drm, channel);
if (ret)
return ret;
@ -718,6 +1006,11 @@ static void imx_ldb_unbind(struct device *dev, struct device *master,
for (i = 0; i < 2; i++) {
struct imx_ldb_channel *channel = &imx_ldb->channel[i];
if (channel->phy_is_on)
phy_power_off(channel->phy);
phy_exit(channel->phy);
if (channel->panel)
drm_panel_detach(channel->panel);