Skip to content
Snippets Groups Projects
Commit f7504385 authored by Liu Ying's avatar Liu Ying Committed by Leonard Crestez
Browse files

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: default avatarLiu Ying <victor.liu@nxp.com>
(cherry picked from commit d8e089c7)
parent 4c67ed34
No related merge requests found
...@@ -9,10 +9,14 @@ nodes describing each of the two LVDS encoder channels of the bridge. ...@@ -9,10 +9,14 @@ nodes describing each of the two LVDS encoder channels of the bridge.
Required properties: Required properties:
- #address-cells : should be <1> - #address-cells : should be <1>
- #size-cells : should be <0> - #size-cells : should be <0>
- compatible : should be "fsl,imx53-ldb" or "fsl,imx6q-ldb". - compatible : should be "fsl,imx53-ldb" or "fsl,imx6q-ldb" or
Both LDB versions are similar, but i.MX6 has an additional "fsl,imx8qm-ldb".
multiplexer in the front to select any of the four IPU display All LDB versions are similar.
interfaces as input for each LVDS channel. 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. - gpr : should be <&gpr> on i.MX53 and i.MX6q.
The phandle points to the iomuxc-gpr region containing the LVDS The phandle points to the iomuxc-gpr region containing the LVDS
control register. control register.
...@@ -29,14 +33,18 @@ Required properties: ...@@ -29,14 +33,18 @@ Required properties:
On i.MX6q the following additional clocks are needed: On i.MX6q the following additional clocks are needed:
"di2_sel" - IPU2 DI0 mux "di2_sel" - IPU2 DI0 mux
"di3_sel" - IPU2 DI1 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 The needed clock numbers for each are documented in
Documentation/devicetree/bindings/clock/imx5-clock.txt, and in Documentation/devicetree/bindings/clock/imx5-clock.txt, and in
Documentation/devicetree/bindings/clock/imx6q-clock.txt. Documentation/devicetree/bindings/clock/imx6q-clock.txt.
- power-domains : phandle pointing to power domain, only required by i.MX8qm.
Optional properties: 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, - 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 - fsl,dual-channel : boolean. if it exists, only LVDS channel 0 should
be configured - one input will be distributed on both outputs in dual be configured - one input will be distributed on both outputs in dual
channel mode channel mode
...@@ -57,9 +65,13 @@ Required properties: ...@@ -57,9 +65,13 @@ Required properties:
(lvds-channel@[0,1], respectively). (lvds-channel@[0,1], respectively).
On i.MX6, there should be four input ports (port@[0-3]) that correspond On i.MX6, there should be four input ports (port@[0-3]) that correspond
to the four LVDS multiplexer inputs. to the four LVDS multiplexer inputs.
A single output port (port@2 on i.MX5, port@4 on i.MX6) must be connected On i.MX8qm, the two channels of LDB connect to one display interface of DPU.
to a panel input port. Optionally, the output port can be left out if A single output port (port@2 on i.MX5, port@4 on i.MX6, port@1 on i.MX8qm)
display-timings are used instead. 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): Optional properties (required if display-timings are used):
- ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing - 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): ...@@ -69,6 +81,7 @@ Optional properties (required if display-timings are used):
This describes how the color bits are laid out in the This describes how the color bits are laid out in the
serialized LVDS signal. serialized LVDS signal.
- fsl,data-width : should be <18> or <24> - fsl,data-width : should be <18> or <24>
Additionally, <30> for i.MX8qm.
example: example:
......
...@@ -28,7 +28,7 @@ config DRM_IMX_LDB ...@@ -28,7 +28,7 @@ config DRM_IMX_LDB
select DRM_PANEL select DRM_PANEL
help help
Choose this to enable the internal LVDS Display Bridge (LDB) 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 config DRM_IMX_IPUV3
tristate tristate
......
...@@ -29,6 +29,8 @@ ...@@ -29,6 +29,8 @@
#include <linux/of_graph.h> #include <linux/of_graph.h>
#include <video/of_display_timing.h> #include <video/of_display_timing.h>
#include <video/of_videomode.h> #include <video/of_videomode.h>
#include <linux/phy/phy.h>
#include <linux/phy/phy-mixel-lvds.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/videodev2.h> #include <linux/videodev2.h>
...@@ -50,6 +52,12 @@ ...@@ -50,6 +52,12 @@
#define LDB_DI0_VS_POL_ACT_LOW (1 << 9) #define LDB_DI0_VS_POL_ACT_LOW (1 << 9)
#define LDB_DI1_VS_POL_ACT_LOW (1 << 10) #define LDB_DI1_VS_POL_ACT_LOW (1 << 10)
#define LDB_BGREF_RMODE_INT (1 << 15) #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; struct imx_ldb;
...@@ -62,6 +70,9 @@ struct imx_ldb_channel { ...@@ -62,6 +70,9 @@ struct imx_ldb_channel {
struct drm_panel *panel; struct drm_panel *panel;
struct drm_bridge *bridge; struct drm_bridge *bridge;
struct phy *phy;
bool phy_is_on;
struct device_node *child; struct device_node *child;
struct i2c_adapter *ddc; struct i2c_adapter *ddc;
int chno; int chno;
...@@ -89,6 +100,21 @@ struct bus_mux { ...@@ -89,6 +100,21 @@ struct bus_mux {
int mask; 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 imx_ldb {
struct regmap *regmap; struct regmap *regmap;
struct device *dev; struct device *dev;
...@@ -97,8 +123,21 @@ struct imx_ldb { ...@@ -97,8 +123,21 @@ struct imx_ldb {
struct clk *clk_sel[4]; /* parent of display clock */ struct clk *clk_sel[4]; /* parent of display clock */
struct clk *clk_parent[4]; /* original parent of clk_sel */ struct clk *clk_parent[4]; /* original parent of clk_sel */
struct clk *clk_pll[2]; /* upstream clock we can adjust */ 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; u32 ldb_ctrl;
const struct bus_mux *lvds_mux; 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, 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, ...@@ -112,16 +151,38 @@ static void imx_ldb_ch_set_bus_format(struct imx_ldb_channel *imx_ldb_ch,
break; break;
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
if (imx_ldb_ch->chno == 0 || dual) 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) 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; break;
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
if (imx_ldb_ch->chno == 0 || dual) 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 |
LDB_BIT_MAP_CH0_JEIDA; LDB_BIT_MAP_CH0_JEIDA;
if (imx_ldb_ch->chno == 1 || dual) 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 |
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; LDB_BIT_MAP_CH1_JEIDA;
break; break;
} }
...@@ -176,6 +237,12 @@ static void imx_ldb_set_clock(struct imx_ldb *ldb, int mux, int chno, ...@@ -176,6 +237,12 @@ static void imx_ldb_set_clock(struct imx_ldb *ldb, int mux, int chno,
{ {
int ret; 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__, dev_dbg(ldb->dev, "%s: now: %ld want: %ld\n", __func__,
clk_get_rate(ldb->clk_pll[chno]), serial_clk); clk_get_rate(ldb->clk_pll[chno]), serial_clk);
clk_set_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) ...@@ -208,14 +275,22 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder)
drm_panel_prepare(imx_ldb_ch->panel); drm_panel_prepare(imx_ldb_ch->panel);
if (dual) { if (ldb->is_imx8) {
clk_set_parent(ldb->clk_sel[mux], ldb->clk[0]); clk_prepare_enable(ldb->clk_pixel);
clk_set_parent(ldb->clk_sel[mux], ldb->clk[1]); clk_prepare_enable(ldb->clk_bypass);
}
clk_prepare_enable(ldb->clk[0]); if (ldb->has_mux) {
clk_prepare_enable(ldb->clk[1]); if (dual) {
} else { clk_set_parent(ldb->clk_sel[mux], ldb->clk[0]);
clk_set_parent(ldb->clk_sel[mux], ldb->clk[imx_ldb_ch->chno]); 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) { if (imx_ldb_ch == &ldb->channel[0] || dual) {
...@@ -245,7 +320,19 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder) ...@@ -245,7 +320,19 @@ static void imx_ldb_encoder_enable(struct drm_encoder *encoder)
mux << lvds_mux->shift); 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); drm_panel_enable(imx_ldb_ch->panel);
} }
...@@ -264,23 +351,35 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, ...@@ -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); int mux = drm_of_encoder_active_port_id(imx_ldb_ch->child, encoder);
u32 bus_format = imx_ldb_ch->bus_format; u32 bus_format = imx_ldb_ch->bus_format;
if (mode->clock > 170000) { if (mode->clock > ldb->max_prate_dual_mode) {
dev_warn(ldb->dev, 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, 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) { if (dual) {
serial_clk = 3500UL * mode->clock; serial_clk = 3500UL * mode->clock;
imx_ldb_set_clock(ldb, mux, 0, serial_clk, di_clk); imx_ldb_set_clock(ldb, mux, 0, serial_clk, di_clk);
imx_ldb_set_clock(ldb, mux, 1, 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 { } else {
serial_clk = 7000UL * mode->clock; serial_clk = 7000UL * mode->clock;
imx_ldb_set_clock(ldb, mux, imx_ldb_ch->chno, serial_clk, imx_ldb_set_clock(ldb, mux, imx_ldb_ch->chno, serial_clk,
di_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 */ /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */
...@@ -297,6 +396,52 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, ...@@ -297,6 +396,52 @@ imx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder,
ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW; 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) { if (!bus_format) {
struct drm_connector *connector = connector_state->connector; struct drm_connector *connector = connector_state->connector;
struct drm_display_info *di = &connector->display_info; struct drm_display_info *di = &connector->display_info;
...@@ -311,22 +456,43 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder) ...@@ -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_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder);
struct imx_ldb *ldb = imx_ldb_ch->ldb; struct imx_ldb *ldb = imx_ldb_ch->ldb;
int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN;
int mux, ret; int mux, ret;
drm_panel_disable(imx_ldb_ch->panel); 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]) if (imx_ldb_ch == &ldb->channel[0])
ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
else if (imx_ldb_ch == &ldb->channel[1]) else if (imx_ldb_ch == &ldb->channel[1])
ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; 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) { if (ldb->is_imx8) {
clk_disable_unprepare(ldb->clk[0]); clk_disable_unprepare(ldb->clk_bypass);
clk_disable_unprepare(ldb->clk[1]); 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) { if (ldb->lvds_mux) {
const struct bus_mux *lvds_mux = NULL; const struct bus_mux *lvds_mux = NULL;
...@@ -349,6 +515,7 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder) ...@@ -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", "unable to set di%d parent clock to original parent\n",
mux); mux);
unprepare_panel:
drm_panel_unprepare(imx_ldb_ch->panel); drm_panel_unprepare(imx_ldb_ch->panel);
} }
...@@ -358,6 +525,7 @@ static int imx_ldb_encoder_atomic_check(struct drm_encoder *encoder, ...@@ -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_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_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; struct drm_display_info *di = &conn_state->connector->display_info;
u32 bus_format = imx_ldb_ch->bus_format; u32 bus_format = imx_ldb_ch->bus_format;
...@@ -371,11 +539,23 @@ static int imx_ldb_encoder_atomic_check(struct drm_encoder *encoder, ...@@ -371,11 +539,23 @@ static int imx_ldb_encoder_atomic_check(struct drm_encoder *encoder,
} }
switch (bus_format) { switch (bus_format) {
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: 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; break;
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: 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; break;
default: default:
return -EINVAL; return -EINVAL;
...@@ -416,6 +596,9 @@ static int imx_ldb_get_clk(struct imx_ldb *ldb, int chno) ...@@ -416,6 +596,9 @@ static int imx_ldb_get_clk(struct imx_ldb *ldb, int chno)
{ {
char clkname[16]; char clkname[16];
if (ldb->is_imx8)
return 0;
snprintf(clkname, sizeof(clkname), "di%d", chno); snprintf(clkname, sizeof(clkname), "di%d", chno);
ldb->clk[chno] = devm_clk_get(ldb->dev, clkname); ldb->clk[chno] = devm_clk_get(ldb->dev, clkname);
if (IS_ERR(ldb->clk[chno])) if (IS_ERR(ldb->clk[chno]))
...@@ -496,12 +679,15 @@ struct imx_ldb_bit_mapping { ...@@ -496,12 +679,15 @@ struct imx_ldb_bit_mapping {
}; };
static const struct imx_ldb_bit_mapping imx_ldb_bit_mappings[] = { static const struct imx_ldb_bit_mapping imx_ldb_bit_mappings[] = {
{ MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, 18, "spwg" }, { MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, 18, "spwg" },
{ MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, 24, "spwg" }, { MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, 24, "spwg" },
{ MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, 24, "jeida" }, { 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; const char *bm;
u32 datawidth = 0; u32 datawidth = 0;
...@@ -513,6 +699,11 @@ static u32 of_get_bus_format(struct device *dev, struct device_node *np) ...@@ -513,6 +699,11 @@ static u32 of_get_bus_format(struct device *dev, struct device_node *np)
of_property_read_u32(np, "fsl,data-width", &datawidth); 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++) { for (i = 0; i < ARRAY_SIZE(imx_ldb_bit_mappings); i++) {
if (!strcasecmp(bm, imx_ldb_bit_mappings[i].mapping) && if (!strcasecmp(bm, imx_ldb_bit_mappings[i].mapping) &&
datawidth == imx_ldb_bit_mappings[i].datawidth) datawidth == imx_ldb_bit_mappings[i].datawidth)
...@@ -524,6 +715,16 @@ static u32 of_get_bus_format(struct device *dev, struct device_node *np) ...@@ -524,6 +715,16 @@ static u32 of_get_bus_format(struct device *dev, struct device_node *np)
return -ENOENT; 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] = { static struct bus_mux imx6q_lvds_mux[2] = {
{ {
.reg = IOMUXC_GPR3, .reg = IOMUXC_GPR3,
...@@ -536,15 +737,39 @@ static struct bus_mux imx6q_lvds_mux[2] = { ...@@ -536,15 +737,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", * For a device declaring compatible = "fsl,imx8qm-ldb", "fsl,imx6q-ldb",
* of_match_device will walk through this list and take the first entry * "fsl,imx53-ldb", of_match_device will walk through this list and take the
* matching any of its compatible values. Therefore, the more generic * first entry matching any of its compatible values.
* entries (in this case fsl,imx53-ldb) need to be ordered last. * 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[] = { static const struct of_device_id imx_ldb_dt_ids[] = {
{ .compatible = "fsl,imx6q-ldb", .data = imx6q_lvds_mux, }, { .compatible = "fsl,imx8qm-ldb", .data = &imx8qm_ldb_devtype, },
{ .compatible = "fsl,imx53-ldb", .data = NULL, }, { .compatible = "fsl,imx6q-ldb", .data = &imx6q_ldb_devtype, },
{ .compatible = "fsl,imx53-ldb", .data = &imx53_ldb_devtype, },
{ } { }
}; };
MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids);
...@@ -595,6 +820,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) ...@@ -595,6 +820,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
struct device_node *np = dev->of_node; struct device_node *np = dev->of_node;
const struct of_device_id *of_id = const struct of_device_id *of_id =
of_match_device(imx_ldb_dt_ids, dev); of_match_device(imx_ldb_dt_ids, dev);
const struct devtype *devtype = of_id->data;
struct device_node *child; struct device_node *child;
struct imx_ldb *imx_ldb; struct imx_ldb *imx_ldb;
int dual; int dual;
...@@ -612,40 +838,64 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) ...@@ -612,40 +838,64 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
} }
imx_ldb->dev = dev; imx_ldb->dev = dev;
imx_ldb->ldb_ctrl_reg = devtype->ctrl_reg;
if (of_id) imx_ldb->lvds_mux = devtype->bus_mux;
imx_ldb->lvds_mux = of_id->data; 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"); dual = of_property_read_bool(np, "fsl,dual-channel");
if (dual) if (dual)
imx_ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN; imx_ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN;
/* if (imx_ldb->is_imx8) {
* There are three different possible clock mux configurations: imx_ldb->clk_pixel = devm_clk_get(imx_ldb->dev, "pixel");
* i.MX53: ipu1_di0_sel, ipu1_di1_sel if (IS_ERR(imx_ldb->clk_pixel))
* i.MX6q: ipu1_di0_sel, ipu1_di1_sel, ipu2_di0_sel, ipu2_di1_sel return PTR_ERR(imx_ldb->clk_pixel);
* 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]); 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) { for_each_child_of_node(np, child) {
struct imx_ldb_channel *channel; struct imx_ldb_channel *channel;
int bus_format; int bus_format;
int port_reg;
bool auxiliary_ch = false;
ret = of_property_read_u32(child, "reg", &i); ret = of_property_read_u32(child, "reg", &i);
if (ret || i < 0 || i > 1) { if (ret || i < 0 || i > 1) {
...@@ -653,6 +903,12 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) ...@@ -653,6 +903,12 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
goto free_child; goto free_child;
} }
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)) if (!of_device_is_available(child))
continue; continue;
...@@ -667,10 +923,15 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) ...@@ -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 * 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, ret = drm_of_find_panel_or_bridge(child,
imx_ldb->lvds_mux ? 4 : 2, 0, port_reg, 0,
&channel->panel, &channel->bridge); &channel->panel, &channel->bridge);
if (ret && ret != -ENODEV) if (ret && ret != -ENODEV)
goto free_child; goto free_child;
...@@ -682,7 +943,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) ...@@ -682,7 +943,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
goto free_child; goto free_child;
} }
bus_format = of_get_bus_format(dev, child); bus_format = of_get_bus_format(dev, imx_ldb, child);
if (bus_format == -EINVAL) { if (bus_format == -EINVAL) {
/* /*
* If no bus format was specified in the device tree, * If no bus format was specified in the device tree,
...@@ -701,6 +962,33 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) ...@@ -701,6 +962,33 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
channel->bus_format = bus_format; channel->bus_format = bus_format;
channel->child = child; channel->child = child;
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); ret = imx_ldb_register(drm, channel);
if (ret) { if (ret) {
channel->child = NULL; channel->child = NULL;
...@@ -726,6 +1014,11 @@ static void imx_ldb_unbind(struct device *dev, struct device *master, ...@@ -726,6 +1014,11 @@ static void imx_ldb_unbind(struct device *dev, struct device *master,
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
struct imx_ldb_channel *channel = &imx_ldb->channel[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) if (channel->panel)
drm_panel_detach(channel->panel); drm_panel_detach(channel->panel);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment