* [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-05-30 16:39 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However the physical bridges are automatically configured by the input video signal, and the driver has no access to the video processing pipeline. The driver is only needed to read EDID from the STDP2690 and to handle HPD events from the STDP4028. The driver communicates with both bridges over i2c. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/5] Configure the mapping between IPUs and external displays on the dts file of the B850v3. Needed so the GPU can drive two Full-HD monitors. [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [5/5] Make the changes to the B850v3 dts file to enalbe the GE B850v3 LVDS/DP++ Bridge. Peter Senna Tschudin (5): drm/imx-ldb: Add support to drm-bridge dts/imx6q-b850v3: Configure IPU assignment order Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 36 ++ drivers/gpu/drm/bridge/Kconfig | 8 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 75 ++-- 7 files changed, 542 insertions(+), 21 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-05-30 16:39 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: linux-arm-kernel The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However the physical bridges are automatically configured by the input video signal, and the driver has no access to the video processing pipeline. The driver is only needed to read EDID from the STDP2690 and to handle HPD events from the STDP4028. The driver communicates with both bridges over i2c. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/5] Configure the mapping between IPUs and external displays on the dts file of the B850v3. Needed so the GPU can drive two Full-HD monitors. [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [5/5] Make the changes to the B850v3 dts file to enalbe the GE B850v3 LVDS/DP++ Bridge. Peter Senna Tschudin (5): drm/imx-ldb: Add support to drm-bridge dts/imx6q-b850v3: Configure IPU assignment order Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 36 ++ drivers/gpu/drm/bridge/Kconfig | 8 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 75 ++-- 7 files changed, 542 insertions(+), 21 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge 2016-05-30 16:39 ` Peter Senna Tschudin @ 2016-05-30 16:39 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- drivers/gpu/drm/imx/imx-ldb.c | 75 +++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index a58eee5..7233a81 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *ext_bridge; + struct device_node *child; int chno; void *edid; @@ -295,6 +299,10 @@ static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder, } } +static void imx_ldb_encoder_enable(struct drm_encoder *encoder) +{ +} + static void imx_ldb_encoder_disable(struct drm_encoder *encoder) { struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); @@ -373,6 +381,7 @@ static const struct drm_encoder_helper_funcs imx_ldb_encoder_helper_funcs = { .prepare = imx_ldb_encoder_prepare, .commit = imx_ldb_encoder_commit, .mode_set = imx_ldb_encoder_mode_set, + .enable = imx_ldb_encoder_enable, .disable = imx_ldb_encoder_disable, }; @@ -417,16 +426,28 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); - - if (imx_ldb_ch->panel) + if (imx_ldb_ch->panel) { + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector); - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, - &imx_ldb_ch->encoder); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + &imx_ldb_ch->encoder); + } + + if (imx_ldb_ch->ext_bridge) { + imx_ldb_ch->ext_bridge->encoder = &imx_ldb_ch->encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->ext_bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->ext_bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } return 0; } @@ -583,23 +604,35 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) endpoint = of_get_child_by_name(port, "endpoint"); if (endpoint) { remote = of_graph_get_remote_port_parent(endpoint); - if (remote) - channel->panel = of_drm_find_panel(remote); - else - return -EPROBE_DEFER; - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; + if (remote) { + /* Only one of these two will succeed */ + channel->panel = + of_drm_find_panel(remote); + + channel->ext_bridge = + of_drm_find_bridge(remote); + + /* + * If the bridge is compiled as a + * module, it may take some time until + * the bridge driver is available. + * Defer until the bridge driver is + * ready. + */ + if (!channel->panel && + !channel->ext_bridge) + return -EPROBE_DEFER; } } } - edidp = of_get_property(child, "edid", &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { + if (channel->panel) { + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) + channel->edid = kmemdup(edidp, + channel->edid_len, GFP_KERNEL); + } else { ret = of_get_drm_display_mode(child, &channel->mode, 0); if (!ret) channel->mode_valid = 1; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-05-30 16:39 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: linux-arm-kernel Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- drivers/gpu/drm/imx/imx-ldb.c | 75 +++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index a58eee5..7233a81 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *ext_bridge; + struct device_node *child; int chno; void *edid; @@ -295,6 +299,10 @@ static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder, } } +static void imx_ldb_encoder_enable(struct drm_encoder *encoder) +{ +} + static void imx_ldb_encoder_disable(struct drm_encoder *encoder) { struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); @@ -373,6 +381,7 @@ static const struct drm_encoder_helper_funcs imx_ldb_encoder_helper_funcs = { .prepare = imx_ldb_encoder_prepare, .commit = imx_ldb_encoder_commit, .mode_set = imx_ldb_encoder_mode_set, + .enable = imx_ldb_encoder_enable, .disable = imx_ldb_encoder_disable, }; @@ -417,16 +426,28 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); - - if (imx_ldb_ch->panel) + if (imx_ldb_ch->panel) { + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector); - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, - &imx_ldb_ch->encoder); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + &imx_ldb_ch->encoder); + } + + if (imx_ldb_ch->ext_bridge) { + imx_ldb_ch->ext_bridge->encoder = &imx_ldb_ch->encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->ext_bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->ext_bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } return 0; } @@ -583,23 +604,35 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) endpoint = of_get_child_by_name(port, "endpoint"); if (endpoint) { remote = of_graph_get_remote_port_parent(endpoint); - if (remote) - channel->panel = of_drm_find_panel(remote); - else - return -EPROBE_DEFER; - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; + if (remote) { + /* Only one of these two will succeed */ + channel->panel = + of_drm_find_panel(remote); + + channel->ext_bridge = + of_drm_find_bridge(remote); + + /* + * If the bridge is compiled as a + * module, it may take some time until + * the bridge driver is available. + * Defer until the bridge driver is + * ready. + */ + if (!channel->panel && + !channel->ext_bridge) + return -EPROBE_DEFER; } } } - edidp = of_get_property(child, "edid", &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { + if (channel->panel) { + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) + channel->edid = kmemdup(edidp, + channel->edid_len, GFP_KERNEL); + } else { ret = of_get_drm_display_mode(child, &channel->mode, 0); if (!ret) channel->mode_valid = 1; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge 2016-05-30 16:39 ` Peter Senna Tschudin (?) @ 2016-06-02 13:09 ` Philipp Zabel -1 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-06-02 13:09 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > drivers/gpu/drm/imx/imx-ldb.c | 75 +++++++++++++++++++++++++++++++------------ > 1 file changed, 54 insertions(+), 21 deletions(-) > > diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c > index a58eee5..7233a81 100644 > --- a/drivers/gpu/drm/imx/imx-ldb.c > +++ b/drivers/gpu/drm/imx/imx-ldb.c > @@ -57,7 +57,11 @@ struct imx_ldb_channel { > struct imx_ldb *ldb; > struct drm_connector connector; > struct drm_encoder encoder; > + > + /* Defines what is connected to the ldb, only one at a time */ > struct drm_panel *panel; > + struct drm_bridge *ext_bridge; > + Just bridge should be clear enough. > struct device_node *child; > int chno; > void *edid; > @@ -295,6 +299,10 @@ static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder, > } > } > > +static void imx_ldb_encoder_enable(struct drm_encoder *encoder) > +{ > +} > + Why? Note that Liu Ying's atomic series touches this also, and after "drm/imx: atomic phase 3 step 3: Legacy callback fixups" the enable callback exists. Depending on how either series proceeds, one will have to be rebased onto the other. > static void imx_ldb_encoder_disable(struct drm_encoder *encoder) > { > struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); > @@ -373,6 +381,7 @@ static const struct drm_encoder_helper_funcs imx_ldb_encoder_helper_funcs = { > .prepare = imx_ldb_encoder_prepare, > .commit = imx_ldb_encoder_commit, > .mode_set = imx_ldb_encoder_mode_set, > + .enable = imx_ldb_encoder_enable, > .disable = imx_ldb_encoder_disable, > }; > > @@ -417,16 +426,28 @@ static int imx_ldb_register(struct drm_device *drm, > drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs, > DRM_MODE_ENCODER_LVDS, NULL); > > - drm_connector_helper_add(&imx_ldb_ch->connector, > - &imx_ldb_connector_helper_funcs); > - drm_connector_init(drm, &imx_ldb_ch->connector, > - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); > - > - if (imx_ldb_ch->panel) > + if (imx_ldb_ch->panel) { if (!imx_ldb_ch->bridge) { > + drm_connector_helper_add(&imx_ldb_ch->connector, > + &imx_ldb_connector_helper_funcs); > + drm_connector_init(drm, &imx_ldb_ch->connector, > + &imx_ldb_connector_funcs, > + DRM_MODE_CONNECTOR_LVDS); > drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector); > > - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, > - &imx_ldb_ch->encoder); > + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, > + &imx_ldb_ch->encoder); > + } > + > + if (imx_ldb_ch->ext_bridge) { > + imx_ldb_ch->ext_bridge->encoder = &imx_ldb_ch->encoder; > + > + imx_ldb_ch->encoder.bridge = imx_ldb_ch->ext_bridge; > + ret = drm_bridge_attach(drm, imx_ldb_ch->ext_bridge); > + if (ret) { > + DRM_ERROR("Failed to initialize bridge with drm\n"); > + return ret; > + } > + } > > return 0; > } > @@ -583,23 +604,35 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > endpoint = of_get_child_by_name(port, "endpoint"); > if (endpoint) { > remote = of_graph_get_remote_port_parent(endpoint); > - if (remote) > - channel->panel = of_drm_find_panel(remote); > - else > - return -EPROBE_DEFER; > - if (!channel->panel) { > - dev_err(dev, "panel not found: %s\n", > - remote->full_name); Let's keep this message, at least as dev_dbg. > - return -EPROBE_DEFER; > + if (remote) { > + /* Only one of these two will succeed */ > + channel->panel = > + of_drm_find_panel(remote); > + > + channel->ext_bridge = > + of_drm_find_bridge(remote); > + > + /* > + * If the bridge is compiled as a > + * module, it may take some time until > + * the bridge driver is available. > + * Defer until the bridge driver is > + * ready. > + */ > + if (!channel->panel && > + !channel->ext_bridge) > + return -EPROBE_DEFER; > } What happens if (!remote)? > } > } > > - edidp = of_get_property(child, "edid", &channel->edid_len); > - if (edidp) { > - channel->edid = kmemdup(edidp, channel->edid_len, > - GFP_KERNEL); > - } else if (!channel->panel) { > + if (channel->panel) { > + edidp = of_get_property(child, "edid", > + &channel->edid_len); > + if (edidp) > + channel->edid = kmemdup(edidp, > + channel->edid_len, GFP_KERNEL); > + } else { Any reason to disallow overriding EDID from DT if there is no panel? I'd leave this part as is. regards Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-06-02 13:09 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-06-02 13:09 UTC (permalink / raw) To: linux-arm-kernel Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > drivers/gpu/drm/imx/imx-ldb.c | 75 +++++++++++++++++++++++++++++++------------ > 1 file changed, 54 insertions(+), 21 deletions(-) > > diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c > index a58eee5..7233a81 100644 > --- a/drivers/gpu/drm/imx/imx-ldb.c > +++ b/drivers/gpu/drm/imx/imx-ldb.c > @@ -57,7 +57,11 @@ struct imx_ldb_channel { > struct imx_ldb *ldb; > struct drm_connector connector; > struct drm_encoder encoder; > + > + /* Defines what is connected to the ldb, only one at a time */ > struct drm_panel *panel; > + struct drm_bridge *ext_bridge; > + Just bridge should be clear enough. > struct device_node *child; > int chno; > void *edid; > @@ -295,6 +299,10 @@ static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder, > } > } > > +static void imx_ldb_encoder_enable(struct drm_encoder *encoder) > +{ > +} > + Why? Note that Liu Ying's atomic series touches this also, and after "drm/imx: atomic phase 3 step 3: Legacy callback fixups" the enable callback exists. Depending on how either series proceeds, one will have to be rebased onto the other. > static void imx_ldb_encoder_disable(struct drm_encoder *encoder) > { > struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); > @@ -373,6 +381,7 @@ static const struct drm_encoder_helper_funcs imx_ldb_encoder_helper_funcs = { > .prepare = imx_ldb_encoder_prepare, > .commit = imx_ldb_encoder_commit, > .mode_set = imx_ldb_encoder_mode_set, > + .enable = imx_ldb_encoder_enable, > .disable = imx_ldb_encoder_disable, > }; > > @@ -417,16 +426,28 @@ static int imx_ldb_register(struct drm_device *drm, > drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs, > DRM_MODE_ENCODER_LVDS, NULL); > > - drm_connector_helper_add(&imx_ldb_ch->connector, > - &imx_ldb_connector_helper_funcs); > - drm_connector_init(drm, &imx_ldb_ch->connector, > - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); > - > - if (imx_ldb_ch->panel) > + if (imx_ldb_ch->panel) { if (!imx_ldb_ch->bridge) { > + drm_connector_helper_add(&imx_ldb_ch->connector, > + &imx_ldb_connector_helper_funcs); > + drm_connector_init(drm, &imx_ldb_ch->connector, > + &imx_ldb_connector_funcs, > + DRM_MODE_CONNECTOR_LVDS); > drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector); > > - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, > - &imx_ldb_ch->encoder); > + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, > + &imx_ldb_ch->encoder); > + } > + > + if (imx_ldb_ch->ext_bridge) { > + imx_ldb_ch->ext_bridge->encoder = &imx_ldb_ch->encoder; > + > + imx_ldb_ch->encoder.bridge = imx_ldb_ch->ext_bridge; > + ret = drm_bridge_attach(drm, imx_ldb_ch->ext_bridge); > + if (ret) { > + DRM_ERROR("Failed to initialize bridge with drm\n"); > + return ret; > + } > + } > > return 0; > } > @@ -583,23 +604,35 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > endpoint = of_get_child_by_name(port, "endpoint"); > if (endpoint) { > remote = of_graph_get_remote_port_parent(endpoint); > - if (remote) > - channel->panel = of_drm_find_panel(remote); > - else > - return -EPROBE_DEFER; > - if (!channel->panel) { > - dev_err(dev, "panel not found: %s\n", > - remote->full_name); Let's keep this message, at least as dev_dbg. > - return -EPROBE_DEFER; > + if (remote) { > + /* Only one of these two will succeed */ > + channel->panel = > + of_drm_find_panel(remote); > + > + channel->ext_bridge = > + of_drm_find_bridge(remote); > + > + /* > + * If the bridge is compiled as a > + * module, it may take some time until > + * the bridge driver is available. > + * Defer until the bridge driver is > + * ready. > + */ > + if (!channel->panel && > + !channel->ext_bridge) > + return -EPROBE_DEFER; > } What happens if (!remote)? > } > } > > - edidp = of_get_property(child, "edid", &channel->edid_len); > - if (edidp) { > - channel->edid = kmemdup(edidp, channel->edid_len, > - GFP_KERNEL); > - } else if (!channel->panel) { > + if (channel->panel) { > + edidp = of_get_property(child, "edid", > + &channel->edid_len); > + if (edidp) > + channel->edid = kmemdup(edidp, > + channel->edid_len, GFP_KERNEL); > + } else { Any reason to disallow overriding EDID from DT if there is no panel? I'd leave this part as is. regards Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-06-02 13:09 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-06-02 13:09 UTC (permalink / raw) To: Peter Senna Tschudin Cc: mark.rutland, dri-devel, tiwai, jslaby, mchehab, linux, treding, linux, martin.donnelly, devicetree, pawel.moll, ijc+devicetree, rmk+kernel, robh+dt, linux-arm-kernel, gregkh, linux-kernel, kernel, galak, enric.balletbo, akpm, shawnguo, davem Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > drivers/gpu/drm/imx/imx-ldb.c | 75 +++++++++++++++++++++++++++++++------------ > 1 file changed, 54 insertions(+), 21 deletions(-) > > diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c > index a58eee5..7233a81 100644 > --- a/drivers/gpu/drm/imx/imx-ldb.c > +++ b/drivers/gpu/drm/imx/imx-ldb.c > @@ -57,7 +57,11 @@ struct imx_ldb_channel { > struct imx_ldb *ldb; > struct drm_connector connector; > struct drm_encoder encoder; > + > + /* Defines what is connected to the ldb, only one at a time */ > struct drm_panel *panel; > + struct drm_bridge *ext_bridge; > + Just bridge should be clear enough. > struct device_node *child; > int chno; > void *edid; > @@ -295,6 +299,10 @@ static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder, > } > } > > +static void imx_ldb_encoder_enable(struct drm_encoder *encoder) > +{ > +} > + Why? Note that Liu Ying's atomic series touches this also, and after "drm/imx: atomic phase 3 step 3: Legacy callback fixups" the enable callback exists. Depending on how either series proceeds, one will have to be rebased onto the other. > static void imx_ldb_encoder_disable(struct drm_encoder *encoder) > { > struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); > @@ -373,6 +381,7 @@ static const struct drm_encoder_helper_funcs imx_ldb_encoder_helper_funcs = { > .prepare = imx_ldb_encoder_prepare, > .commit = imx_ldb_encoder_commit, > .mode_set = imx_ldb_encoder_mode_set, > + .enable = imx_ldb_encoder_enable, > .disable = imx_ldb_encoder_disable, > }; > > @@ -417,16 +426,28 @@ static int imx_ldb_register(struct drm_device *drm, > drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs, > DRM_MODE_ENCODER_LVDS, NULL); > > - drm_connector_helper_add(&imx_ldb_ch->connector, > - &imx_ldb_connector_helper_funcs); > - drm_connector_init(drm, &imx_ldb_ch->connector, > - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); > - > - if (imx_ldb_ch->panel) > + if (imx_ldb_ch->panel) { if (!imx_ldb_ch->bridge) { > + drm_connector_helper_add(&imx_ldb_ch->connector, > + &imx_ldb_connector_helper_funcs); > + drm_connector_init(drm, &imx_ldb_ch->connector, > + &imx_ldb_connector_funcs, > + DRM_MODE_CONNECTOR_LVDS); > drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector); > > - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, > - &imx_ldb_ch->encoder); > + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, > + &imx_ldb_ch->encoder); > + } > + > + if (imx_ldb_ch->ext_bridge) { > + imx_ldb_ch->ext_bridge->encoder = &imx_ldb_ch->encoder; > + > + imx_ldb_ch->encoder.bridge = imx_ldb_ch->ext_bridge; > + ret = drm_bridge_attach(drm, imx_ldb_ch->ext_bridge); > + if (ret) { > + DRM_ERROR("Failed to initialize bridge with drm\n"); > + return ret; > + } > + } > > return 0; > } > @@ -583,23 +604,35 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > endpoint = of_get_child_by_name(port, "endpoint"); > if (endpoint) { > remote = of_graph_get_remote_port_parent(endpoint); > - if (remote) > - channel->panel = of_drm_find_panel(remote); > - else > - return -EPROBE_DEFER; > - if (!channel->panel) { > - dev_err(dev, "panel not found: %s\n", > - remote->full_name); Let's keep this message, at least as dev_dbg. > - return -EPROBE_DEFER; > + if (remote) { > + /* Only one of these two will succeed */ > + channel->panel = > + of_drm_find_panel(remote); > + > + channel->ext_bridge = > + of_drm_find_bridge(remote); > + > + /* > + * If the bridge is compiled as a > + * module, it may take some time until > + * the bridge driver is available. > + * Defer until the bridge driver is > + * ready. > + */ > + if (!channel->panel && > + !channel->ext_bridge) > + return -EPROBE_DEFER; > } What happens if (!remote)? > } > } > > - edidp = of_get_property(child, "edid", &channel->edid_len); > - if (edidp) { > - channel->edid = kmemdup(edidp, channel->edid_len, > - GFP_KERNEL); > - } else if (!channel->panel) { > + if (channel->panel) { > + edidp = of_get_property(child, "edid", > + &channel->edid_len); > + if (edidp) > + channel->edid = kmemdup(edidp, > + channel->edid_len, GFP_KERNEL); > + } else { Any reason to disallow overriding EDID from DT if there is no panel? I'd leave this part as is. regards Philipp _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order 2016-05-30 16:39 ` Peter Senna Tschudin @ 2016-05-30 16:39 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Configure the IPU assignment order to assign one IPU per external display. A single IPU can drive multiple external displays but there are resolution restrictions. After this patch the GPU is capalbe of driving two Full-HD monitors. Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..88a70de 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -51,6 +51,11 @@ chosen { stdout-path = &uart3; }; + + display-subsystem { + compatible = "fsl,imx-display-subsystem"; + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; + }; }; &clks { -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order @ 2016-05-30 16:39 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: linux-arm-kernel Configure the IPU assignment order to assign one IPU per external display. A single IPU can drive multiple external displays but there are resolution restrictions. After this patch the GPU is capalbe of driving two Full-HD monitors. Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..88a70de 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -51,6 +51,11 @@ chosen { stdout-path = &uart3; }; + + display-subsystem { + compatible = "fsl,imx-display-subsystem"; + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; + }; }; &clks { -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order 2016-05-30 16:39 ` Peter Senna Tschudin (?) @ 2016-05-30 16:49 ` Fabio Estevam -1 siblings, 0 replies; 172+ messages in thread From: Fabio Estevam @ 2016-05-30 16:49 UTC (permalink / raw) To: Peter Senna Tschudin Cc: David Airlie, Andrew Morton, David S. Miller, devicetree, DRI mailing list, enric.balletbo, Kumar Gala, Greg Kroah-Hartman, Heiko Stübner, Ian Campbell, Jiri Slaby, Sascha Hauer, linux-arm-kernel, Russell King - ARM Linux, linux-kernel, Guenter Roeck, Mark Rutland, Martin Donnelly, martyn.welch, Mauro Carvalho Chehab, Pawel Moll, Philipp Zabel, rmk+kernel, robh+dt, Shawn Guo, Takashi Iwai, Thierry Reding, Yakir Yang On Mon, May 30, 2016 at 1:39 PM, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Configure the IPU assignment order to assign one IPU per external > display. A single IPU can drive multiple external displays but there are > resolution restrictions. After this patch the GPU is capalbe of driving two I think you meant "the IPU is capable" ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order @ 2016-05-30 16:49 ` Fabio Estevam 0 siblings, 0 replies; 172+ messages in thread From: Fabio Estevam @ 2016-05-30 16:49 UTC (permalink / raw) To: linux-arm-kernel On Mon, May 30, 2016 at 1:39 PM, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Configure the IPU assignment order to assign one IPU per external > display. A single IPU can drive multiple external displays but there are > resolution restrictions. After this patch the GPU is capalbe of driving two I think you meant "the IPU is capable" ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order @ 2016-05-30 16:49 ` Fabio Estevam 0 siblings, 0 replies; 172+ messages in thread From: Fabio Estevam @ 2016-05-30 16:49 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Mark Rutland, DRI mailing list, Takashi Iwai, Jiri Slaby, Mauro Carvalho Chehab, Russell King - ARM Linux, Thierry Reding, Guenter Roeck, Martin Donnelly, devicetree, Pawel Moll, Ian Campbell, rmk+kernel, robh+dt, linux-arm-kernel, Greg Kroah-Hartman, linux-kernel, Sascha Hauer, Kumar Gala, enric.balletbo, Andrew Morton On Mon, May 30, 2016 at 1:39 PM, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Configure the IPU assignment order to assign one IPU per external > display. A single IPU can drive multiple external displays but there are > resolution restrictions. After this patch the GPU is capalbe of driving two I think you meant "the IPU is capable" _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order 2016-05-30 16:39 ` Peter Senna Tschudin (?) @ 2016-06-02 12:55 ` Philipp Zabel -1 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-06-02 12:55 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > Configure the IPU assignment order to assign one IPU per external > display. A single IPU can drive multiple external displays but there are > resolution restrictions. After this patch the GPU is capalbe of driving two > Full-HD monitors. It's also capable to do it before this patch, if you use the first and third crtc. You are just reordering the crtcs. Unfortunately the IPU has combined limitations across multiple crtcs in one IPU, which currently can't be communicated to userspace. regards Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order @ 2016-06-02 12:55 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-06-02 12:55 UTC (permalink / raw) To: linux-arm-kernel Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > Configure the IPU assignment order to assign one IPU per external > display. A single IPU can drive multiple external displays but there are > resolution restrictions. After this patch the GPU is capalbe of driving two > Full-HD monitors. It's also capable to do it before this patch, if you use the first and third crtc. You are just reordering the crtcs. Unfortunately the IPU has combined limitations across multiple crtcs in one IPU, which currently can't be communicated to userspace. regards Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order @ 2016-06-02 12:55 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-06-02 12:55 UTC (permalink / raw) To: Peter Senna Tschudin Cc: mark.rutland, dri-devel, tiwai, jslaby, mchehab, linux, treding, linux, martin.donnelly, devicetree, pawel.moll, ijc+devicetree, rmk+kernel, robh+dt, linux-arm-kernel, gregkh, linux-kernel, kernel, galak, enric.balletbo, akpm, shawnguo, davem Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > Configure the IPU assignment order to assign one IPU per external > display. A single IPU can drive multiple external displays but there are > resolution restrictions. After this patch the GPU is capalbe of driving two > Full-HD monitors. It's also capable to do it before this patch, if you use the first and third crtc. You are just reordering the crtcs. Unfortunately the IPU has combined limitations across multiple crtcs in one IPU, which currently can't be communicated to userspace. regards Philipp _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp 2016-05-30 16:39 ` Peter Senna Tschudin @ 2016-05-30 16:39 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt new file mode 100644 index 0000000..32e123a --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt @@ -0,0 +1,38 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3_lvds_dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : should link to the gpio used as interrupt + source on the host. + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the vide signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3_dp_bridge { + compatible = "ge,b850v3_lvds_dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port@0 { + reg = <0>; + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp @ 2016-05-30 16:39 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: linux-arm-kernel Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt new file mode 100644 index 0000000..32e123a --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt @@ -0,0 +1,38 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3_lvds_dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : should link to the gpio used as interrupt + source on the host. + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the vide signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3_dp_bridge { + compatible = "ge,b850v3_lvds_dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port at 0 { + reg = <0>; + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp 2016-05-30 16:39 ` Peter Senna Tschudin (?) @ 2016-06-02 12:49 ` Philipp Zabel -1 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-06-02 12:49 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Hi Peter, Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > new file mode 100644 > index 0000000..32e123a > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > @@ -0,0 +1,38 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3_lvds_dp". In the cover mail you write that this is a combination of the STDP4028 DP transmitter and STDP2690 DP/DP++ converter. So shouldn't this binding comprise two device tree nodes, one with compatible "st,stdp4028", one with "st,stdp2690", then? Is the stdp2690 connected to the stdp4028's i2c master? regards Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp @ 2016-06-02 12:49 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-06-02 12:49 UTC (permalink / raw) To: linux-arm-kernel Hi Peter, Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > new file mode 100644 > index 0000000..32e123a > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > @@ -0,0 +1,38 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3_lvds_dp". In the cover mail you write that this is a combination of the STDP4028 DP transmitter and STDP2690 DP/DP++ converter. So shouldn't this binding comprise two device tree nodes, one with compatible "st,stdp4028", one with "st,stdp2690", then? Is the stdp2690 connected to the stdp4028's i2c master? regards Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp @ 2016-06-02 12:49 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-06-02 12:49 UTC (permalink / raw) To: Peter Senna Tschudin Cc: mark.rutland, dri-devel, tiwai, jslaby, mchehab, linux, treding, linux, martin.donnelly, devicetree, pawel.moll, ijc+devicetree, rmk+kernel, robh+dt, linux-arm-kernel, gregkh, linux-kernel, kernel, galak, enric.balletbo, akpm, shawnguo, davem Hi Peter, Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > new file mode 100644 > index 0000000..32e123a > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > @@ -0,0 +1,38 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3_lvds_dp". In the cover mail you write that this is a combination of the STDP4028 DP transmitter and STDP2690 DP/DP++ converter. So shouldn't this binding comprise two device tree nodes, one with compatible "st,stdp4028", one with "st,stdp2690", then? Is the stdp2690 connected to the stdp4028's i2c master? regards Philipp _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp @ 2016-06-02 23:19 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-02 23:19 UTC (permalink / raw) To: Philipp Zabel Cc: martin.donnelly, martyn.welch, linux-arm-kernel, shawnguo, akpm, galak, linux-kernel, ijc+devicetree, pawel.moll, treding, devicetree, mark.rutland, kernel, robh+dt, airlied, enric.balletbo, linux, heiko, mchehab, tiwai, davem, gregkh, linux, jslaby, rmk+kernel, ykk, dri-devel, Peter Senna Tschudin Hi Philipp, Thank you very much for the review! I'll send V2 soon, doing my best to avoid collisions with other patches that are under review. On Thursday, June 2, 2016 14:49 CEST, Philipp Zabel <p.zabel@pengutronix.de> wrote: > Hi Peter, > > Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > > display bridge. > > > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > --- > > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > > 1 file changed, 38 insertions(+) > > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > new file mode 100644 > > index 0000000..32e123a > > --- /dev/null > > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > @@ -0,0 +1,38 @@ > > +Driver for GE B850v3 LVDS/DP++ display bridge > > + > > +Required properties: > > + - compatible : should be "ge,b850v3_lvds_dp". > > In the cover mail you write that this is a combination of the STDP4028 > DP transmitter and STDP2690 DP/DP++ converter. So shouldn't this binding > comprise two device tree nodes, one with compatible "st,stdp4028", one > with "st,stdp2690", then? > Is the stdp2690 connected to the stdp4028's i2c master? The hardware and firmware made it complicated for this binding to comprise two device tree nodes. The hardware and firmware are designed to configure both bridges based on the LVDS signal. This part works very well, but it leave the driver powerless to control the video processing pipeline. The bridges sort of behave as a single bridge, the sort of part is the need of interacting with both bridges: EDID from the STDP2690 and HPD events from the STDP4028. So the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. I would find it nicer to be author of two drivers instead of one, but as the behavior handled by the driver is specific of the B850v3, and do not reflect the standard behavior of each bridge, I find it unlikely that the driver could be useful to handle STDP4028 and STPD2690 with stock firmware, or in other designs. On the other hand, I made sure to use the chip name on the defines to maximize chances of reuse. Peter ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp @ 2016-06-02 23:19 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-02 23:19 UTC (permalink / raw) To: linux-arm-kernel Hi Philipp, Thank you very much for the review! I'll send V2 soon, doing my best to avoid collisions with other patches that are under review. On Thursday, June 2, 2016 14:49 CEST, Philipp Zabel <p.zabel@pengutronix.de> wrote: > Hi Peter, > > Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > > display bridge. > > > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > --- > > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > > 1 file changed, 38 insertions(+) > > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > new file mode 100644 > > index 0000000..32e123a > > --- /dev/null > > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > @@ -0,0 +1,38 @@ > > +Driver for GE B850v3 LVDS/DP++ display bridge > > + > > +Required properties: > > + - compatible : should be "ge,b850v3_lvds_dp". > > In the cover mail you write that this is a combination of the STDP4028 > DP transmitter and STDP2690 DP/DP++ converter. So shouldn't this binding > comprise two device tree nodes, one with compatible "st,stdp4028", one > with "st,stdp2690", then? > Is the stdp2690 connected to the stdp4028's i2c master? The hardware and firmware made it complicated for this binding to comprise two device tree nodes. The hardware and firmware are designed to configure both bridges based on the LVDS signal. This part works very well, but it leave the driver powerless to control the video processing pipeline. The bridges sort of behave as a single bridge, the sort of part is the need of interacting with both bridges: EDID from the STDP2690 and HPD events from the STDP4028. So the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. I would find it nicer to be author of two drivers instead of one, but as the behavior handled by the driver is specific of the B850v3, and do not reflect the standard behavior of each bridge, I find it unlikely that the driver could be useful to handle STDP4028 and STPD2690 with stock firmware, or in other designs. On the other hand, I made sure to use the chip name on the defines to maximize chances of reuse. Peter ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp @ 2016-06-02 23:19 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-02 23:19 UTC (permalink / raw) To: Philipp Zabel Cc: martin.donnelly-JJi787mZWgc, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, galak-sgV2jX0FEOL9JmXXK+q4OQ, linux-kernel-u79uwXL29TY76Z2rM5mHXA, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, pawel.moll-5wv7dgnIgG8, treding-DDmLM1+adcrQT0dZR+AlfA, devicetree-u79uwXL29TY76Z2rM5mHXA, mark.rutland-5wv7dgnIgG8, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, airlied-cv59FeDIM0c, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, linux-I+IVW8TIWO2tmTQ+vhA3Yw, heiko-4mtYJXux2i+zQB+pC5nmwQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, tiwai-IBi9RG/b67k, davem-fT/PcQaiUtIeIZ0/mPfg9Q, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, linux-0h96xk9xTtrk1uMJSBkQmQ, jslaby-AlSwsSmVLrQ, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, ykk-TNX95d0MmH7DzftRWevZcw, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Peter Senna Tschudin Hi Philipp, Thank you very much for the review! I'll send V2 soon, doing my best to avoid collisions with other patches that are under review. On Thursday, June 2, 2016 14:49 CEST, Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> wrote: > Hi Peter, > > Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin: > > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > > display bridge. > > > > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > > --- > > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > > 1 file changed, 38 insertions(+) > > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > new file mode 100644 > > index 0000000..32e123a > > --- /dev/null > > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > @@ -0,0 +1,38 @@ > > +Driver for GE B850v3 LVDS/DP++ display bridge > > + > > +Required properties: > > + - compatible : should be "ge,b850v3_lvds_dp". > > In the cover mail you write that this is a combination of the STDP4028 > DP transmitter and STDP2690 DP/DP++ converter. So shouldn't this binding > comprise two device tree nodes, one with compatible "st,stdp4028", one > with "st,stdp2690", then? > Is the stdp2690 connected to the stdp4028's i2c master? The hardware and firmware made it complicated for this binding to comprise two device tree nodes. The hardware and firmware are designed to configure both bridges based on the LVDS signal. This part works very well, but it leave the driver powerless to control the video processing pipeline. The bridges sort of behave as a single bridge, the sort of part is the need of interacting with both bridges: EDID from the STDP2690 and HPD events from the STDP4028. So the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. I would find it nicer to be author of two drivers instead of one, but as the behavior handled by the driver is specific of the B850v3, and do not reflect the standard behavior of each bridge, I find it unlikely that the driver could be useful to handle STDP4028 and STPD2690 with stock firmware, or in other designs. On the other hand, I made sure to use the chip name on the defines to maximize chances of reuse. Peter -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp 2016-05-30 16:39 ` Peter Senna Tschudin @ 2016-06-02 22:57 ` Rob Herring -1 siblings, 0 replies; 172+ messages in thread From: Rob Herring @ 2016-06-02 22:57 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, p.zabel, rmk+kernel, shawnguo, tiwai, treding, ykk On Mon, May 30, 2016 at 06:39:43PM +0200, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > new file mode 100644 > index 0000000..32e123a > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > @@ -0,0 +1,38 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3_lvds_dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : should link to the gpio used as interrupt > + source on the host. > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information This should be known based on the bridge chip you are using. > + - port : should describe the vide signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3_dp_bridge { Don't use '_' in node or property names or compatible strings. > + compatible = "ge,b850v3_lvds_dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port@0 { > + reg = <0>; > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp @ 2016-06-02 22:57 ` Rob Herring 0 siblings, 0 replies; 172+ messages in thread From: Rob Herring @ 2016-06-02 22:57 UTC (permalink / raw) To: linux-arm-kernel On Mon, May 30, 2016 at 06:39:43PM +0200, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > new file mode 100644 > index 0000000..32e123a > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > @@ -0,0 +1,38 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3_lvds_dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : should link to the gpio used as interrupt > + source on the host. > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information This should be known based on the bridge chip you are using. > + - port : should describe the vide signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3_dp_bridge { Don't use '_' in node or property names or compatible strings. > + compatible = "ge,b850v3_lvds_dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port at 0 { > + reg = <0>; > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-05-30 16:39 ` Peter Senna Tschudin @ 2016-05-30 16:39 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3(imx6q-b850v3.dts). There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However the physical bridges are automatically configured by the input video signal, and the driver has no access to the video processing pipeline. The driver is only needed to read EDID from the STDP2690 and to handle HPD events from the STDP4028. The driver communicates with both bridges over i2c. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 8 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ 4 files changed, 414 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index 3273ffa..7bb5e89 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5009,6 +5009,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Martin Donnelly <martin.donnelly@ge.com> +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 8f7423f..e9c32fc 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -52,4 +52,12 @@ config DRM_PARADE_PS8622 source "drivers/gpu/drm/bridge/analogix/Kconfig" +config DRM_GE_B850V3_LVDS_DP + tristate "LVDS/DP bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + Driver for GE B850v3 DP2 LVDS to DP+ Bridge + endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 96b13b3..ba2e355 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..37a4e7a --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,397 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + + * + */ +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include "drm_crtc.h" +#include "drm_crtc_helper.h" +#include "drm_edid.h" +#include "drmP.h" + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +static void ge_b850v3_lvds_dp_pre_enable(struct drm_bridge *bridge) +{ +} + +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) +{ +} + +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) +{ +} + +static void ge_b850v3_lvds_dp_post_disable(struct drm_bridge *bridge) +{ +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + u8 *block; + int num_modes; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + block = stdp2690_get_edid(client); + ptn_bridge->edid = (struct edid *) block; + + drm_mode_connector_update_edid_property(connector, ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + + return num_modes; +} + +static struct drm_encoder +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + + return ptn_bridge->bridge.encoder; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .best_encoder = ge_b850v3_lvds_dp_best_encoder, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static void ge_b850v3_lvds_dp_connector_force(struct drm_connector *connector) +{ +} + +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = ge_b850v3_lvds_dp_connector_destroy, + .force = ge_b850v3_lvds_dp_connector_force, +}; + +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; + +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge_b850v3_lvds_dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .pre_enable = ge_b850v3_lvds_dp_pre_enable, + .enable = ge_b850v3_lvds_dp_enable, + .disable = ge_b850v3_lvds_dp_disable, + .post_disable = ge_b850v3_lvds_dp_post_disable, + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + + /* Configures the bridge to re-enable interrupts after each ack */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3_lvds_dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3_lvds_dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "ge,b850v3_lvds_dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-05-30 16:39 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: linux-arm-kernel This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3(imx6q-b850v3.dts). There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However the physical bridges are automatically configured by the input video signal, and the driver has no access to the video processing pipeline. The driver is only needed to read EDID from the STDP2690 and to handle HPD events from the STDP4028. The driver communicates with both bridges over i2c. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 8 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ 4 files changed, 414 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index 3273ffa..7bb5e89 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5009,6 +5009,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Martin Donnelly <martin.donnelly@ge.com> +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 8f7423f..e9c32fc 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -52,4 +52,12 @@ config DRM_PARADE_PS8622 source "drivers/gpu/drm/bridge/analogix/Kconfig" +config DRM_GE_B850V3_LVDS_DP + tristate "LVDS/DP bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + Driver for GE B850v3 DP2 LVDS to DP+ Bridge + endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 96b13b3..ba2e355 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..37a4e7a --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,397 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + + * + */ +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include "drm_crtc.h" +#include "drm_crtc_helper.h" +#include "drm_edid.h" +#include "drmP.h" + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +static void ge_b850v3_lvds_dp_pre_enable(struct drm_bridge *bridge) +{ +} + +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) +{ +} + +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) +{ +} + +static void ge_b850v3_lvds_dp_post_disable(struct drm_bridge *bridge) +{ +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + u8 *block; + int num_modes; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + block = stdp2690_get_edid(client); + ptn_bridge->edid = (struct edid *) block; + + drm_mode_connector_update_edid_property(connector, ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + + return num_modes; +} + +static struct drm_encoder +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + + return ptn_bridge->bridge.encoder; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .best_encoder = ge_b850v3_lvds_dp_best_encoder, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static void ge_b850v3_lvds_dp_connector_force(struct drm_connector *connector) +{ +} + +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = ge_b850v3_lvds_dp_connector_destroy, + .force = ge_b850v3_lvds_dp_connector_force, +}; + +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; + +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge_b850v3_lvds_dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .pre_enable = ge_b850v3_lvds_dp_pre_enable, + .enable = ge_b850v3_lvds_dp_enable, + .disable = ge_b850v3_lvds_dp_disable, + .post_disable = ge_b850v3_lvds_dp_post_disable, + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + + /* Configures the bridge to re-enable interrupts after each ack */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3_lvds_dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3_lvds_dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "ge,b850v3_lvds_dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-05-30 16:39 ` Peter Senna Tschudin (?) @ 2016-05-31 7:48 ` Enric Balletbo Serra -1 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-05-31 7:48 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied, akpm, David Miller, devicetree, dri-devel, Enric Balletbo i Serra, Kumar Gala, Greg Kroah-Hartman, Heiko Stübner, Ian Campbell, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, Mark Rutland, martin.donnelly, martyn.welch, Mauro Carvalho Chehab, Pawel Moll, Philipp Zabel, rmk+kernel, Rob Herring, shawnguo, Takashi Iwai, Thierry Reding, ykk Hi Peter, Just some comments that I received when I submitted my bridge patches and that I think can help you ... 2016-05-30 18:39 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > display bridge of the GE B850v3(imx6q-b850v3.dts). There are two physical > bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a > STDP2690(DP to DP++). However the physical bridges are automatically > configured by the input video signal, and the driver has no access to > the video processing pipeline. The driver is only needed to read EDID > from the STDP2690 and to handle HPD events from the STDP4028. The driver > communicates with both bridges over i2c. The video signal pipeline is as > follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 8 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ > 4 files changed, 414 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 3273ffa..7bb5e89 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5009,6 +5009,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..e9c32fc 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -52,4 +52,12 @@ config DRM_PARADE_PS8622 > > source "drivers/gpu/drm/bridge/analogix/Kconfig" > > +config DRM_GE_B850V3_LVDS_DP > + tristate "LVDS/DP bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + Driver for GE B850v3 DP2 LVDS to DP+ Bridge > + I think the maintainer prefers the entries ordered alphabetically (by vendor, then name), so your config should go before "config DRM_NXP_PTN3460" > endmenu > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..ba2e355 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -6,3 +6,4 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o Same here, this needs to be sorted by vendor, then name as well. Also I think is preferred use hyphens like the others drivers. Hmm, I see now that CONFIG_DRM_ANALOGIX_DP is not sorted, but I guess the preferred is be sorted. > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..37a4e7a > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,397 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_pre_enable(struct drm_bridge *bridge) > +{ > +} > + You don't need to keep empty callbacks for the (pre/post) enable/disable drm_bridge ops anymore so you can remove this function and the below. > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_post_disable(struct drm_bridge *bridge) > +{ > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + u8 *block; > + int num_modes; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); Move this assignation to ptn_bridge declaration > + client = ptn_bridge->edid_i2c; > + > + block = stdp2690_get_edid(client); I think you can remove the client variable and pass ptn_bridge->edid_i2c directly to the stdp2690_get_edid function. Note that get_modes is called several times and can be called from userspace, you are allocating memory on every call to get_modes that I think is not released or I'm missing something? > + ptn_bridge->edid = (struct edid *) block; > + What if block is NULL here ? > + drm_mode_connector_update_edid_property(connector, ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + > + return num_modes; nit: return drm_add_edid_modes(connector, ptn_bridge->edid); ? And remove num_modes ? > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_force(struct drm_connector *connector) > +{ > +} > + Guess you can remove this empty callback. > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > + .force = ge_b850v3_lvds_dp_connector_force, Remove .force > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > + Remove the empty line. > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge_b850v3_lvds_dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .pre_enable = ge_b850v3_lvds_dp_pre_enable, > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, > + .post_disable = ge_b850v3_lvds_dp_post_disable, Remove the above empty callbacks. > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + Remove this empty line between these two blocks. > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); Use DRM_ERROR? > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + Remove empty line. > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3_lvds_dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3_lvds_dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3_lvds_dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > Best regards, Enric ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-05-31 7:48 ` Enric Balletbo Serra 0 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-05-31 7:48 UTC (permalink / raw) To: linux-arm-kernel Hi Peter, Just some comments that I received when I submitted my bridge patches and that I think can help you ... 2016-05-30 18:39 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > display bridge of the GE B850v3(imx6q-b850v3.dts). There are two physical > bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a > STDP2690(DP to DP++). However the physical bridges are automatically > configured by the input video signal, and the driver has no access to > the video processing pipeline. The driver is only needed to read EDID > from the STDP2690 and to handle HPD events from the STDP4028. The driver > communicates with both bridges over i2c. The video signal pipeline is as > follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 8 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ > 4 files changed, 414 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 3273ffa..7bb5e89 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5009,6 +5009,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..e9c32fc 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -52,4 +52,12 @@ config DRM_PARADE_PS8622 > > source "drivers/gpu/drm/bridge/analogix/Kconfig" > > +config DRM_GE_B850V3_LVDS_DP > + tristate "LVDS/DP bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + Driver for GE B850v3 DP2 LVDS to DP+ Bridge > + I think the maintainer prefers the entries ordered alphabetically (by vendor, then name), so your config should go before "config DRM_NXP_PTN3460" > endmenu > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..ba2e355 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -6,3 +6,4 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o Same here, this needs to be sorted by vendor, then name as well. Also I think is preferred use hyphens like the others drivers. Hmm, I see now that CONFIG_DRM_ANALOGIX_DP is not sorted, but I guess the preferred is be sorted. > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..37a4e7a > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,397 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_pre_enable(struct drm_bridge *bridge) > +{ > +} > + You don't need to keep empty callbacks for the (pre/post) enable/disable drm_bridge ops anymore so you can remove this function and the below. > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_post_disable(struct drm_bridge *bridge) > +{ > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + u8 *block; > + int num_modes; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); Move this assignation to ptn_bridge declaration > + client = ptn_bridge->edid_i2c; > + > + block = stdp2690_get_edid(client); I think you can remove the client variable and pass ptn_bridge->edid_i2c directly to the stdp2690_get_edid function. Note that get_modes is called several times and can be called from userspace, you are allocating memory on every call to get_modes that I think is not released or I'm missing something? > + ptn_bridge->edid = (struct edid *) block; > + What if block is NULL here ? > + drm_mode_connector_update_edid_property(connector, ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + > + return num_modes; nit: return drm_add_edid_modes(connector, ptn_bridge->edid); ? And remove num_modes ? > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_force(struct drm_connector *connector) > +{ > +} > + Guess you can remove this empty callback. > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > + .force = ge_b850v3_lvds_dp_connector_force, Remove .force > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > + Remove the empty line. > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge_b850v3_lvds_dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .pre_enable = ge_b850v3_lvds_dp_pre_enable, > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, > + .post_disable = ge_b850v3_lvds_dp_post_disable, Remove the above empty callbacks. > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + Remove this empty line between these two blocks. > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); Use DRM_ERROR? > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + Remove empty line. > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3_lvds_dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3_lvds_dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3_lvds_dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > Best regards, Enric ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-05-31 7:48 ` Enric Balletbo Serra 0 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-05-31 7:48 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Mark Rutland, dri-devel, Takashi Iwai, jslaby, Mauro Carvalho Chehab, linux, Thierry Reding, linux, martin.donnelly, devicetree, Pawel Moll, Ian Campbell, rmk+kernel, Rob Herring, linux-arm-kernel, Greg Kroah-Hartman, linux-kernel, kernel, Kumar Gala, Enric Balletbo i Serra, akpm, shawnguo, David Miller Hi Peter, Just some comments that I received when I submitted my bridge patches and that I think can help you ... 2016-05-30 18:39 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > display bridge of the GE B850v3(imx6q-b850v3.dts). There are two physical > bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a > STDP2690(DP to DP++). However the physical bridges are automatically > configured by the input video signal, and the driver has no access to > the video processing pipeline. The driver is only needed to read EDID > from the STDP2690 and to handle HPD events from the STDP4028. The driver > communicates with both bridges over i2c. The video signal pipeline is as > follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 8 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ > 4 files changed, 414 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 3273ffa..7bb5e89 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5009,6 +5009,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..e9c32fc 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -52,4 +52,12 @@ config DRM_PARADE_PS8622 > > source "drivers/gpu/drm/bridge/analogix/Kconfig" > > +config DRM_GE_B850V3_LVDS_DP > + tristate "LVDS/DP bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + Driver for GE B850v3 DP2 LVDS to DP+ Bridge > + I think the maintainer prefers the entries ordered alphabetically (by vendor, then name), so your config should go before "config DRM_NXP_PTN3460" > endmenu > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..ba2e355 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -6,3 +6,4 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o Same here, this needs to be sorted by vendor, then name as well. Also I think is preferred use hyphens like the others drivers. Hmm, I see now that CONFIG_DRM_ANALOGIX_DP is not sorted, but I guess the preferred is be sorted. > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..37a4e7a > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,397 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_pre_enable(struct drm_bridge *bridge) > +{ > +} > + You don't need to keep empty callbacks for the (pre/post) enable/disable drm_bridge ops anymore so you can remove this function and the below. > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_post_disable(struct drm_bridge *bridge) > +{ > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + u8 *block; > + int num_modes; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); Move this assignation to ptn_bridge declaration > + client = ptn_bridge->edid_i2c; > + > + block = stdp2690_get_edid(client); I think you can remove the client variable and pass ptn_bridge->edid_i2c directly to the stdp2690_get_edid function. Note that get_modes is called several times and can be called from userspace, you are allocating memory on every call to get_modes that I think is not released or I'm missing something? > + ptn_bridge->edid = (struct edid *) block; > + What if block is NULL here ? > + drm_mode_connector_update_edid_property(connector, ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + > + return num_modes; nit: return drm_add_edid_modes(connector, ptn_bridge->edid); ? And remove num_modes ? > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_force(struct drm_connector *connector) > +{ > +} > + Guess you can remove this empty callback. > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > + .force = ge_b850v3_lvds_dp_connector_force, Remove .force > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > + Remove the empty line. > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge_b850v3_lvds_dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .pre_enable = ge_b850v3_lvds_dp_pre_enable, > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, > + .post_disable = ge_b850v3_lvds_dp_post_disable, Remove the above empty callbacks. > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + Remove this empty line between these two blocks. > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); Use DRM_ERROR? > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + Remove empty line. > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3_lvds_dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3_lvds_dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3_lvds_dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > Best regards, Enric _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 5/5] arm/dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge 2016-05-30 16:39 ` Peter Senna Tschudin @ 2016-05-30 16:39 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Configure the GE B850v3 to use the LVDS/DP++ bridge. Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- arch/arm/boot/dts/imx6q-b850v3.dts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 88a70de..d366384 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -77,6 +77,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port@4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -147,3 +154,27 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3_lvds_dp_bridge { + compatible = "ge,b850v3_lvds_dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port@0 { + reg = <0>; + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH 5/5] arm/dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-05-30 16:39 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw) To: linux-arm-kernel Configure the GE B850v3 to use the LVDS/DP++ bridge. Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- arch/arm/boot/dts/imx6q-b850v3.dts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 88a70de..d366384 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -77,6 +77,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port at 4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -147,3 +154,27 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3_lvds_dp_bridge { + compatible = "ge,b850v3_lvds_dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port at 0 { + reg = <0>; + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH 5/5] arm/dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge 2016-05-30 16:39 ` Peter Senna Tschudin (?) @ 2016-05-30 16:54 ` Fabio Estevam -1 siblings, 0 replies; 172+ messages in thread From: Fabio Estevam @ 2016-05-30 16:54 UTC (permalink / raw) To: Peter Senna Tschudin Cc: David Airlie, Andrew Morton, David S. Miller, devicetree, DRI mailing list, enric.balletbo, Kumar Gala, Greg Kroah-Hartman, Heiko Stübner, Ian Campbell, Jiri Slaby, Sascha Hauer, linux-arm-kernel, Russell King - ARM Linux, linux-kernel, Guenter Roeck, Mark Rutland, Martin Donnelly, martyn.welch, Mauro Carvalho Chehab, Pawel Moll, Philipp Zabel, rmk+kernel, robh+dt, Shawn Guo, Takashi Iwai, Thierry Reding, Yakir Yang On Mon, May 30, 2016 at 1:39 PM, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3_lvds_dp_bridge { > + compatible = "ge,b850v3_lvds_dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; You use 'reg = <0x73>' here, but no @73. ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH 5/5] arm/dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-05-30 16:54 ` Fabio Estevam 0 siblings, 0 replies; 172+ messages in thread From: Fabio Estevam @ 2016-05-30 16:54 UTC (permalink / raw) To: linux-arm-kernel On Mon, May 30, 2016 at 1:39 PM, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3_lvds_dp_bridge { > + compatible = "ge,b850v3_lvds_dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; You use 'reg = <0x73>' here, but no @73. ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH 5/5] arm/dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-05-30 16:54 ` Fabio Estevam 0 siblings, 0 replies; 172+ messages in thread From: Fabio Estevam @ 2016-05-30 16:54 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Mark Rutland, DRI mailing list, Takashi Iwai, Jiri Slaby, Mauro Carvalho Chehab, Russell King - ARM Linux, Thierry Reding, Guenter Roeck, Martin Donnelly, devicetree, Pawel Moll, Ian Campbell, rmk+kernel, robh+dt, linux-arm-kernel, Greg Kroah-Hartman, linux-kernel, Sascha Hauer, Kumar Gala, enric.balletbo, Andrew Morton On Mon, May 30, 2016 at 1:39 PM, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3_lvds_dp_bridge { > + compatible = "ge,b850v3_lvds_dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; You use 'reg = <0x73>' here, but no @73. _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/5] Configure the mapping between IPUs and external displays on the dts file of the B850v3. Needed if connect two Full-HD monitors. [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [5/5] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Peter Senna Tschudin (5): drm/imx-ldb: Add support to drm-bridge dts/imx6q-b850v3: Configure IPU assignment order Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 36 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 121 ++++--- 7 files changed, 565 insertions(+), 42 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: linux-arm-kernel The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/5] Configure the mapping between IPUs and external displays on the dts file of the B850v3. Needed if connect two Full-HD monitors. [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [5/5] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Peter Senna Tschudin (5): drm/imx-ldb: Add support to drm-bridge dts/imx6q-b850v3: Configure IPU assignment order Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 36 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 121 ++++--- 7 files changed, 565 insertions(+), 42 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied-cv59FeDIM0c, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, davem-fT/PcQaiUtIeIZ0/mPfg9Q, devicetree-u79uwXL29TY76Z2rM5mHXA, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, galak-sgV2jX0FEOL9JmXXK+q4OQ, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, heiko-4mtYJXux2i+zQB+pC5nmwQ, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, jslaby-AlSwsSmVLrQ, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-I+IVW8TIWO2tmTQ+vhA3Yw, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-0h96xk9xTtrk1uMJSBkQmQ, mark.rutland-5wv7dgnIgG8, martin.donnelly-JJi787mZWgc, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, pawel.moll-5wv7dgnIgG8, peter.senna-ZGY8ohtN/8qB+jHODAdFcQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, tiwai-IBi9RG/b67k, treding-DDmLM1+adcrQT0dZR+AlfA, ykk-TNX95d0MmH7DzftRWevZcw The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/5] Configure the mapping between IPUs and external displays on the dts file of the B850v3. Needed if connect two Full-HD monitors. [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [5/5] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Peter Senna Tschudin (5): drm/imx-ldb: Add support to drm-bridge dts/imx6q-b850v3: Configure IPU assignment order Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 36 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 121 ++++--- 7 files changed, 565 insertions(+), 42 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Cc: David Airlie <airlied@linux.ie> Cc: Thierry Reding <treding@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 121 +++++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index beff793..55e94cc 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -58,7 +58,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -422,16 +426,27 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); - - if (imx_ldb_ch->panel) + if (imx_ldb_ch->panel) { + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + &imx_ldb_ch->encoder); + } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, - &imx_ldb_ch->encoder); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = &imx_ldb_ch->encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } return 0; } @@ -501,6 +516,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -508,7 +562,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -558,7 +611,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; ret = of_property_read_u32(child, "reg", &i); @@ -590,46 +642,31 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; + of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + /* + * If the bridge is compiled as a module, it may take + * some time until the bridge driver is available. + * Defer until the bridge driver is ready. + */ + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* Only does panel ddc work if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } channel->bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: linux-arm-kernel Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Cc: David Airlie <airlied@linux.ie> Cc: Thierry Reding <treding@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 121 +++++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index beff793..55e94cc 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -58,7 +58,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -422,16 +426,27 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); - - if (imx_ldb_ch->panel) + if (imx_ldb_ch->panel) { + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + &imx_ldb_ch->encoder); + } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, - &imx_ldb_ch->encoder); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = &imx_ldb_ch->encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } return 0; } @@ -501,6 +516,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -508,7 +562,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -558,7 +611,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; ret = of_property_read_u32(child, "reg", &i); @@ -590,46 +642,31 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; + of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + /* + * If the bridge is compiled as a module, it may take + * some time until the bridge driver is available. + * Defer until the bridge driver is ready. + */ + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* Only does panel ddc work if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } channel->bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied-cv59FeDIM0c, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, davem-fT/PcQaiUtIeIZ0/mPfg9Q, devicetree-u79uwXL29TY76Z2rM5mHXA, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, galak-sgV2jX0FEOL9JmXXK+q4OQ, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, heiko-4mtYJXux2i+zQB+pC5nmwQ, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, jslaby-AlSwsSmVLrQ, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-I+IVW8TIWO2tmTQ+vhA3Yw, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-0h96xk9xTtrk1uMJSBkQmQ, mark.rutland-5wv7dgnIgG8, martin.donnelly-JJi787mZWgc, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, pawel.moll-5wv7dgnIgG8, peter.senna-ZGY8ohtN/8qB+jHODAdFcQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, tiwai-IBi9RG/b67k, treding-DDmLM1+adcrQT0dZR+AlfA, ykk-TNX95d0MmH7DzftRWevZcw Cc: Rob Herring, Fabio Estevam Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> Cc: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> Cc: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> Cc: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> --- Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 121 +++++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index beff793..55e94cc 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -58,7 +58,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -422,16 +426,27 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); - - if (imx_ldb_ch->panel) + if (imx_ldb_ch->panel) { + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + &imx_ldb_ch->encoder); + } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, - &imx_ldb_ch->encoder); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = &imx_ldb_ch->encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } return 0; } @@ -501,6 +516,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -508,7 +562,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -558,7 +611,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; ret = of_property_read_u32(child, "reg", &i); @@ -590,46 +642,31 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; + of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + /* + * If the bridge is compiled as a module, it may take + * some time until the bridge driver is available. + * Defer until the bridge driver is ready. + */ + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* Only does panel ddc work if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } channel->bus_format = of_get_bus_format(dev, child); -- 2.5.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 2/5] dts/imx6q-b850v3: Configure IPU assignment order 2016-06-09 16:25 ` Peter Senna Tschudin (?) @ 2016-06-09 16:25 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam As the IPU has combined limitations across multiple crtcs, and as that can't be communicated to userspace at the moment, reorder the crtcs to allow support to two Full-HD monitors by avoiding assigning two monitors to a single IPU. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - New commit message arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..88a70de 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -51,6 +51,11 @@ chosen { stdout-path = &uart3; }; + + display-subsystem { + compatible = "fsl,imx-display-subsystem"; + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; + }; }; &clks { -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: linux-arm-kernel As the IPU has combined limitations across multiple crtcs, and as that can't be communicated to userspace at the moment, reorder the crtcs to allow support to two Full-HD monitors by avoiding assigning two monitors to a single IPU. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - New commit message arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..88a70de 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -51,6 +51,11 @@ chosen { stdout-path = &uart3; }; + + display-subsystem { + compatible = "fsl,imx-display-subsystem"; + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; + }; }; &clks { -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Fabio Estevam As the IPU has combined limitations across multiple crtcs, and as that can't be communicated to userspace at the moment, reorder the crtcs to allow support to two Full-HD monitors by avoiding assigning two monitors to a single IPU. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - New commit message arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..88a70de 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -51,6 +51,11 @@ chosen { stdout-path = &uart3; }; + + display-subsystem { + compatible = "fsl,imx-display-subsystem"; + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; + }; }; &clks { -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp 2016-06-09 16:25 ` Peter Senna Tschudin (?) @ 2016-06-09 16:25 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt new file mode 100644 index 0000000..46bbea9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt @@ -0,0 +1,38 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3_lvds_dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : should link to the gpio used as interrupt + source on the host. + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the vide signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port@0 { + reg = <0>; + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: linux-arm-kernel Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt new file mode 100644 index 0000000..46bbea9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt @@ -0,0 +1,38 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3_lvds_dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : should link to the gpio used as interrupt + source on the host. + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the vide signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge at 73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port at 0 { + reg = <0>; + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Fabio Estevam Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt new file mode 100644 index 0000000..46bbea9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt @@ -0,0 +1,38 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3_lvds_dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : should link to the gpio used as interrupt + source on the host. + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the vide signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port@0 { + reg = <0>; + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp 2016-06-09 16:25 ` Peter Senna Tschudin @ 2016-06-10 17:42 ` Rob Herring -1 siblings, 0 replies; 172+ messages in thread From: Rob Herring @ 2016-06-10 17:42 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, thierry.reding, rmk+kernel, shawnguo, tiwai, treding, ykk, Fabio Estevam On Thu, Jun 09, 2016 at 06:25:03PM +0200, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt Acked-by: Rob Herring <robh@kernel.org> ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-06-10 17:42 ` Rob Herring 0 siblings, 0 replies; 172+ messages in thread From: Rob Herring @ 2016-06-10 17:42 UTC (permalink / raw) To: linux-arm-kernel On Thu, Jun 09, 2016 at 06:25:03PM +0200, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt Acked-by: Rob Herring <robh@kernel.org> ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-06-10 18:54 ` Javier Martinez Canillas 0 siblings, 0 replies; 172+ messages in thread From: Javier Martinez Canillas @ 2016-06-10 18:54 UTC (permalink / raw) To: Peter Senna Tschudin Cc: David Airlie, akpm, David S. Miller, devicetree, dri-devel, Enric Balletbo i Serra, Enric Balletbo Serra, Kumar Gala, Greg Kroah-Hartman, Heiko Stübner, Ian Campbell, jslaby, Sascha Hauer, linux-arm-kernel, linux, Linux Kernel, Guenter Roeck, Mark Rutland, martin.donnelly, martyn.welch, Mauro Carvalho Chehab, Pawel Moll, peter.senna, p.zabel, Thierry Reding, rmk+kernel, Rob Herring, shawnguo, tiwai, Thierry Reding, ykk, Fabio Estevam Hello Peter, On Thu, Jun 9, 2016 at 12:25 PM, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > new file mode 100644 > index 0000000..46bbea9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > @@ -0,0 +1,38 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3_lvds_dp". It seems you forgot to replace '_' by '-' (you did in the example though) > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : should link to the gpio used as interrupt > + source on the host. Is the interrupt parent always a GPIO controller since that is what this description says. Shouldn't be instead something like instead? interrupt-parent: phandle of the interrupt controller that services interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the vide signal connection between the host s/vide/video > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port@0 { > + reg = <0>; AFAIU a unit-address and reg property for ports are only needed if you have more than one port according to Documentation/devicetree/bindings/graph.txt and Documentation/devicetree/bindings/media/video-interfaces.txt. > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- Best regards, Javier ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-06-10 18:54 ` Javier Martinez Canillas 0 siblings, 0 replies; 172+ messages in thread From: Javier Martinez Canillas @ 2016-06-10 18:54 UTC (permalink / raw) To: linux-arm-kernel Hello Peter, On Thu, Jun 9, 2016 at 12:25 PM, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > new file mode 100644 > index 0000000..46bbea9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > @@ -0,0 +1,38 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3_lvds_dp". It seems you forgot to replace '_' by '-' (you did in the example though) > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : should link to the gpio used as interrupt > + source on the host. Is the interrupt parent always a GPIO controller since that is what this description says. Shouldn't be instead something like instead? interrupt-parent: phandle of the interrupt controller that services interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the vide signal connection between the host s/vide/video > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge at 73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port at 0 { > + reg = <0>; AFAIU a unit-address and reg property for ports are only needed if you have more than one port according to Documentation/devicetree/bindings/graph.txt and Documentation/devicetree/bindings/media/video-interfaces.txt. > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- Best regards, Javier ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-06-10 18:54 ` Javier Martinez Canillas 0 siblings, 0 replies; 172+ messages in thread From: Javier Martinez Canillas @ 2016-06-10 18:54 UTC (permalink / raw) To: Peter Senna Tschudin Cc: David Airlie, akpm, David S. Miller, devicetree-u79uwXL29TY76Z2rM5mHXA, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Enric Balletbo i Serra, Enric Balletbo Serra, Kumar Gala, Greg Kroah-Hartman, Heiko Stübner, Ian Campbell, jslaby-AlSwsSmVLrQ, Sascha Hauer, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-I+IVW8TIWO2tmTQ+vhA3Yw, Linux Kernel, Guenter Roeck, Mark Rutland, martin.donnelly-JJi787mZWgc, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, Mauro Carvalho Chehab Hello Peter, On Thu, Jun 9, 2016 at 12:25 PM, Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > --- > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3_lvds_dp.txt | 38 ++++++++++++++++++++++ > 1 file changed, 38 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > new file mode 100644 > index 0000000..46bbea9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt > @@ -0,0 +1,38 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3_lvds_dp". It seems you forgot to replace '_' by '-' (you did in the example though) > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : should link to the gpio used as interrupt > + source on the host. Is the interrupt parent always a GPIO controller since that is what this description says. Shouldn't be instead something like instead? interrupt-parent: phandle of the interrupt controller that services interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the vide signal connection between the host s/vide/video > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port@0 { > + reg = <0>; AFAIU a unit-address and reg property for ports are only needed if you have more than one port according to Documentation/devicetree/bindings/graph.txt and Documentation/devicetree/bindings/media/video-interfaces.txt. > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- Best regards, Javier -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-06-09 16:25 ` Peter Senna Tschudin @ 2016-06-09 16:25 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam Add a driver that create a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> CC: David Airlie <airlied@linux.ie> CC: Thierry Reding <treding@nvidia.com> CC: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - New commit message - Removed 3 empty entry points - Removed memory leak from ge_b850v3_lvds_dp_get_modes() - Added a lock for mode setting - Removed a few blank lines - Changed the order at Makefile and Kconfig MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ 4 files changed, 412 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index 2ce5e91..2dd3d7f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5010,6 +5010,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Martin Donnelly <martin.donnelly@ge.com> +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 8f7423f..93dae5bd 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO Designware HDMI block. This is used in conjunction with the i.MX6 HDMI driver. +config DRM_GE_B850V3_LVDS_DP + tristate "GE B850v3 LVDS to DP++ display bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + This is a driver for the display bridge of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 96b13b3..47ea6c1 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..c73cd77 --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,392 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + + * + */ +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include "drm_crtc.h" +#include "drm_crtc_helper.h" +#include "drm_edid.h" +#include "drmP.h" + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; + struct mutex lock; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) +{ +} + +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) +{ +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + int num_modes = 0; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + mutex_lock(&ptn_bridge->lock); + + kfree(ptn_bridge->edid); + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); + + if (ptn_bridge->edid) { + drm_mode_connector_update_edid_property(connector, + ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + } + + mutex_unlock(&ptn_bridge->lock); + + return num_modes; +} + +static struct drm_encoder +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + + return ptn_bridge->bridge.encoder; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .best_encoder = ge_b850v3_lvds_dp_best_encoder, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = ge_b850v3_lvds_dp_connector_destroy, +}; + +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + mutex_lock(&ptn_bridge->lock); + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + mutex_unlock(&ptn_bridge->lock); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .enable = ge_b850v3_lvds_dp_enable, + .disable = ge_b850v3_lvds_dp_disable, + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + mutex_init(&ptn_bridge->lock); + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + /* Configures the bridge to re-enable interrupts after each ack */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3-lvds-dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3-lvds-dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "ge,b850v3-lvds-dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: linux-arm-kernel Add a driver that create a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> CC: David Airlie <airlied@linux.ie> CC: Thierry Reding <treding@nvidia.com> CC: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - New commit message - Removed 3 empty entry points - Removed memory leak from ge_b850v3_lvds_dp_get_modes() - Added a lock for mode setting - Removed a few blank lines - Changed the order at Makefile and Kconfig MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ 4 files changed, 412 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index 2ce5e91..2dd3d7f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5010,6 +5010,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Martin Donnelly <martin.donnelly@ge.com> +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 8f7423f..93dae5bd 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO Designware HDMI block. This is used in conjunction with the i.MX6 HDMI driver. +config DRM_GE_B850V3_LVDS_DP + tristate "GE B850v3 LVDS to DP++ display bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + This is a driver for the display bridge of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 96b13b3..47ea6c1 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..c73cd77 --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,392 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + + * + */ +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include "drm_crtc.h" +#include "drm_crtc_helper.h" +#include "drm_edid.h" +#include "drmP.h" + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; + struct mutex lock; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) +{ +} + +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) +{ +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + int num_modes = 0; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + mutex_lock(&ptn_bridge->lock); + + kfree(ptn_bridge->edid); + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); + + if (ptn_bridge->edid) { + drm_mode_connector_update_edid_property(connector, + ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + } + + mutex_unlock(&ptn_bridge->lock); + + return num_modes; +} + +static struct drm_encoder +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + + return ptn_bridge->bridge.encoder; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .best_encoder = ge_b850v3_lvds_dp_best_encoder, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = ge_b850v3_lvds_dp_connector_destroy, +}; + +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + mutex_lock(&ptn_bridge->lock); + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + mutex_unlock(&ptn_bridge->lock); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .enable = ge_b850v3_lvds_dp_enable, + .disable = ge_b850v3_lvds_dp_disable, + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + mutex_init(&ptn_bridge->lock); + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + /* Configures the bridge to re-enable interrupts after each ack */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3-lvds-dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3-lvds-dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "ge,b850v3-lvds-dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-10 7:39 ` Enric Balletbo Serra 0 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-06-10 7:39 UTC (permalink / raw) To: Peter Senna Tschudin Cc: David Airlie, akpm, David Miller, devicetree, dri-devel, Enric Balletbo i Serra, Kumar Gala, Greg Kroah-Hartman, Heiko Stübner, Ian Campbell, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, Guenter Roeck, Mark Rutland, martin.donnelly, martyn.welch, Mauro Carvalho Chehab, Pawel Moll, peter.senna, Philipp Zabel, Thierry Reding, rmk+kernel, Rob Herring, shawnguo, Takashi Iwai, Thierry Reding, Yakir Yang, Rob Herring, Fabio Estevam Hi Peter, Only a few comments ;) 2016-06-09 18:25 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > 4 files changed, 412 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 2ce5e91..2dd3d7f 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..93dae5bd 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..47ea6c1 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..c73cd77 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,392 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + You can remove this function, see http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L277 > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} > + And this one, see http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L182 > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, Remove the above empty callbacks. > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); Guess you need to free ptn_bridge->edid here. > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-10 7:39 ` Enric Balletbo Serra 0 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-06-10 7:39 UTC (permalink / raw) To: linux-arm-kernel Hi Peter, Only a few comments ;) 2016-06-09 18:25 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > 4 files changed, 412 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 2ce5e91..2dd3d7f 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..93dae5bd 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..47ea6c1 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..c73cd77 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,392 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + You can remove this function, see http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L277 > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} > + And this one, see http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L182 > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, Remove the above empty callbacks. > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); Guess you need to free ptn_bridge->edid here. > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-10 7:39 ` Enric Balletbo Serra 0 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-06-10 7:39 UTC (permalink / raw) To: Peter Senna Tschudin Cc: David Airlie, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, David Miller, devicetree-u79uwXL29TY76Z2rM5mHXA, dri-devel, Enric Balletbo i Serra, Kumar Gala, Greg Kroah-Hartman, Heiko Stübner, Ian Campbell, jslaby-AlSwsSmVLrQ, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-I+IVW8TIWO2tmTQ+vhA3Yw, linux-kernel-u79uwXL29TY76Z2rM5mHXA, Guenter Roeck, Mark Rutland, martin.donnelly-JJi787mZWgc, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, Mauro Carvalho Chehab, Pawel Moll, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, Philipp Zabel Hi Peter, Only a few comments ;) 2016-06-09 18:25 GMT+02:00 Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org>: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > CC: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> > CC: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> > CC: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > --- > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > 4 files changed, 412 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 2ce5e91..2dd3d7f 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > +M: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > +M: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..93dae5bd 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..47ea6c1 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..c73cd77 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,392 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + You can remove this function, see http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L277 > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} > + And this one, see http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L182 > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, Remove the above empty callbacks. > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); Guess you need to free ptn_bridge->edid here. > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-10 9:44 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-10 9:44 UTC (permalink / raw) To: Enric Balletbo Serra Cc: linux-kernel, Kumar Gala, Philipp Zabel, Rob Herring, martin.donnelly, Ian Campbell, Rob Herring, akpm, linux-arm-kernel, dri-devel, Pawel Moll, Thierry Reding, Guenter Roeck, Greg Kroah-Hartman, Thierry Reding, Peter Senna Tschudin, Fabio Estevam, jslaby, shawnguo, David Miller, martyn.welch, linux, Enric Balletbo i Serra, peter.senna, Yakir Yang, Mark Rutland, Heiko Stübner, rmk+kernel, David Airlie, kernel, Takashi Iwai, devicetree, Mauro Carvalho Chehab Hi Enric, On Friday, June 10, 2016 09:39 CEST, Enric Balletbo Serra <eballetbo@gmail.com> wrote: > Hi Peter, > > Only a few comments ;) Thanks a lot for the review! > > 2016-06-09 18:25 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > > Add a driver that create a drm_bridge and a drm_connector for the LVDS > > to DP++ display bridge of the GE B850v3. > > > > There are two physical bridges on the video signal pipeline: a > > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > > firmware made it complicated for this binding to comprise two device > > tree nodes, as the design goal is to configure both bridges based on > > the LVDS signal, which leave the driver powerless to control the video > > processing pipeline. The two bridges behaves as a single bridge, and > > the driver is only needed for telling the host about EDID / HPD, and > > for giving the host powers to ack interrupts. The video signal pipeline > > is as follows: > > > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > Cc: Rob Herring <robh@kernel.org> > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > CC: David Airlie <airlied@linux.ie> > > CC: Thierry Reding <treding@nvidia.com> > > CC: Thierry Reding <thierry.reding@gmail.com> > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > --- > > Changes from V1: > > - New commit message > > - Removed 3 empty entry points > > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > > - Added a lock for mode setting > > - Removed a few blank lines > > - Changed the order at Makefile and Kconfig > > > > MAINTAINERS | 8 + > > drivers/gpu/drm/bridge/Kconfig | 11 + > > drivers/gpu/drm/bridge/Makefile | 1 + > > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > > 4 files changed, 412 insertions(+) > > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index 2ce5e91..2dd3d7f 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > > S: Maintained > > F: drivers/media/radio/radio-gemtek* > > > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > > +M: Martin Donnelly <martin.donnelly@ge.com> > > +M: Peter Senna Tschudin <peter.senna@collabora.com> > > +M: Martyn Welch <martyn.welch@collabora.co.uk> > > +S: Maintained > > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > > + > > GENERIC GPIO I2C DRIVER > > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > > S: Supported > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > > index 8f7423f..93dae5bd 100644 > > --- a/drivers/gpu/drm/bridge/Kconfig > > +++ b/drivers/gpu/drm/bridge/Kconfig > > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > > Designware HDMI block. This is used in conjunction with > > the i.MX6 HDMI driver. > > > > +config DRM_GE_B850V3_LVDS_DP > > + tristate "GE B850v3 LVDS to DP++ display bridge" > > + depends on OF > > + select DRM_KMS_HELPER > > + select DRM_PANEL > > + ---help--- > > + This is a driver for the display bridge of > > + GE B850v3 that convert dual channel LVDS > > + to DP++. This is used with the i.MX6 imx-ldb > > + driver. > > + > > config DRM_NXP_PTN3460 > > tristate "NXP PTN3460 DP/LVDS bridge" > > depends on OF > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > > index 96b13b3..47ea6c1 100644 > > --- a/drivers/gpu/drm/bridge/Makefile > > +++ b/drivers/gpu/drm/bridge/Makefile > > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > new file mode 100644 > > index 0000000..c73cd77 > > --- /dev/null > > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > @@ -0,0 +1,392 @@ > > +/* > > + * Driver for GE B850v3 DP display bridge > > + > > + * Copyright (c) 2016, Collabora Ltd. > > + * Copyright (c) 2016, General Electric Company > > + > > + * This program is free software; you can redistribute it and/or modify it > > + * under the terms and conditions of the GNU General Public License, > > + * version 2, as published by the Free Software Foundation. > > + > > + * This program is distributed in the hope it will be useful, but WITHOUT > > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > > + * more details. > > + > > + * You should have received a copy of the GNU General Public License > > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > > + > > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > > + * display bridge of the GE B850v3. There are two physical bridges on the video > > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > > + * the physical bridges are automatically configured by the input video signal, > > + * and the driver has no access to the video processing pipeline. The driver is > > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > > + * STDP4028. The driver communicates with both bridges over i2c. The video > > + * signal pipeline is as follows: > > + * > > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > + > > + * > > + */ > > +#include <linux/gpio.h> > > +#include <linux/i2c.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include "drm_crtc.h" > > +#include "drm_crtc_helper.h" > > +#include "drm_edid.h" > > +#include "drmP.h" > > + > > +#define EDID_EXT_BLOCK_CNT 0x7E > > + > > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > > +#define STDP4028_DPTX_STS_REG 0x3E > > + > > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > > + > > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > > +#define STDP4028_DPTX_IRQ_CONFIG \ > > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > > + > > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > > +#define STDP4028_DPTX_LINK_STS 0x1000 > > +#define STDP4028_CON_STATE_CONNECTED \ > > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > > + > > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > > +#define STDP4028_DPTX_IRQ_CLEAR \ > > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > > + > > +struct ge_b850v3_lvds_dp { > > + struct drm_connector connector; > > + struct drm_bridge bridge; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > > + struct i2c_client *edid_i2c; > > + struct edid *edid; > > + struct mutex lock; > > +}; > > + > > +static inline struct ge_b850v3_lvds_dp * > > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > > +{ > > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > > +} > > + > > +static inline struct ge_b850v3_lvds_dp * > > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > > +{ > > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > > +} > > + > > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > > +{ > > +} > > + > > You can remove this function, see > http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L277 You are right, I removed the empty callbacks, but they returned due me doing something wrong when rebasing. V3 will be out soon. > > > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > > +{ > > +} > > + > > And this one, see > http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L182 Same. This should not be here. > > > +u8 *stdp2690_get_edid(struct i2c_client *client) > > +{ > > + struct i2c_adapter *adapter = client->adapter; > > + unsigned char start = 0x00; > > + unsigned int total_size; > > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > > + > > + struct i2c_msg msgs[] = { > > + { > > + .addr = client->addr, > > + .flags = 0, > > + .len = 1, > > + .buf = &start, > > + }, { > > + .addr = client->addr, > > + .flags = I2C_M_RD, > > + .len = EDID_LENGTH, > > + .buf = block, > > + } > > + }; > > + > > + if (!block) > > + return NULL; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID.\n"); > > + goto err; > > + } > > + > > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > > + DRM_ERROR("Invalid EDID block\n"); > > + goto err; > > + } > > + > > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > > + if (total_size > EDID_LENGTH) { > > + kfree(block); > > + block = kmalloc(total_size, GFP_KERNEL); > > + if (!block) > > + return NULL; > > + > > + /* Yes, read the entire buffer, and do not skip the first > > + * EDID_LENGTH bytes. > > + */ > > + start = 0x00; > > + msgs[1].len = total_size; > > + msgs[1].buf = block; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > > + goto err; > > + } > > + } > > + > > + return block; > > + > > +err: > > + kfree(block); > > + return NULL; > > +} > > + > > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge; > > + struct i2c_client *client; > > + int num_modes = 0; > > + > > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > > + client = ptn_bridge->edid_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + kfree(ptn_bridge->edid); > > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > > + > > + if (ptn_bridge->edid) { > > + drm_mode_connector_update_edid_property(connector, > > + ptn_bridge->edid); > > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > > + } > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + return num_modes; > > +} > > + > > +static struct drm_encoder > > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + connector_to_ge_b850v3_lvds_dp(connector); > > + > > + return ptn_bridge->bridge.encoder; > > +} > > + > > +static const struct > > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > > + .get_modes = ge_b850v3_lvds_dp_get_modes, > > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > > +}; > > + > > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > > + struct drm_connector *connector, bool force) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + connector_to_ge_b850v3_lvds_dp(connector); > > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + s32 link_state; > > + > > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_STS_REG); > > + > > + if (link_state == STDP4028_CON_STATE_CONNECTED) > > + return connector_status_connected; > > + > > + if (link_state == 0) > > + return connector_status_disconnected; > > + > > + return connector_status_unknown; > > +} > > + > > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > > +{ > > + drm_connector_cleanup(connector); > > +} > > + > > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > > + .dpms = drm_helper_connector_dpms, > > + .fill_modes = drm_helper_probe_single_connector_modes, > > + .detect = ge_b850v3_lvds_dp_detect, > > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > > +}; > > + > > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + if (ptn_bridge->connector.dev) > > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > > + > > + return IRQ_HANDLED; > > +} > > + > > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge > > + = bridge_to_ge_b850v3_lvds_dp(bridge); > > + struct drm_connector *connector = &ptn_bridge->connector; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + int ret; > > + > > + if (!bridge->encoder) { > > + DRM_ERROR("Parent encoder object not found"); > > + return -ENODEV; > > + } > > + > > + connector->polled = DRM_CONNECTOR_POLL_HPD; > > + > > + drm_connector_helper_add(connector, > > + &ge_b850v3_lvds_dp_connector_helper_funcs); > > + > > + ret = drm_connector_init(bridge->dev, connector, > > + &ge_b850v3_lvds_dp_connector_funcs, > > + DRM_MODE_CONNECTOR_DisplayPort); > > + if (ret) { > > + DRM_ERROR("Failed to initialize connector with drm\n"); > > + return ret; > > + } > > + > > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > > + if (ret) > > + return ret; > > + > > + drm_bridge_enable(bridge); > > + if (ge_b850v3_lvds_dp_i2c->irq) { > > + drm_helper_hpd_irq_event(connector->dev); > > + > > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > > + ge_b850v3_lvds_dp_i2c->irq, NULL, > > + ge_b850v3_lvds_dp_irq_handler, > > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > > + "ge-b850v3-lvds-dp", ptn_bridge); > > + if (ret) > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > > + .enable = ge_b850v3_lvds_dp_enable, > > + .disable = ge_b850v3_lvds_dp_disable, > > Remove the above empty callbacks. Here too. > > > + .attach = ge_b850v3_lvds_dp_attach, > > +}; > > + > > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > > + const struct i2c_device_id *id) > > +{ > > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > > + struct ge_b850v3_lvds_dp *ptn_bridge; > > + int ret; > > + u32 edid_i2c_reg; > > + > > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > > + if (!ptn_bridge) > > + return -ENOMEM; > > + > > + mutex_init(&ptn_bridge->lock); > > + > > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > > + ptn_bridge->bridge.driver_private = ptn_bridge; > > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > > + > > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > > + if (ret) { > > + dev_err(dev, "edid-reg not specified, aborting...\n"); > > + return -ENODEV; > > + } > > + > > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > > + sizeof(struct i2c_client), GFP_KERNEL); > > + > > + if (!ptn_bridge->edid_i2c) > > + return -ENOMEM; > > + > > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > > + sizeof(struct i2c_client)); > > + > > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > > + > > + /* Configures the bridge to re-enable interrupts after each ack */ > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > > + > > + /* Clear pending interrupts since power up. */ > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > > + > > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > > + ptn_bridge->bridge.of_node = dev->of_node; > > + ret = drm_bridge_add(&ptn_bridge->bridge); > > + if (ret) { > > + DRM_ERROR("Failed to add bridge\n"); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > > + > > + drm_bridge_remove(&ptn_bridge->bridge); > > Guess you need to free ptn_bridge->edid here. Thanks a lot! I'll fix it when sending V3. Thank you for the review! ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-10 9:44 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-10 9:44 UTC (permalink / raw) To: linux-arm-kernel Hi Enric, On Friday, June 10, 2016 09:39 CEST, Enric Balletbo Serra <eballetbo@gmail.com> wrote: > Hi Peter, > > Only a few comments ;) Thanks a lot for the review! > > 2016-06-09 18:25 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > > Add a driver that create a drm_bridge and a drm_connector for the LVDS > > to DP++ display bridge of the GE B850v3. > > > > There are two physical bridges on the video signal pipeline: a > > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > > firmware made it complicated for this binding to comprise two device > > tree nodes, as the design goal is to configure both bridges based on > > the LVDS signal, which leave the driver powerless to control the video > > processing pipeline. The two bridges behaves as a single bridge, and > > the driver is only needed for telling the host about EDID / HPD, and > > for giving the host powers to ack interrupts. The video signal pipeline > > is as follows: > > > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > Cc: Rob Herring <robh@kernel.org> > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > CC: David Airlie <airlied@linux.ie> > > CC: Thierry Reding <treding@nvidia.com> > > CC: Thierry Reding <thierry.reding@gmail.com> > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > --- > > Changes from V1: > > - New commit message > > - Removed 3 empty entry points > > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > > - Added a lock for mode setting > > - Removed a few blank lines > > - Changed the order at Makefile and Kconfig > > > > MAINTAINERS | 8 + > > drivers/gpu/drm/bridge/Kconfig | 11 + > > drivers/gpu/drm/bridge/Makefile | 1 + > > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > > 4 files changed, 412 insertions(+) > > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index 2ce5e91..2dd3d7f 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > > S: Maintained > > F: drivers/media/radio/radio-gemtek* > > > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > > +M: Martin Donnelly <martin.donnelly@ge.com> > > +M: Peter Senna Tschudin <peter.senna@collabora.com> > > +M: Martyn Welch <martyn.welch@collabora.co.uk> > > +S: Maintained > > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > > + > > GENERIC GPIO I2C DRIVER > > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > > S: Supported > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > > index 8f7423f..93dae5bd 100644 > > --- a/drivers/gpu/drm/bridge/Kconfig > > +++ b/drivers/gpu/drm/bridge/Kconfig > > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > > Designware HDMI block. This is used in conjunction with > > the i.MX6 HDMI driver. > > > > +config DRM_GE_B850V3_LVDS_DP > > + tristate "GE B850v3 LVDS to DP++ display bridge" > > + depends on OF > > + select DRM_KMS_HELPER > > + select DRM_PANEL > > + ---help--- > > + This is a driver for the display bridge of > > + GE B850v3 that convert dual channel LVDS > > + to DP++. This is used with the i.MX6 imx-ldb > > + driver. > > + > > config DRM_NXP_PTN3460 > > tristate "NXP PTN3460 DP/LVDS bridge" > > depends on OF > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > > index 96b13b3..47ea6c1 100644 > > --- a/drivers/gpu/drm/bridge/Makefile > > +++ b/drivers/gpu/drm/bridge/Makefile > > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > new file mode 100644 > > index 0000000..c73cd77 > > --- /dev/null > > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > @@ -0,0 +1,392 @@ > > +/* > > + * Driver for GE B850v3 DP display bridge > > + > > + * Copyright (c) 2016, Collabora Ltd. > > + * Copyright (c) 2016, General Electric Company > > + > > + * This program is free software; you can redistribute it and/or modify it > > + * under the terms and conditions of the GNU General Public License, > > + * version 2, as published by the Free Software Foundation. > > + > > + * This program is distributed in the hope it will be useful, but WITHOUT > > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > > + * more details. > > + > > + * You should have received a copy of the GNU General Public License > > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > > + > > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > > + * display bridge of the GE B850v3. There are two physical bridges on the video > > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > > + * the physical bridges are automatically configured by the input video signal, > > + * and the driver has no access to the video processing pipeline. The driver is > > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > > + * STDP4028. The driver communicates with both bridges over i2c. The video > > + * signal pipeline is as follows: > > + * > > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > + > > + * > > + */ > > +#include <linux/gpio.h> > > +#include <linux/i2c.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include "drm_crtc.h" > > +#include "drm_crtc_helper.h" > > +#include "drm_edid.h" > > +#include "drmP.h" > > + > > +#define EDID_EXT_BLOCK_CNT 0x7E > > + > > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > > +#define STDP4028_DPTX_STS_REG 0x3E > > + > > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > > + > > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > > +#define STDP4028_DPTX_IRQ_CONFIG \ > > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > > + > > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > > +#define STDP4028_DPTX_LINK_STS 0x1000 > > +#define STDP4028_CON_STATE_CONNECTED \ > > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > > + > > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > > +#define STDP4028_DPTX_IRQ_CLEAR \ > > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > > + > > +struct ge_b850v3_lvds_dp { > > + struct drm_connector connector; > > + struct drm_bridge bridge; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > > + struct i2c_client *edid_i2c; > > + struct edid *edid; > > + struct mutex lock; > > +}; > > + > > +static inline struct ge_b850v3_lvds_dp * > > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > > +{ > > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > > +} > > + > > +static inline struct ge_b850v3_lvds_dp * > > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > > +{ > > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > > +} > > + > > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > > +{ > > +} > > + > > You can remove this function, see > http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L277 You are right, I removed the empty callbacks, but they returned due me doing something wrong when rebasing. V3 will be out soon. > > > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > > +{ > > +} > > + > > And this one, see > http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L182 Same. This should not be here. > > > +u8 *stdp2690_get_edid(struct i2c_client *client) > > +{ > > + struct i2c_adapter *adapter = client->adapter; > > + unsigned char start = 0x00; > > + unsigned int total_size; > > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > > + > > + struct i2c_msg msgs[] = { > > + { > > + .addr = client->addr, > > + .flags = 0, > > + .len = 1, > > + .buf = &start, > > + }, { > > + .addr = client->addr, > > + .flags = I2C_M_RD, > > + .len = EDID_LENGTH, > > + .buf = block, > > + } > > + }; > > + > > + if (!block) > > + return NULL; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID.\n"); > > + goto err; > > + } > > + > > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > > + DRM_ERROR("Invalid EDID block\n"); > > + goto err; > > + } > > + > > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > > + if (total_size > EDID_LENGTH) { > > + kfree(block); > > + block = kmalloc(total_size, GFP_KERNEL); > > + if (!block) > > + return NULL; > > + > > + /* Yes, read the entire buffer, and do not skip the first > > + * EDID_LENGTH bytes. > > + */ > > + start = 0x00; > > + msgs[1].len = total_size; > > + msgs[1].buf = block; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > > + goto err; > > + } > > + } > > + > > + return block; > > + > > +err: > > + kfree(block); > > + return NULL; > > +} > > + > > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge; > > + struct i2c_client *client; > > + int num_modes = 0; > > + > > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > > + client = ptn_bridge->edid_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + kfree(ptn_bridge->edid); > > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > > + > > + if (ptn_bridge->edid) { > > + drm_mode_connector_update_edid_property(connector, > > + ptn_bridge->edid); > > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > > + } > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + return num_modes; > > +} > > + > > +static struct drm_encoder > > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + connector_to_ge_b850v3_lvds_dp(connector); > > + > > + return ptn_bridge->bridge.encoder; > > +} > > + > > +static const struct > > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > > + .get_modes = ge_b850v3_lvds_dp_get_modes, > > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > > +}; > > + > > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > > + struct drm_connector *connector, bool force) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + connector_to_ge_b850v3_lvds_dp(connector); > > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + s32 link_state; > > + > > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_STS_REG); > > + > > + if (link_state == STDP4028_CON_STATE_CONNECTED) > > + return connector_status_connected; > > + > > + if (link_state == 0) > > + return connector_status_disconnected; > > + > > + return connector_status_unknown; > > +} > > + > > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > > +{ > > + drm_connector_cleanup(connector); > > +} > > + > > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > > + .dpms = drm_helper_connector_dpms, > > + .fill_modes = drm_helper_probe_single_connector_modes, > > + .detect = ge_b850v3_lvds_dp_detect, > > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > > +}; > > + > > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + if (ptn_bridge->connector.dev) > > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > > + > > + return IRQ_HANDLED; > > +} > > + > > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge > > + = bridge_to_ge_b850v3_lvds_dp(bridge); > > + struct drm_connector *connector = &ptn_bridge->connector; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + int ret; > > + > > + if (!bridge->encoder) { > > + DRM_ERROR("Parent encoder object not found"); > > + return -ENODEV; > > + } > > + > > + connector->polled = DRM_CONNECTOR_POLL_HPD; > > + > > + drm_connector_helper_add(connector, > > + &ge_b850v3_lvds_dp_connector_helper_funcs); > > + > > + ret = drm_connector_init(bridge->dev, connector, > > + &ge_b850v3_lvds_dp_connector_funcs, > > + DRM_MODE_CONNECTOR_DisplayPort); > > + if (ret) { > > + DRM_ERROR("Failed to initialize connector with drm\n"); > > + return ret; > > + } > > + > > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > > + if (ret) > > + return ret; > > + > > + drm_bridge_enable(bridge); > > + if (ge_b850v3_lvds_dp_i2c->irq) { > > + drm_helper_hpd_irq_event(connector->dev); > > + > > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > > + ge_b850v3_lvds_dp_i2c->irq, NULL, > > + ge_b850v3_lvds_dp_irq_handler, > > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > > + "ge-b850v3-lvds-dp", ptn_bridge); > > + if (ret) > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > > + .enable = ge_b850v3_lvds_dp_enable, > > + .disable = ge_b850v3_lvds_dp_disable, > > Remove the above empty callbacks. Here too. > > > + .attach = ge_b850v3_lvds_dp_attach, > > +}; > > + > > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > > + const struct i2c_device_id *id) > > +{ > > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > > + struct ge_b850v3_lvds_dp *ptn_bridge; > > + int ret; > > + u32 edid_i2c_reg; > > + > > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > > + if (!ptn_bridge) > > + return -ENOMEM; > > + > > + mutex_init(&ptn_bridge->lock); > > + > > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > > + ptn_bridge->bridge.driver_private = ptn_bridge; > > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > > + > > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > > + if (ret) { > > + dev_err(dev, "edid-reg not specified, aborting...\n"); > > + return -ENODEV; > > + } > > + > > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > > + sizeof(struct i2c_client), GFP_KERNEL); > > + > > + if (!ptn_bridge->edid_i2c) > > + return -ENOMEM; > > + > > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > > + sizeof(struct i2c_client)); > > + > > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > > + > > + /* Configures the bridge to re-enable interrupts after each ack */ > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > > + > > + /* Clear pending interrupts since power up. */ > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > > + > > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > > + ptn_bridge->bridge.of_node = dev->of_node; > > + ret = drm_bridge_add(&ptn_bridge->bridge); > > + if (ret) { > > + DRM_ERROR("Failed to add bridge\n"); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > > + > > + drm_bridge_remove(&ptn_bridge->bridge); > > Guess you need to free ptn_bridge->edid here. Thanks a lot! I'll fix it when sending V3. Thank you for the review! ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-10 9:44 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-10 9:44 UTC (permalink / raw) To: Enric Balletbo Serra Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Kumar Gala, Philipp Zabel, Rob Herring, martin.donnelly-JJi787mZWgc, Ian Campbell, Rob Herring, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, linux-arm-kernel@lists.infradead.org, dri-devel, Pawel Moll, Thierry Reding, Guenter Roeck, Greg Kroah-Hartman, Thierry Reding, Peter Senna Tschudin, Fabio Estevam, jslaby-AlSwsSmVLrQ, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, David Miller, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, linux-I+IVW8TIWO2tmTQ+vhA3Yw Hi Enric, On Friday, June 10, 2016 09:39 CEST, Enric Balletbo Serra <eballetbo-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > Hi Peter, > > Only a few comments ;) Thanks a lot for the review! > > 2016-06-09 18:25 GMT+02:00 Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org>: > > Add a driver that create a drm_bridge and a drm_connector for the LVDS > > to DP++ display bridge of the GE B850v3. > > > > There are two physical bridges on the video signal pipeline: a > > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > > firmware made it complicated for this binding to comprise two device > > tree nodes, as the design goal is to configure both bridges based on > > the LVDS signal, which leave the driver powerless to control the video > > processing pipeline. The two bridges behaves as a single bridge, and > > the driver is only needed for telling the host about EDID / HPD, and > > for giving the host powers to ack interrupts. The video signal pipeline > > is as follows: > > > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > > > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > > CC: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> > > CC: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> > > CC: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > > --- > > Changes from V1: > > - New commit message > > - Removed 3 empty entry points > > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > > - Added a lock for mode setting > > - Removed a few blank lines > > - Changed the order at Makefile and Kconfig > > > > MAINTAINERS | 8 + > > drivers/gpu/drm/bridge/Kconfig | 11 + > > drivers/gpu/drm/bridge/Makefile | 1 + > > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > > 4 files changed, 412 insertions(+) > > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index 2ce5e91..2dd3d7f 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > > S: Maintained > > F: drivers/media/radio/radio-gemtek* > > > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > > +M: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > > +M: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > > +M: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > > +S: Maintained > > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > > + > > GENERIC GPIO I2C DRIVER > > M: Haavard Skinnemoen <hskinnemoen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > > S: Supported > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > > index 8f7423f..93dae5bd 100644 > > --- a/drivers/gpu/drm/bridge/Kconfig > > +++ b/drivers/gpu/drm/bridge/Kconfig > > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > > Designware HDMI block. This is used in conjunction with > > the i.MX6 HDMI driver. > > > > +config DRM_GE_B850V3_LVDS_DP > > + tristate "GE B850v3 LVDS to DP++ display bridge" > > + depends on OF > > + select DRM_KMS_HELPER > > + select DRM_PANEL > > + ---help--- > > + This is a driver for the display bridge of > > + GE B850v3 that convert dual channel LVDS > > + to DP++. This is used with the i.MX6 imx-ldb > > + driver. > > + > > config DRM_NXP_PTN3460 > > tristate "NXP PTN3460 DP/LVDS bridge" > > depends on OF > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > > index 96b13b3..47ea6c1 100644 > > --- a/drivers/gpu/drm/bridge/Makefile > > +++ b/drivers/gpu/drm/bridge/Makefile > > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > new file mode 100644 > > index 0000000..c73cd77 > > --- /dev/null > > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > @@ -0,0 +1,392 @@ > > +/* > > + * Driver for GE B850v3 DP display bridge > > + > > + * Copyright (c) 2016, Collabora Ltd. > > + * Copyright (c) 2016, General Electric Company > > + > > + * This program is free software; you can redistribute it and/or modify it > > + * under the terms and conditions of the GNU General Public License, > > + * version 2, as published by the Free Software Foundation. > > + > > + * This program is distributed in the hope it will be useful, but WITHOUT > > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > > + * more details. > > + > > + * You should have received a copy of the GNU General Public License > > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > > + > > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > > + * display bridge of the GE B850v3. There are two physical bridges on the video > > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > > + * the physical bridges are automatically configured by the input video signal, > > + * and the driver has no access to the video processing pipeline. The driver is > > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > > + * STDP4028. The driver communicates with both bridges over i2c. The video > > + * signal pipeline is as follows: > > + * > > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > + > > + * > > + */ > > +#include <linux/gpio.h> > > +#include <linux/i2c.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include "drm_crtc.h" > > +#include "drm_crtc_helper.h" > > +#include "drm_edid.h" > > +#include "drmP.h" > > + > > +#define EDID_EXT_BLOCK_CNT 0x7E > > + > > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > > +#define STDP4028_DPTX_STS_REG 0x3E > > + > > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > > + > > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > > +#define STDP4028_DPTX_IRQ_CONFIG \ > > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > > + > > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > > +#define STDP4028_DPTX_LINK_STS 0x1000 > > +#define STDP4028_CON_STATE_CONNECTED \ > > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > > + > > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > > +#define STDP4028_DPTX_IRQ_CLEAR \ > > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > > + > > +struct ge_b850v3_lvds_dp { > > + struct drm_connector connector; > > + struct drm_bridge bridge; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > > + struct i2c_client *edid_i2c; > > + struct edid *edid; > > + struct mutex lock; > > +}; > > + > > +static inline struct ge_b850v3_lvds_dp * > > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > > +{ > > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > > +} > > + > > +static inline struct ge_b850v3_lvds_dp * > > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > > +{ > > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > > +} > > + > > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > > +{ > > +} > > + > > You can remove this function, see > http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L277 You are right, I removed the empty callbacks, but they returned due me doing something wrong when rebasing. V3 will be out soon. > > > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > > +{ > > +} > > + > > And this one, see > http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L182 Same. This should not be here. > > > +u8 *stdp2690_get_edid(struct i2c_client *client) > > +{ > > + struct i2c_adapter *adapter = client->adapter; > > + unsigned char start = 0x00; > > + unsigned int total_size; > > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > > + > > + struct i2c_msg msgs[] = { > > + { > > + .addr = client->addr, > > + .flags = 0, > > + .len = 1, > > + .buf = &start, > > + }, { > > + .addr = client->addr, > > + .flags = I2C_M_RD, > > + .len = EDID_LENGTH, > > + .buf = block, > > + } > > + }; > > + > > + if (!block) > > + return NULL; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID.\n"); > > + goto err; > > + } > > + > > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > > + DRM_ERROR("Invalid EDID block\n"); > > + goto err; > > + } > > + > > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > > + if (total_size > EDID_LENGTH) { > > + kfree(block); > > + block = kmalloc(total_size, GFP_KERNEL); > > + if (!block) > > + return NULL; > > + > > + /* Yes, read the entire buffer, and do not skip the first > > + * EDID_LENGTH bytes. > > + */ > > + start = 0x00; > > + msgs[1].len = total_size; > > + msgs[1].buf = block; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > > + goto err; > > + } > > + } > > + > > + return block; > > + > > +err: > > + kfree(block); > > + return NULL; > > +} > > + > > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge; > > + struct i2c_client *client; > > + int num_modes = 0; > > + > > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > > + client = ptn_bridge->edid_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + kfree(ptn_bridge->edid); > > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > > + > > + if (ptn_bridge->edid) { > > + drm_mode_connector_update_edid_property(connector, > > + ptn_bridge->edid); > > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > > + } > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + return num_modes; > > +} > > + > > +static struct drm_encoder > > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + connector_to_ge_b850v3_lvds_dp(connector); > > + > > + return ptn_bridge->bridge.encoder; > > +} > > + > > +static const struct > > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > > + .get_modes = ge_b850v3_lvds_dp_get_modes, > > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > > +}; > > + > > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > > + struct drm_connector *connector, bool force) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + connector_to_ge_b850v3_lvds_dp(connector); > > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + s32 link_state; > > + > > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_STS_REG); > > + > > + if (link_state == STDP4028_CON_STATE_CONNECTED) > > + return connector_status_connected; > > + > > + if (link_state == 0) > > + return connector_status_disconnected; > > + > > + return connector_status_unknown; > > +} > > + > > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > > +{ > > + drm_connector_cleanup(connector); > > +} > > + > > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > > + .dpms = drm_helper_connector_dpms, > > + .fill_modes = drm_helper_probe_single_connector_modes, > > + .detect = ge_b850v3_lvds_dp_detect, > > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > > +}; > > + > > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + if (ptn_bridge->connector.dev) > > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > > + > > + return IRQ_HANDLED; > > +} > > + > > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge > > + = bridge_to_ge_b850v3_lvds_dp(bridge); > > + struct drm_connector *connector = &ptn_bridge->connector; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + int ret; > > + > > + if (!bridge->encoder) { > > + DRM_ERROR("Parent encoder object not found"); > > + return -ENODEV; > > + } > > + > > + connector->polled = DRM_CONNECTOR_POLL_HPD; > > + > > + drm_connector_helper_add(connector, > > + &ge_b850v3_lvds_dp_connector_helper_funcs); > > + > > + ret = drm_connector_init(bridge->dev, connector, > > + &ge_b850v3_lvds_dp_connector_funcs, > > + DRM_MODE_CONNECTOR_DisplayPort); > > + if (ret) { > > + DRM_ERROR("Failed to initialize connector with drm\n"); > > + return ret; > > + } > > + > > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > > + if (ret) > > + return ret; > > + > > + drm_bridge_enable(bridge); > > + if (ge_b850v3_lvds_dp_i2c->irq) { > > + drm_helper_hpd_irq_event(connector->dev); > > + > > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > > + ge_b850v3_lvds_dp_i2c->irq, NULL, > > + ge_b850v3_lvds_dp_irq_handler, > > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > > + "ge-b850v3-lvds-dp", ptn_bridge); > > + if (ret) > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > > + .enable = ge_b850v3_lvds_dp_enable, > > + .disable = ge_b850v3_lvds_dp_disable, > > Remove the above empty callbacks. Here too. > > > + .attach = ge_b850v3_lvds_dp_attach, > > +}; > > + > > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > > + const struct i2c_device_id *id) > > +{ > > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > > + struct ge_b850v3_lvds_dp *ptn_bridge; > > + int ret; > > + u32 edid_i2c_reg; > > + > > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > > + if (!ptn_bridge) > > + return -ENOMEM; > > + > > + mutex_init(&ptn_bridge->lock); > > + > > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > > + ptn_bridge->bridge.driver_private = ptn_bridge; > > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > > + > > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > > + if (ret) { > > + dev_err(dev, "edid-reg not specified, aborting...\n"); > > + return -ENODEV; > > + } > > + > > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > > + sizeof(struct i2c_client), GFP_KERNEL); > > + > > + if (!ptn_bridge->edid_i2c) > > + return -ENOMEM; > > + > > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > > + sizeof(struct i2c_client)); > > + > > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > > + > > + /* Configures the bridge to re-enable interrupts after each ack */ > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > > + > > + /* Clear pending interrupts since power up. */ > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > > + > > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > > + ptn_bridge->bridge.of_node = dev->of_node; > > + ret = drm_bridge_add(&ptn_bridge->bridge); > > + if (ret) { > > + DRM_ERROR("Failed to add bridge\n"); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > > + > > + drm_bridge_remove(&ptn_bridge->bridge); > > Guess you need to free ptn_bridge->edid here. Thanks a lot! I'll fix it when sending V3. Thank you for the review! -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-10 14:13 ` Daniel Vetter 0 siblings, 0 replies; 172+ messages in thread From: Daniel Vetter @ 2016-06-10 14:13 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk, Fabio Estevam On Thu, Jun 09, 2016 at 06:25:04PM +0200, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig Two comments below. > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > 4 files changed, 412 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 2ce5e91..2dd3d7f 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..93dae5bd 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..47ea6c1 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..c73cd77 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,392 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} Please remove the above two dummy funcs, they should be unecessary. If they're not that would be an issue in there core - we want to avoid boilerplat like this. > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} Please remove your best_encoder callback, that just became unecessary in drm-next, thanks to some work from Boris. -Daniel > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-10 14:13 ` Daniel Vetter 0 siblings, 0 replies; 172+ messages in thread From: Daniel Vetter @ 2016-06-10 14:13 UTC (permalink / raw) To: linux-arm-kernel On Thu, Jun 09, 2016 at 06:25:04PM +0200, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig Two comments below. > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > 4 files changed, 412 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 2ce5e91..2dd3d7f 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..93dae5bd 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..47ea6c1 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..c73cd77 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,392 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} Please remove the above two dummy funcs, they should be unecessary. If they're not that would be an issue in there core - we want to avoid boilerplat like this. > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} Please remove your best_encoder callback, that just became unecessary in drm-next, thanks to some work from Boris. -Daniel > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > > _______________________________________________ > dri-devel mailing list > dri-devel at lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-10 14:13 ` Daniel Vetter 0 siblings, 0 replies; 172+ messages in thread From: Daniel Vetter @ 2016-06-10 14:13 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied-cv59FeDIM0c, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, davem-fT/PcQaiUtIeIZ0/mPfg9Q, devicetree-u79uwXL29TY76Z2rM5mHXA, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, galak-sgV2jX0FEOL9JmXXK+q4OQ, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, heiko-4mtYJXux2i+zQB+pC5nmwQ, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, jslaby-AlSwsSmVLrQ, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-I+IVW8TIWO2tmTQ+vhA3Yw, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-0h96xk9xTtrk1uMJSBkQmQ, mark.rutland-5wv7dgnIgG8, martin.donnelly-JJi787mZWgc, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, pawel.moll-5wv7dgnIgG8, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, tiwai-IBi9RG/b67k, treding-DDmLM1+adcrQT0dZR+AlfA, ykk-TNX95d0MmH7DzftRWevZcw, Fabio Estevam On Thu, Jun 09, 2016 at 06:25:04PM +0200, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > CC: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> > CC: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> > CC: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > --- > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig Two comments below. > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > 4 files changed, 412 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 2ce5e91..2dd3d7f 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > +M: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > +M: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..93dae5bd 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..47ea6c1 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..c73cd77 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,392 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} Please remove the above two dummy funcs, they should be unecessary. If they're not that would be an issue in there core - we want to avoid boilerplat like this. > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} Please remove your best_encoder callback, that just became unecessary in drm-next, thanks to some work from Boris. -Daniel > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > > _______________________________________________ > dri-devel mailing list > dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-06-09 16:25 ` Peter Senna Tschudin @ 2016-06-22 8:34 ` Archit Taneja -1 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-06-22 8:34 UTC (permalink / raw) To: Peter Senna Tschudin, airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam On 6/9/2016 9:55 PM, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Are these two chips always expected to be used together? I don't think it's right to pair up two encoder chips into one driver just for one board. Is one device @0x72 and other @0x73? Or is only one of them an i2c slave? What's preventing us to create these as two different bridge drivers? The drm framework allows us to daisy chain encoder bridges. The only problem I see is that we don't have a clear-cut way to tell the bridge driver whether we want it to create a connector for us or not. Because, it looks like both can potentially create connectors. This isn't a big problem either if we have DT. We just need to check whether our output port is connected to another bridge or a connector. Thanks, Archit > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > 4 files changed, 412 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 2ce5e91..2dd3d7f 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..93dae5bd 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..47ea6c1 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..c73cd77 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,392 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-06-22 8:34 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-06-22 8:34 UTC (permalink / raw) To: linux-arm-kernel On 6/9/2016 9:55 PM, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Are these two chips always expected to be used together? I don't think it's right to pair up two encoder chips into one driver just for one board. Is one device @0x72 and other @0x73? Or is only one of them an i2c slave? What's preventing us to create these as two different bridge drivers? The drm framework allows us to daisy chain encoder bridges. The only problem I see is that we don't have a clear-cut way to tell the bridge driver whether we want it to create a connector for us or not. Because, it looks like both can potentially create connectors. This isn't a big problem either if we have DT. We just need to check whether our output port is connected to another bridge or a connector. Thanks, Archit > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++ > 4 files changed, 412 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 2ce5e91..2dd3d7f 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5010,6 +5010,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 8f7423f..93dae5bd 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 96b13b3..47ea6c1 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..c73cd77 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,392 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + > + * > + */ > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > +#include "drm_edid.h" > +#include "drmP.h" > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge) > +{ > +} > + > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge) > +{ > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > +static struct drm_encoder > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + > + return ptn_bridge->bridge.encoder; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .best_encoder = ge_b850v3_lvds_dp_best_encoder, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector) > +{ > + drm_connector_cleanup(connector); > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = ge_b850v3_lvds_dp_connector_destroy, > +}; > + > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .enable = ge_b850v3_lvds_dp_enable, > + .disable = ge_b850v3_lvds_dp_disable, > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)"); > +MODULE_LICENSE("GPL v2"); > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V2 5/5] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge 2016-06-09 16:25 ` Peter Senna Tschudin @ 2016-06-09 16:25 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam Configures the GE B850v3 LVDS/DP++ bridge on the dts file. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to b850v3-lvds-dp-bridge arch/arm/boot/dts/imx6q-b850v3.dts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 88a70de..a57dd5b 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -77,6 +77,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port@4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -147,3 +154,27 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port@0 { + reg = <0>; + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V2 5/5] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-06-09 16:25 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw) To: linux-arm-kernel Configures the GE B850v3 LVDS/DP++ bridge on the dts file. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to b850v3-lvds-dp-bridge arch/arm/boot/dts/imx6q-b850v3.dts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 88a70de..a57dd5b 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -77,6 +77,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port at 4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -147,3 +154,27 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge at 73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port at 0 { + reg = <0>; + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/5] Configure the mapping between IPUs and external displays on the dts file of the B850v3. Needed to support two simultaneos Full-HD monitors. [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [5/5] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Peter Senna Tschudin (5): drm/imx-ldb: Add support to drm-bridge dts/imx6q-b850v3: Configure IPU assignment order Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 35 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 111 +++--- 7 files changed, 559 insertions(+), 41 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V3 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: linux-arm-kernel The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/5] Configure the mapping between IPUs and external displays on the dts file of the B850v3. Needed to support two simultaneos Full-HD monitors. [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [5/5] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Peter Senna Tschudin (5): drm/imx-ldb: Add support to drm-bridge dts/imx6q-b850v3: Configure IPU assignment order Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 35 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 111 +++--- 7 files changed, 559 insertions(+), 41 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V3 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, airlied-cv59FeDIM0c, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, peter.senna-ZGY8ohtN/8qB+jHODAdFcQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q, geert-Td1EMuHUCqxL1ZNQvxDV9g, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, mchehab-DgEjT+Ai2ygdnm+yROfE0A, linux-0h96xk9xTtrk1uMJSBkQmQ, treding-DDmLM1+adcrQT0dZR+AlfA, architt-sgV2jX0FEOL9JmXXK+q4OQ, ykk-TNX95d0MmH7DzftRWevZcw, andrey.gusakov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8, boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/5] Configure the mapping between IPUs and external displays on the dts file of the B850v3. Needed to support two simultaneos Full-HD monitors. [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [5/5] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Peter Senna Tschudin (5): drm/imx-ldb: Add support to drm-bridge dts/imx6q-b850v3: Configure IPU assignment order Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 35 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 111 +++--- 7 files changed, 559 insertions(+), 41 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Rob Herring, Thierry Reding Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Cc: David Airlie <airlied@linux.ie> Cc: Thierry Reding <treding@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V2: - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic. - Tested on next-20160729. Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 111 ++++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index 5d2831d..ebe9abd 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -469,19 +473,28 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); - if (imx_ldb_ch->panel) { + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); ret = drm_panel_attach(imx_ldb_ch->panel, - &imx_ldb_ch->connector); + &imx_ldb_ch->connector); if (ret) return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } return 0; } @@ -551,6 +564,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -558,7 +610,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -608,7 +659,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -641,46 +691,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: linux-arm-kernel Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Cc: David Airlie <airlied@linux.ie> Cc: Thierry Reding <treding@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V2: - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic. - Tested on next-20160729. Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 111 ++++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index 5d2831d..ebe9abd 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -469,19 +473,28 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); - if (imx_ldb_ch->panel) { + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); ret = drm_panel_attach(imx_ldb_ch->panel, - &imx_ldb_ch->connector); + &imx_ldb_ch->connector); if (ret) return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } return 0; } @@ -551,6 +564,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -558,7 +610,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -608,7 +659,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -641,46 +691,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, airlied-cv59FeDIM0c, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, peter.senna-ZGY8ohtN/8qB+jHODAdFcQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q, geert-Td1EMuHUCqxL1ZNQvxDV9g, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, mchehab-DgEjT+Ai2ygdnm+yROfE0A, linux-0h96xk9xTtrk1uMJSBkQmQ, treding-DDmLM1+adcrQT0dZR+AlfA, architt-sgV2jX0FEOL9JmXXK+q4OQ, ykk-TNX95d0MmH7DzftRWevZcw, andrey.gusakov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8, boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW Cc: Rob Herring, Thierry Reding Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> Cc: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> Cc: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> Cc: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> --- Changes from V2: - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic. - Tested on next-20160729. Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 111 ++++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index 5d2831d..ebe9abd 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -469,19 +473,28 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); - if (imx_ldb_ch->panel) { + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); ret = drm_panel_attach(imx_ldb_ch->panel, - &imx_ldb_ch->connector); + &imx_ldb_ch->connector); if (ret) return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } return 0; } @@ -551,6 +564,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -558,7 +610,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -608,7 +659,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -641,46 +691,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-08-01 10:21 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-08-01 10:21 UTC (permalink / raw) To: Peter Senna Tschudin Cc: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel, Rob Herring, Thierry Reding Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > This patch does a simple code refactoring by moving code > from for_each_child_of_node iterator to a new function named > imx_ldb_panel_ddc(). This was necessary to allow the panel ddc > code to run only when the imx_ldb is not attached to a bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Cc: David Airlie <airlied@linux.ie> > Cc: Thierry Reding <treding@nvidia.com> > Cc: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V2: > - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic. > - Tested on next-20160729. [...] > @@ -469,19 +473,28 @@ static int imx_ldb_register(struct drm_device *drm, > drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, > DRM_MODE_ENCODER_LVDS, NULL); > > - drm_connector_helper_add(&imx_ldb_ch->connector, > - &imx_ldb_connector_helper_funcs); > - drm_connector_init(drm, &imx_ldb_ch->connector, > - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); > - > if (imx_ldb_ch->panel) { > + drm_connector_helper_add(&imx_ldb_ch->connector, > + &imx_ldb_connector_helper_funcs); > + drm_connector_init(drm, &imx_ldb_ch->connector, > + &imx_ldb_connector_funcs, > + DRM_MODE_CONNECTOR_LVDS); This is still not right. We want to add the connector whenever there is no bridge that brings its own, not only when there is a panel. For historical reasons, the ldb driver can also work without a panel. > ret = drm_panel_attach(imx_ldb_ch->panel, > - &imx_ldb_ch->connector); > + &imx_ldb_ch->connector); What is the purpose of this change? > if (ret) > return ret; > } > > - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); Where is this gone? > + if (imx_ldb_ch->bridge) { > + imx_ldb_ch->bridge->encoder = encoder; > + > + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; > + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); > + if (ret) { > + DRM_ERROR("Failed to initialize bridge with drm\n"); > + return ret; > + } > + } > > return 0; > } regards Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-08-01 10:21 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-08-01 10:21 UTC (permalink / raw) To: linux-arm-kernel Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > This patch does a simple code refactoring by moving code > from for_each_child_of_node iterator to a new function named > imx_ldb_panel_ddc(). This was necessary to allow the panel ddc > code to run only when the imx_ldb is not attached to a bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Cc: David Airlie <airlied@linux.ie> > Cc: Thierry Reding <treding@nvidia.com> > Cc: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V2: > - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic. > - Tested on next-20160729. [...] > @@ -469,19 +473,28 @@ static int imx_ldb_register(struct drm_device *drm, > drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, > DRM_MODE_ENCODER_LVDS, NULL); > > - drm_connector_helper_add(&imx_ldb_ch->connector, > - &imx_ldb_connector_helper_funcs); > - drm_connector_init(drm, &imx_ldb_ch->connector, > - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); > - > if (imx_ldb_ch->panel) { > + drm_connector_helper_add(&imx_ldb_ch->connector, > + &imx_ldb_connector_helper_funcs); > + drm_connector_init(drm, &imx_ldb_ch->connector, > + &imx_ldb_connector_funcs, > + DRM_MODE_CONNECTOR_LVDS); This is still not right. We want to add the connector whenever there is no bridge that brings its own, not only when there is a panel. For historical reasons, the ldb driver can also work without a panel. > ret = drm_panel_attach(imx_ldb_ch->panel, > - &imx_ldb_ch->connector); > + &imx_ldb_ch->connector); What is the purpose of this change? > if (ret) > return ret; > } > > - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); Where is this gone? > + if (imx_ldb_ch->bridge) { > + imx_ldb_ch->bridge->encoder = encoder; > + > + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; > + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); > + if (ret) { > + DRM_ERROR("Failed to initialize bridge with drm\n"); > + return ret; > + } > + } > > return 0; > } regards Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-08-01 10:21 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-08-01 10:21 UTC (permalink / raw) To: Peter Senna Tschudin Cc: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, airlied-cv59FeDIM0c, davem-fT/PcQaiUtIeIZ0/mPfg9Q, geert-Td1EMuHUCqxL1ZNQvxDV9g, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, mchehab-DgEjT+Ai2ygdnm+yROfE0A, linux-0h96xk9xTtrk1uMJSBkQmQ, treding-DDmLM1+adcrQT0dZR+AlfA, architt-sgV2jX0FEOL9JmXXK+q4OQ, ykk-TNX95d0MmH7DzftRWevZcw, andrey.gusakov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8, boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Rob Herring, Thierry Reding Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > This patch does a simple code refactoring by moving code > from for_each_child_of_node iterator to a new function named > imx_ldb_panel_ddc(). This was necessary to allow the panel ddc > code to run only when the imx_ldb is not attached to a bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > Cc: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> > Cc: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> > Cc: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > --- > Changes from V2: > - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic. > - Tested on next-20160729. [...] > @@ -469,19 +473,28 @@ static int imx_ldb_register(struct drm_device *drm, > drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, > DRM_MODE_ENCODER_LVDS, NULL); > > - drm_connector_helper_add(&imx_ldb_ch->connector, > - &imx_ldb_connector_helper_funcs); > - drm_connector_init(drm, &imx_ldb_ch->connector, > - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); > - > if (imx_ldb_ch->panel) { > + drm_connector_helper_add(&imx_ldb_ch->connector, > + &imx_ldb_connector_helper_funcs); > + drm_connector_init(drm, &imx_ldb_ch->connector, > + &imx_ldb_connector_funcs, > + DRM_MODE_CONNECTOR_LVDS); This is still not right. We want to add the connector whenever there is no bridge that brings its own, not only when there is a panel. For historical reasons, the ldb driver can also work without a panel. > ret = drm_panel_attach(imx_ldb_ch->panel, > - &imx_ldb_ch->connector); > + &imx_ldb_ch->connector); What is the purpose of this change? > if (ret) > return ret; > } > > - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); Where is this gone? > + if (imx_ldb_ch->bridge) { > + imx_ldb_ch->bridge->encoder = encoder; > + > + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; > + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); > + if (ret) { > + DRM_ERROR("Failed to initialize bridge with drm\n"); > + return ret; > + } > + } > > return 0; > } regards Philipp -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge 2016-08-01 10:21 ` Philipp Zabel (?) @ 2016-08-02 18:46 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-02 18:46 UTC (permalink / raw) To: Philipp Zabel Cc: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel, Rob Herring, Thierry Reding Hi Philipp, Thank you for the review. I'm preparing V4, what about this: --- drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index b03919e..4a33077 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } else { + /* + * We want to add the connector whenever there is no bridge + * that brings its own, not only when there is a panel. For + * historical reasons, the ldb driver can also work without + * a panel. + */ + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + encoder); + } if (imx_ldb_ch->panel) { ret = drm_panel_attach(imx_ldb_ch->panel, @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); - return 0; } @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-08-02 18:46 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-02 18:46 UTC (permalink / raw) To: linux-arm-kernel Hi Philipp, Thank you for the review. I'm preparing V4, what about this: --- drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index b03919e..4a33077 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } else { + /* + * We want to add the connector whenever there is no bridge + * that brings its own, not only when there is a panel. For + * historical reasons, the ldb driver can also work without + * a panel. + */ + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + encoder); + } if (imx_ldb_ch->panel) { ret = drm_panel_attach(imx_ldb_ch->panel, @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); - return 0; } @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge @ 2016-08-02 18:46 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-02 18:46 UTC (permalink / raw) To: Philipp Zabel Cc: mark.rutland, airlied, dri-devel, Thierry Reding, ykk, boris.brezillon, architt, linux, geert, treding, linux, devicetree, andrey.gusakov, fabio.estevam, robh+dt, mchehab, linux-arm-kernel, gregkh, linux-kernel, kernel, enric.balletbo, akpm, shawnguo, davem Hi Philipp, Thank you for the review. I'm preparing V4, what about this: --- drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index b03919e..4a33077 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } else { + /* + * We want to add the connector whenever there is no bridge + * that brings its own, not only when there is a panel. For + * historical reasons, the ldb driver can also work without + * a panel. + */ + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + encoder); + } if (imx_ldb_ch->panel) { ret = drm_panel_attach(imx_ldb_ch->panel, @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); - return 0; } @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Rob Herring As the IPU has combined limitations across multiple crtcs, and as that can't be communicated to userspace at the moment, reorder the crtcs to allow support to two Full-HD monitors by avoiding assigning two monitors to a single IPU. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V2. Changes from V1: - New commit message arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..88a70de 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -51,6 +51,11 @@ chosen { stdout-path = &uart3; }; + + display-subsystem { + compatible = "fsl,imx-display-subsystem"; + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; + }; }; &clks { -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: linux-arm-kernel As the IPU has combined limitations across multiple crtcs, and as that can't be communicated to userspace at the moment, reorder the crtcs to allow support to two Full-HD monitors by avoiding assigning two monitors to a single IPU. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V2. Changes from V1: - New commit message arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..88a70de 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -51,6 +51,11 @@ chosen { stdout-path = &uart3; }; + + display-subsystem { + compatible = "fsl,imx-display-subsystem"; + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; + }; }; &clks { -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, airlied-cv59FeDIM0c, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, peter.senna-ZGY8ohtN/8qB+jHODAdFcQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q, geert-Td1EMuHUCqxL1ZNQvxDV9g, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, mchehab-DgEjT+Ai2ygdnm+yROfE0A, linux-0h96xk9xTtrk1uMJSBkQmQ, treding-DDmLM1+adcrQT0dZR+AlfA, architt-sgV2jX0FEOL9JmXXK+q4OQ, ykk-TNX95d0MmH7DzftRWevZcw, andrey.gusakov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8, boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW Cc: Rob Herring As the IPU has combined limitations across multiple crtcs, and as that can't be communicated to userspace at the moment, reorder the crtcs to allow support to two Full-HD monitors by avoiding assigning two monitors to a single IPU. Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> --- Unchanged from V2. Changes from V1: - New commit message arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..88a70de 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -51,6 +51,11 @@ chosen { stdout-path = &uart3; }; + + display-subsystem { + compatible = "fsl,imx-display-subsystem"; + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; + }; }; &clks { -- 2.5.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-08-01 8:54 ` Lucas Stach 0 siblings, 0 replies; 172+ messages in thread From: Lucas Stach @ 2016-08-01 8:54 UTC (permalink / raw) To: Peter Senna Tschudin Cc: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel, Rob Herring Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > As the IPU has combined limitations across multiple crtcs, and as that > can't be communicated to userspace at the moment, reorder the crtcs to > allow support to two Full-HD monitors by avoiding assigning two > monitors to a single IPU. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> NACK. This is a userspace issue. Changing the assignment order of the CRTCs just shifts the failure to a userspace that want to use CRTC 0 and 2 now. imx-drm just got atomic support and with the atomic check it should be possible to inform userspace in a reasonable way about such issues. Regards, Lucas > --- > Unchanged from V2. > > Changes from V1: > - New commit message > > arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ > 1 file changed, 5 insertions(+) > > diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts > index 167f744..88a70de 100644 > --- a/arch/arm/boot/dts/imx6q-b850v3.dts > +++ b/arch/arm/boot/dts/imx6q-b850v3.dts > @@ -51,6 +51,11 @@ > chosen { > stdout-path = &uart3; > }; > + > + display-subsystem { > + compatible = "fsl,imx-display-subsystem"; > + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; > + }; > }; > > &clks { ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-08-01 8:54 ` Lucas Stach 0 siblings, 0 replies; 172+ messages in thread From: Lucas Stach @ 2016-08-01 8:54 UTC (permalink / raw) To: linux-arm-kernel Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > As the IPU has combined limitations across multiple crtcs, and as that > can't be communicated to userspace at the moment, reorder the crtcs to > allow support to two Full-HD monitors by avoiding assigning two > monitors to a single IPU. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> NACK. This is a userspace issue. Changing the assignment order of the CRTCs just shifts the failure to a userspace that want to use CRTC 0 and 2 now. imx-drm just got atomic support and with the atomic check it should be possible to inform userspace in a reasonable way about such issues. Regards, Lucas > --- > Unchanged from V2. > > Changes from V1: > - New commit message > > arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ > 1 file changed, 5 insertions(+) > > diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts > index 167f744..88a70de 100644 > --- a/arch/arm/boot/dts/imx6q-b850v3.dts > +++ b/arch/arm/boot/dts/imx6q-b850v3.dts > @@ -51,6 +51,11 @@ > chosen { > stdout-path = &uart3; > }; > + > + display-subsystem { > + compatible = "fsl,imx-display-subsystem"; > + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; > + }; > }; > > &clks { ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-08-01 8:54 ` Lucas Stach 0 siblings, 0 replies; 172+ messages in thread From: Lucas Stach @ 2016-08-01 8:54 UTC (permalink / raw) To: Peter Senna Tschudin Cc: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, airlied-cv59FeDIM0c, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q, geert-Td1EMuHUCqxL1ZNQvxDV9g, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, mchehab-DgEjT+Ai2ygdnm+yROfE0A, linux-0h96xk9xTtrk1uMJSBkQmQ, treding-DDmLM1+adcrQT0dZR+AlfA, architt-sgV2jX0FEOL9JmXXK+q4OQ, ykk-TNX95d0MmH7DzftRWevZcw, andrey.gusakov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8, boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Rob Herring Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > As the IPU has combined limitations across multiple crtcs, and as that > can't be communicated to userspace at the moment, reorder the crtcs to > allow support to two Full-HD monitors by avoiding assigning two > monitors to a single IPU. > > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> NACK. This is a userspace issue. Changing the assignment order of the CRTCs just shifts the failure to a userspace that want to use CRTC 0 and 2 now. imx-drm just got atomic support and with the atomic check it should be possible to inform userspace in a reasonable way about such issues. Regards, Lucas > --- > Unchanged from V2. > > Changes from V1: > - New commit message > > arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++ > 1 file changed, 5 insertions(+) > > diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts > index 167f744..88a70de 100644 > --- a/arch/arm/boot/dts/imx6q-b850v3.dts > +++ b/arch/arm/boot/dts/imx6q-b850v3.dts > @@ -51,6 +51,11 @@ > chosen { > stdout-path = &uart3; > }; > + > + display-subsystem { > + compatible = "fsl,imx-display-subsystem"; > + ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>; > + }; > }; > > &clks { -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-08-01 12:30 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-01 12:30 UTC (permalink / raw) To: Lucas Stach Cc: andrey.gusakov, boris.brezillon, mchehab, dri-devel, mark.rutland, airlied, treding, geert, devicetree, Daniel Stone, kernel, ykk, akpm, linux-arm-kernel, robh+dt, linux, linux, davem, enric.balletbo, Rob Herring, shawnguo, p.zabel, architt, gregkh, linux-kernel, fabio.estevam, Peter Senna Tschudin Hi Lucas, Thank you for the prompt review. On Monday, August 1, 2016 10:54 CEST, Lucas Stach <l.stach@pengutronix.de> wrote: > Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > > As the IPU has combined limitations across multiple crtcs, and as that > > can't be communicated to userspace at the moment, reorder the crtcs to > > allow support to two Full-HD monitors by avoiding assigning two > > monitors to a single IPU. > > > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > Cc: Rob Herring <robh@kernel.org> > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > NACK. This is a userspace issue. Changing the assignment order of the > CRTCs just shifts the failure to a userspace that want to use CRTC 0 and > 2 now. Err, yeah user space issue... But how the kernel is currently telling user space about what exactly went wrong and how user space might fix it? How Weston(our user space) is going to know that reshuffling crtcs is going to lead to success; how could it? I guess some platform-specific code in user space is needed for this to work... > > imx-drm just got atomic support and with the atomic check it should be > possible to inform userspace in a reasonable way about such issues. Should be possible, but I guess it isn't, and wont be until a considerable effort is put on both kernel and user space. Or am I missing something? What do you propose? I got inspiration from: arch/arm/boot/dts/imx6q.dtsi ... display-subsystem { compatible = "fsl,imx-display-subsystem"; ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>; }; ... This is there for more than 2 years now, and I get that the idea here is not ordering, but just declaring. However even if this patch is not the perfect solution, it allows us to stay close to upstream now without creating problems(does it create any issue?). Can you reconsider or propose a concrete solution that is not more complex than our entire driver? Thanks a lot! ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-08-01 12:30 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-01 12:30 UTC (permalink / raw) To: linux-arm-kernel Hi Lucas, Thank you for the prompt review. On Monday, August 1, 2016 10:54 CEST, Lucas Stach <l.stach@pengutronix.de> wrote: > Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > > As the IPU has combined limitations across multiple crtcs, and as that > > can't be communicated to userspace at the moment, reorder the crtcs to > > allow support to two Full-HD monitors by avoiding assigning two > > monitors to a single IPU. > > > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > Cc: Rob Herring <robh@kernel.org> > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > NACK. This is a userspace issue. Changing the assignment order of the > CRTCs just shifts the failure to a userspace that want to use CRTC 0 and > 2 now. Err, yeah user space issue... But how the kernel is currently telling user space about what exactly went wrong and how user space might fix it? How Weston(our user space) is going to know that reshuffling crtcs is going to lead to success; how could it? I guess some platform-specific code in user space is needed for this to work... > > imx-drm just got atomic support and with the atomic check it should be > possible to inform userspace in a reasonable way about such issues. Should be possible, but I guess it isn't, and wont be until a considerable effort is put on both kernel and user space. Or am I missing something? What do you propose? I got inspiration from: arch/arm/boot/dts/imx6q.dtsi ... display-subsystem { compatible = "fsl,imx-display-subsystem"; ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>; }; ... This is there for more than 2 years now, and I get that the idea here is not ordering, but just declaring. However even if this patch is not the perfect solution, it allows us to stay close to upstream now without creating problems(does it create any issue?). Can you reconsider or propose a concrete solution that is not more complex than our entire driver? Thanks a lot! ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-08-01 12:30 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-01 12:30 UTC (permalink / raw) To: Lucas Stach Cc: andrey.gusakov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8, boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8, mchehab-DgEjT+Ai2ygdnm+yROfE0A, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, mark.rutland-5wv7dgnIgG8, airlied-cv59FeDIM0c, treding-DDmLM1+adcrQT0dZR+AlfA, geert-Td1EMuHUCqxL1ZNQvxDV9g, devicetree-u79uwXL29TY76Z2rM5mHXA, Daniel Stone, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, ykk-TNX95d0MmH7DzftRWevZcw, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, linux-I+IVW8TIWO2tmTQ+vhA3Yw, linux-0h96xk9xTtrk1uMJSBkQmQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, Rob Herring, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, architt-sgV2jX0FEOL9JmXXK+q4OQ, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, linux-kernel-u79uwXL29TY76Z2rM5mHXA, fabio.estevam-3arQi8VN3Tc, Peter Senna Tschudin Hi Lucas, Thank you for the prompt review. On Monday, August 1, 2016 10:54 CEST, Lucas Stach <l.stach-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> wrote: > Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > > As the IPU has combined limitations across multiple crtcs, and as that > > can't be communicated to userspace at the moment, reorder the crtcs to > > allow support to two Full-HD monitors by avoiding assigning two > > monitors to a single IPU. > > > > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > > NACK. This is a userspace issue. Changing the assignment order of the > CRTCs just shifts the failure to a userspace that want to use CRTC 0 and > 2 now. Err, yeah user space issue... But how the kernel is currently telling user space about what exactly went wrong and how user space might fix it? How Weston(our user space) is going to know that reshuffling crtcs is going to lead to success; how could it? I guess some platform-specific code in user space is needed for this to work... > > imx-drm just got atomic support and with the atomic check it should be > possible to inform userspace in a reasonable way about such issues. Should be possible, but I guess it isn't, and wont be until a considerable effort is put on both kernel and user space. Or am I missing something? What do you propose? I got inspiration from: arch/arm/boot/dts/imx6q.dtsi ... display-subsystem { compatible = "fsl,imx-display-subsystem"; ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>; }; ... This is there for more than 2 years now, and I get that the idea here is not ordering, but just declaring. However even if this patch is not the perfect solution, it allows us to stay close to upstream now without creating problems(does it create any issue?). Can you reconsider or propose a concrete solution that is not more complex than our entire driver? Thanks a lot! -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order 2016-08-01 12:30 ` Peter Senna Tschudin (?) @ 2016-08-02 13:13 ` Daniel Vetter -1 siblings, 0 replies; 172+ messages in thread From: Daniel Vetter @ 2016-08-02 13:13 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Lucas Stach, mark.rutland, dri-devel, linux, geert, Peter Senna Tschudin, treding, linux, devicetree, kernel, andrey.gusakov, fabio.estevam, robh+dt, mchehab, linux-arm-kernel, gregkh, linux-kernel, Daniel Stone, enric.balletbo, akpm, shawnguo, davem On Mon, Aug 01, 2016 at 01:30:57PM +0100, Peter Senna Tschudin wrote: > Hi Lucas, > > Thank you for the prompt review. > > On Monday, August 1, 2016 10:54 CEST, Lucas Stach <l.stach@pengutronix.de> wrote: > > > Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > > > As the IPU has combined limitations across multiple crtcs, and as that > > > can't be communicated to userspace at the moment, reorder the crtcs to > > > allow support to two Full-HD monitors by avoiding assigning two > > > monitors to a single IPU. > > > > > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > > Cc: Rob Herring <robh@kernel.org> > > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > > > NACK. This is a userspace issue. Changing the assignment order of the > > CRTCs just shifts the failure to a userspace that want to use CRTC 0 and > > 2 now. > > Err, yeah user space issue... But how the kernel is currently telling > user space about what exactly went wrong and how user space might fix > it? How Weston(our user space) is going to know that reshuffling crtcs > is going to lead to success; how could it? I guess some > platform-specific code in user space is needed for this to work... atomic with the TEST_ONLY flag. This is what userspace should do: 1. submit atomic TEST_ONLY request with 1 screen on first crtc 2. If reject, move to another crtc or if those are all tried, reduce mode (this should never happen for the 1st screen, kernel /should/ filter out should impossible modes. But for 2nd/3rd screen combined modes might not all work). 3. Once you have a successfuly config for the 1st screen, add 2nd screen. 4. goto 2 with that 2nd screen. 5. Once all the screens have a mode/crtc they can be used on, do the real atomic request without TEST_ONLY. Like Lucas said, no need to fumble around with ordering of CRTCs. The only thing we do in the driver is move the preferred output (if there is any) to the front of the _connector_ list, e.g. for built-in panels. No need at all for platform specific code. Cheers, Daniel > > > > > imx-drm just got atomic support and with the atomic check it should be > > possible to inform userspace in a reasonable way about such issues. > > Should be possible, but I guess it isn't, and wont be until a considerable effort is put on both kernel and user space. Or am I missing something? What do you propose? > > I got inspiration from: arch/arm/boot/dts/imx6q.dtsi > ... > display-subsystem { > compatible = "fsl,imx-display-subsystem"; > ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>; > }; > ... > > This is there for more than 2 years now, and I get that the idea here is not ordering, but just declaring. > > However even if this patch is not the perfect solution, it allows us to stay close to upstream now without creating problems(does it create any issue?). > > Can you reconsider or propose a concrete solution that is not more complex than our entire driver? > > Thanks a lot! > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-08-02 13:13 ` Daniel Vetter 0 siblings, 0 replies; 172+ messages in thread From: Daniel Vetter @ 2016-08-02 13:13 UTC (permalink / raw) To: linux-arm-kernel On Mon, Aug 01, 2016 at 01:30:57PM +0100, Peter Senna Tschudin wrote: > Hi Lucas, > > Thank you for the prompt review. > > On Monday, August 1, 2016 10:54 CEST, Lucas Stach <l.stach@pengutronix.de> wrote: > > > Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > > > As the IPU has combined limitations across multiple crtcs, and as that > > > can't be communicated to userspace at the moment, reorder the crtcs to > > > allow support to two Full-HD monitors by avoiding assigning two > > > monitors to a single IPU. > > > > > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > > Cc: Rob Herring <robh@kernel.org> > > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > > > NACK. This is a userspace issue. Changing the assignment order of the > > CRTCs just shifts the failure to a userspace that want to use CRTC 0 and > > 2 now. > > Err, yeah user space issue... But how the kernel is currently telling > user space about what exactly went wrong and how user space might fix > it? How Weston(our user space) is going to know that reshuffling crtcs > is going to lead to success; how could it? I guess some > platform-specific code in user space is needed for this to work... atomic with the TEST_ONLY flag. This is what userspace should do: 1. submit atomic TEST_ONLY request with 1 screen on first crtc 2. If reject, move to another crtc or if those are all tried, reduce mode (this should never happen for the 1st screen, kernel /should/ filter out should impossible modes. But for 2nd/3rd screen combined modes might not all work). 3. Once you have a successfuly config for the 1st screen, add 2nd screen. 4. goto 2 with that 2nd screen. 5. Once all the screens have a mode/crtc they can be used on, do the real atomic request without TEST_ONLY. Like Lucas said, no need to fumble around with ordering of CRTCs. The only thing we do in the driver is move the preferred output (if there is any) to the front of the _connector_ list, e.g. for built-in panels. No need at all for platform specific code. Cheers, Daniel > > > > > imx-drm just got atomic support and with the atomic check it should be > > possible to inform userspace in a reasonable way about such issues. > > Should be possible, but I guess it isn't, and wont be until a considerable effort is put on both kernel and user space. Or am I missing something? What do you propose? > > I got inspiration from: arch/arm/boot/dts/imx6q.dtsi > ... > display-subsystem { > compatible = "fsl,imx-display-subsystem"; > ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>; > }; > ... > > This is there for more than 2 years now, and I get that the idea here is not ordering, but just declaring. > > However even if this patch is not the perfect solution, it allows us to stay close to upstream now without creating problems(does it create any issue?). > > Can you reconsider or propose a concrete solution that is not more complex than our entire driver? > > Thanks a lot! > > _______________________________________________ > dri-devel mailing list > dri-devel at lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order @ 2016-08-02 13:13 ` Daniel Vetter 0 siblings, 0 replies; 172+ messages in thread From: Daniel Vetter @ 2016-08-02 13:13 UTC (permalink / raw) To: Peter Senna Tschudin Cc: mark.rutland, dri-devel, linux, Peter Senna Tschudin, geert, treding, linux, devicetree, andrey.gusakov, fabio.estevam, robh+dt, mchehab, linux-arm-kernel, gregkh, linux-kernel, Daniel Stone, kernel, enric.balletbo, akpm, shawnguo, davem On Mon, Aug 01, 2016 at 01:30:57PM +0100, Peter Senna Tschudin wrote: > Hi Lucas, > > Thank you for the prompt review. > > On Monday, August 1, 2016 10:54 CEST, Lucas Stach <l.stach@pengutronix.de> wrote: > > > Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin: > > > As the IPU has combined limitations across multiple crtcs, and as that > > > can't be communicated to userspace at the moment, reorder the crtcs to > > > allow support to two Full-HD monitors by avoiding assigning two > > > monitors to a single IPU. > > > > > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > > Cc: Rob Herring <robh@kernel.org> > > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > > > NACK. This is a userspace issue. Changing the assignment order of the > > CRTCs just shifts the failure to a userspace that want to use CRTC 0 and > > 2 now. > > Err, yeah user space issue... But how the kernel is currently telling > user space about what exactly went wrong and how user space might fix > it? How Weston(our user space) is going to know that reshuffling crtcs > is going to lead to success; how could it? I guess some > platform-specific code in user space is needed for this to work... atomic with the TEST_ONLY flag. This is what userspace should do: 1. submit atomic TEST_ONLY request with 1 screen on first crtc 2. If reject, move to another crtc or if those are all tried, reduce mode (this should never happen for the 1st screen, kernel /should/ filter out should impossible modes. But for 2nd/3rd screen combined modes might not all work). 3. Once you have a successfuly config for the 1st screen, add 2nd screen. 4. goto 2 with that 2nd screen. 5. Once all the screens have a mode/crtc they can be used on, do the real atomic request without TEST_ONLY. Like Lucas said, no need to fumble around with ordering of CRTCs. The only thing we do in the driver is move the preferred output (if there is any) to the front of the _connector_ list, e.g. for built-in panels. No need at all for platform specific code. Cheers, Daniel > > > > > imx-drm just got atomic support and with the atomic check it should be > > possible to inform userspace in a reasonable way about such issues. > > Should be possible, but I guess it isn't, and wont be until a considerable effort is put on both kernel and user space. Or am I missing something? What do you propose? > > I got inspiration from: arch/arm/boot/dts/imx6q.dtsi > ... > display-subsystem { > compatible = "fsl,imx-display-subsystem"; > ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>; > }; > ... > > This is there for more than 2 years now, and I get that the idea here is not ordering, but just declaring. > > However even if this patch is not the perfect solution, it allows us to stay close to upstream now without creating problems(does it create any issue?). > > Can you reconsider or propose a concrete solution that is not more complex than our entire driver? > > Thanks a lot! > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Javier Martinez Canillas, Rob Herring Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V2. Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt new file mode 100644 index 0000000..f05c3e9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt @@ -0,0 +1,37 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3-lvds-dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : phandle of the interrupt controller that services + interrupts to the device + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the video signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: linux-arm-kernel Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V2. Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt new file mode 100644 index 0000000..f05c3e9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt @@ -0,0 +1,37 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3-lvds-dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : phandle of the interrupt controller that services + interrupts to the device + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the video signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge at 73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, airlied-cv59FeDIM0c, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, peter.senna-ZGY8ohtN/8qB+jHODAdFcQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q, geert-Td1EMuHUCqxL1ZNQvxDV9g, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, mchehab-DgEjT+Ai2ygdnm+yROfE0A, linux-0h96xk9xTtrk1uMJSBkQmQ, treding-DDmLM1+adcrQT0dZR+AlfA, architt-sgV2jX0FEOL9JmXXK+q4OQ, ykk-TNX95d0MmH7DzftRWevZcw, andrey.gusakov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8, boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW Cc: Javier Martinez Canillas, Rob Herring Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Javier Martinez Canillas <javier-0uQlZySMnqxg9hUCZPvPmw@public.gmane.org> Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> --- Unchanged from V2. Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt new file mode 100644 index 0000000..f05c3e9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt @@ -0,0 +1,37 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3-lvds-dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : phandle of the interrupt controller that services + interrupts to the device + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the video signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp 2016-07-31 19:55 ` Peter Senna Tschudin (?) @ 2016-08-01 16:59 ` Rob Herring -1 siblings, 0 replies; 172+ messages in thread From: Rob Herring @ 2016-08-01 16:59 UTC (permalink / raw) To: Peter Senna Tschudin Cc: mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel, Javier Martinez Canillas On Sun, Jul 31, 2016 at 09:55:36PM +0200, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Unchanged from V2. > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt Acked-by: Rob Herring <robh@kernel.org> ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-08-01 16:59 ` Rob Herring 0 siblings, 0 replies; 172+ messages in thread From: Rob Herring @ 2016-08-01 16:59 UTC (permalink / raw) To: linux-arm-kernel On Sun, Jul 31, 2016 at 09:55:36PM +0200, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Unchanged from V2. > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt Acked-by: Rob Herring <robh@kernel.org> ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-08-01 16:59 ` Rob Herring 0 siblings, 0 replies; 172+ messages in thread From: Rob Herring @ 2016-08-01 16:59 UTC (permalink / raw) To: Peter Senna Tschudin Cc: mark.rutland, dri-devel, linux, geert, Javier Martinez Canillas, treding, linux, devicetree, kernel, andrey.gusakov, fabio.estevam, mchehab, linux-arm-kernel, gregkh, linux-kernel, enric.balletbo, akpm, shawnguo, davem On Sun, Jul 31, 2016 at 09:55:36PM +0200, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Unchanged from V2. > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt Acked-by: Rob Herring <robh@kernel.org> _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V3 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-07-31 19:55 ` Peter Senna Tschudin @ 2016-07-31 19:55 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Daniel Vetter, Rob Herring, Thierry Reding Add a driver that create a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Cc: Daniel Vetter <daniel.vetter@ffwll.ch> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> CC: David Airlie <airlied@linux.ie> CC: Thierry Reding <treding@nvidia.com> CC: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- - Made it atomic to be applied on next-20160729 on top of Liu Ying changes that made imx-ldb atomic. Changes from V1: - New commit message - Removed 3 empty entry points - Removed memory leak from ge_b850v3_lvds_dp_get_modes() - Added a lock for mode setting - Removed a few blank lines - Changed the order at Makefile and Kconfig MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index aaf36c0..ec52e17 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5139,6 +5139,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martin Donnelly <martin.donnelly@ge.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index b590e67..b4b70fb 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO Designware HDMI block. This is used in conjunction with the i.MX6 HDMI driver. +config DRM_GE_B850V3_LVDS_DP + tristate "GE B850v3 LVDS to DP++ display bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + This is a driver for the display bridge of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index efdb07e..b9606f3 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_SII902X) += sii902x.o diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..eee8eac --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,397 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + * + */ + +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drmP.h> + +/* + * 220Mhz is a limitation of the host, as the bridge is capable of up to + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications + * Processor Reference Manual for more information about the 220Mhz limit. + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work + * fine. + */ +#define MAX_PIXEL_CLOCK 220000 + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; + struct mutex lock; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + int num_modes = 0; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + mutex_lock(&ptn_bridge->lock); + + kfree(ptn_bridge->edid); + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); + + if (ptn_bridge->edid) { + drm_mode_connector_update_edid_property(connector, + ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + } + + mutex_unlock(&ptn_bridge->lock); + + return num_modes; +} + + +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( + struct drm_connector *connector, struct drm_display_mode *mode) +{ + if (mode->clock > MAX_PIXEL_CLOCK) { + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", + mode->name); + return MODE_CLOCK_HIGH; + } + + return MODE_OK; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .mode_valid = ge_b850v3_lvds_dp_mode_valid, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = drm_connector_cleanup, + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + mutex_lock(&ptn_bridge->lock); + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + mutex_unlock(&ptn_bridge->lock); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_register(connector); + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + mutex_init(&ptn_bridge->lock); + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + /* Configures the bridge to re-enable interrupts after each ack */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + kfree(ptn_bridge->edid); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3-lvds-dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3-lvds-dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "ge,b850v3-lvds-dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: linux-arm-kernel Add a driver that create a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Cc: Daniel Vetter <daniel.vetter@ffwll.ch> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> CC: David Airlie <airlied@linux.ie> CC: Thierry Reding <treding@nvidia.com> CC: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- - Made it atomic to be applied on next-20160729 on top of Liu Ying changes that made imx-ldb atomic. Changes from V1: - New commit message - Removed 3 empty entry points - Removed memory leak from ge_b850v3_lvds_dp_get_modes() - Added a lock for mode setting - Removed a few blank lines - Changed the order at Makefile and Kconfig MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index aaf36c0..ec52e17 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5139,6 +5139,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martin Donnelly <martin.donnelly@ge.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index b590e67..b4b70fb 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO Designware HDMI block. This is used in conjunction with the i.MX6 HDMI driver. +config DRM_GE_B850V3_LVDS_DP + tristate "GE B850v3 LVDS to DP++ display bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + This is a driver for the display bridge of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index efdb07e..b9606f3 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_SII902X) += sii902x.o diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..eee8eac --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,397 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + * + */ + +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drmP.h> + +/* + * 220Mhz is a limitation of the host, as the bridge is capable of up to + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications + * Processor Reference Manual for more information about the 220Mhz limit. + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work + * fine. + */ +#define MAX_PIXEL_CLOCK 220000 + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; + struct mutex lock; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + int num_modes = 0; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + mutex_lock(&ptn_bridge->lock); + + kfree(ptn_bridge->edid); + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); + + if (ptn_bridge->edid) { + drm_mode_connector_update_edid_property(connector, + ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + } + + mutex_unlock(&ptn_bridge->lock); + + return num_modes; +} + + +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( + struct drm_connector *connector, struct drm_display_mode *mode) +{ + if (mode->clock > MAX_PIXEL_CLOCK) { + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", + mode->name); + return MODE_CLOCK_HIGH; + } + + return MODE_OK; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .mode_valid = ge_b850v3_lvds_dp_mode_valid, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = drm_connector_cleanup, + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + mutex_lock(&ptn_bridge->lock); + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + mutex_unlock(&ptn_bridge->lock); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_register(connector); + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + mutex_init(&ptn_bridge->lock); + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + /* Configures the bridge to re-enable interrupts after each ack */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + kfree(ptn_bridge->edid); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3-lvds-dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3-lvds-dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "ge,b850v3-lvds-dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 5/5] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm, mchehab, linux, treding, architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Javier Martinez Canillas, Rob Herring Configures the GE B850v3 LVDS/DP++ bridge on the dts file. Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V2. Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to b850v3-lvds-dp-bridge arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 88a70de..10dfc3b 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -77,6 +77,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port@4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -147,3 +154,26 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 5/5] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: linux-arm-kernel Configures the GE B850v3 LVDS/DP++ bridge on the dts file. Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V2. Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to b850v3-lvds-dp-bridge arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 88a70de..10dfc3b 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -77,6 +77,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port at 4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -147,3 +154,26 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge at 73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V3 5/5] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-07-31 19:55 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw) To: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, airlied-cv59FeDIM0c, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, peter.senna-ZGY8ohtN/8qB+jHODAdFcQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q, geert-Td1EMuHUCqxL1ZNQvxDV9g, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, mchehab-DgEjT+Ai2ygdnm+yROfE0A, linux-0h96xk9xTtrk1uMJSBkQmQ, treding-DDmLM1+adcrQT0dZR+AlfA, architt-sgV2jX0FEOL9JmXXK+q4OQ, ykk-TNX95d0MmH7DzftRWevZcw, andrey.gusakov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8, boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW Cc: Javier Martinez Canillas, Rob Herring Configures the GE B850v3 LVDS/DP++ bridge on the dts file. Cc: Javier Martinez Canillas <javier-0uQlZySMnqxg9hUCZPvPmw@public.gmane.org> Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> --- Unchanged from V2. Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to b850v3-lvds-dp-bridge arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 88a70de..10dfc3b 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -77,6 +77,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port@4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -147,3 +154,26 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V4 0/4] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-04 22:36 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna, peter.senna, treding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/4] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/4] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [3/4] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [4/4] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Changes from V3: - Removed the patch that was configuring the mapping between IPUs and external displays on the dts file Peter Senna Tschudin (4): drm/imx-ldb: Add support to drm-bridge Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 118 +++--- 7 files changed, 562 insertions(+), 40 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 0/4] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-04 22:36 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw) To: linux-arm-kernel The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/4] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/4] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [3/4] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [4/4] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Changes from V3: - Removed the patch that was configuring the mapping between IPUs and external displays on the dts file Peter Senna Tschudin (4): drm/imx-ldb: Add support to drm-bridge Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 118 +++--- 7 files changed, 562 insertions(+), 40 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 0/4] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-04 22:36 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw) To: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, airlied-cv59FeDIM0c, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, martin.donnelly-JJi787mZWgc, peter.senna-ZGY8ohtN/8qB+jHODAdFcQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, treding-DDmLM1+adcrQT0dZR+AlfA, architt-sgV2jX0FEOL9JmXXK+q4OQ, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/4] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/4] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [3/4] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [4/4] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Changes from V3: - Removed the patch that was configuring the mapping between IPUs and external displays on the dts file Peter Senna Tschudin (4): drm/imx-ldb: Add support to drm-bridge Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 118 +++--- 7 files changed, 562 insertions(+), 40 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge 2016-08-04 22:36 ` Peter Senna Tschudin @ 2016-08-04 22:36 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna, peter.senna, treding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Enric Balletbo i Serra, Rob Herring, Thierry Reding Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Cc: David Airlie <airlied@linux.ie> Cc: Thierry Reding <treding@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V3: - The connector is created when there is no bridge instead of only when there is a pannel - Tested on next-20160804 Changes from V2: - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic - Tested on next-20160729 Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index b03919e..4a33077 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } else { + /* + * We want to add the connector whenever there is no bridge + * that brings its own, not only when there is a panel. For + * historical reasons, the ldb driver can also work without + * a panel. + */ + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + encoder); + } if (imx_ldb_ch->panel) { ret = drm_panel_attach(imx_ldb_ch->panel, @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); - return 0; } @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge @ 2016-08-04 22:36 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw) To: linux-arm-kernel Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Cc: David Airlie <airlied@linux.ie> Cc: Thierry Reding <treding@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V3: - The connector is created when there is no bridge instead of only when there is a pannel - Tested on next-20160804 Changes from V2: - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic - Tested on next-20160729 Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index b03919e..4a33077 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } else { + /* + * We want to add the connector whenever there is no bridge + * that brings its own, not only when there is a panel. For + * historical reasons, the ldb driver can also work without + * a panel. + */ + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + encoder); + } if (imx_ldb_ch->panel) { ret = drm_panel_attach(imx_ldb_ch->panel, @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); - return 0; } @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge @ 2016-08-16 15:40 ` Martyn Welch 0 siblings, 0 replies; 172+ messages in thread From: Martyn Welch @ 2016-08-16 15:40 UTC (permalink / raw) To: Peter Senna Tschudin, robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, martin.donnelly, peter.senna, treding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Enric Balletbo i Serra, Rob Herring, Thierry Reding On 04/08/16 23:36, Peter Senna Tschudin wrote: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > This patch does a simple code refactoring by moving code > from for_each_child_of_node iterator to a new function named > imx_ldb_panel_ddc(). This was necessary to allow the panel ddc > code to run only when the imx_ldb is not attached to a bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Cc: David Airlie <airlied@linux.ie> > Cc: Thierry Reding <treding@nvidia.com> > Cc: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> Acked-by: Martyn Welch <martyn.welch@collabora.co.uk> > --- > Changes from V3: > - The connector is created when there is no bridge instead of only when > there is a pannel > - Tested on next-20160804 > > Changes from V2: > - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic > - Tested on next-20160729 > > Changes from V1: > - Reanmed ext_bridge to bridge > - Removed empty entry point imx_ldb_encoder_enable() > - Adapted the code to apply to the latest linux next: next-20160609 > > drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- > 1 file changed, 78 insertions(+), 40 deletions(-) > > diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c > index b03919e..4a33077 100644 > --- a/drivers/gpu/drm/imx/imx-ldb.c > +++ b/drivers/gpu/drm/imx/imx-ldb.c > @@ -57,7 +57,11 @@ struct imx_ldb_channel { > struct imx_ldb *ldb; > struct drm_connector connector; > struct drm_encoder encoder; > + > + /* Defines what is connected to the ldb, only one at a time */ > struct drm_panel *panel; > + struct drm_bridge *bridge; > + > struct device_node *child; > struct i2c_adapter *ddc; > int chno; > @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, > drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, > DRM_MODE_ENCODER_LVDS, NULL); > > - drm_connector_helper_add(&imx_ldb_ch->connector, > - &imx_ldb_connector_helper_funcs); > - drm_connector_init(drm, &imx_ldb_ch->connector, > - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); > + if (imx_ldb_ch->bridge) { > + imx_ldb_ch->bridge->encoder = encoder; > + > + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; > + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); > + if (ret) { > + DRM_ERROR("Failed to initialize bridge with drm\n"); > + return ret; > + } > + } else { > + /* > + * We want to add the connector whenever there is no bridge > + * that brings its own, not only when there is a panel. For > + * historical reasons, the ldb driver can also work without > + * a panel. > + */ > + drm_connector_helper_add(&imx_ldb_ch->connector, > + &imx_ldb_connector_helper_funcs); > + drm_connector_init(drm, &imx_ldb_ch->connector, > + &imx_ldb_connector_funcs, > + DRM_MODE_CONNECTOR_LVDS); > + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, > + encoder); > + } > > if (imx_ldb_ch->panel) { > ret = drm_panel_attach(imx_ldb_ch->panel, > @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, > return ret; > } > > - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); > - > return 0; > } > > @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { > }; > MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); > > +static int imx_ldb_panel_ddc(struct device *dev, > + struct imx_ldb_channel *channel, struct device_node *child) > +{ > + struct device_node *ddc_node; > + const u8 *edidp; > + int ret; > + > + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); > + if (ddc_node) { > + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); > + of_node_put(ddc_node); > + if (!channel->ddc) { > + dev_warn(dev, "failed to get ddc i2c adapter\n"); > + return -EPROBE_DEFER; > + } > + } > + > + if (!channel->ddc) { > + /* if no DDC available, fallback to hardcoded EDID */ > + dev_dbg(dev, "no ddc available\n"); > + > + edidp = of_get_property(child, "edid", > + &channel->edid_len); > + if (edidp) { > + channel->edid = kmemdup(edidp, > + channel->edid_len, > + GFP_KERNEL); > + } else if (!channel->panel) { > + /* fallback to display-timings node */ > + ret = of_get_drm_display_mode(child, > + &channel->mode, > + OF_USE_NATIVE_MODE); > + if (!ret) > + channel->mode_valid = 1; > + } > + } > + return 0; > +} > + > static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > { > struct drm_device *drm = data; > @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > const struct of_device_id *of_id = > of_match_device(imx_ldb_dt_ids, dev); > struct device_node *child; > - const u8 *edidp; > struct imx_ldb *imx_ldb; > int dual; > int ret; > @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > > for_each_child_of_node(np, child) { > struct imx_ldb_channel *channel; > - struct device_node *ddc_node; > struct device_node *ep; > int bus_format; > > @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > > remote = of_graph_get_remote_port_parent(ep); > of_node_put(ep); > - if (remote) > + if (remote) { > channel->panel = of_drm_find_panel(remote); > - else > + channel->bridge = of_drm_find_bridge(remote); > + } else > return -EPROBE_DEFER; > of_node_put(remote); > - if (!channel->panel) { > - dev_err(dev, "panel not found: %s\n", > - remote->full_name); > - return -EPROBE_DEFER; > - } > - } > > - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); > - if (ddc_node) { > - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); > - of_node_put(ddc_node); > - if (!channel->ddc) { > - dev_warn(dev, "failed to get ddc i2c adapter\n"); > + if (!channel->panel && !channel->bridge) { > + dev_err(dev, "panel/bridge not found: %s\n", > + remote->full_name); > return -EPROBE_DEFER; > } > } > > - if (!channel->ddc) { > - /* if no DDC available, fallback to hardcoded EDID */ > - dev_dbg(dev, "no ddc available\n"); > - > - edidp = of_get_property(child, "edid", > - &channel->edid_len); > - if (edidp) { > - channel->edid = kmemdup(edidp, > - channel->edid_len, > - GFP_KERNEL); > - } else if (!channel->panel) { > - /* fallback to display-timings node */ > - ret = of_get_drm_display_mode(child, > - &channel->mode, > - OF_USE_NATIVE_MODE); > - if (!ret) > - channel->mode_valid = 1; > - } > + /* panel ddc only if there is no bridge */ > + if (!channel->bridge) { > + ret = imx_ldb_panel_ddc(dev, channel, child); > + if (ret) > + return ret; > } > > bus_format = of_get_bus_format(dev, child); > ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge @ 2016-08-16 15:40 ` Martyn Welch 0 siblings, 0 replies; 172+ messages in thread From: Martyn Welch @ 2016-08-16 15:40 UTC (permalink / raw) To: linux-arm-kernel On 04/08/16 23:36, Peter Senna Tschudin wrote: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > This patch does a simple code refactoring by moving code > from for_each_child_of_node iterator to a new function named > imx_ldb_panel_ddc(). This was necessary to allow the panel ddc > code to run only when the imx_ldb is not attached to a bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Cc: David Airlie <airlied@linux.ie> > Cc: Thierry Reding <treding@nvidia.com> > Cc: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> Acked-by: Martyn Welch <martyn.welch@collabora.co.uk> > --- > Changes from V3: > - The connector is created when there is no bridge instead of only when > there is a pannel > - Tested on next-20160804 > > Changes from V2: > - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic > - Tested on next-20160729 > > Changes from V1: > - Reanmed ext_bridge to bridge > - Removed empty entry point imx_ldb_encoder_enable() > - Adapted the code to apply to the latest linux next: next-20160609 > > drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- > 1 file changed, 78 insertions(+), 40 deletions(-) > > diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c > index b03919e..4a33077 100644 > --- a/drivers/gpu/drm/imx/imx-ldb.c > +++ b/drivers/gpu/drm/imx/imx-ldb.c > @@ -57,7 +57,11 @@ struct imx_ldb_channel { > struct imx_ldb *ldb; > struct drm_connector connector; > struct drm_encoder encoder; > + > + /* Defines what is connected to the ldb, only one at a time */ > struct drm_panel *panel; > + struct drm_bridge *bridge; > + > struct device_node *child; > struct i2c_adapter *ddc; > int chno; > @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, > drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, > DRM_MODE_ENCODER_LVDS, NULL); > > - drm_connector_helper_add(&imx_ldb_ch->connector, > - &imx_ldb_connector_helper_funcs); > - drm_connector_init(drm, &imx_ldb_ch->connector, > - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); > + if (imx_ldb_ch->bridge) { > + imx_ldb_ch->bridge->encoder = encoder; > + > + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; > + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); > + if (ret) { > + DRM_ERROR("Failed to initialize bridge with drm\n"); > + return ret; > + } > + } else { > + /* > + * We want to add the connector whenever there is no bridge > + * that brings its own, not only when there is a panel. For > + * historical reasons, the ldb driver can also work without > + * a panel. > + */ > + drm_connector_helper_add(&imx_ldb_ch->connector, > + &imx_ldb_connector_helper_funcs); > + drm_connector_init(drm, &imx_ldb_ch->connector, > + &imx_ldb_connector_funcs, > + DRM_MODE_CONNECTOR_LVDS); > + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, > + encoder); > + } > > if (imx_ldb_ch->panel) { > ret = drm_panel_attach(imx_ldb_ch->panel, > @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, > return ret; > } > > - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); > - > return 0; > } > > @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { > }; > MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); > > +static int imx_ldb_panel_ddc(struct device *dev, > + struct imx_ldb_channel *channel, struct device_node *child) > +{ > + struct device_node *ddc_node; > + const u8 *edidp; > + int ret; > + > + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); > + if (ddc_node) { > + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); > + of_node_put(ddc_node); > + if (!channel->ddc) { > + dev_warn(dev, "failed to get ddc i2c adapter\n"); > + return -EPROBE_DEFER; > + } > + } > + > + if (!channel->ddc) { > + /* if no DDC available, fallback to hardcoded EDID */ > + dev_dbg(dev, "no ddc available\n"); > + > + edidp = of_get_property(child, "edid", > + &channel->edid_len); > + if (edidp) { > + channel->edid = kmemdup(edidp, > + channel->edid_len, > + GFP_KERNEL); > + } else if (!channel->panel) { > + /* fallback to display-timings node */ > + ret = of_get_drm_display_mode(child, > + &channel->mode, > + OF_USE_NATIVE_MODE); > + if (!ret) > + channel->mode_valid = 1; > + } > + } > + return 0; > +} > + > static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > { > struct drm_device *drm = data; > @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > const struct of_device_id *of_id = > of_match_device(imx_ldb_dt_ids, dev); > struct device_node *child; > - const u8 *edidp; > struct imx_ldb *imx_ldb; > int dual; > int ret; > @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > > for_each_child_of_node(np, child) { > struct imx_ldb_channel *channel; > - struct device_node *ddc_node; > struct device_node *ep; > int bus_format; > > @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > > remote = of_graph_get_remote_port_parent(ep); > of_node_put(ep); > - if (remote) > + if (remote) { > channel->panel = of_drm_find_panel(remote); > - else > + channel->bridge = of_drm_find_bridge(remote); > + } else > return -EPROBE_DEFER; > of_node_put(remote); > - if (!channel->panel) { > - dev_err(dev, "panel not found: %s\n", > - remote->full_name); > - return -EPROBE_DEFER; > - } > - } > > - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); > - if (ddc_node) { > - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); > - of_node_put(ddc_node); > - if (!channel->ddc) { > - dev_warn(dev, "failed to get ddc i2c adapter\n"); > + if (!channel->panel && !channel->bridge) { > + dev_err(dev, "panel/bridge not found: %s\n", > + remote->full_name); > return -EPROBE_DEFER; > } > } > > - if (!channel->ddc) { > - /* if no DDC available, fallback to hardcoded EDID */ > - dev_dbg(dev, "no ddc available\n"); > - > - edidp = of_get_property(child, "edid", > - &channel->edid_len); > - if (edidp) { > - channel->edid = kmemdup(edidp, > - channel->edid_len, > - GFP_KERNEL); > - } else if (!channel->panel) { > - /* fallback to display-timings node */ > - ret = of_get_drm_display_mode(child, > - &channel->mode, > - OF_USE_NATIVE_MODE); > - if (!ret) > - channel->mode_valid = 1; > - } > + /* panel ddc only if there is no bridge */ > + if (!channel->bridge) { > + ret = imx_ldb_panel_ddc(dev, channel, child); > + if (ret) > + return ret; > } > > bus_format = of_get_bus_format(dev, child); > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge @ 2016-08-16 15:40 ` Martyn Welch 0 siblings, 0 replies; 172+ messages in thread From: Martyn Welch @ 2016-08-16 15:40 UTC (permalink / raw) To: Peter Senna Tschudin, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, fabio.estevam-3arQi8VN3Tc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, airlied-cv59FeDIM0c, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, martin.donnelly-JJi787mZWgc, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, treding-DDmLM1+adcrQT0dZR+AlfA, architt-sgV2jX0FEOL9JmXXK+q4OQ, devicetree-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW Cc: Enric Balletbo i Serra, Rob Herring, Thierry Reding On 04/08/16 23:36, Peter Senna Tschudin wrote: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > This patch does a simple code refactoring by moving code > from for_each_child_of_node iterator to a new function named > imx_ldb_panel_ddc(). This was necessary to allow the panel ddc > code to run only when the imx_ldb is not attached to a bridge. > > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > Cc: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> > Cc: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> > Cc: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> Acked-by: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > --- > Changes from V3: > - The connector is created when there is no bridge instead of only when > there is a pannel > - Tested on next-20160804 > > Changes from V2: > - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic > - Tested on next-20160729 > > Changes from V1: > - Reanmed ext_bridge to bridge > - Removed empty entry point imx_ldb_encoder_enable() > - Adapted the code to apply to the latest linux next: next-20160609 > > drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- > 1 file changed, 78 insertions(+), 40 deletions(-) > > diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c > index b03919e..4a33077 100644 > --- a/drivers/gpu/drm/imx/imx-ldb.c > +++ b/drivers/gpu/drm/imx/imx-ldb.c > @@ -57,7 +57,11 @@ struct imx_ldb_channel { > struct imx_ldb *ldb; > struct drm_connector connector; > struct drm_encoder encoder; > + > + /* Defines what is connected to the ldb, only one at a time */ > struct drm_panel *panel; > + struct drm_bridge *bridge; > + > struct device_node *child; > struct i2c_adapter *ddc; > int chno; > @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, > drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, > DRM_MODE_ENCODER_LVDS, NULL); > > - drm_connector_helper_add(&imx_ldb_ch->connector, > - &imx_ldb_connector_helper_funcs); > - drm_connector_init(drm, &imx_ldb_ch->connector, > - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); > + if (imx_ldb_ch->bridge) { > + imx_ldb_ch->bridge->encoder = encoder; > + > + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; > + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); > + if (ret) { > + DRM_ERROR("Failed to initialize bridge with drm\n"); > + return ret; > + } > + } else { > + /* > + * We want to add the connector whenever there is no bridge > + * that brings its own, not only when there is a panel. For > + * historical reasons, the ldb driver can also work without > + * a panel. > + */ > + drm_connector_helper_add(&imx_ldb_ch->connector, > + &imx_ldb_connector_helper_funcs); > + drm_connector_init(drm, &imx_ldb_ch->connector, > + &imx_ldb_connector_funcs, > + DRM_MODE_CONNECTOR_LVDS); > + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, > + encoder); > + } > > if (imx_ldb_ch->panel) { > ret = drm_panel_attach(imx_ldb_ch->panel, > @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, > return ret; > } > > - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); > - > return 0; > } > > @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { > }; > MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); > > +static int imx_ldb_panel_ddc(struct device *dev, > + struct imx_ldb_channel *channel, struct device_node *child) > +{ > + struct device_node *ddc_node; > + const u8 *edidp; > + int ret; > + > + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); > + if (ddc_node) { > + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); > + of_node_put(ddc_node); > + if (!channel->ddc) { > + dev_warn(dev, "failed to get ddc i2c adapter\n"); > + return -EPROBE_DEFER; > + } > + } > + > + if (!channel->ddc) { > + /* if no DDC available, fallback to hardcoded EDID */ > + dev_dbg(dev, "no ddc available\n"); > + > + edidp = of_get_property(child, "edid", > + &channel->edid_len); > + if (edidp) { > + channel->edid = kmemdup(edidp, > + channel->edid_len, > + GFP_KERNEL); > + } else if (!channel->panel) { > + /* fallback to display-timings node */ > + ret = of_get_drm_display_mode(child, > + &channel->mode, > + OF_USE_NATIVE_MODE); > + if (!ret) > + channel->mode_valid = 1; > + } > + } > + return 0; > +} > + > static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > { > struct drm_device *drm = data; > @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > const struct of_device_id *of_id = > of_match_device(imx_ldb_dt_ids, dev); > struct device_node *child; > - const u8 *edidp; > struct imx_ldb *imx_ldb; > int dual; > int ret; > @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > > for_each_child_of_node(np, child) { > struct imx_ldb_channel *channel; > - struct device_node *ddc_node; > struct device_node *ep; > int bus_format; > > @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) > > remote = of_graph_get_remote_port_parent(ep); > of_node_put(ep); > - if (remote) > + if (remote) { > channel->panel = of_drm_find_panel(remote); > - else > + channel->bridge = of_drm_find_bridge(remote); > + } else > return -EPROBE_DEFER; > of_node_put(remote); > - if (!channel->panel) { > - dev_err(dev, "panel not found: %s\n", > - remote->full_name); > - return -EPROBE_DEFER; > - } > - } > > - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); > - if (ddc_node) { > - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); > - of_node_put(ddc_node); > - if (!channel->ddc) { > - dev_warn(dev, "failed to get ddc i2c adapter\n"); > + if (!channel->panel && !channel->bridge) { > + dev_err(dev, "panel/bridge not found: %s\n", > + remote->full_name); > return -EPROBE_DEFER; > } > } > > - if (!channel->ddc) { > - /* if no DDC available, fallback to hardcoded EDID */ > - dev_dbg(dev, "no ddc available\n"); > - > - edidp = of_get_property(child, "edid", > - &channel->edid_len); > - if (edidp) { > - channel->edid = kmemdup(edidp, > - channel->edid_len, > - GFP_KERNEL); > - } else if (!channel->panel) { > - /* fallback to display-timings node */ > - ret = of_get_drm_display_mode(child, > - &channel->mode, > - OF_USE_NATIVE_MODE); > - if (!ret) > - channel->mode_valid = 1; > - } > + /* panel ddc only if there is no bridge */ > + if (!channel->bridge) { > + ret = imx_ldb_panel_ddc(dev, channel, child); > + if (ret) > + return ret; > } > > bus_format = of_get_bus_format(dev, child); > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp 2016-08-04 22:36 ` Peter Senna Tschudin @ 2016-08-04 22:36 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna, peter.senna, treding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Javier Martinez Canillas, Enric Balletbo i Serra, Rob Herring Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V3: - 2/4 instead of 3/5 Unchanged from V2 Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt new file mode 100644 index 0000000..f05c3e9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt @@ -0,0 +1,37 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3-lvds-dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : phandle of the interrupt controller that services + interrupts to the device + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the video signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-08-04 22:36 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw) To: linux-arm-kernel Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V3: - 2/4 instead of 3/5 Unchanged from V2 Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt new file mode 100644 index 0000000..f05c3e9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt @@ -0,0 +1,37 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3-lvds-dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : phandle of the interrupt controller that services + interrupts to the device + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the video signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge at 73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp 2016-08-04 22:36 ` Peter Senna Tschudin (?) @ 2016-08-05 7:28 ` Enric Balletbo Serra -1 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-08-05 7:28 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Rob Herring, Mark Rutland, shawnguo, Sascha Hauer, Fabio Estevam, linux, David Airlie, Philipp Zabel, Martyn Welch, martin.donnelly, Peter Senna Tschudin, Thierry Reding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel, Javier Martinez Canillas, Enric Balletbo i Serra, Rob Herring Hi Peter, 2016-08-05 0:36 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> I think your previous version was acked by Rob and this version has no changes from previous so would be good add here the Acked-by: Rob Herring <robh@kernel.org> > --- > Changes from V3: > - 2/4 instead of 3/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > new file mode 100644 > index 0000000..f05c3e9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > @@ -0,0 +1,37 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3-lvds-dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : phandle of the interrupt controller that services > + interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the video signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-08-05 7:28 ` Enric Balletbo Serra 0 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-08-05 7:28 UTC (permalink / raw) To: linux-arm-kernel Hi Peter, 2016-08-05 0:36 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> I think your previous version was acked by Rob and this version has no changes from previous so would be good add here the Acked-by: Rob Herring <robh@kernel.org> > --- > Changes from V3: > - 2/4 instead of 3/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > new file mode 100644 > index 0000000..f05c3e9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > @@ -0,0 +1,37 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3-lvds-dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : phandle of the interrupt controller that services > + interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the video signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge at 73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-08-05 7:28 ` Enric Balletbo Serra 0 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-08-05 7:28 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Rob Herring, Mark Rutland, shawnguo, Sascha Hauer, Fabio Estevam, linux, David Airlie, Philipp Zabel, Martyn Welch, martin.donnelly, Peter Senna Tschudin, Thierry Reding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel, Javier Martinez Canillas, Enric Balletbo i Serra, Rob Herring Hi Peter, 2016-08-05 0:36 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> I think your previous version was acked by Rob and this version has no changes from previous so would be good add here the Acked-by: Rob Herring <robh@kernel.org> > --- > Changes from V3: > - 2/4 instead of 3/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > new file mode 100644 > index 0000000..f05c3e9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > @@ -0,0 +1,37 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3-lvds-dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : phandle of the interrupt controller that services > + interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the video signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp 2016-08-04 22:36 ` Peter Senna Tschudin (?) @ 2016-08-16 15:59 ` Martyn Welch -1 siblings, 0 replies; 172+ messages in thread From: Martyn Welch @ 2016-08-16 15:59 UTC (permalink / raw) To: Peter Senna Tschudin, robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, martin.donnelly, peter.senna, treding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Javier Martinez Canillas, Enric Balletbo i Serra, Rob Herring On 04/08/16 23:36, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> Acked-by: Martyn Welch <martyn.welch@collabora.co.uk> > --- > Changes from V3: > - 2/4 instead of 3/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > new file mode 100644 > index 0000000..f05c3e9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > @@ -0,0 +1,37 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3-lvds-dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : phandle of the interrupt controller that services > + interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the video signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-08-16 15:59 ` Martyn Welch 0 siblings, 0 replies; 172+ messages in thread From: Martyn Welch @ 2016-08-16 15:59 UTC (permalink / raw) To: linux-arm-kernel On 04/08/16 23:36, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> Acked-by: Martyn Welch <martyn.welch@collabora.co.uk> > --- > Changes from V3: > - 2/4 instead of 3/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > new file mode 100644 > index 0000000..f05c3e9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > @@ -0,0 +1,37 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3-lvds-dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : phandle of the interrupt controller that services > + interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the video signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge at 73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-08-16 15:59 ` Martyn Welch 0 siblings, 0 replies; 172+ messages in thread From: Martyn Welch @ 2016-08-16 15:59 UTC (permalink / raw) To: Peter Senna Tschudin, robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, martin.donnelly, peter.senna, treding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Enric Balletbo i Serra, Javier Martinez Canillas On 04/08/16 23:36, Peter Senna Tschudin wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> Acked-by: Martyn Welch <martyn.welch@collabora.co.uk> > --- > Changes from V3: > - 2/4 instead of 3/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > new file mode 100644 > index 0000000..f05c3e9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > @@ -0,0 +1,37 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3-lvds-dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : phandle of the interrupt controller that services > + interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the video signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-08-04 22:36 ` Peter Senna Tschudin @ 2016-08-04 22:37 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:37 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna, peter.senna, treding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Daniel Vetter, Enric Balletbo i Serra, Rob Herring, Thierry Reding Add a driver that create a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Cc: Daniel Vetter <daniel.vetter@ffwll.ch> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> CC: David Airlie <airlied@linux.ie> CC: Thierry Reding <treding@nvidia.com> CC: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V3: - 3/4 instead of 4/5 - Tested on next-20160804 Changes from V2: - Made it atomic to be applied on next-20160729 on top of Liu Ying changes that made imx-ldb atomic Changes from V1: - New commit message - Removed 3 empty entry points - Removed memory leak from ge_b850v3_lvds_dp_get_modes() - Added a lock for mode setting - Removed a few blank lines - Changed the order at Makefile and Kconfig MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index a5b6569..f51123c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5142,6 +5142,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martin Donnelly <martin.donnelly@ge.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index b590e67..b4b70fb 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO Designware HDMI block. This is used in conjunction with the i.MX6 HDMI driver. +config DRM_GE_B850V3_LVDS_DP + tristate "GE B850v3 LVDS to DP++ display bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + This is a driver for the display bridge of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index efdb07e..b9606f3 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_SII902X) += sii902x.o diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..eee8eac --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,397 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + * + */ + +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drmP.h> + +/* + * 220Mhz is a limitation of the host, as the bridge is capable of up to + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications + * Processor Reference Manual for more information about the 220Mhz limit. + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work + * fine. + */ +#define MAX_PIXEL_CLOCK 220000 + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; + struct mutex lock; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + int num_modes = 0; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + mutex_lock(&ptn_bridge->lock); + + kfree(ptn_bridge->edid); + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); + + if (ptn_bridge->edid) { + drm_mode_connector_update_edid_property(connector, + ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + } + + mutex_unlock(&ptn_bridge->lock); + + return num_modes; +} + + +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( + struct drm_connector *connector, struct drm_display_mode *mode) +{ + if (mode->clock > MAX_PIXEL_CLOCK) { + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", + mode->name); + return MODE_CLOCK_HIGH; + } + + return MODE_OK; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .mode_valid = ge_b850v3_lvds_dp_mode_valid, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = drm_connector_cleanup, + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + mutex_lock(&ptn_bridge->lock); + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + mutex_unlock(&ptn_bridge->lock); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_register(connector); + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + mutex_init(&ptn_bridge->lock); + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + /* Configures the bridge to re-enable interrupts after each ack */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + kfree(ptn_bridge->edid); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3-lvds-dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3-lvds-dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "ge,b850v3-lvds-dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-04 22:37 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:37 UTC (permalink / raw) To: linux-arm-kernel Add a driver that create a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Cc: Daniel Vetter <daniel.vetter@ffwll.ch> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> CC: David Airlie <airlied@linux.ie> CC: Thierry Reding <treding@nvidia.com> CC: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V3: - 3/4 instead of 4/5 - Tested on next-20160804 Changes from V2: - Made it atomic to be applied on next-20160729 on top of Liu Ying changes that made imx-ldb atomic Changes from V1: - New commit message - Removed 3 empty entry points - Removed memory leak from ge_b850v3_lvds_dp_get_modes() - Added a lock for mode setting - Removed a few blank lines - Changed the order at Makefile and Kconfig MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index a5b6569..f51123c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5142,6 +5142,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martin Donnelly <martin.donnelly@ge.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index b590e67..b4b70fb 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO Designware HDMI block. This is used in conjunction with the i.MX6 HDMI driver. +config DRM_GE_B850V3_LVDS_DP + tristate "GE B850v3 LVDS to DP++ display bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + This is a driver for the display bridge of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index efdb07e..b9606f3 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_SII902X) += sii902x.o diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..eee8eac --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,397 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + * + */ + +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drmP.h> + +/* + * 220Mhz is a limitation of the host, as the bridge is capable of up to + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications + * Processor Reference Manual for more information about the 220Mhz limit. + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work + * fine. + */ +#define MAX_PIXEL_CLOCK 220000 + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; + struct mutex lock; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + int num_modes = 0; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + mutex_lock(&ptn_bridge->lock); + + kfree(ptn_bridge->edid); + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); + + if (ptn_bridge->edid) { + drm_mode_connector_update_edid_property(connector, + ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + } + + mutex_unlock(&ptn_bridge->lock); + + return num_modes; +} + + +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( + struct drm_connector *connector, struct drm_display_mode *mode) +{ + if (mode->clock > MAX_PIXEL_CLOCK) { + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", + mode->name); + return MODE_CLOCK_HIGH; + } + + return MODE_OK; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .mode_valid = ge_b850v3_lvds_dp_mode_valid, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = drm_connector_cleanup, + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + mutex_lock(&ptn_bridge->lock); + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + mutex_unlock(&ptn_bridge->lock); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_register(connector); + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + mutex_init(&ptn_bridge->lock); + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + /* Configures the bridge to re-enable interrupts after each ack */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + kfree(ptn_bridge->edid); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3-lvds-dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3-lvds-dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "ge,b850v3-lvds-dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-08-04 22:37 ` Peter Senna Tschudin (?) @ 2016-08-05 7:38 ` Enric Balletbo Serra -1 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-08-05 7:38 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Rob Herring, Mark Rutland, shawnguo, Sascha Hauer, Fabio Estevam, linux, David Airlie, Philipp Zabel, Martyn Welch, martin.donnelly, Peter Senna Tschudin, Thierry Reding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel, Daniel Vetter, Enric Balletbo i Serra, Rob Herring, Thierry Reding Hi Peter, Only one comment below and one nitpick 2016-08-05 0:37 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ > 4 files changed, 417 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a5b6569..f51123c 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..eee8eac > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,397 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); I'd check here and below the return value. Guess this is the first place where the comunication with the chip takes place, if something is wrong with the i2c communication, i.e the dt configures a wrong address, you should fail. > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + kfree(ptn_bridge->edid); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, nit: Use { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, nit: Use { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > After solving the i2c thing, Reviewed-by: Enric Balletbo i Serra <enric.balletbo@collabora.com> ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-05 7:38 ` Enric Balletbo Serra 0 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-08-05 7:38 UTC (permalink / raw) To: linux-arm-kernel Hi Peter, Only one comment below and one nitpick 2016-08-05 0:37 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ > 4 files changed, 417 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a5b6569..f51123c 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..eee8eac > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,397 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); I'd check here and below the return value. Guess this is the first place where the comunication with the chip takes place, if something is wrong with the i2c communication, i.e the dt configures a wrong address, you should fail. > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + kfree(ptn_bridge->edid); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, nit: Use { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, nit: Use { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > After solving the i2c thing, Reviewed-by: Enric Balletbo i Serra <enric.balletbo@collabora.com> ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-05 7:38 ` Enric Balletbo Serra 0 siblings, 0 replies; 172+ messages in thread From: Enric Balletbo Serra @ 2016-08-05 7:38 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Mark Rutland, devicetree, Thierry Reding, Daniel Vetter, Peter Senna Tschudin, linux, dri-devel, linux-kernel, Enric Balletbo i Serra, Rob Herring, Sascha Hauer, Fabio Estevam, martin.donnelly, shawnguo, linux-arm-kernel, Martyn Welch Hi Peter, Only one comment below and one nitpick 2016-08-05 0:37 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++ > 4 files changed, 417 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a5b6569..f51123c 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..eee8eac > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,397 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* Configures the bridge to re-enable interrupts after each ack */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); I'd check here and below the return value. Guess this is the first place where the comunication with the chip takes place, if something is wrong with the i2c communication, i.e the dt configures a wrong address, you should fail. > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + kfree(ptn_bridge->edid); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, nit: Use { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, nit: Use { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "ge,b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > After solving the i2c thing, Reviewed-by: Enric Balletbo i Serra <enric.balletbo@collabora.com> _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V4 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge 2016-08-04 22:36 ` Peter Senna Tschudin @ 2016-08-04 22:37 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:37 UTC (permalink / raw) To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna, peter.senna, treding, architt, devicetree, linux-kernel, linux-arm-kernel, dri-devel Cc: Javier Martinez Canillas, Enric Balletbo i Serra, Rob Herring Configures the GE B850v3 LVDS/DP++ bridge on the dts file. Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V3: - 4/4 instead of 5/5 Unchanged from V2 Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to b850v3-lvds-dp-bridge arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..8db3bf2 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -72,6 +72,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port@4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -142,3 +149,26 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V4 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-08-04 22:37 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-04 22:37 UTC (permalink / raw) To: linux-arm-kernel Configures the GE B850v3 LVDS/DP++ bridge on the dts file. Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V3: - 4/4 instead of 5/5 Unchanged from V2 Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to b850v3-lvds-dp-bridge arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..8db3bf2 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -72,6 +72,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port at 4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -142,3 +149,26 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge at 73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V5 0/4] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-09 16:41 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/4] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/4] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [3/4] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [4/4] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Changes from V3: - Removed the patch that was configuring the mapping between IPUs and external displays on the dts file Peter Senna Tschudin (4): drm/imx-ldb: Add support to drm-bridge Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 118 ++++-- 7 files changed, 570 insertions(+), 40 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 0/4] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-09 16:41 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: linux-arm-kernel The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/4] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/4] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [3/4] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [4/4] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Changes from V3: - Removed the patch that was configuring the mapping between IPUs and external displays on the dts file Peter Senna Tschudin (4): drm/imx-ldb: Add support to drm-bridge Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 118 ++++-- 7 files changed, 570 insertions(+), 40 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 0/4] Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-09 16:41 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: airlied-cv59FeDIM0c, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, daniel.vetter-/w4YWyX8dFk, davem-fT/PcQaiUtIeIZ0/mPfg9Q, devicetree-u79uwXL29TY76Z2rM5mHXA, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, galak-sgV2jX0FEOL9JmXXK+q4OQ, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, heiko-4mtYJXux2i+zQB+pC5nmwQ, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, javier-0uQlZySMnqxg9hUCZPvPmw, jslaby-AlSwsSmVLrQ, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-I+IVW8TIWO2tmTQ+vhA3Yw, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-0h96xk9xTtrk1uMJSBkQmQ, mark.rutland-5wv7dgnIgG8, martin.donnelly-JJi787mZWgc, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, pawel.moll-5wv7dgnIgG8, peter.senna-ZGY8ohtN/8qB+jHODAdFcQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, tiwai-IBi9RG/b67k, treding-DDmLM1+adcrQT0dZR+AlfA, ykk-TNX95d0MmH7DzftRWevZcw The series adds a driver that creates a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output The patches from the series: [1/4] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS panel. [2/4] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge [3/4] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile [4/4] Make the changes to the B850v3 dts file to enable the GE B850v3 LVDS/DP++ Bridge. Changes from V3: - Removed the patch that was configuring the mapping between IPUs and external displays on the dts file Peter Senna Tschudin (4): drm/imx-ldb: Add support to drm-bridge Documentation/devicetree/bindings: b850v3_lvds_dp drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++ MAINTAINERS | 8 + arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++ drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++ drivers/gpu/drm/imx/imx-ldb.c | 118 ++++-- 7 files changed, 570 insertions(+), 40 deletions(-) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c -- 2.5.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge 2016-08-09 16:41 ` Peter Senna Tschudin @ 2016-08-09 16:41 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Martyn Welch <martyn.welch@collabora.co.uk> Cc: Martin Donnelly <martin.donnelly@ge.com> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Cc: David Airlie <airlied@linux.ie> Cc: Thierry Reding <treding@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V4 Changes from V3: - The connector is created when there is no bridge instead of only when there is a pannel - Tested on next-20160804 Changes from V2: - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic - Tested on next-20160729 Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index b03919e..4a33077 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } else { + /* + * We want to add the connector whenever there is no bridge + * that brings its own, not only when there is a panel. For + * historical reasons, the ldb driver can also work without + * a panel. + */ + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + encoder); + } if (imx_ldb_ch->panel) { ret = drm_panel_attach(imx_ldb_ch->panel, @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); - return 0; } @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge @ 2016-08-09 16:41 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: linux-arm-kernel Add support to attach a drm_bridge to imx-ldb in addition to existing support to attach a LVDS panel. This patch does a simple code refactoring by moving code from for_each_child_of_node iterator to a new function named imx_ldb_panel_ddc(). This was necessary to allow the panel ddc code to run only when the imx_ldb is not attached to a bridge. Cc: Martyn Welch <martyn.welch@collabora.co.uk> Cc: Martin Donnelly <martin.donnelly@ge.com> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Cc: David Airlie <airlied@linux.ie> Cc: Thierry Reding <treding@nvidia.com> Cc: Thierry Reding <thierry.reding@gmail.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V4 Changes from V3: - The connector is created when there is no bridge instead of only when there is a pannel - Tested on next-20160804 Changes from V2: - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic - Tested on next-20160729 Changes from V1: - Reanmed ext_bridge to bridge - Removed empty entry point imx_ldb_encoder_enable() - Adapted the code to apply to the latest linux next: next-20160609 drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c index b03919e..4a33077 100644 --- a/drivers/gpu/drm/imx/imx-ldb.c +++ b/drivers/gpu/drm/imx/imx-ldb.c @@ -57,7 +57,11 @@ struct imx_ldb_channel { struct imx_ldb *ldb; struct drm_connector connector; struct drm_encoder encoder; + + /* Defines what is connected to the ldb, only one at a time */ struct drm_panel *panel; + struct drm_bridge *bridge; + struct device_node *child; struct i2c_adapter *ddc; int chno; @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm, drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs, DRM_MODE_ENCODER_LVDS, NULL); - drm_connector_helper_add(&imx_ldb_ch->connector, - &imx_ldb_connector_helper_funcs); - drm_connector_init(drm, &imx_ldb_ch->connector, - &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (imx_ldb_ch->bridge) { + imx_ldb_ch->bridge->encoder = encoder; + + imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge; + ret = drm_bridge_attach(drm, imx_ldb_ch->bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + } else { + /* + * We want to add the connector whenever there is no bridge + * that brings its own, not only when there is a panel. For + * historical reasons, the ldb driver can also work without + * a panel. + */ + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + drm_connector_init(drm, &imx_ldb_ch->connector, + &imx_ldb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + encoder); + } if (imx_ldb_ch->panel) { ret = drm_panel_attach(imx_ldb_ch->panel, @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm, return ret; } - drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder); - return 0; } @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); +static int imx_ldb_panel_ddc(struct device *dev, + struct imx_ldb_channel *channel, struct device_node *child) +{ + struct device_node *ddc_node; + const u8 *edidp; + int ret; + + ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); + if (ddc_node) { + channel->ddc = of_find_i2c_adapter_by_node(ddc_node); + of_node_put(ddc_node); + if (!channel->ddc) { + dev_warn(dev, "failed to get ddc i2c adapter\n"); + return -EPROBE_DEFER; + } + } + + if (!channel->ddc) { + /* if no DDC available, fallback to hardcoded EDID */ + dev_dbg(dev, "no ddc available\n"); + + edidp = of_get_property(child, "edid", + &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, + channel->edid_len, + GFP_KERNEL); + } else if (!channel->panel) { + /* fallback to display-timings node */ + ret = of_get_drm_display_mode(child, + &channel->mode, + OF_USE_NATIVE_MODE); + if (!ret) + channel->mode_valid = 1; + } + } + return 0; +} + static int imx_ldb_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); struct device_node *child; - const u8 *edidp; struct imx_ldb *imx_ldb; int dual; int ret; @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; - struct device_node *ddc_node; struct device_node *ep; int bus_format; @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data) remote = of_graph_get_remote_port_parent(ep); of_node_put(ep); - if (remote) + if (remote) { channel->panel = of_drm_find_panel(remote); - else + channel->bridge = of_drm_find_bridge(remote); + } else return -EPROBE_DEFER; of_node_put(remote); - if (!channel->panel) { - dev_err(dev, "panel not found: %s\n", - remote->full_name); - return -EPROBE_DEFER; - } - } - ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); - if (ddc_node) { - channel->ddc = of_find_i2c_adapter_by_node(ddc_node); - of_node_put(ddc_node); - if (!channel->ddc) { - dev_warn(dev, "failed to get ddc i2c adapter\n"); + if (!channel->panel && !channel->bridge) { + dev_err(dev, "panel/bridge not found: %s\n", + remote->full_name); return -EPROBE_DEFER; } } - if (!channel->ddc) { - /* if no DDC available, fallback to hardcoded EDID */ - dev_dbg(dev, "no ddc available\n"); - - edidp = of_get_property(child, "edid", - &channel->edid_len); - if (edidp) { - channel->edid = kmemdup(edidp, - channel->edid_len, - GFP_KERNEL); - } else if (!channel->panel) { - /* fallback to display-timings node */ - ret = of_get_drm_display_mode(child, - &channel->mode, - OF_USE_NATIVE_MODE); - if (!ret) - channel->mode_valid = 1; - } + /* panel ddc only if there is no bridge */ + if (!channel->bridge) { + ret = imx_ldb_panel_ddc(dev, channel, child); + if (ret) + return ret; } bus_format = of_get_bus_format(dev, child); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge @ 2016-08-11 9:38 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-08-11 9:38 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk, Rob Herring, Fabio Estevam Am Dienstag, den 09.08.2016, 18:41 +0200 schrieb Peter Senna Tschudin: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > This patch does a simple code refactoring by moving code > from for_each_child_of_node iterator to a new function named > imx_ldb_panel_ddc(). This was necessary to allow the panel ddc > code to run only when the imx_ldb is not attached to a bridge. > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Cc: David Airlie <airlied@linux.ie> > Cc: Thierry Reding <treding@nvidia.com> > Cc: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Unchanged from V4 I've rebased and applied patch 1. thanks Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge @ 2016-08-11 9:38 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-08-11 9:38 UTC (permalink / raw) To: linux-arm-kernel Am Dienstag, den 09.08.2016, 18:41 +0200 schrieb Peter Senna Tschudin: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > This patch does a simple code refactoring by moving code > from for_each_child_of_node iterator to a new function named > imx_ldb_panel_ddc(). This was necessary to allow the panel ddc > code to run only when the imx_ldb is not attached to a bridge. > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Cc: David Airlie <airlied@linux.ie> > Cc: Thierry Reding <treding@nvidia.com> > Cc: Thierry Reding <thierry.reding@gmail.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Unchanged from V4 I've rebased and applied patch 1. thanks Philipp ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge @ 2016-08-11 9:38 ` Philipp Zabel 0 siblings, 0 replies; 172+ messages in thread From: Philipp Zabel @ 2016-08-11 9:38 UTC (permalink / raw) To: Peter Senna Tschudin Cc: airlied-cv59FeDIM0c, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, daniel.vetter-/w4YWyX8dFk, davem-fT/PcQaiUtIeIZ0/mPfg9Q, devicetree-u79uwXL29TY76Z2rM5mHXA, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, galak-sgV2jX0FEOL9JmXXK+q4OQ, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, heiko-4mtYJXux2i+zQB+pC5nmwQ, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, javier-0uQlZySMnqxg9hUCZPvPmw, jslaby-AlSwsSmVLrQ, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-I+IVW8TIWO2tmTQ+vhA3Yw, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-0h96xk9xTtrk1uMJSBkQmQ, mark.rutland-5wv7dgnIgG8, martin.donnelly-JJi787mZWgc, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, pawel.moll-5wv7dgnIgG8, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, tiwai-IBi9RG/b67k, treding-DDmLM1+adcrQT0dZR+AlfA, ykk-TNX95d0MmH7DzftRWevZcw, Rob Herring, Fabio Estevam Am Dienstag, den 09.08.2016, 18:41 +0200 schrieb Peter Senna Tschudin: > Add support to attach a drm_bridge to imx-ldb in addition to > existing support to attach a LVDS panel. > > This patch does a simple code refactoring by moving code > from for_each_child_of_node iterator to a new function named > imx_ldb_panel_ddc(). This was necessary to allow the panel ddc > code to run only when the imx_ldb is not attached to a bridge. > > Cc: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > Cc: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > Cc: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> > Cc: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> > Cc: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > --- > Unchanged from V4 I've rebased and applied patch 1. thanks Philipp -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp 2016-08-09 16:41 ` Peter Senna Tschudin @ 2016-08-09 16:41 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Martyn Welch <martyn.welch@collabora.co.uk> Cc: Martin Donnelly <martin.donnelly@ge.com> Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Acked-by: Rob Herring <robh@kernel.org> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V4 Changes from V3: - 2/4 instead of 3/5 Unchanged from V2 Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt new file mode 100644 index 0000000..f05c3e9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt @@ -0,0 +1,37 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3-lvds-dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : phandle of the interrupt controller that services + interrupts to the device + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the video signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-08-09 16:41 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: linux-arm-kernel Devicetree bindings documentation for the GE B850v3 LVDS/DP++ display bridge. Cc: Martyn Welch <martyn.welch@collabora.co.uk> Cc: Martin Donnelly <martin.donnelly@ge.com> Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Acked-by: Rob Herring <robh@kernel.org> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V4 Changes from V3: - 2/4 instead of 3/5 Unchanged from V2 Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to the example .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt new file mode 100644 index 0000000..f05c3e9 --- /dev/null +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt @@ -0,0 +1,37 @@ +Driver for GE B850v3 LVDS/DP++ display bridge + +Required properties: + - compatible : should be "ge,b850v3-lvds-dp". + - reg : should contain the address used to ack the interrupts. + - interrupt-parent : phandle of the interrupt controller that services + interrupts to the device + - interrupts : one interrupt should be described here, as in + <0 IRQ_TYPE_LEVEL_HIGH>. + - edid-reg : should contain the address used to read edid information + - port : should describe the video signal connection between the host + and the bridge. + +Example: + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge at 73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-09-26 8:26 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:26 UTC (permalink / raw) To: Peter Senna Tschudin Cc: linux-arm-kernel, eballetbo, pawel.moll, treding, linux, heiko, thierry.reding, daniel.vetter, Fabio Estevam, jslaby, dri-devel, martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk, ijc+devicetree, rmk+kernel, davem, mark.rutland, kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel, akpm, Rob Herring, javier, robh+dt, devicetree, martin.donnelly Patch 1/4 is already on linux-next, but what about this one? Ping? On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Acked-by: Rob Herring <robh@kernel.org> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Unchanged from V4 > > Changes from V3: > - 2/4 instead of 3/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > new file mode 100644 > index 0000000..f05c3e9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > @@ -0,0 +1,37 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3-lvds-dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : phandle of the interrupt controller that services > + interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the video signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-09-26 8:26 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:26 UTC (permalink / raw) To: linux-arm-kernel Patch 1/4 is already on linux-next, but what about this one? Ping? On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Acked-by: Rob Herring <robh@kernel.org> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Unchanged from V4 > > Changes from V3: > - 2/4 instead of 3/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > new file mode 100644 > index 0000000..f05c3e9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > @@ -0,0 +1,37 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3-lvds-dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : phandle of the interrupt controller that services > + interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the video signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge at 73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp @ 2016-09-26 8:26 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:26 UTC (permalink / raw) To: Peter Senna Tschudin Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, pawel.moll-5wv7dgnIgG8, treding-DDmLM1+adcrQT0dZR+AlfA, linux-0h96xk9xTtrk1uMJSBkQmQ, heiko-4mtYJXux2i+zQB+pC5nmwQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, daniel.vetter-/w4YWyX8dFk, Fabio Estevam, jslaby-AlSwsSmVLrQ, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, linux-I+IVW8TIWO2tmTQ+vhA3Yw, galak-sgV2jX0FEOL9JmXXK+q4OQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, airlied-cv59FeDIM0c, ykk-TNX95d0MmH7DzftRWevZcw, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, davem-fT/PcQaiUtIeIZ0/mPfg9Q, mark.rutland-5wv7dgnIgG8, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, tiwai-IBi9RG/b67k, linux-kernel-u79uwXL29TY76Z2rM5mHXA, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, Rob Herring, javier-0uQlZySMnqxg9hUCZPvPmw Patch 1/4 is already on linux-next, but what about this one? Ping? On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> wrote: > Devicetree bindings documentation for the GE B850v3 LVDS/DP++ > display bridge. > > Cc: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > Cc: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > Cc: Javier Martinez Canillas <javier-0uQlZySMnqxg9hUCZPvPmw@public.gmane.org> > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > --- > Unchanged from V4 > > Changes from V3: > - 2/4 instead of 3/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to the example > > > .../devicetree/bindings/ge/b850v3-lvds-dp.txt | 37 ++++++++++++++++++++++ > 1 file changed, 37 insertions(+) > create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > > diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > new file mode 100644 > index 0000000..f05c3e9 > --- /dev/null > +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt > @@ -0,0 +1,37 @@ > +Driver for GE B850v3 LVDS/DP++ display bridge > + > +Required properties: > + - compatible : should be "ge,b850v3-lvds-dp". > + - reg : should contain the address used to ack the interrupts. > + - interrupt-parent : phandle of the interrupt controller that services > + interrupts to the device > + - interrupts : one interrupt should be described here, as in > + <0 IRQ_TYPE_LEVEL_HIGH>. > + - edid-reg : should contain the address used to read edid information > + - port : should describe the video signal connection between the host > + and the bridge. > + > +Example: > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-08-09 16:41 ` Peter Senna Tschudin @ 2016-08-09 16:41 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam, Archit Taneja Add a driver that create a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Cc: Martyn Welch <martyn.welch@collabora.co.uk> Cc: Martin Donnelly <martin.donnelly@ge.com> Cc: Daniel Vetter <daniel.vetter@ffwll.ch> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> CC: David Airlie <airlied@linux.ie> CC: Thierry Reding <treding@nvidia.com> CC: Thierry Reding <thierry.reding@gmail.com> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V4: - Check the output of the first call to i2c_smbus_write_word_data() and return it's error code for failing gracefully on i2c issues - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to remove the comma from the driver name Changes from V3: - 3/4 instead of 4/5 - Tested on next-20160804 Changes from V2: - Made it atomic to be applied on next-20160729 on top of Liu Ying changes that made imx-ldb atomic Changes from V1: - New commit message - Removed 3 empty entry points - Removed memory leak from ge_b850v3_lvds_dp_get_modes() - Added a lock for mode setting - Removed a few blank lines - Changed the order at Makefile and Kconfig MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index a306795..e8d106a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5142,6 +5142,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martin Donnelly <martin.donnelly@ge.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index b590e67..b4b70fb 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO Designware HDMI block. This is used in conjunction with the i.MX6 HDMI driver. +config DRM_GE_B850V3_LVDS_DP + tristate "GE B850v3 LVDS to DP++ display bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + This is a driver for the display bridge of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index efdb07e..b9606f3 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_SII902X) += sii902x.o diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..81e9279 --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,405 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + * + */ + +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drmP.h> + +/* + * 220Mhz is a limitation of the host, as the bridge is capable of up to + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications + * Processor Reference Manual for more information about the 220Mhz limit. + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work + * fine. + */ +#define MAX_PIXEL_CLOCK 220000 + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; + struct mutex lock; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + int num_modes = 0; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + mutex_lock(&ptn_bridge->lock); + + kfree(ptn_bridge->edid); + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); + + if (ptn_bridge->edid) { + drm_mode_connector_update_edid_property(connector, + ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + } + + mutex_unlock(&ptn_bridge->lock); + + return num_modes; +} + + +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( + struct drm_connector *connector, struct drm_display_mode *mode) +{ + if (mode->clock > MAX_PIXEL_CLOCK) { + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", + mode->name); + return MODE_CLOCK_HIGH; + } + + return MODE_OK; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .mode_valid = ge_b850v3_lvds_dp_mode_valid, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = drm_connector_cleanup, + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + mutex_lock(&ptn_bridge->lock); + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + mutex_unlock(&ptn_bridge->lock); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_register(connector); + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + mutex_init(&ptn_bridge->lock); + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + /* + * Configures the bridge to re-enable interrupts after each ack. As + * this is the first communication with the chip, fail on error. + */ + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + if (ret) { + dev_err(dev, "i2c communication failed, aborting...\n"); + return ret; + } + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + kfree(ptn_bridge->edid); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3-lvds-dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3-lvds-dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "b850v3-lvds-dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-09 16:41 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: linux-arm-kernel Add a driver that create a drm_bridge and a drm_connector for the LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and firmware made it complicated for this binding to comprise two device tree nodes, as the design goal is to configure both bridges based on the LVDS signal, which leave the driver powerless to control the video processing pipeline. The two bridges behaves as a single bridge, and the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts. The video signal pipeline is as follows: Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output Cc: Martyn Welch <martyn.welch@collabora.co.uk> Cc: Martin Donnelly <martin.donnelly@ge.com> Cc: Daniel Vetter <daniel.vetter@ffwll.ch> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> CC: David Airlie <airlied@linux.ie> CC: Thierry Reding <treding@nvidia.com> CC: Thierry Reding <thierry.reding@gmail.com> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Changes from V4: - Check the output of the first call to i2c_smbus_write_word_data() and return it's error code for failing gracefully on i2c issues - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to remove the comma from the driver name Changes from V3: - 3/4 instead of 4/5 - Tested on next-20160804 Changes from V2: - Made it atomic to be applied on next-20160729 on top of Liu Ying changes that made imx-ldb atomic Changes from V1: - New commit message - Removed 3 empty entry points - Removed memory leak from ge_b850v3_lvds_dp_get_modes() - Added a lock for mode setting - Removed a few blank lines - Changed the order at Makefile and Kconfig MAINTAINERS | 8 + drivers/gpu/drm/bridge/Kconfig | 11 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c diff --git a/MAINTAINERS b/MAINTAINERS index a306795..e8d106a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5142,6 +5142,14 @@ W: https://linuxtv.org S: Maintained F: drivers/media/radio/radio-gemtek* +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE +M: Peter Senna Tschudin <peter.senna@collabora.com> +M: Martin Donnelly <martin.donnelly@ge.com> +M: Martyn Welch <martyn.welch@collabora.co.uk> +S: Maintained +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt + GENERIC GPIO I2C DRIVER M: Haavard Skinnemoen <hskinnemoen@gmail.com> S: Supported diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index b590e67..b4b70fb 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO Designware HDMI block. This is used in conjunction with the i.MX6 HDMI driver. +config DRM_GE_B850V3_LVDS_DP + tristate "GE B850v3 LVDS to DP++ display bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- + This is a driver for the display bridge of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index efdb07e..b9606f3 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_SII902X) += sii902x.o diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c new file mode 100644 index 0000000..81e9279 --- /dev/null +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c @@ -0,0 +1,405 @@ +/* + * Driver for GE B850v3 DP display bridge + + * Copyright (c) 2016, Collabora Ltd. + * Copyright (c) 2016, General Electric Company + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ + * display bridge of the GE B850v3. There are two physical bridges on the video + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However + * the physical bridges are automatically configured by the input video signal, + * and the driver has no access to the video processing pipeline. The driver is + * only needed to read EDID from the STDP2690 and to handle HPD events from the + * STDP4028. The driver communicates with both bridges over i2c. The video + * signal pipeline is as follows: + * + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output + * + */ + +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drmP.h> + +/* + * 220Mhz is a limitation of the host, as the bridge is capable of up to + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications + * Processor Reference Manual for more information about the 220Mhz limit. + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work + * fine. + */ +#define MAX_PIXEL_CLOCK 220000 + +#define EDID_EXT_BLOCK_CNT 0x7E + +#define STDP4028_IRQ_OUT_CONF_REG 0x02 +#define STDP4028_DPTX_IRQ_EN_REG 0x3C +#define STDP4028_DPTX_IRQ_STS_REG 0x3D +#define STDP4028_DPTX_STS_REG 0x3E + +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 + +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 +#define STDP4028_DPTX_IRQ_CONFIG \ + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) + +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 +#define STDP4028_DPTX_LINK_STS 0x1000 +#define STDP4028_CON_STATE_CONNECTED \ + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) + +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 +#define STDP4028_DPTX_LINK_CH_STS 0x2000 +#define STDP4028_DPTX_IRQ_CLEAR \ + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) + +struct ge_b850v3_lvds_dp { + struct drm_connector connector; + struct drm_bridge bridge; + struct i2c_client *ge_b850v3_lvds_dp_i2c; + struct i2c_client *edid_i2c; + struct edid *edid; + struct mutex lock; +}; + +static inline struct ge_b850v3_lvds_dp * + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); +} + +static inline struct ge_b850v3_lvds_dp * + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) +{ + return container_of(connector, struct ge_b850v3_lvds_dp, connector); +} + +u8 *stdp2690_get_edid(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + unsigned char start = 0x00; + unsigned int total_size; + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = block, + } + }; + + if (!block) + return NULL; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID.\n"); + goto err; + } + + if (!drm_edid_block_valid(block, 0, false, NULL)) { + DRM_ERROR("Invalid EDID block\n"); + goto err; + } + + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; + if (total_size > EDID_LENGTH) { + kfree(block); + block = kmalloc(total_size, GFP_KERNEL); + if (!block) + return NULL; + + /* Yes, read the entire buffer, and do not skip the first + * EDID_LENGTH bytes. + */ + start = 0x00; + msgs[1].len = total_size; + msgs[1].buf = block; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + DRM_ERROR("Unable to read EDID extension blocks.\n"); + goto err; + } + } + + return block; + +err: + kfree(block); + return NULL; +} + +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) +{ + struct ge_b850v3_lvds_dp *ptn_bridge; + struct i2c_client *client; + int num_modes = 0; + + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); + client = ptn_bridge->edid_i2c; + + mutex_lock(&ptn_bridge->lock); + + kfree(ptn_bridge->edid); + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); + + if (ptn_bridge->edid) { + drm_mode_connector_update_edid_property(connector, + ptn_bridge->edid); + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + } + + mutex_unlock(&ptn_bridge->lock); + + return num_modes; +} + + +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( + struct drm_connector *connector, struct drm_display_mode *mode) +{ + if (mode->clock > MAX_PIXEL_CLOCK) { + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", + mode->name); + return MODE_CLOCK_HIGH; + } + + return MODE_OK; +} + +static const struct +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { + .get_modes = ge_b850v3_lvds_dp_get_modes, + .mode_valid = ge_b850v3_lvds_dp_mode_valid, +}; + +static enum drm_connector_status ge_b850v3_lvds_dp_detect( + struct drm_connector *connector, bool force) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + connector_to_ge_b850v3_lvds_dp(connector); + struct i2c_client *ge_b850v3_lvds_dp_i2c = + ptn_bridge->ge_b850v3_lvds_dp_i2c; + s32 link_state; + + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_STS_REG); + + if (link_state == STDP4028_CON_STATE_CONNECTED) + return connector_status_connected; + + if (link_state == 0) + return connector_status_disconnected; + + return connector_status_unknown; +} + +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ge_b850v3_lvds_dp_detect, + .destroy = drm_connector_cleanup, + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + + mutex_lock(&ptn_bridge->lock); + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + mutex_unlock(&ptn_bridge->lock); + + if (ptn_bridge->connector.dev) + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); + + return IRQ_HANDLED; +} + +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) +{ + struct ge_b850v3_lvds_dp *ptn_bridge + = bridge_to_ge_b850v3_lvds_dp(bridge); + struct drm_connector *connector = &ptn_bridge->connector; + struct i2c_client *ge_b850v3_lvds_dp_i2c + = ptn_bridge->ge_b850v3_lvds_dp_i2c; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, + &ge_b850v3_lvds_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &ge_b850v3_lvds_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_register(connector); + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); + if (ret) + return ret; + + drm_bridge_enable(bridge); + if (ge_b850v3_lvds_dp_i2c->irq) { + drm_helper_hpd_irq_event(connector->dev); + + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, + ge_b850v3_lvds_dp_i2c->irq, NULL, + ge_b850v3_lvds_dp_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ptn_bridge); + if (ret) + return ret; + } + + return 0; +} + +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { + .attach = ge_b850v3_lvds_dp_attach, +}; + +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; + struct ge_b850v3_lvds_dp *ptn_bridge; + int ret; + u32 edid_i2c_reg; + + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) + return -ENOMEM; + + mutex_init(&ptn_bridge->lock); + + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; + ptn_bridge->bridge.driver_private = ptn_bridge; + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); + + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); + if (ret) { + dev_err(dev, "edid-reg not specified, aborting...\n"); + return -ENODEV; + } + + ptn_bridge->edid_i2c = devm_kzalloc(dev, + sizeof(struct i2c_client), GFP_KERNEL); + + if (!ptn_bridge->edid_i2c) + return -ENOMEM; + + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, + sizeof(struct i2c_client)); + + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; + + /* + * Configures the bridge to re-enable interrupts after each ack. As + * this is the first communication with the chip, fail on error. + */ + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); + if (ret) { + dev_err(dev, "i2c communication failed, aborting...\n"); + return ret; + } + + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); + + /* Clear pending interrupts since power up. */ + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); + + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; + ptn_bridge->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&ptn_bridge->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + return ret; + } + + return 0; +} + +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) +{ + struct ge_b850v3_lvds_dp *ptn_bridge = + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); + + drm_bridge_remove(&ptn_bridge->bridge); + + kfree(ptn_bridge->edid); + + return 0; +} + +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { + {"b850v3-lvds-dp", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); + +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { + { .compatible = "ge,b850v3-lvds-dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); + +static struct i2c_driver ge_b850v3_lvds_dp_driver = { + .id_table = ge_b850v3_lvds_dp_i2c_table, + .probe = ge_b850v3_lvds_dp_probe, + .remove = ge_b850v3_lvds_dp_remove, + .driver = { + .name = "b850v3-lvds-dp", + .of_match_table = ge_b850v3_lvds_dp_match, + }, +}; +module_i2c_driver(ge_b850v3_lvds_dp_driver); + +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); +MODULE_LICENSE("GPL v2"); -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-16 4:15 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-08-16 4:15 UTC (permalink / raw) To: Peter Senna Tschudin, airlied, akpm, daniel.vetter, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam Hi, On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > I'd commented on an earlier revision (v2) of this patch, but hadn't got a response on it. Pasting the query again: Are these two chips always expected to be used together? I don't think it's right to pair up two encoder chips into one driver just for one board. Is one device @0x72 and other @0x73? Or is only one of them an i2c slave? What's preventing us to create these as two different bridge drivers? The drm framework allows us to daisy chain encoder bridges. The only problem I see is that we don't have a clear-cut way to tell the bridge driver whether we want it to create a connector for us or not. Because, it looks like both can potentially create connectors. This isn't a big problem either if we have DT. We just need to check whether our output port is connected to another bridge or a connector. Thanks, Archit > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V4: > - Check the output of the first call to i2c_smbus_write_word_data() and return > it's error code for failing gracefully on i2c issues > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > remove the comma from the driver name > > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > 4 files changed, 425 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a306795..e8d106a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..81e9279 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,405 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* > + * Configures the bridge to re-enable interrupts after each ack. As > + * this is the first communication with the chip, fail on error. > + */ > + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + if (ret) { > + dev_err(dev, "i2c communication failed, aborting...\n"); > + return ret; > + } > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + kfree(ptn_bridge->edid); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); > +MODULE_LICENSE("GPL v2"); > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-16 4:15 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-08-16 4:15 UTC (permalink / raw) To: linux-arm-kernel Hi, On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > I'd commented on an earlier revision (v2) of this patch, but hadn't got a response on it. Pasting the query again: Are these two chips always expected to be used together? I don't think it's right to pair up two encoder chips into one driver just for one board. Is one device @0x72 and other @0x73? Or is only one of them an i2c slave? What's preventing us to create these as two different bridge drivers? The drm framework allows us to daisy chain encoder bridges. The only problem I see is that we don't have a clear-cut way to tell the bridge driver whether we want it to create a connector for us or not. Because, it looks like both can potentially create connectors. This isn't a big problem either if we have DT. We just need to check whether our output port is connected to another bridge or a connector. Thanks, Archit > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V4: > - Check the output of the first call to i2c_smbus_write_word_data() and return > it's error code for failing gracefully on i2c issues > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > remove the comma from the driver name > > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > 4 files changed, 425 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a306795..e8d106a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..81e9279 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,405 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* > + * Configures the bridge to re-enable interrupts after each ack. As > + * this is the first communication with the chip, fail on error. > + */ > + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + if (ret) { > + dev_err(dev, "i2c communication failed, aborting...\n"); > + return ret; > + } > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + kfree(ptn_bridge->edid); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); > +MODULE_LICENSE("GPL v2"); > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-08-16 4:15 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-08-16 4:15 UTC (permalink / raw) To: Peter Senna Tschudin, airlied-cv59FeDIM0c, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, daniel.vetter-/w4YWyX8dFk, davem-fT/PcQaiUtIeIZ0/mPfg9Q, devicetree-u79uwXL29TY76Z2rM5mHXA, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, galak-sgV2jX0FEOL9JmXXK+q4OQ, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, heiko-4mtYJXux2i+zQB+pC5nmwQ, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, javier-0uQlZySMnqxg9hUCZPvPmw, jslaby-AlSwsSmVLrQ, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-I+IVW8TIWO2tmTQ+vhA3Yw, linux-kernel-u79uwXL29TY76Z2rM5mHXA, linux-0h96xk9xTtrk1uMJSBkQmQ, mark.rutland-5wv7dgnIgG8, martin.donnelly-JJi787mZWgc, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, pawel.moll-5wv7dgnIgG8, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, tiwai-IBi9RG/b67k, treding-DDmLM1+adcrQT0dZR+AlfA, ykk-TNX95d0MmH7DzftRWevZcw Cc: Rob Herring, Fabio Estevam Hi, On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > I'd commented on an earlier revision (v2) of this patch, but hadn't got a response on it. Pasting the query again: Are these two chips always expected to be used together? I don't think it's right to pair up two encoder chips into one driver just for one board. Is one device @0x72 and other @0x73? Or is only one of them an i2c slave? What's preventing us to create these as two different bridge drivers? The drm framework allows us to daisy chain encoder bridges. The only problem I see is that we don't have a clear-cut way to tell the bridge driver whether we want it to create a connector for us or not. Because, it looks like both can potentially create connectors. This isn't a big problem either if we have DT. We just need to check whether our output port is connected to another bridge or a connector. Thanks, Archit > Cc: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > Cc: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > Cc: Daniel Vetter <daniel.vetter-/w4YWyX8dFk@public.gmane.org> > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > CC: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> > CC: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> > CC: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > Reviewed-by: Enric Balletbo <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > --- > Changes from V4: > - Check the output of the first call to i2c_smbus_write_word_data() and return > it's error code for failing gracefully on i2c issues > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > remove the comma from the driver name > > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > 4 files changed, 425 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a306795..e8d106a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > +M: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > +M: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..81e9279 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,405 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* > + * Configures the bridge to re-enable interrupts after each ack. As > + * this is the first communication with the chip, fail on error. > + */ > + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + if (ret) { > + dev_err(dev, "i2c communication failed, aborting...\n"); > + return ret; > + } > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + kfree(ptn_bridge->edid); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); > +MODULE_LICENSE("GPL v2"); > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:27 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:27 UTC (permalink / raw) To: Peter Senna Tschudin Cc: linux-arm-kernel, eballetbo, pawel.moll, treding, linux, heiko, thierry.reding, daniel.vetter, Fabio Estevam, jslaby, dri-devel, martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk, ijc+devicetree, rmk+kernel, davem, mark.rutland, Archit Taneja, kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel, akpm, Rob Herring, javier, robh+dt, devicetree, martin.donnelly Patch 1/4 is already on linux-next, but what about this one? Ping? On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V4: > - Check the output of the first call to i2c_smbus_write_word_data() and return > it's error code for failing gracefully on i2c issues > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > remove the comma from the driver name > > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > 4 files changed, 425 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a306795..e8d106a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..81e9279 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,405 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* > + * Configures the bridge to re-enable interrupts after each ack. As > + * this is the first communication with the chip, fail on error. > + */ > + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + if (ret) { > + dev_err(dev, "i2c communication failed, aborting...\n"); > + return ret; > + } > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + kfree(ptn_bridge->edid); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:27 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:27 UTC (permalink / raw) To: linux-arm-kernel Patch 1/4 is already on linux-next, but what about this one? Ping? On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V4: > - Check the output of the first call to i2c_smbus_write_word_data() and return > it's error code for failing gracefully on i2c issues > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > remove the comma from the driver name > > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > 4 files changed, 425 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a306795..e8d106a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..81e9279 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,405 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* > + * Configures the bridge to re-enable interrupts after each ack. As > + * this is the first communication with the chip, fail on error. > + */ > + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + if (ret) { > + dev_err(dev, "i2c communication failed, aborting...\n"); > + return ret; > + } > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + kfree(ptn_bridge->edid); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:27 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:27 UTC (permalink / raw) To: Peter Senna Tschudin Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, pawel.moll-5wv7dgnIgG8, treding-DDmLM1+adcrQT0dZR+AlfA, linux-0h96xk9xTtrk1uMJSBkQmQ, heiko-4mtYJXux2i+zQB+pC5nmwQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, daniel.vetter-/w4YWyX8dFk, Fabio Estevam, jslaby-AlSwsSmVLrQ, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, linux-I+IVW8TIWO2tmTQ+vhA3Yw, galak-sgV2jX0FEOL9JmXXK+q4OQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, airlied-cv59FeDIM0c, ykk-TNX95d0MmH7DzftRWevZcw, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, davem-fT/PcQaiUtIeIZ0/mPfg9Q, mark.rutland-5wv7dgnIgG8, Archit Taneja, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, tiwai-IBi9RG/b67k, linux-kernel-u79uwXL29TY76Z2rM5mHXA, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, Rob Patch 1/4 is already on linux-next, but what about this one? Ping? On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > Cc: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > Cc: Daniel Vetter <daniel.vetter-/w4YWyX8dFk@public.gmane.org> > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > CC: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> > CC: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> > CC: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > Reviewed-by: Enric Balletbo <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > --- > Changes from V4: > - Check the output of the first call to i2c_smbus_write_word_data() and return > it's error code for failing gracefully on i2c issues > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > remove the comma from the driver name > > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > 4 files changed, 425 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a306795..e8d106a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > +M: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > +M: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> > S: Supported > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..81e9279 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,405 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { > + .attach = ge_b850v3_lvds_dp_attach, > +}; > + > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; > + struct ge_b850v3_lvds_dp *ptn_bridge; > + int ret; > + u32 edid_i2c_reg; > + > + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); > + if (!ptn_bridge) > + return -ENOMEM; > + > + mutex_init(&ptn_bridge->lock); > + > + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; > + ptn_bridge->bridge.driver_private = ptn_bridge; > + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); > + > + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); > + if (ret) { > + dev_err(dev, "edid-reg not specified, aborting...\n"); > + return -ENODEV; > + } > + > + ptn_bridge->edid_i2c = devm_kzalloc(dev, > + sizeof(struct i2c_client), GFP_KERNEL); > + > + if (!ptn_bridge->edid_i2c) > + return -ENOMEM; > + > + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, > + sizeof(struct i2c_client)); > + > + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; > + > + /* > + * Configures the bridge to re-enable interrupts after each ack. As > + * this is the first communication with the chip, fail on error. > + */ > + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); > + if (ret) { > + dev_err(dev, "i2c communication failed, aborting...\n"); > + return ret; > + } > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); > + > + /* Clear pending interrupts since power up. */ > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; > + ptn_bridge->bridge.of_node = dev->of_node; > + ret = drm_bridge_add(&ptn_bridge->bridge); > + if (ret) { > + DRM_ERROR("Failed to add bridge\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); > + > + drm_bridge_remove(&ptn_bridge->bridge); > + > + kfree(ptn_bridge->edid); > + > + return 0; > +} > + > +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { > + {"b850v3-lvds-dp", 0}, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); > + > +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { > + { .compatible = "ge,b850v3-lvds-dp" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); > + > +static struct i2c_driver ge_b850v3_lvds_dp_driver = { > + .id_table = ge_b850v3_lvds_dp_i2c_table, > + .probe = ge_b850v3_lvds_dp_probe, > + .remove = ge_b850v3_lvds_dp_remove, > + .driver = { > + .name = "b850v3-lvds-dp", > + .of_match_table = ge_b850v3_lvds_dp_match, > + }, > +}; > +module_i2c_driver(ge_b850v3_lvds_dp_driver); > + > +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org>"); > +MODULE_AUTHOR("Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org>"); > +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); > +MODULE_LICENSE("GPL v2"); > -- > 2.5.5 > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-09-26 8:27 ` Peter Senna Tschudin (?) @ 2016-09-26 8:31 ` Archit Taneja -1 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 8:31 UTC (permalink / raw) To: Peter Senna Tschudin, Peter Senna Tschudin Cc: linux-arm-kernel, eballetbo, pawel.moll, treding, linux, heiko, thierry.reding, daniel.vetter, Fabio Estevam, jslaby, dri-devel, martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk, ijc+devicetree, rmk+kernel, davem, mark.rutland, kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel, akpm, Rob Herring, javier, robh+dt, devicetree, martin.donnelly Hi Peter, On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote: > Patch 1/4 is already on linux-next, but what about this one? Ping? I'd posted some queries a couple of times which you didn't answer to. Could you please respond to them before we try to get this merged? Archit > > On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > >> Add a driver that create a drm_bridge and a drm_connector for the LVDS >> to DP++ display bridge of the GE B850v3. >> >> There are two physical bridges on the video signal pipeline: a >> STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and >> firmware made it complicated for this binding to comprise two device >> tree nodes, as the design goal is to configure both bridges based on >> the LVDS signal, which leave the driver powerless to control the video >> processing pipeline. The two bridges behaves as a single bridge, and >> the driver is only needed for telling the host about EDID / HPD, and >> for giving the host powers to ack interrupts. The video signal pipeline >> is as follows: >> >> Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >> >> Cc: Martyn Welch <martyn.welch@collabora.co.uk> >> Cc: Martin Donnelly <martin.donnelly@ge.com> >> Cc: Daniel Vetter <daniel.vetter@ffwll.ch> >> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> >> Cc: Philipp Zabel <p.zabel@pengutronix.de> >> Cc: Rob Herring <robh@kernel.org> >> Cc: Fabio Estevam <fabio.estevam@nxp.com> >> CC: David Airlie <airlied@linux.ie> >> CC: Thierry Reding <treding@nvidia.com> >> CC: Thierry Reding <thierry.reding@gmail.com> >> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> >> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> >> --- >> Changes from V4: >> - Check the output of the first call to i2c_smbus_write_word_data() and return >> it's error code for failing gracefully on i2c issues >> - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to >> remove the comma from the driver name >> >> Changes from V3: >> - 3/4 instead of 4/5 >> - Tested on next-20160804 >> >> Changes from V2: >> - Made it atomic to be applied on next-20160729 on top of Liu Ying changes >> that made imx-ldb atomic >> >> Changes from V1: >> - New commit message >> - Removed 3 empty entry points >> - Removed memory leak from ge_b850v3_lvds_dp_get_modes() >> - Added a lock for mode setting >> - Removed a few blank lines >> - Changed the order at Makefile and Kconfig >> >> MAINTAINERS | 8 + >> drivers/gpu/drm/bridge/Kconfig | 11 + >> drivers/gpu/drm/bridge/Makefile | 1 + >> drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ >> 4 files changed, 425 insertions(+) >> create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index a306795..e8d106a 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -5142,6 +5142,14 @@ W: https://linuxtv.org >> S: Maintained >> F: drivers/media/radio/radio-gemtek* >> >> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE >> +M: Peter Senna Tschudin <peter.senna@collabora.com> >> +M: Martin Donnelly <martin.donnelly@ge.com> >> +M: Martyn Welch <martyn.welch@collabora.co.uk> >> +S: Maintained >> +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c >> +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt >> + >> GENERIC GPIO I2C DRIVER >> M: Haavard Skinnemoen <hskinnemoen@gmail.com> >> S: Supported >> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig >> index b590e67..b4b70fb 100644 >> --- a/drivers/gpu/drm/bridge/Kconfig >> +++ b/drivers/gpu/drm/bridge/Kconfig >> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO >> Designware HDMI block. This is used in conjunction with >> the i.MX6 HDMI driver. >> >> +config DRM_GE_B850V3_LVDS_DP >> + tristate "GE B850v3 LVDS to DP++ display bridge" >> + depends on OF >> + select DRM_KMS_HELPER >> + select DRM_PANEL >> + ---help--- >> + This is a driver for the display bridge of >> + GE B850v3 that convert dual channel LVDS >> + to DP++. This is used with the i.MX6 imx-ldb >> + driver. >> + >> config DRM_NXP_PTN3460 >> tristate "NXP PTN3460 DP/LVDS bridge" >> depends on OF >> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile >> index efdb07e..b9606f3 100644 >> --- a/drivers/gpu/drm/bridge/Makefile >> +++ b/drivers/gpu/drm/bridge/Makefile >> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm >> obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o >> obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o >> obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o >> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o >> obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o >> obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o >> obj-$(CONFIG_DRM_SII902X) += sii902x.o >> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >> new file mode 100644 >> index 0000000..81e9279 >> --- /dev/null >> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >> @@ -0,0 +1,405 @@ >> +/* >> + * Driver for GE B850v3 DP display bridge >> + >> + * Copyright (c) 2016, Collabora Ltd. >> + * Copyright (c) 2016, General Electric Company >> + >> + * This program is free software; you can redistribute it and/or modify it >> + * under the terms and conditions of the GNU General Public License, >> + * version 2, as published by the Free Software Foundation. >> + >> + * This program is distributed in the hope it will be useful, but WITHOUT >> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or >> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for >> + * more details. >> + >> + * You should have received a copy of the GNU General Public License >> + * along with this program. If not, see <http://www.gnu.org/licenses/>. >> + >> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ >> + * display bridge of the GE B850v3. There are two physical bridges on the video >> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However >> + * the physical bridges are automatically configured by the input video signal, >> + * and the driver has no access to the video processing pipeline. The driver is >> + * only needed to read EDID from the STDP2690 and to handle HPD events from the >> + * STDP4028. The driver communicates with both bridges over i2c. The video >> + * signal pipeline is as follows: >> + * >> + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >> + * >> + */ >> + >> +#include <linux/gpio.h> >> +#include <linux/i2c.h> >> +#include <linux/module.h> >> +#include <linux/of.h> >> +#include <drm/drm_atomic.h> >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_crtc_helper.h> >> +#include <drm/drm_edid.h> >> +#include <drm/drmP.h> >> + >> +/* >> + * 220Mhz is a limitation of the host, as the bridge is capable of up to >> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications >> + * Processor Reference Manual for more information about the 220Mhz limit. >> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work >> + * fine. >> + */ >> +#define MAX_PIXEL_CLOCK 220000 >> + >> +#define EDID_EXT_BLOCK_CNT 0x7E >> + >> +#define STDP4028_IRQ_OUT_CONF_REG 0x02 >> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C >> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D >> +#define STDP4028_DPTX_STS_REG 0x3E >> + >> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 >> + >> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 >> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 >> +#define STDP4028_DPTX_IRQ_CONFIG \ >> + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) >> + >> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 >> +#define STDP4028_DPTX_LINK_STS 0x1000 >> +#define STDP4028_CON_STATE_CONNECTED \ >> + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) >> + >> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 >> +#define STDP4028_DPTX_LINK_CH_STS 0x2000 >> +#define STDP4028_DPTX_IRQ_CLEAR \ >> + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) >> + >> +struct ge_b850v3_lvds_dp { >> + struct drm_connector connector; >> + struct drm_bridge bridge; >> + struct i2c_client *ge_b850v3_lvds_dp_i2c; >> + struct i2c_client *edid_i2c; >> + struct edid *edid; >> + struct mutex lock; >> +}; >> + >> +static inline struct ge_b850v3_lvds_dp * >> + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) >> +{ >> + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); >> +} >> + >> +static inline struct ge_b850v3_lvds_dp * >> + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) >> +{ >> + return container_of(connector, struct ge_b850v3_lvds_dp, connector); >> +} >> + >> +u8 *stdp2690_get_edid(struct i2c_client *client) >> +{ >> + struct i2c_adapter *adapter = client->adapter; >> + unsigned char start = 0x00; >> + unsigned int total_size; >> + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); >> + >> + struct i2c_msg msgs[] = { >> + { >> + .addr = client->addr, >> + .flags = 0, >> + .len = 1, >> + .buf = &start, >> + }, { >> + .addr = client->addr, >> + .flags = I2C_M_RD, >> + .len = EDID_LENGTH, >> + .buf = block, >> + } >> + }; >> + >> + if (!block) >> + return NULL; >> + >> + if (i2c_transfer(adapter, msgs, 2) != 2) { >> + DRM_ERROR("Unable to read EDID.\n"); >> + goto err; >> + } >> + >> + if (!drm_edid_block_valid(block, 0, false, NULL)) { >> + DRM_ERROR("Invalid EDID block\n"); >> + goto err; >> + } >> + >> + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; >> + if (total_size > EDID_LENGTH) { >> + kfree(block); >> + block = kmalloc(total_size, GFP_KERNEL); >> + if (!block) >> + return NULL; >> + >> + /* Yes, read the entire buffer, and do not skip the first >> + * EDID_LENGTH bytes. >> + */ >> + start = 0x00; >> + msgs[1].len = total_size; >> + msgs[1].buf = block; >> + >> + if (i2c_transfer(adapter, msgs, 2) != 2) { >> + DRM_ERROR("Unable to read EDID extension blocks.\n"); >> + goto err; >> + } >> + } >> + >> + return block; >> + >> +err: >> + kfree(block); >> + return NULL; >> +} >> + >> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge; >> + struct i2c_client *client; >> + int num_modes = 0; >> + >> + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); >> + client = ptn_bridge->edid_i2c; >> + >> + mutex_lock(&ptn_bridge->lock); >> + >> + kfree(ptn_bridge->edid); >> + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); >> + >> + if (ptn_bridge->edid) { >> + drm_mode_connector_update_edid_property(connector, >> + ptn_bridge->edid); >> + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); >> + } >> + >> + mutex_unlock(&ptn_bridge->lock); >> + >> + return num_modes; >> +} >> + >> + >> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( >> + struct drm_connector *connector, struct drm_display_mode *mode) >> +{ >> + if (mode->clock > MAX_PIXEL_CLOCK) { >> + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", >> + mode->name); >> + return MODE_CLOCK_HIGH; >> + } >> + >> + return MODE_OK; >> +} >> + >> +static const struct >> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { >> + .get_modes = ge_b850v3_lvds_dp_get_modes, >> + .mode_valid = ge_b850v3_lvds_dp_mode_valid, >> +}; >> + >> +static enum drm_connector_status ge_b850v3_lvds_dp_detect( >> + struct drm_connector *connector, bool force) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge = >> + connector_to_ge_b850v3_lvds_dp(connector); >> + struct i2c_client *ge_b850v3_lvds_dp_i2c = >> + ptn_bridge->ge_b850v3_lvds_dp_i2c; >> + s32 link_state; >> + >> + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_STS_REG); >> + >> + if (link_state == STDP4028_CON_STATE_CONNECTED) >> + return connector_status_connected; >> + >> + if (link_state == 0) >> + return connector_status_disconnected; >> + >> + return connector_status_unknown; >> +} >> + >> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { >> + .dpms = drm_atomic_helper_connector_dpms, >> + .fill_modes = drm_helper_probe_single_connector_modes, >> + .detect = ge_b850v3_lvds_dp_detect, >> + .destroy = drm_connector_cleanup, >> + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; >> + struct i2c_client *ge_b850v3_lvds_dp_i2c >> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >> + >> + mutex_lock(&ptn_bridge->lock); >> + >> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); >> + >> + mutex_unlock(&ptn_bridge->lock); >> + >> + if (ptn_bridge->connector.dev) >> + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); >> + >> + return IRQ_HANDLED; >> +} >> + >> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge >> + = bridge_to_ge_b850v3_lvds_dp(bridge); >> + struct drm_connector *connector = &ptn_bridge->connector; >> + struct i2c_client *ge_b850v3_lvds_dp_i2c >> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >> + int ret; >> + >> + if (!bridge->encoder) { >> + DRM_ERROR("Parent encoder object not found"); >> + return -ENODEV; >> + } >> + >> + connector->polled = DRM_CONNECTOR_POLL_HPD; >> + >> + drm_connector_helper_add(connector, >> + &ge_b850v3_lvds_dp_connector_helper_funcs); >> + >> + ret = drm_connector_init(bridge->dev, connector, >> + &ge_b850v3_lvds_dp_connector_funcs, >> + DRM_MODE_CONNECTOR_DisplayPort); >> + if (ret) { >> + DRM_ERROR("Failed to initialize connector with drm\n"); >> + return ret; >> + } >> + >> + drm_connector_register(connector); >> + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); >> + if (ret) >> + return ret; >> + >> + drm_bridge_enable(bridge); >> + if (ge_b850v3_lvds_dp_i2c->irq) { >> + drm_helper_hpd_irq_event(connector->dev); >> + >> + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, >> + ge_b850v3_lvds_dp_i2c->irq, NULL, >> + ge_b850v3_lvds_dp_irq_handler, >> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, >> + "ge-b850v3-lvds-dp", ptn_bridge); >> + if (ret) >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { >> + .attach = ge_b850v3_lvds_dp_attach, >> +}; >> + >> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, >> + const struct i2c_device_id *id) >> +{ >> + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; >> + struct ge_b850v3_lvds_dp *ptn_bridge; >> + int ret; >> + u32 edid_i2c_reg; >> + >> + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); >> + if (!ptn_bridge) >> + return -ENOMEM; >> + >> + mutex_init(&ptn_bridge->lock); >> + >> + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; >> + ptn_bridge->bridge.driver_private = ptn_bridge; >> + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); >> + >> + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); >> + if (ret) { >> + dev_err(dev, "edid-reg not specified, aborting...\n"); >> + return -ENODEV; >> + } >> + >> + ptn_bridge->edid_i2c = devm_kzalloc(dev, >> + sizeof(struct i2c_client), GFP_KERNEL); >> + >> + if (!ptn_bridge->edid_i2c) >> + return -ENOMEM; >> + >> + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, >> + sizeof(struct i2c_client)); >> + >> + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; >> + >> + /* >> + * Configures the bridge to re-enable interrupts after each ack. As >> + * this is the first communication with the chip, fail on error. >> + */ >> + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); >> + if (ret) { >> + dev_err(dev, "i2c communication failed, aborting...\n"); >> + return ret; >> + } >> + >> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); >> + >> + /* Clear pending interrupts since power up. */ >> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); >> + >> + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; >> + ptn_bridge->bridge.of_node = dev->of_node; >> + ret = drm_bridge_add(&ptn_bridge->bridge); >> + if (ret) { >> + DRM_ERROR("Failed to add bridge\n"); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge = >> + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); >> + >> + drm_bridge_remove(&ptn_bridge->bridge); >> + >> + kfree(ptn_bridge->edid); >> + >> + return 0; >> +} >> + >> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { >> + {"b850v3-lvds-dp", 0}, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); >> + >> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { >> + { .compatible = "ge,b850v3-lvds-dp" }, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); >> + >> +static struct i2c_driver ge_b850v3_lvds_dp_driver = { >> + .id_table = ge_b850v3_lvds_dp_i2c_table, >> + .probe = ge_b850v3_lvds_dp_probe, >> + .remove = ge_b850v3_lvds_dp_remove, >> + .driver = { >> + .name = "b850v3-lvds-dp", >> + .of_match_table = ge_b850v3_lvds_dp_match, >> + }, >> +}; >> +module_i2c_driver(ge_b850v3_lvds_dp_driver); >> + >> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); >> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); >> +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); >> +MODULE_LICENSE("GPL v2"); >> -- >> 2.5.5 >> > > > > > > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:31 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 8:31 UTC (permalink / raw) To: linux-arm-kernel Hi Peter, On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote: > Patch 1/4 is already on linux-next, but what about this one? Ping? I'd posted some queries a couple of times which you didn't answer to. Could you please respond to them before we try to get this merged? Archit > > On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > >> Add a driver that create a drm_bridge and a drm_connector for the LVDS >> to DP++ display bridge of the GE B850v3. >> >> There are two physical bridges on the video signal pipeline: a >> STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and >> firmware made it complicated for this binding to comprise two device >> tree nodes, as the design goal is to configure both bridges based on >> the LVDS signal, which leave the driver powerless to control the video >> processing pipeline. The two bridges behaves as a single bridge, and >> the driver is only needed for telling the host about EDID / HPD, and >> for giving the host powers to ack interrupts. The video signal pipeline >> is as follows: >> >> Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >> >> Cc: Martyn Welch <martyn.welch@collabora.co.uk> >> Cc: Martin Donnelly <martin.donnelly@ge.com> >> Cc: Daniel Vetter <daniel.vetter@ffwll.ch> >> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> >> Cc: Philipp Zabel <p.zabel@pengutronix.de> >> Cc: Rob Herring <robh@kernel.org> >> Cc: Fabio Estevam <fabio.estevam@nxp.com> >> CC: David Airlie <airlied@linux.ie> >> CC: Thierry Reding <treding@nvidia.com> >> CC: Thierry Reding <thierry.reding@gmail.com> >> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> >> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> >> --- >> Changes from V4: >> - Check the output of the first call to i2c_smbus_write_word_data() and return >> it's error code for failing gracefully on i2c issues >> - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to >> remove the comma from the driver name >> >> Changes from V3: >> - 3/4 instead of 4/5 >> - Tested on next-20160804 >> >> Changes from V2: >> - Made it atomic to be applied on next-20160729 on top of Liu Ying changes >> that made imx-ldb atomic >> >> Changes from V1: >> - New commit message >> - Removed 3 empty entry points >> - Removed memory leak from ge_b850v3_lvds_dp_get_modes() >> - Added a lock for mode setting >> - Removed a few blank lines >> - Changed the order at Makefile and Kconfig >> >> MAINTAINERS | 8 + >> drivers/gpu/drm/bridge/Kconfig | 11 + >> drivers/gpu/drm/bridge/Makefile | 1 + >> drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ >> 4 files changed, 425 insertions(+) >> create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index a306795..e8d106a 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -5142,6 +5142,14 @@ W: https://linuxtv.org >> S: Maintained >> F: drivers/media/radio/radio-gemtek* >> >> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE >> +M: Peter Senna Tschudin <peter.senna@collabora.com> >> +M: Martin Donnelly <martin.donnelly@ge.com> >> +M: Martyn Welch <martyn.welch@collabora.co.uk> >> +S: Maintained >> +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c >> +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt >> + >> GENERIC GPIO I2C DRIVER >> M: Haavard Skinnemoen <hskinnemoen@gmail.com> >> S: Supported >> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig >> index b590e67..b4b70fb 100644 >> --- a/drivers/gpu/drm/bridge/Kconfig >> +++ b/drivers/gpu/drm/bridge/Kconfig >> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO >> Designware HDMI block. This is used in conjunction with >> the i.MX6 HDMI driver. >> >> +config DRM_GE_B850V3_LVDS_DP >> + tristate "GE B850v3 LVDS to DP++ display bridge" >> + depends on OF >> + select DRM_KMS_HELPER >> + select DRM_PANEL >> + ---help--- >> + This is a driver for the display bridge of >> + GE B850v3 that convert dual channel LVDS >> + to DP++. This is used with the i.MX6 imx-ldb >> + driver. >> + >> config DRM_NXP_PTN3460 >> tristate "NXP PTN3460 DP/LVDS bridge" >> depends on OF >> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile >> index efdb07e..b9606f3 100644 >> --- a/drivers/gpu/drm/bridge/Makefile >> +++ b/drivers/gpu/drm/bridge/Makefile >> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm >> obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o >> obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o >> obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o >> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o >> obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o >> obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o >> obj-$(CONFIG_DRM_SII902X) += sii902x.o >> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >> new file mode 100644 >> index 0000000..81e9279 >> --- /dev/null >> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >> @@ -0,0 +1,405 @@ >> +/* >> + * Driver for GE B850v3 DP display bridge >> + >> + * Copyright (c) 2016, Collabora Ltd. >> + * Copyright (c) 2016, General Electric Company >> + >> + * This program is free software; you can redistribute it and/or modify it >> + * under the terms and conditions of the GNU General Public License, >> + * version 2, as published by the Free Software Foundation. >> + >> + * This program is distributed in the hope it will be useful, but WITHOUT >> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or >> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for >> + * more details. >> + >> + * You should have received a copy of the GNU General Public License >> + * along with this program. If not, see <http://www.gnu.org/licenses/>. >> + >> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ >> + * display bridge of the GE B850v3. There are two physical bridges on the video >> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However >> + * the physical bridges are automatically configured by the input video signal, >> + * and the driver has no access to the video processing pipeline. The driver is >> + * only needed to read EDID from the STDP2690 and to handle HPD events from the >> + * STDP4028. The driver communicates with both bridges over i2c. The video >> + * signal pipeline is as follows: >> + * >> + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >> + * >> + */ >> + >> +#include <linux/gpio.h> >> +#include <linux/i2c.h> >> +#include <linux/module.h> >> +#include <linux/of.h> >> +#include <drm/drm_atomic.h> >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_crtc_helper.h> >> +#include <drm/drm_edid.h> >> +#include <drm/drmP.h> >> + >> +/* >> + * 220Mhz is a limitation of the host, as the bridge is capable of up to >> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications >> + * Processor Reference Manual for more information about the 220Mhz limit. >> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work >> + * fine. >> + */ >> +#define MAX_PIXEL_CLOCK 220000 >> + >> +#define EDID_EXT_BLOCK_CNT 0x7E >> + >> +#define STDP4028_IRQ_OUT_CONF_REG 0x02 >> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C >> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D >> +#define STDP4028_DPTX_STS_REG 0x3E >> + >> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 >> + >> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 >> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 >> +#define STDP4028_DPTX_IRQ_CONFIG \ >> + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) >> + >> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 >> +#define STDP4028_DPTX_LINK_STS 0x1000 >> +#define STDP4028_CON_STATE_CONNECTED \ >> + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) >> + >> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 >> +#define STDP4028_DPTX_LINK_CH_STS 0x2000 >> +#define STDP4028_DPTX_IRQ_CLEAR \ >> + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) >> + >> +struct ge_b850v3_lvds_dp { >> + struct drm_connector connector; >> + struct drm_bridge bridge; >> + struct i2c_client *ge_b850v3_lvds_dp_i2c; >> + struct i2c_client *edid_i2c; >> + struct edid *edid; >> + struct mutex lock; >> +}; >> + >> +static inline struct ge_b850v3_lvds_dp * >> + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) >> +{ >> + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); >> +} >> + >> +static inline struct ge_b850v3_lvds_dp * >> + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) >> +{ >> + return container_of(connector, struct ge_b850v3_lvds_dp, connector); >> +} >> + >> +u8 *stdp2690_get_edid(struct i2c_client *client) >> +{ >> + struct i2c_adapter *adapter = client->adapter; >> + unsigned char start = 0x00; >> + unsigned int total_size; >> + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); >> + >> + struct i2c_msg msgs[] = { >> + { >> + .addr = client->addr, >> + .flags = 0, >> + .len = 1, >> + .buf = &start, >> + }, { >> + .addr = client->addr, >> + .flags = I2C_M_RD, >> + .len = EDID_LENGTH, >> + .buf = block, >> + } >> + }; >> + >> + if (!block) >> + return NULL; >> + >> + if (i2c_transfer(adapter, msgs, 2) != 2) { >> + DRM_ERROR("Unable to read EDID.\n"); >> + goto err; >> + } >> + >> + if (!drm_edid_block_valid(block, 0, false, NULL)) { >> + DRM_ERROR("Invalid EDID block\n"); >> + goto err; >> + } >> + >> + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; >> + if (total_size > EDID_LENGTH) { >> + kfree(block); >> + block = kmalloc(total_size, GFP_KERNEL); >> + if (!block) >> + return NULL; >> + >> + /* Yes, read the entire buffer, and do not skip the first >> + * EDID_LENGTH bytes. >> + */ >> + start = 0x00; >> + msgs[1].len = total_size; >> + msgs[1].buf = block; >> + >> + if (i2c_transfer(adapter, msgs, 2) != 2) { >> + DRM_ERROR("Unable to read EDID extension blocks.\n"); >> + goto err; >> + } >> + } >> + >> + return block; >> + >> +err: >> + kfree(block); >> + return NULL; >> +} >> + >> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge; >> + struct i2c_client *client; >> + int num_modes = 0; >> + >> + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); >> + client = ptn_bridge->edid_i2c; >> + >> + mutex_lock(&ptn_bridge->lock); >> + >> + kfree(ptn_bridge->edid); >> + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); >> + >> + if (ptn_bridge->edid) { >> + drm_mode_connector_update_edid_property(connector, >> + ptn_bridge->edid); >> + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); >> + } >> + >> + mutex_unlock(&ptn_bridge->lock); >> + >> + return num_modes; >> +} >> + >> + >> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( >> + struct drm_connector *connector, struct drm_display_mode *mode) >> +{ >> + if (mode->clock > MAX_PIXEL_CLOCK) { >> + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", >> + mode->name); >> + return MODE_CLOCK_HIGH; >> + } >> + >> + return MODE_OK; >> +} >> + >> +static const struct >> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { >> + .get_modes = ge_b850v3_lvds_dp_get_modes, >> + .mode_valid = ge_b850v3_lvds_dp_mode_valid, >> +}; >> + >> +static enum drm_connector_status ge_b850v3_lvds_dp_detect( >> + struct drm_connector *connector, bool force) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge = >> + connector_to_ge_b850v3_lvds_dp(connector); >> + struct i2c_client *ge_b850v3_lvds_dp_i2c = >> + ptn_bridge->ge_b850v3_lvds_dp_i2c; >> + s32 link_state; >> + >> + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_STS_REG); >> + >> + if (link_state == STDP4028_CON_STATE_CONNECTED) >> + return connector_status_connected; >> + >> + if (link_state == 0) >> + return connector_status_disconnected; >> + >> + return connector_status_unknown; >> +} >> + >> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { >> + .dpms = drm_atomic_helper_connector_dpms, >> + .fill_modes = drm_helper_probe_single_connector_modes, >> + .detect = ge_b850v3_lvds_dp_detect, >> + .destroy = drm_connector_cleanup, >> + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; >> + struct i2c_client *ge_b850v3_lvds_dp_i2c >> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >> + >> + mutex_lock(&ptn_bridge->lock); >> + >> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); >> + >> + mutex_unlock(&ptn_bridge->lock); >> + >> + if (ptn_bridge->connector.dev) >> + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); >> + >> + return IRQ_HANDLED; >> +} >> + >> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge >> + = bridge_to_ge_b850v3_lvds_dp(bridge); >> + struct drm_connector *connector = &ptn_bridge->connector; >> + struct i2c_client *ge_b850v3_lvds_dp_i2c >> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >> + int ret; >> + >> + if (!bridge->encoder) { >> + DRM_ERROR("Parent encoder object not found"); >> + return -ENODEV; >> + } >> + >> + connector->polled = DRM_CONNECTOR_POLL_HPD; >> + >> + drm_connector_helper_add(connector, >> + &ge_b850v3_lvds_dp_connector_helper_funcs); >> + >> + ret = drm_connector_init(bridge->dev, connector, >> + &ge_b850v3_lvds_dp_connector_funcs, >> + DRM_MODE_CONNECTOR_DisplayPort); >> + if (ret) { >> + DRM_ERROR("Failed to initialize connector with drm\n"); >> + return ret; >> + } >> + >> + drm_connector_register(connector); >> + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); >> + if (ret) >> + return ret; >> + >> + drm_bridge_enable(bridge); >> + if (ge_b850v3_lvds_dp_i2c->irq) { >> + drm_helper_hpd_irq_event(connector->dev); >> + >> + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, >> + ge_b850v3_lvds_dp_i2c->irq, NULL, >> + ge_b850v3_lvds_dp_irq_handler, >> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, >> + "ge-b850v3-lvds-dp", ptn_bridge); >> + if (ret) >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { >> + .attach = ge_b850v3_lvds_dp_attach, >> +}; >> + >> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, >> + const struct i2c_device_id *id) >> +{ >> + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; >> + struct ge_b850v3_lvds_dp *ptn_bridge; >> + int ret; >> + u32 edid_i2c_reg; >> + >> + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); >> + if (!ptn_bridge) >> + return -ENOMEM; >> + >> + mutex_init(&ptn_bridge->lock); >> + >> + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; >> + ptn_bridge->bridge.driver_private = ptn_bridge; >> + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); >> + >> + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); >> + if (ret) { >> + dev_err(dev, "edid-reg not specified, aborting...\n"); >> + return -ENODEV; >> + } >> + >> + ptn_bridge->edid_i2c = devm_kzalloc(dev, >> + sizeof(struct i2c_client), GFP_KERNEL); >> + >> + if (!ptn_bridge->edid_i2c) >> + return -ENOMEM; >> + >> + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, >> + sizeof(struct i2c_client)); >> + >> + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; >> + >> + /* >> + * Configures the bridge to re-enable interrupts after each ack. As >> + * this is the first communication with the chip, fail on error. >> + */ >> + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); >> + if (ret) { >> + dev_err(dev, "i2c communication failed, aborting...\n"); >> + return ret; >> + } >> + >> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); >> + >> + /* Clear pending interrupts since power up. */ >> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); >> + >> + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; >> + ptn_bridge->bridge.of_node = dev->of_node; >> + ret = drm_bridge_add(&ptn_bridge->bridge); >> + if (ret) { >> + DRM_ERROR("Failed to add bridge\n"); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge = >> + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); >> + >> + drm_bridge_remove(&ptn_bridge->bridge); >> + >> + kfree(ptn_bridge->edid); >> + >> + return 0; >> +} >> + >> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { >> + {"b850v3-lvds-dp", 0}, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); >> + >> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { >> + { .compatible = "ge,b850v3-lvds-dp" }, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); >> + >> +static struct i2c_driver ge_b850v3_lvds_dp_driver = { >> + .id_table = ge_b850v3_lvds_dp_i2c_table, >> + .probe = ge_b850v3_lvds_dp_probe, >> + .remove = ge_b850v3_lvds_dp_remove, >> + .driver = { >> + .name = "b850v3-lvds-dp", >> + .of_match_table = ge_b850v3_lvds_dp_match, >> + }, >> +}; >> +module_i2c_driver(ge_b850v3_lvds_dp_driver); >> + >> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>"); >> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>"); >> +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); >> +MODULE_LICENSE("GPL v2"); >> -- >> 2.5.5 >> > > > > > > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:31 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 8:31 UTC (permalink / raw) To: Peter Senna Tschudin, Peter Senna Tschudin Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, pawel.moll-5wv7dgnIgG8, treding-DDmLM1+adcrQT0dZR+AlfA, linux-0h96xk9xTtrk1uMJSBkQmQ, heiko-4mtYJXux2i+zQB+pC5nmwQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, daniel.vetter-/w4YWyX8dFk, Fabio Estevam, jslaby-AlSwsSmVLrQ, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, linux-I+IVW8TIWO2tmTQ+vhA3Yw, galak-sgV2jX0FEOL9JmXXK+q4OQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, airlied-cv59FeDIM0c, ykk-TNX95d0MmH7DzftRWevZcw, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, davem-fT/PcQaiUtIeIZ0/mPfg9Q, mark.rutland-5wv7dgnIgG8, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, tiwai-IBi9RG/b67k, linux-kernel-u79uwXL29TY76Z2rM5mHXA, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, Rob Herring, javier-0uQlZySMnqxg9hUCZPvPmw Hi Peter, On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote: > Patch 1/4 is already on linux-next, but what about this one? Ping? I'd posted some queries a couple of times which you didn't answer to. Could you please respond to them before we try to get this merged? Archit > > On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> wrote: > >> Add a driver that create a drm_bridge and a drm_connector for the LVDS >> to DP++ display bridge of the GE B850v3. >> >> There are two physical bridges on the video signal pipeline: a >> STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and >> firmware made it complicated for this binding to comprise two device >> tree nodes, as the design goal is to configure both bridges based on >> the LVDS signal, which leave the driver powerless to control the video >> processing pipeline. The two bridges behaves as a single bridge, and >> the driver is only needed for telling the host about EDID / HPD, and >> for giving the host powers to ack interrupts. The video signal pipeline >> is as follows: >> >> Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >> >> Cc: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> >> Cc: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> >> Cc: Daniel Vetter <daniel.vetter-/w4YWyX8dFk@public.gmane.org> >> Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> >> Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> >> Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> >> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> >> CC: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> >> CC: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> >> CC: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> >> Reviewed-by: Enric Balletbo <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> >> Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> >> --- >> Changes from V4: >> - Check the output of the first call to i2c_smbus_write_word_data() and return >> it's error code for failing gracefully on i2c issues >> - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to >> remove the comma from the driver name >> >> Changes from V3: >> - 3/4 instead of 4/5 >> - Tested on next-20160804 >> >> Changes from V2: >> - Made it atomic to be applied on next-20160729 on top of Liu Ying changes >> that made imx-ldb atomic >> >> Changes from V1: >> - New commit message >> - Removed 3 empty entry points >> - Removed memory leak from ge_b850v3_lvds_dp_get_modes() >> - Added a lock for mode setting >> - Removed a few blank lines >> - Changed the order at Makefile and Kconfig >> >> MAINTAINERS | 8 + >> drivers/gpu/drm/bridge/Kconfig | 11 + >> drivers/gpu/drm/bridge/Makefile | 1 + >> drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ >> 4 files changed, 425 insertions(+) >> create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index a306795..e8d106a 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -5142,6 +5142,14 @@ W: https://linuxtv.org >> S: Maintained >> F: drivers/media/radio/radio-gemtek* >> >> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE >> +M: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> >> +M: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> >> +M: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> >> +S: Maintained >> +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c >> +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt >> + >> GENERIC GPIO I2C DRIVER >> M: Haavard Skinnemoen <hskinnemoen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> >> S: Supported >> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig >> index b590e67..b4b70fb 100644 >> --- a/drivers/gpu/drm/bridge/Kconfig >> +++ b/drivers/gpu/drm/bridge/Kconfig >> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO >> Designware HDMI block. This is used in conjunction with >> the i.MX6 HDMI driver. >> >> +config DRM_GE_B850V3_LVDS_DP >> + tristate "GE B850v3 LVDS to DP++ display bridge" >> + depends on OF >> + select DRM_KMS_HELPER >> + select DRM_PANEL >> + ---help--- >> + This is a driver for the display bridge of >> + GE B850v3 that convert dual channel LVDS >> + to DP++. This is used with the i.MX6 imx-ldb >> + driver. >> + >> config DRM_NXP_PTN3460 >> tristate "NXP PTN3460 DP/LVDS bridge" >> depends on OF >> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile >> index efdb07e..b9606f3 100644 >> --- a/drivers/gpu/drm/bridge/Makefile >> +++ b/drivers/gpu/drm/bridge/Makefile >> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm >> obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o >> obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o >> obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o >> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o >> obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o >> obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o >> obj-$(CONFIG_DRM_SII902X) += sii902x.o >> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >> new file mode 100644 >> index 0000000..81e9279 >> --- /dev/null >> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >> @@ -0,0 +1,405 @@ >> +/* >> + * Driver for GE B850v3 DP display bridge >> + >> + * Copyright (c) 2016, Collabora Ltd. >> + * Copyright (c) 2016, General Electric Company >> + >> + * This program is free software; you can redistribute it and/or modify it >> + * under the terms and conditions of the GNU General Public License, >> + * version 2, as published by the Free Software Foundation. >> + >> + * This program is distributed in the hope it will be useful, but WITHOUT >> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or >> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for >> + * more details. >> + >> + * You should have received a copy of the GNU General Public License >> + * along with this program. If not, see <http://www.gnu.org/licenses/>. >> + >> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ >> + * display bridge of the GE B850v3. There are two physical bridges on the video >> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However >> + * the physical bridges are automatically configured by the input video signal, >> + * and the driver has no access to the video processing pipeline. The driver is >> + * only needed to read EDID from the STDP2690 and to handle HPD events from the >> + * STDP4028. The driver communicates with both bridges over i2c. The video >> + * signal pipeline is as follows: >> + * >> + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >> + * >> + */ >> + >> +#include <linux/gpio.h> >> +#include <linux/i2c.h> >> +#include <linux/module.h> >> +#include <linux/of.h> >> +#include <drm/drm_atomic.h> >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_crtc_helper.h> >> +#include <drm/drm_edid.h> >> +#include <drm/drmP.h> >> + >> +/* >> + * 220Mhz is a limitation of the host, as the bridge is capable of up to >> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications >> + * Processor Reference Manual for more information about the 220Mhz limit. >> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work >> + * fine. >> + */ >> +#define MAX_PIXEL_CLOCK 220000 >> + >> +#define EDID_EXT_BLOCK_CNT 0x7E >> + >> +#define STDP4028_IRQ_OUT_CONF_REG 0x02 >> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C >> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D >> +#define STDP4028_DPTX_STS_REG 0x3E >> + >> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 >> + >> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 >> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 >> +#define STDP4028_DPTX_IRQ_CONFIG \ >> + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) >> + >> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 >> +#define STDP4028_DPTX_LINK_STS 0x1000 >> +#define STDP4028_CON_STATE_CONNECTED \ >> + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) >> + >> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 >> +#define STDP4028_DPTX_LINK_CH_STS 0x2000 >> +#define STDP4028_DPTX_IRQ_CLEAR \ >> + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) >> + >> +struct ge_b850v3_lvds_dp { >> + struct drm_connector connector; >> + struct drm_bridge bridge; >> + struct i2c_client *ge_b850v3_lvds_dp_i2c; >> + struct i2c_client *edid_i2c; >> + struct edid *edid; >> + struct mutex lock; >> +}; >> + >> +static inline struct ge_b850v3_lvds_dp * >> + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) >> +{ >> + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); >> +} >> + >> +static inline struct ge_b850v3_lvds_dp * >> + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) >> +{ >> + return container_of(connector, struct ge_b850v3_lvds_dp, connector); >> +} >> + >> +u8 *stdp2690_get_edid(struct i2c_client *client) >> +{ >> + struct i2c_adapter *adapter = client->adapter; >> + unsigned char start = 0x00; >> + unsigned int total_size; >> + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); >> + >> + struct i2c_msg msgs[] = { >> + { >> + .addr = client->addr, >> + .flags = 0, >> + .len = 1, >> + .buf = &start, >> + }, { >> + .addr = client->addr, >> + .flags = I2C_M_RD, >> + .len = EDID_LENGTH, >> + .buf = block, >> + } >> + }; >> + >> + if (!block) >> + return NULL; >> + >> + if (i2c_transfer(adapter, msgs, 2) != 2) { >> + DRM_ERROR("Unable to read EDID.\n"); >> + goto err; >> + } >> + >> + if (!drm_edid_block_valid(block, 0, false, NULL)) { >> + DRM_ERROR("Invalid EDID block\n"); >> + goto err; >> + } >> + >> + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; >> + if (total_size > EDID_LENGTH) { >> + kfree(block); >> + block = kmalloc(total_size, GFP_KERNEL); >> + if (!block) >> + return NULL; >> + >> + /* Yes, read the entire buffer, and do not skip the first >> + * EDID_LENGTH bytes. >> + */ >> + start = 0x00; >> + msgs[1].len = total_size; >> + msgs[1].buf = block; >> + >> + if (i2c_transfer(adapter, msgs, 2) != 2) { >> + DRM_ERROR("Unable to read EDID extension blocks.\n"); >> + goto err; >> + } >> + } >> + >> + return block; >> + >> +err: >> + kfree(block); >> + return NULL; >> +} >> + >> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge; >> + struct i2c_client *client; >> + int num_modes = 0; >> + >> + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); >> + client = ptn_bridge->edid_i2c; >> + >> + mutex_lock(&ptn_bridge->lock); >> + >> + kfree(ptn_bridge->edid); >> + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); >> + >> + if (ptn_bridge->edid) { >> + drm_mode_connector_update_edid_property(connector, >> + ptn_bridge->edid); >> + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); >> + } >> + >> + mutex_unlock(&ptn_bridge->lock); >> + >> + return num_modes; >> +} >> + >> + >> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( >> + struct drm_connector *connector, struct drm_display_mode *mode) >> +{ >> + if (mode->clock > MAX_PIXEL_CLOCK) { >> + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", >> + mode->name); >> + return MODE_CLOCK_HIGH; >> + } >> + >> + return MODE_OK; >> +} >> + >> +static const struct >> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { >> + .get_modes = ge_b850v3_lvds_dp_get_modes, >> + .mode_valid = ge_b850v3_lvds_dp_mode_valid, >> +}; >> + >> +static enum drm_connector_status ge_b850v3_lvds_dp_detect( >> + struct drm_connector *connector, bool force) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge = >> + connector_to_ge_b850v3_lvds_dp(connector); >> + struct i2c_client *ge_b850v3_lvds_dp_i2c = >> + ptn_bridge->ge_b850v3_lvds_dp_i2c; >> + s32 link_state; >> + >> + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_STS_REG); >> + >> + if (link_state == STDP4028_CON_STATE_CONNECTED) >> + return connector_status_connected; >> + >> + if (link_state == 0) >> + return connector_status_disconnected; >> + >> + return connector_status_unknown; >> +} >> + >> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { >> + .dpms = drm_atomic_helper_connector_dpms, >> + .fill_modes = drm_helper_probe_single_connector_modes, >> + .detect = ge_b850v3_lvds_dp_detect, >> + .destroy = drm_connector_cleanup, >> + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; >> + struct i2c_client *ge_b850v3_lvds_dp_i2c >> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >> + >> + mutex_lock(&ptn_bridge->lock); >> + >> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); >> + >> + mutex_unlock(&ptn_bridge->lock); >> + >> + if (ptn_bridge->connector.dev) >> + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); >> + >> + return IRQ_HANDLED; >> +} >> + >> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge >> + = bridge_to_ge_b850v3_lvds_dp(bridge); >> + struct drm_connector *connector = &ptn_bridge->connector; >> + struct i2c_client *ge_b850v3_lvds_dp_i2c >> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >> + int ret; >> + >> + if (!bridge->encoder) { >> + DRM_ERROR("Parent encoder object not found"); >> + return -ENODEV; >> + } >> + >> + connector->polled = DRM_CONNECTOR_POLL_HPD; >> + >> + drm_connector_helper_add(connector, >> + &ge_b850v3_lvds_dp_connector_helper_funcs); >> + >> + ret = drm_connector_init(bridge->dev, connector, >> + &ge_b850v3_lvds_dp_connector_funcs, >> + DRM_MODE_CONNECTOR_DisplayPort); >> + if (ret) { >> + DRM_ERROR("Failed to initialize connector with drm\n"); >> + return ret; >> + } >> + >> + drm_connector_register(connector); >> + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); >> + if (ret) >> + return ret; >> + >> + drm_bridge_enable(bridge); >> + if (ge_b850v3_lvds_dp_i2c->irq) { >> + drm_helper_hpd_irq_event(connector->dev); >> + >> + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, >> + ge_b850v3_lvds_dp_i2c->irq, NULL, >> + ge_b850v3_lvds_dp_irq_handler, >> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, >> + "ge-b850v3-lvds-dp", ptn_bridge); >> + if (ret) >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = { >> + .attach = ge_b850v3_lvds_dp_attach, >> +}; >> + >> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c, >> + const struct i2c_device_id *id) >> +{ >> + struct device *dev = &ge_b850v3_lvds_dp_i2c->dev; >> + struct ge_b850v3_lvds_dp *ptn_bridge; >> + int ret; >> + u32 edid_i2c_reg; >> + >> + ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL); >> + if (!ptn_bridge) >> + return -ENOMEM; >> + >> + mutex_init(&ptn_bridge->lock); >> + >> + ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c; >> + ptn_bridge->bridge.driver_private = ptn_bridge; >> + i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge); >> + >> + ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg); >> + if (ret) { >> + dev_err(dev, "edid-reg not specified, aborting...\n"); >> + return -ENODEV; >> + } >> + >> + ptn_bridge->edid_i2c = devm_kzalloc(dev, >> + sizeof(struct i2c_client), GFP_KERNEL); >> + >> + if (!ptn_bridge->edid_i2c) >> + return -ENOMEM; >> + >> + memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c, >> + sizeof(struct i2c_client)); >> + >> + ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg; >> + >> + /* >> + * Configures the bridge to re-enable interrupts after each ack. As >> + * this is the first communication with the chip, fail on error. >> + */ >> + ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN); >> + if (ret) { >> + dev_err(dev, "i2c communication failed, aborting...\n"); >> + return ret; >> + } >> + >> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG); >> + >> + /* Clear pending interrupts since power up. */ >> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >> + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); >> + >> + ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs; >> + ptn_bridge->bridge.of_node = dev->of_node; >> + ret = drm_bridge_add(&ptn_bridge->bridge); >> + if (ret) { >> + DRM_ERROR("Failed to add bridge\n"); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c) >> +{ >> + struct ge_b850v3_lvds_dp *ptn_bridge = >> + i2c_get_clientdata(ge_b850v3_lvds_dp_i2c); >> + >> + drm_bridge_remove(&ptn_bridge->bridge); >> + >> + kfree(ptn_bridge->edid); >> + >> + return 0; >> +} >> + >> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = { >> + {"b850v3-lvds-dp", 0}, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table); >> + >> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = { >> + { .compatible = "ge,b850v3-lvds-dp" }, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match); >> + >> +static struct i2c_driver ge_b850v3_lvds_dp_driver = { >> + .id_table = ge_b850v3_lvds_dp_i2c_table, >> + .probe = ge_b850v3_lvds_dp_probe, >> + .remove = ge_b850v3_lvds_dp_remove, >> + .driver = { >> + .name = "b850v3-lvds-dp", >> + .of_match_table = ge_b850v3_lvds_dp_match, >> + }, >> +}; >> +module_i2c_driver(ge_b850v3_lvds_dp_driver); >> + >> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org>"); >> +MODULE_AUTHOR("Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org>"); >> +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)"); >> +MODULE_LICENSE("GPL v2"); >> -- >> 2.5.5 >> > > > > > > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:58 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:58 UTC (permalink / raw) To: Archit Taneja Cc: galak, thierry.reding, kernel, Rob Herring, ykk, jslaby, tiwai, eballetbo, devicetree, shawnguo, linux, davem, p.zabel, peter.senna, daniel.vetter, enric.balletbo, Peter Senna Tschudin, javier, dri-devel, linux-kernel, pawel.moll, ijc+devicetree, martin.donnelly, linux, heiko, mark.rutland, akpm, airlied, Fabio Estevam, mchehab, linux-arm-kernel, robh+dt, martyn.welch, gregkh, treding, rmk+kernel Hi Archit, On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt@codeaurora.org> wrote: > Hi Peter, > > On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote: > > Patch 1/4 is already on linux-next, but what about this one? Ping? > > I'd posted some queries a couple of times which you didn't answer to. > Could you please respond to them before we try to get this merged? Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore. On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point. Thanks, Peter ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:58 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:58 UTC (permalink / raw) To: linux-arm-kernel Hi Archit, On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt@codeaurora.org> wrote: > Hi Peter, > > On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote: > > Patch 1/4 is already on linux-next, but what about this one? Ping? > > I'd posted some queries a couple of times which you didn't answer to. > Could you please respond to them before we try to get this merged? Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore. On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point. Thanks, Peter ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:58 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:58 UTC (permalink / raw) To: Archit Taneja Cc: galak-sgV2jX0FEOL9JmXXK+q4OQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, Rob Herring, ykk-TNX95d0MmH7DzftRWevZcw, jslaby-AlSwsSmVLrQ, tiwai-IBi9RG/b67k, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, devicetree-u79uwXL29TY76Z2rM5mHXA, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, linux-0h96xk9xTtrk1uMJSBkQmQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, daniel.vetter-/w4YWyX8dFk, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, Peter Senna Tschudin, javier-0uQlZySMnqxg9hUCZPvPmw, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, linux-kernel-u79uwXL29TY76Z2rM5mHXA, pawel.moll-5wv7dgnIgG8, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, martin.donnelly-JJi787mZWgc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, heiko-4mtYJXux2i+zQB+pC5nmwQ, mark.rutland-5wv7dgnIgG8, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, airlied-cv59FeDIM0c, Fabio Estevam, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, linux-arm-kernel-IAPFreCvJWM1dQTEkxZZdg Hi Archit, On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org> wrote: > Hi Peter, > > On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote: > > Patch 1/4 is already on linux-next, but what about this one? Ping? > > I'd posted some queries a couple of times which you didn't answer to. > Could you please respond to them before we try to get this merged? Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore. On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point. Thanks, Peter -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-09-26 8:58 ` Peter Senna Tschudin (?) @ 2016-09-26 10:28 ` Archit Taneja -1 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 10:28 UTC (permalink / raw) To: Peter Senna Tschudin Cc: galak, thierry.reding, kernel, Rob Herring, ykk, jslaby, tiwai, eballetbo, devicetree, shawnguo, linux, davem, p.zabel, peter.senna, daniel.vetter, enric.balletbo, Peter Senna Tschudin, javier, dri-devel, linux-kernel, pawel.moll, ijc+devicetree, martin.donnelly, linux, heiko, mark.rutland, akpm, airlied, Fabio Estevam, mchehab, linux-arm-kernel, robh+dt, martyn.welch, gregkh, treding, rmk+kernel Hi, On 09/26/2016 02:28 PM, Peter Senna Tschudin wrote: > Hi Archit, > > On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt@codeaurora.org> wrote: > >> Hi Peter, >> >> On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote: >>> Patch 1/4 is already on linux-next, but what about this one? Ping? > >> >> I'd posted some queries a couple of times which you didn't answer to. >> Could you please respond to them before we try to get this merged? > > Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore. Thanks for the reply. It wasn't entirely clear from the commit message that a custom firmware was exclusively used on this board to program these chips in order to get these 2 working together. I browsed the earlier versions of the patch and saw you explained the same thing to someone else. Sorry about that, I missed reading that before. Could you please specify this explicitly in the commit message? Perhaps, also mention that there is an external microcontroller with a custom firmware that manages most of the video operations. For the sake of completeness, could you also mention the part name of the controller that's running this firmware? Also, in the comments in the beginning of the driver: "However the physical bridges are automatically configured by the input video signal, and the driver has no access to the video processing pipeline." Is the automatic configuration done by the firmware, or is it a feature of the chips itself? > > On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point. Thanks, that would be handy for later. I had some comments on the code. I'll share those in another reply. Archit > > Thanks, > > Peter > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 10:28 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 10:28 UTC (permalink / raw) To: linux-arm-kernel Hi, On 09/26/2016 02:28 PM, Peter Senna Tschudin wrote: > Hi Archit, > > On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt@codeaurora.org> wrote: > >> Hi Peter, >> >> On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote: >>> Patch 1/4 is already on linux-next, but what about this one? Ping? > >> >> I'd posted some queries a couple of times which you didn't answer to. >> Could you please respond to them before we try to get this merged? > > Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore. Thanks for the reply. It wasn't entirely clear from the commit message that a custom firmware was exclusively used on this board to program these chips in order to get these 2 working together. I browsed the earlier versions of the patch and saw you explained the same thing to someone else. Sorry about that, I missed reading that before. Could you please specify this explicitly in the commit message? Perhaps, also mention that there is an external microcontroller with a custom firmware that manages most of the video operations. For the sake of completeness, could you also mention the part name of the controller that's running this firmware? Also, in the comments in the beginning of the driver: "However the physical bridges are automatically configured by the input video signal, and the driver has no access to the video processing pipeline." Is the automatic configuration done by the firmware, or is it a feature of the chips itself? > > On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point. Thanks, that would be handy for later. I had some comments on the code. I'll share those in another reply. Archit > > Thanks, > > Peter > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 10:28 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 10:28 UTC (permalink / raw) To: Peter Senna Tschudin Cc: galak-sgV2jX0FEOL9JmXXK+q4OQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, Rob Herring, ykk-TNX95d0MmH7DzftRWevZcw, jslaby-AlSwsSmVLrQ, tiwai-IBi9RG/b67k, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, devicetree-u79uwXL29TY76Z2rM5mHXA, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, linux-0h96xk9xTtrk1uMJSBkQmQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, daniel.vetter-/w4YWyX8dFk, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, Peter Senna Tschudin, javier-0uQlZySMnqxg9hUCZPvPmw, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, linux-kernel-u79uwXL29TY76Z2rM5mHXA, pawel.moll-5wv7dgnIgG8, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, martin.donnelly-JJi787mZWgc, linux-I+IVW8TIWO2tmTQ+vhA3Yw, heiko-4mtYJXux2i+zQB+pC5nmwQ, mark.rutland-5wv7dgnIgG8, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, airlied-cv59FeDIM0c, Fabio Estevam, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r Hi, On 09/26/2016 02:28 PM, Peter Senna Tschudin wrote: > Hi Archit, > > On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org> wrote: > >> Hi Peter, >> >> On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote: >>> Patch 1/4 is already on linux-next, but what about this one? Ping? > >> >> I'd posted some queries a couple of times which you didn't answer to. >> Could you please respond to them before we try to get this merged? > > Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore. Thanks for the reply. It wasn't entirely clear from the commit message that a custom firmware was exclusively used on this board to program these chips in order to get these 2 working together. I browsed the earlier versions of the patch and saw you explained the same thing to someone else. Sorry about that, I missed reading that before. Could you please specify this explicitly in the commit message? Perhaps, also mention that there is an external microcontroller with a custom firmware that manages most of the video operations. For the sake of completeness, could you also mention the part name of the controller that's running this firmware? Also, in the comments in the beginning of the driver: "However the physical bridges are automatically configured by the input video signal, and the driver has no access to the video processing pipeline." Is the automatic configuration done by the firmware, or is it a feature of the chips itself? > > On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point. Thanks, that would be handy for later. I had some comments on the code. I'll share those in another reply. Archit > > Thanks, > > Peter > -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-08-09 16:41 ` Peter Senna Tschudin (?) @ 2016-09-26 10:29 ` Archit Taneja -1 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 10:29 UTC (permalink / raw) To: Peter Senna Tschudin, airlied, akpm, daniel.vetter, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam Hi, Some comments. On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V4: > - Check the output of the first call to i2c_smbus_write_word_data() and return > it's error code for failing gracefully on i2c issues > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > remove the comma from the driver name > > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > 4 files changed, 425 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a306795..e8d106a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported Could you move the MAINTAINERS change to a different patch? It would make it easier to integrate separately. > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..81e9279 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,405 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); Connectors shouldn't be registered in the bridge driver, they should be registered by the kms driver after drm_dev_register is called. The drm_connector_register_all() API is used for that. > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); This drm_bridge_enable() doesn't seem to serve any purpose here. It also doesn't seem to make much sense to call drm_bridge_() funcs within a bridge op itself. > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); Is there a reason why we register the interrupt handler here and not in probe? Thanks, Archit -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 10:29 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 10:29 UTC (permalink / raw) To: linux-arm-kernel Hi, Some comments. On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V4: > - Check the output of the first call to i2c_smbus_write_word_data() and return > it's error code for failing gracefully on i2c issues > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > remove the comma from the driver name > > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > 4 files changed, 425 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a306795..e8d106a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported Could you move the MAINTAINERS change to a different patch? It would make it easier to integrate separately. > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..81e9279 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,405 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); Connectors shouldn't be registered in the bridge driver, they should be registered by the kms driver after drm_dev_register is called. The drm_connector_register_all() API is used for that. > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); This drm_bridge_enable() doesn't seem to serve any purpose here. It also doesn't seem to make much sense to call drm_bridge_() funcs within a bridge op itself. > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); Is there a reason why we register the interrupt handler here and not in probe? Thanks, Archit -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 10:29 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 10:29 UTC (permalink / raw) To: Peter Senna Tschudin, airlied, akpm, daniel.vetter, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai Cc: Rob Herring, Fabio Estevam Hi, Some comments. On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: > Add a driver that create a drm_bridge and a drm_connector for the LVDS > to DP++ display bridge of the GE B850v3. > > There are two physical bridges on the video signal pipeline: a > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > firmware made it complicated for this binding to comprise two device > tree nodes, as the design goal is to configure both bridges based on > the LVDS signal, which leave the driver powerless to control the video > processing pipeline. The two bridges behaves as a single bridge, and > the driver is only needed for telling the host about EDID / HPD, and > for giving the host powers to ack interrupts. The video signal pipeline > is as follows: > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > CC: David Airlie <airlied@linux.ie> > CC: Thierry Reding <treding@nvidia.com> > CC: Thierry Reding <thierry.reding@gmail.com> > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Changes from V4: > - Check the output of the first call to i2c_smbus_write_word_data() and return > it's error code for failing gracefully on i2c issues > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > remove the comma from the driver name > > Changes from V3: > - 3/4 instead of 4/5 > - Tested on next-20160804 > > Changes from V2: > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > that made imx-ldb atomic > > Changes from V1: > - New commit message > - Removed 3 empty entry points > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > - Added a lock for mode setting > - Removed a few blank lines > - Changed the order at Makefile and Kconfig > > MAINTAINERS | 8 + > drivers/gpu/drm/bridge/Kconfig | 11 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > 4 files changed, 425 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a306795..e8d106a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > S: Maintained > F: drivers/media/radio/radio-gemtek* > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > +M: Peter Senna Tschudin <peter.senna@collabora.com> > +M: Martin Donnelly <martin.donnelly@ge.com> > +M: Martyn Welch <martyn.welch@collabora.co.uk> > +S: Maintained > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > + > GENERIC GPIO I2C DRIVER > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > S: Supported Could you move the MAINTAINERS change to a different patch? It would make it easier to integrate separately. > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index b590e67..b4b70fb 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > Designware HDMI block. This is used in conjunction with > the i.MX6 HDMI driver. > > +config DRM_GE_B850V3_LVDS_DP > + tristate "GE B850v3 LVDS to DP++ display bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + ---help--- > + This is a driver for the display bridge of > + GE B850v3 that convert dual channel LVDS > + to DP++. This is used with the i.MX6 imx-ldb > + driver. > + > config DRM_NXP_PTN3460 > tristate "NXP PTN3460 DP/LVDS bridge" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index efdb07e..b9606f3 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > obj-$(CONFIG_DRM_SII902X) += sii902x.o > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > new file mode 100644 > index 0000000..81e9279 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > @@ -0,0 +1,405 @@ > +/* > + * Driver for GE B850v3 DP display bridge > + > + * Copyright (c) 2016, Collabora Ltd. > + * Copyright (c) 2016, General Electric Company > + > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > + * display bridge of the GE B850v3. There are two physical bridges on the video > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > + * the physical bridges are automatically configured by the input video signal, > + * and the driver has no access to the video processing pipeline. The driver is > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > + * STDP4028. The driver communicates with both bridges over i2c. The video > + * signal pipeline is as follows: > + * > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > + * > + */ > + > +#include <linux/gpio.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drmP.h> > + > +/* > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > + * Processor Reference Manual for more information about the 220Mhz limit. > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > + * fine. > + */ > +#define MAX_PIXEL_CLOCK 220000 > + > +#define EDID_EXT_BLOCK_CNT 0x7E > + > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > +#define STDP4028_DPTX_STS_REG 0x3E > + > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > + > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > +#define STDP4028_DPTX_IRQ_CONFIG \ > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > + > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > +#define STDP4028_DPTX_LINK_STS 0x1000 > +#define STDP4028_CON_STATE_CONNECTED \ > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > + > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > +#define STDP4028_DPTX_IRQ_CLEAR \ > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > + > +struct ge_b850v3_lvds_dp { > + struct drm_connector connector; > + struct drm_bridge bridge; > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > + struct i2c_client *edid_i2c; > + struct edid *edid; > + struct mutex lock; > +}; > + > +static inline struct ge_b850v3_lvds_dp * > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > +} > + > +static inline struct ge_b850v3_lvds_dp * > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > +{ > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > +} > + > +u8 *stdp2690_get_edid(struct i2c_client *client) > +{ > + struct i2c_adapter *adapter = client->adapter; > + unsigned char start = 0x00; > + unsigned int total_size; > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > + > + struct i2c_msg msgs[] = { > + { > + .addr = client->addr, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, { > + .addr = client->addr, > + .flags = I2C_M_RD, > + .len = EDID_LENGTH, > + .buf = block, > + } > + }; > + > + if (!block) > + return NULL; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID.\n"); > + goto err; > + } > + > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > + DRM_ERROR("Invalid EDID block\n"); > + goto err; > + } > + > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > + if (total_size > EDID_LENGTH) { > + kfree(block); > + block = kmalloc(total_size, GFP_KERNEL); > + if (!block) > + return NULL; > + > + /* Yes, read the entire buffer, and do not skip the first > + * EDID_LENGTH bytes. > + */ > + start = 0x00; > + msgs[1].len = total_size; > + msgs[1].buf = block; > + > + if (i2c_transfer(adapter, msgs, 2) != 2) { > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > + goto err; > + } > + } > + > + return block; > + > +err: > + kfree(block); > + return NULL; > +} > + > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge; > + struct i2c_client *client; > + int num_modes = 0; > + > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > + client = ptn_bridge->edid_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + kfree(ptn_bridge->edid); > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > + > + if (ptn_bridge->edid) { > + drm_mode_connector_update_edid_property(connector, > + ptn_bridge->edid); > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > + } > + > + mutex_unlock(&ptn_bridge->lock); > + > + return num_modes; > +} > + > + > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > + struct drm_connector *connector, struct drm_display_mode *mode) > +{ > + if (mode->clock > MAX_PIXEL_CLOCK) { > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > + mode->name); > + return MODE_CLOCK_HIGH; > + } > + > + return MODE_OK; > +} > + > +static const struct > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > + .get_modes = ge_b850v3_lvds_dp_get_modes, > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > +}; > + > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > + struct drm_connector *connector, bool force) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = > + connector_to_ge_b850v3_lvds_dp(connector); > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > + s32 link_state; > + > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_STS_REG); > + > + if (link_state == STDP4028_CON_STATE_CONNECTED) > + return connector_status_connected; > + > + if (link_state == 0) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > + .dpms = drm_atomic_helper_connector_dpms, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .detect = ge_b850v3_lvds_dp_detect, > + .destroy = drm_connector_cleanup, > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + > + mutex_lock(&ptn_bridge->lock); > + > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > + > + mutex_unlock(&ptn_bridge->lock); > + > + if (ptn_bridge->connector.dev) > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > + > + return IRQ_HANDLED; > +} > + > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > +{ > + struct ge_b850v3_lvds_dp *ptn_bridge > + = bridge_to_ge_b850v3_lvds_dp(bridge); > + struct drm_connector *connector = &ptn_bridge->connector; > + struct i2c_client *ge_b850v3_lvds_dp_i2c > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > + int ret; > + > + if (!bridge->encoder) { > + DRM_ERROR("Parent encoder object not found"); > + return -ENODEV; > + } > + > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_helper_add(connector, > + &ge_b850v3_lvds_dp_connector_helper_funcs); > + > + ret = drm_connector_init(bridge->dev, connector, > + &ge_b850v3_lvds_dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) { > + DRM_ERROR("Failed to initialize connector with drm\n"); > + return ret; > + } > + > + drm_connector_register(connector); Connectors shouldn't be registered in the bridge driver, they should be registered by the kms driver after drm_dev_register is called. The drm_connector_register_all() API is used for that. > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > + if (ret) > + return ret; > + > + drm_bridge_enable(bridge); This drm_bridge_enable() doesn't seem to serve any purpose here. It also doesn't seem to make much sense to call drm_bridge_() funcs within a bridge op itself. > + if (ge_b850v3_lvds_dp_i2c->irq) { > + drm_helper_hpd_irq_event(connector->dev); > + > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > + ge_b850v3_lvds_dp_i2c->irq, NULL, > + ge_b850v3_lvds_dp_irq_handler, > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > + "ge-b850v3-lvds-dp", ptn_bridge); Is there a reason why we register the interrupt handler here and not in probe? Thanks, Archit -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-09-26 10:29 ` Archit Taneja (?) @ 2016-09-26 11:54 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 11:54 UTC (permalink / raw) To: Archit Taneja Cc: kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel, akpm, Rob Herring, javier, devicetree, martin.donnelly, robh+dt, linux-arm-kernel, eballetbo, pawel.moll, Peter Senna Tschudin, treding, heiko, thierry.reding, daniel.vetter, linux, Fabio Estevam, jslaby, dri-devel, martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk, ijc+devicetree, rmk+kernel, davem, mark.rutland On Monday, September 26, 2016 12:29 CEST, Archit Taneja <architt@codeaurora.org> wrote: > Hi, > > Some comments. Thank you for the review! > > On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: > > Add a driver that create a drm_bridge and a drm_connector for the LVDS > > to DP++ display bridge of the GE B850v3. > > > > There are two physical bridges on the video signal pipeline: a > > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > > firmware made it complicated for this binding to comprise two device > > tree nodes, as the design goal is to configure both bridges based on > > the LVDS signal, which leave the driver powerless to control the video > > processing pipeline. The two bridges behaves as a single bridge, and > > the driver is only needed for telling the host about EDID / HPD, and > > for giving the host powers to ack interrupts. The video signal pipeline > > is as follows: > > > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > > Cc: Martin Donnelly <martin.donnelly@ge.com> > > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > Cc: Rob Herring <robh@kernel.org> > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > CC: David Airlie <airlied@linux.ie> > > CC: Thierry Reding <treding@nvidia.com> > > CC: Thierry Reding <thierry.reding@gmail.com> > > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > --- > > Changes from V4: > > - Check the output of the first call to i2c_smbus_write_word_data() and return > > it's error code for failing gracefully on i2c issues > > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > > remove the comma from the driver name > > > > Changes from V3: > > - 3/4 instead of 4/5 > > - Tested on next-20160804 > > > > Changes from V2: > > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > > that made imx-ldb atomic > > > > Changes from V1: > > - New commit message > > - Removed 3 empty entry points > > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > > - Added a lock for mode setting > > - Removed a few blank lines > > - Changed the order at Makefile and Kconfig > > > > MAINTAINERS | 8 + > > drivers/gpu/drm/bridge/Kconfig | 11 + > > drivers/gpu/drm/bridge/Makefile | 1 + > > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > > 4 files changed, 425 insertions(+) > > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index a306795..e8d106a 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > > S: Maintained > > F: drivers/media/radio/radio-gemtek* > > > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > > +M: Peter Senna Tschudin <peter.senna@collabora.com> > > +M: Martin Donnelly <martin.donnelly@ge.com> > > +M: Martyn Welch <martyn.welch@collabora.co.uk> > > +S: Maintained > > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > > + > > GENERIC GPIO I2C DRIVER > > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > > S: Supported > > Could you move the MAINTAINERS change to a different patch? It would > make it easier to integrate separately. If needed, yes sure. > > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > > index b590e67..b4b70fb 100644 > > --- a/drivers/gpu/drm/bridge/Kconfig > > +++ b/drivers/gpu/drm/bridge/Kconfig > > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > > Designware HDMI block. This is used in conjunction with > > the i.MX6 HDMI driver. > > > > +config DRM_GE_B850V3_LVDS_DP > > + tristate "GE B850v3 LVDS to DP++ display bridge" > > + depends on OF > > + select DRM_KMS_HELPER > > + select DRM_PANEL > > + ---help--- > > + This is a driver for the display bridge of > > + GE B850v3 that convert dual channel LVDS > > + to DP++. This is used with the i.MX6 imx-ldb > > + driver. > > + > > config DRM_NXP_PTN3460 > > tristate "NXP PTN3460 DP/LVDS bridge" > > depends on OF > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > > index efdb07e..b9606f3 100644 > > --- a/drivers/gpu/drm/bridge/Makefile > > +++ b/drivers/gpu/drm/bridge/Makefile > > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > > obj-$(CONFIG_DRM_SII902X) += sii902x.o > > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > new file mode 100644 > > index 0000000..81e9279 > > --- /dev/null > > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > @@ -0,0 +1,405 @@ > > +/* > > + * Driver for GE B850v3 DP display bridge > > + > > + * Copyright (c) 2016, Collabora Ltd. > > + * Copyright (c) 2016, General Electric Company > > + > > + * This program is free software; you can redistribute it and/or modify it > > + * under the terms and conditions of the GNU General Public License, > > + * version 2, as published by the Free Software Foundation. > > + > > + * This program is distributed in the hope it will be useful, but WITHOUT > > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > > + * more details. > > + > > + * You should have received a copy of the GNU General Public License > > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > > + > > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > > + * display bridge of the GE B850v3. There are two physical bridges on the video > > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > > + * the physical bridges are automatically configured by the input video signal, > > + * and the driver has no access to the video processing pipeline. The driver is > > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > > + * STDP4028. The driver communicates with both bridges over i2c. The video > > + * signal pipeline is as follows: > > + * > > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > + * > > + */ > > + > > +#include <linux/gpio.h> > > +#include <linux/i2c.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <drm/drm_atomic.h> > > +#include <drm/drm_atomic_helper.h> > > +#include <drm/drm_crtc_helper.h> > > +#include <drm/drm_edid.h> > > +#include <drm/drmP.h> > > + > > +/* > > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > > + * Processor Reference Manual for more information about the 220Mhz limit. > > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > > + * fine. > > + */ > > +#define MAX_PIXEL_CLOCK 220000 > > + > > +#define EDID_EXT_BLOCK_CNT 0x7E > > + > > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > > +#define STDP4028_DPTX_STS_REG 0x3E > > + > > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > > + > > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > > +#define STDP4028_DPTX_IRQ_CONFIG \ > > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > > + > > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > > +#define STDP4028_DPTX_LINK_STS 0x1000 > > +#define STDP4028_CON_STATE_CONNECTED \ > > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > > + > > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > > +#define STDP4028_DPTX_IRQ_CLEAR \ > > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > > + > > +struct ge_b850v3_lvds_dp { > > + struct drm_connector connector; > > + struct drm_bridge bridge; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > > + struct i2c_client *edid_i2c; > > + struct edid *edid; > > + struct mutex lock; > > +}; > > + > > +static inline struct ge_b850v3_lvds_dp * > > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > > +{ > > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > > +} > > + > > +static inline struct ge_b850v3_lvds_dp * > > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > > +{ > > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > > +} > > + > > +u8 *stdp2690_get_edid(struct i2c_client *client) > > +{ > > + struct i2c_adapter *adapter = client->adapter; > > + unsigned char start = 0x00; > > + unsigned int total_size; > > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > > + > > + struct i2c_msg msgs[] = { > > + { > > + .addr = client->addr, > > + .flags = 0, > > + .len = 1, > > + .buf = &start, > > + }, { > > + .addr = client->addr, > > + .flags = I2C_M_RD, > > + .len = EDID_LENGTH, > > + .buf = block, > > + } > > + }; > > + > > + if (!block) > > + return NULL; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID.\n"); > > + goto err; > > + } > > + > > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > > + DRM_ERROR("Invalid EDID block\n"); > > + goto err; > > + } > > + > > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > > + if (total_size > EDID_LENGTH) { > > + kfree(block); > > + block = kmalloc(total_size, GFP_KERNEL); > > + if (!block) > > + return NULL; > > + > > + /* Yes, read the entire buffer, and do not skip the first > > + * EDID_LENGTH bytes. > > + */ > > + start = 0x00; > > + msgs[1].len = total_size; > > + msgs[1].buf = block; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > > + goto err; > > + } > > + } > > + > > + return block; > > + > > +err: > > + kfree(block); > > + return NULL; > > +} > > + > > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge; > > + struct i2c_client *client; > > + int num_modes = 0; > > + > > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > > + client = ptn_bridge->edid_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + kfree(ptn_bridge->edid); > > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > > + > > + if (ptn_bridge->edid) { > > + drm_mode_connector_update_edid_property(connector, > > + ptn_bridge->edid); > > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > > + } > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + return num_modes; > > +} > > + > > + > > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > > + struct drm_connector *connector, struct drm_display_mode *mode) > > +{ > > + if (mode->clock > MAX_PIXEL_CLOCK) { > > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > > + mode->name); > > + return MODE_CLOCK_HIGH; > > + } > > + > > + return MODE_OK; > > +} > > + > > +static const struct > > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > > + .get_modes = ge_b850v3_lvds_dp_get_modes, > > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > > +}; > > + > > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > > + struct drm_connector *connector, bool force) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + connector_to_ge_b850v3_lvds_dp(connector); > > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + s32 link_state; > > + > > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_STS_REG); > > + > > + if (link_state == STDP4028_CON_STATE_CONNECTED) > > + return connector_status_connected; > > + > > + if (link_state == 0) > > + return connector_status_disconnected; > > + > > + return connector_status_unknown; > > +} > > + > > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > > + .dpms = drm_atomic_helper_connector_dpms, > > + .fill_modes = drm_helper_probe_single_connector_modes, > > + .detect = ge_b850v3_lvds_dp_detect, > > + .destroy = drm_connector_cleanup, > > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + if (ptn_bridge->connector.dev) > > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > > + > > + return IRQ_HANDLED; > > +} > > + > > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge > > + = bridge_to_ge_b850v3_lvds_dp(bridge); > > + struct drm_connector *connector = &ptn_bridge->connector; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + int ret; > > + > > + if (!bridge->encoder) { > > + DRM_ERROR("Parent encoder object not found"); > > + return -ENODEV; > > + } > > + > > + connector->polled = DRM_CONNECTOR_POLL_HPD; > > + > > + drm_connector_helper_add(connector, > > + &ge_b850v3_lvds_dp_connector_helper_funcs); > > + > > + ret = drm_connector_init(bridge->dev, connector, > > + &ge_b850v3_lvds_dp_connector_funcs, > > + DRM_MODE_CONNECTOR_DisplayPort); > > + if (ret) { > > + DRM_ERROR("Failed to initialize connector with drm\n"); > > + return ret; > > + } > > + > > + drm_connector_register(connector); > > Connectors shouldn't be registered in the bridge driver, they should > be registered by the kms driver after drm_dev_register is called. > The drm_connector_register_all() API is used for that. Hmm, I got this from: drivers/gpu/drm/bridge/nxp-ptn3460.c: drm_connector_register(&ptn_bridge->connector); drivers/gpu/drm/bridge/parade-ps8622.c: drm_connector_register(&ps8622->connector); drivers/gpu/drm/bridge/analogix-anx78xx.c: err = drm_connector_register(&anx78xx->connector); > > > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > > + if (ret) > > + return ret; > > + > > + drm_bridge_enable(bridge); > > This drm_bridge_enable() doesn't seem to serve any purpose here. It also > doesn't seem to make much sense to call drm_bridge_() funcs within a > bridge op itself. Yes, removing this line has no effect. I'll send V6. Thank you! > > > + if (ge_b850v3_lvds_dp_i2c->irq) { > > + drm_helper_hpd_irq_event(connector->dev); > > + > > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > > + ge_b850v3_lvds_dp_i2c->irq, NULL, > > + ge_b850v3_lvds_dp_irq_handler, > > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > > + "ge-b850v3-lvds-dp", ptn_bridge); > > Is there a reason why we register the interrupt handler here and not in > probe? Yes, drm_bridge_funcs.attach() is called when drm decides it is time to initialize the bridge, which means the drm is already running. i2c_driver.probe() is likely to be called way before drm initializes, and if for some reason the bridge is not attached, there is no need for the interrupt handler. In this case the interrupt handler is mostly used for events related to display plugging/unplugging. ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 11:54 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 11:54 UTC (permalink / raw) To: linux-arm-kernel On Monday, September 26, 2016 12:29 CEST, Archit Taneja <architt@codeaurora.org> wrote: > Hi, > > Some comments. Thank you for the review! > > On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: > > Add a driver that create a drm_bridge and a drm_connector for the LVDS > > to DP++ display bridge of the GE B850v3. > > > > There are two physical bridges on the video signal pipeline: a > > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > > firmware made it complicated for this binding to comprise two device > > tree nodes, as the design goal is to configure both bridges based on > > the LVDS signal, which leave the driver powerless to control the video > > processing pipeline. The two bridges behaves as a single bridge, and > > the driver is only needed for telling the host about EDID / HPD, and > > for giving the host powers to ack interrupts. The video signal pipeline > > is as follows: > > > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > > Cc: Martin Donnelly <martin.donnelly@ge.com> > > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > Cc: Rob Herring <robh@kernel.org> > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > CC: David Airlie <airlied@linux.ie> > > CC: Thierry Reding <treding@nvidia.com> > > CC: Thierry Reding <thierry.reding@gmail.com> > > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > --- > > Changes from V4: > > - Check the output of the first call to i2c_smbus_write_word_data() and return > > it's error code for failing gracefully on i2c issues > > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > > remove the comma from the driver name > > > > Changes from V3: > > - 3/4 instead of 4/5 > > - Tested on next-20160804 > > > > Changes from V2: > > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > > that made imx-ldb atomic > > > > Changes from V1: > > - New commit message > > - Removed 3 empty entry points > > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > > - Added a lock for mode setting > > - Removed a few blank lines > > - Changed the order at Makefile and Kconfig > > > > MAINTAINERS | 8 + > > drivers/gpu/drm/bridge/Kconfig | 11 + > > drivers/gpu/drm/bridge/Makefile | 1 + > > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > > 4 files changed, 425 insertions(+) > > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index a306795..e8d106a 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > > S: Maintained > > F: drivers/media/radio/radio-gemtek* > > > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > > +M: Peter Senna Tschudin <peter.senna@collabora.com> > > +M: Martin Donnelly <martin.donnelly@ge.com> > > +M: Martyn Welch <martyn.welch@collabora.co.uk> > > +S: Maintained > > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > > + > > GENERIC GPIO I2C DRIVER > > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > > S: Supported > > Could you move the MAINTAINERS change to a different patch? It would > make it easier to integrate separately. If needed, yes sure. > > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > > index b590e67..b4b70fb 100644 > > --- a/drivers/gpu/drm/bridge/Kconfig > > +++ b/drivers/gpu/drm/bridge/Kconfig > > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > > Designware HDMI block. This is used in conjunction with > > the i.MX6 HDMI driver. > > > > +config DRM_GE_B850V3_LVDS_DP > > + tristate "GE B850v3 LVDS to DP++ display bridge" > > + depends on OF > > + select DRM_KMS_HELPER > > + select DRM_PANEL > > + ---help--- > > + This is a driver for the display bridge of > > + GE B850v3 that convert dual channel LVDS > > + to DP++. This is used with the i.MX6 imx-ldb > > + driver. > > + > > config DRM_NXP_PTN3460 > > tristate "NXP PTN3460 DP/LVDS bridge" > > depends on OF > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > > index efdb07e..b9606f3 100644 > > --- a/drivers/gpu/drm/bridge/Makefile > > +++ b/drivers/gpu/drm/bridge/Makefile > > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > > obj-$(CONFIG_DRM_SII902X) += sii902x.o > > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > new file mode 100644 > > index 0000000..81e9279 > > --- /dev/null > > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > @@ -0,0 +1,405 @@ > > +/* > > + * Driver for GE B850v3 DP display bridge > > + > > + * Copyright (c) 2016, Collabora Ltd. > > + * Copyright (c) 2016, General Electric Company > > + > > + * This program is free software; you can redistribute it and/or modify it > > + * under the terms and conditions of the GNU General Public License, > > + * version 2, as published by the Free Software Foundation. > > + > > + * This program is distributed in the hope it will be useful, but WITHOUT > > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > > + * more details. > > + > > + * You should have received a copy of the GNU General Public License > > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > > + > > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > > + * display bridge of the GE B850v3. There are two physical bridges on the video > > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > > + * the physical bridges are automatically configured by the input video signal, > > + * and the driver has no access to the video processing pipeline. The driver is > > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > > + * STDP4028. The driver communicates with both bridges over i2c. The video > > + * signal pipeline is as follows: > > + * > > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > + * > > + */ > > + > > +#include <linux/gpio.h> > > +#include <linux/i2c.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <drm/drm_atomic.h> > > +#include <drm/drm_atomic_helper.h> > > +#include <drm/drm_crtc_helper.h> > > +#include <drm/drm_edid.h> > > +#include <drm/drmP.h> > > + > > +/* > > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > > + * Processor Reference Manual for more information about the 220Mhz limit. > > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > > + * fine. > > + */ > > +#define MAX_PIXEL_CLOCK 220000 > > + > > +#define EDID_EXT_BLOCK_CNT 0x7E > > + > > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > > +#define STDP4028_DPTX_STS_REG 0x3E > > + > > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > > + > > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > > +#define STDP4028_DPTX_IRQ_CONFIG \ > > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > > + > > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > > +#define STDP4028_DPTX_LINK_STS 0x1000 > > +#define STDP4028_CON_STATE_CONNECTED \ > > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > > + > > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > > +#define STDP4028_DPTX_IRQ_CLEAR \ > > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > > + > > +struct ge_b850v3_lvds_dp { > > + struct drm_connector connector; > > + struct drm_bridge bridge; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > > + struct i2c_client *edid_i2c; > > + struct edid *edid; > > + struct mutex lock; > > +}; > > + > > +static inline struct ge_b850v3_lvds_dp * > > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > > +{ > > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > > +} > > + > > +static inline struct ge_b850v3_lvds_dp * > > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > > +{ > > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > > +} > > + > > +u8 *stdp2690_get_edid(struct i2c_client *client) > > +{ > > + struct i2c_adapter *adapter = client->adapter; > > + unsigned char start = 0x00; > > + unsigned int total_size; > > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > > + > > + struct i2c_msg msgs[] = { > > + { > > + .addr = client->addr, > > + .flags = 0, > > + .len = 1, > > + .buf = &start, > > + }, { > > + .addr = client->addr, > > + .flags = I2C_M_RD, > > + .len = EDID_LENGTH, > > + .buf = block, > > + } > > + }; > > + > > + if (!block) > > + return NULL; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID.\n"); > > + goto err; > > + } > > + > > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > > + DRM_ERROR("Invalid EDID block\n"); > > + goto err; > > + } > > + > > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > > + if (total_size > EDID_LENGTH) { > > + kfree(block); > > + block = kmalloc(total_size, GFP_KERNEL); > > + if (!block) > > + return NULL; > > + > > + /* Yes, read the entire buffer, and do not skip the first > > + * EDID_LENGTH bytes. > > + */ > > + start = 0x00; > > + msgs[1].len = total_size; > > + msgs[1].buf = block; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > > + goto err; > > + } > > + } > > + > > + return block; > > + > > +err: > > + kfree(block); > > + return NULL; > > +} > > + > > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge; > > + struct i2c_client *client; > > + int num_modes = 0; > > + > > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > > + client = ptn_bridge->edid_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + kfree(ptn_bridge->edid); > > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > > + > > + if (ptn_bridge->edid) { > > + drm_mode_connector_update_edid_property(connector, > > + ptn_bridge->edid); > > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > > + } > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + return num_modes; > > +} > > + > > + > > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > > + struct drm_connector *connector, struct drm_display_mode *mode) > > +{ > > + if (mode->clock > MAX_PIXEL_CLOCK) { > > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > > + mode->name); > > + return MODE_CLOCK_HIGH; > > + } > > + > > + return MODE_OK; > > +} > > + > > +static const struct > > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > > + .get_modes = ge_b850v3_lvds_dp_get_modes, > > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > > +}; > > + > > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > > + struct drm_connector *connector, bool force) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + connector_to_ge_b850v3_lvds_dp(connector); > > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + s32 link_state; > > + > > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_STS_REG); > > + > > + if (link_state == STDP4028_CON_STATE_CONNECTED) > > + return connector_status_connected; > > + > > + if (link_state == 0) > > + return connector_status_disconnected; > > + > > + return connector_status_unknown; > > +} > > + > > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > > + .dpms = drm_atomic_helper_connector_dpms, > > + .fill_modes = drm_helper_probe_single_connector_modes, > > + .detect = ge_b850v3_lvds_dp_detect, > > + .destroy = drm_connector_cleanup, > > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + if (ptn_bridge->connector.dev) > > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > > + > > + return IRQ_HANDLED; > > +} > > + > > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge > > + = bridge_to_ge_b850v3_lvds_dp(bridge); > > + struct drm_connector *connector = &ptn_bridge->connector; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + int ret; > > + > > + if (!bridge->encoder) { > > + DRM_ERROR("Parent encoder object not found"); > > + return -ENODEV; > > + } > > + > > + connector->polled = DRM_CONNECTOR_POLL_HPD; > > + > > + drm_connector_helper_add(connector, > > + &ge_b850v3_lvds_dp_connector_helper_funcs); > > + > > + ret = drm_connector_init(bridge->dev, connector, > > + &ge_b850v3_lvds_dp_connector_funcs, > > + DRM_MODE_CONNECTOR_DisplayPort); > > + if (ret) { > > + DRM_ERROR("Failed to initialize connector with drm\n"); > > + return ret; > > + } > > + > > + drm_connector_register(connector); > > Connectors shouldn't be registered in the bridge driver, they should > be registered by the kms driver after drm_dev_register is called. > The drm_connector_register_all() API is used for that. Hmm, I got this from: drivers/gpu/drm/bridge/nxp-ptn3460.c: drm_connector_register(&ptn_bridge->connector); drivers/gpu/drm/bridge/parade-ps8622.c: drm_connector_register(&ps8622->connector); drivers/gpu/drm/bridge/analogix-anx78xx.c: err = drm_connector_register(&anx78xx->connector); > > > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > > + if (ret) > > + return ret; > > + > > + drm_bridge_enable(bridge); > > This drm_bridge_enable() doesn't seem to serve any purpose here. It also > doesn't seem to make much sense to call drm_bridge_() funcs within a > bridge op itself. Yes, removing this line has no effect. I'll send V6. Thank you! > > > + if (ge_b850v3_lvds_dp_i2c->irq) { > > + drm_helper_hpd_irq_event(connector->dev); > > + > > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > > + ge_b850v3_lvds_dp_i2c->irq, NULL, > > + ge_b850v3_lvds_dp_irq_handler, > > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > > + "ge-b850v3-lvds-dp", ptn_bridge); > > Is there a reason why we register the interrupt handler here and not in > probe? Yes, drm_bridge_funcs.attach() is called when drm decides it is time to initialize the bridge, which means the drm is already running. i2c_driver.probe() is likely to be called way before drm initializes, and if for some reason the bridge is not attached, there is no need for the interrupt handler. In this case the interrupt handler is mostly used for events related to display plugging/unplugging. ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 11:54 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 11:54 UTC (permalink / raw) To: Archit Taneja Cc: kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel, akpm, Rob Herring, javier, devicetree, martin.donnelly, robh+dt, linux-arm-kernel, eballetbo, pawel.moll, Peter Senna Tschudin, treding, heiko, thierry.reding, daniel.vetter, linux, Fabio Estevam, jslaby, dri-devel, martyn.welch, shawnguo, linux, galak, peter.senna, airlied On Monday, September 26, 2016 12:29 CEST, Archit Taneja <architt@codeaurora.org> wrote: > Hi, > > Some comments. Thank you for the review! > > On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: > > Add a driver that create a drm_bridge and a drm_connector for the LVDS > > to DP++ display bridge of the GE B850v3. > > > > There are two physical bridges on the video signal pipeline: a > > STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and > > firmware made it complicated for this binding to comprise two device > > tree nodes, as the design goal is to configure both bridges based on > > the LVDS signal, which leave the driver powerless to control the video > > processing pipeline. The two bridges behaves as a single bridge, and > > the driver is only needed for telling the host about EDID / HPD, and > > for giving the host powers to ack interrupts. The video signal pipeline > > is as follows: > > > > Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > > Cc: Martin Donnelly <martin.donnelly@ge.com> > > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > > Cc: Philipp Zabel <p.zabel@pengutronix.de> > > Cc: Rob Herring <robh@kernel.org> > > Cc: Fabio Estevam <fabio.estevam@nxp.com> > > CC: David Airlie <airlied@linux.ie> > > CC: Thierry Reding <treding@nvidia.com> > > CC: Thierry Reding <thierry.reding@gmail.com> > > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > > --- > > Changes from V4: > > - Check the output of the first call to i2c_smbus_write_word_data() and return > > it's error code for failing gracefully on i2c issues > > - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to > > remove the comma from the driver name > > > > Changes from V3: > > - 3/4 instead of 4/5 > > - Tested on next-20160804 > > > > Changes from V2: > > - Made it atomic to be applied on next-20160729 on top of Liu Ying changes > > that made imx-ldb atomic > > > > Changes from V1: > > - New commit message > > - Removed 3 empty entry points > > - Removed memory leak from ge_b850v3_lvds_dp_get_modes() > > - Added a lock for mode setting > > - Removed a few blank lines > > - Changed the order at Makefile and Kconfig > > > > MAINTAINERS | 8 + > > drivers/gpu/drm/bridge/Kconfig | 11 + > > drivers/gpu/drm/bridge/Makefile | 1 + > > drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ > > 4 files changed, 425 insertions(+) > > create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index a306795..e8d106a 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -5142,6 +5142,14 @@ W: https://linuxtv.org > > S: Maintained > > F: drivers/media/radio/radio-gemtek* > > > > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE > > +M: Peter Senna Tschudin <peter.senna@collabora.com> > > +M: Martin Donnelly <martin.donnelly@ge.com> > > +M: Martyn Welch <martyn.welch@collabora.co.uk> > > +S: Maintained > > +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c > > +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt > > + > > GENERIC GPIO I2C DRIVER > > M: Haavard Skinnemoen <hskinnemoen@gmail.com> > > S: Supported > > Could you move the MAINTAINERS change to a different patch? It would > make it easier to integrate separately. If needed, yes sure. > > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > > index b590e67..b4b70fb 100644 > > --- a/drivers/gpu/drm/bridge/Kconfig > > +++ b/drivers/gpu/drm/bridge/Kconfig > > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO > > Designware HDMI block. This is used in conjunction with > > the i.MX6 HDMI driver. > > > > +config DRM_GE_B850V3_LVDS_DP > > + tristate "GE B850v3 LVDS to DP++ display bridge" > > + depends on OF > > + select DRM_KMS_HELPER > > + select DRM_PANEL > > + ---help--- > > + This is a driver for the display bridge of > > + GE B850v3 that convert dual channel LVDS > > + to DP++. This is used with the i.MX6 imx-ldb > > + driver. > > + > > config DRM_NXP_PTN3460 > > tristate "NXP PTN3460 DP/LVDS bridge" > > depends on OF > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > > index efdb07e..b9606f3 100644 > > --- a/drivers/gpu/drm/bridge/Makefile > > +++ b/drivers/gpu/drm/bridge/Makefile > > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm > > obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o > > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o > > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > > obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o > > obj-$(CONFIG_DRM_SII902X) += sii902x.o > > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > new file mode 100644 > > index 0000000..81e9279 > > --- /dev/null > > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c > > @@ -0,0 +1,405 @@ > > +/* > > + * Driver for GE B850v3 DP display bridge > > + > > + * Copyright (c) 2016, Collabora Ltd. > > + * Copyright (c) 2016, General Electric Company > > + > > + * This program is free software; you can redistribute it and/or modify it > > + * under the terms and conditions of the GNU General Public License, > > + * version 2, as published by the Free Software Foundation. > > + > > + * This program is distributed in the hope it will be useful, but WITHOUT > > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > > + * more details. > > + > > + * You should have received a copy of the GNU General Public License > > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > > + > > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ > > + * display bridge of the GE B850v3. There are two physical bridges on the video > > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However > > + * the physical bridges are automatically configured by the input video signal, > > + * and the driver has no access to the video processing pipeline. The driver is > > + * only needed to read EDID from the STDP2690 and to handle HPD events from the > > + * STDP4028. The driver communicates with both bridges over i2c. The video > > + * signal pipeline is as follows: > > + * > > + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output > > + * > > + */ > > + > > +#include <linux/gpio.h> > > +#include <linux/i2c.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <drm/drm_atomic.h> > > +#include <drm/drm_atomic_helper.h> > > +#include <drm/drm_crtc_helper.h> > > +#include <drm/drm_edid.h> > > +#include <drm/drmP.h> > > + > > +/* > > + * 220Mhz is a limitation of the host, as the bridge is capable of up to > > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications > > + * Processor Reference Manual for more information about the 220Mhz limit. > > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work > > + * fine. > > + */ > > +#define MAX_PIXEL_CLOCK 220000 > > + > > +#define EDID_EXT_BLOCK_CNT 0x7E > > + > > +#define STDP4028_IRQ_OUT_CONF_REG 0x02 > > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C > > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D > > +#define STDP4028_DPTX_STS_REG 0x3E > > + > > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 > > + > > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 > > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 > > +#define STDP4028_DPTX_IRQ_CONFIG \ > > + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) > > + > > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 > > +#define STDP4028_DPTX_LINK_STS 0x1000 > > +#define STDP4028_CON_STATE_CONNECTED \ > > + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) > > + > > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 > > +#define STDP4028_DPTX_LINK_CH_STS 0x2000 > > +#define STDP4028_DPTX_IRQ_CLEAR \ > > + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) > > + > > +struct ge_b850v3_lvds_dp { > > + struct drm_connector connector; > > + struct drm_bridge bridge; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c; > > + struct i2c_client *edid_i2c; > > + struct edid *edid; > > + struct mutex lock; > > +}; > > + > > +static inline struct ge_b850v3_lvds_dp * > > + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) > > +{ > > + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); > > +} > > + > > +static inline struct ge_b850v3_lvds_dp * > > + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) > > +{ > > + return container_of(connector, struct ge_b850v3_lvds_dp, connector); > > +} > > + > > +u8 *stdp2690_get_edid(struct i2c_client *client) > > +{ > > + struct i2c_adapter *adapter = client->adapter; > > + unsigned char start = 0x00; > > + unsigned int total_size; > > + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); > > + > > + struct i2c_msg msgs[] = { > > + { > > + .addr = client->addr, > > + .flags = 0, > > + .len = 1, > > + .buf = &start, > > + }, { > > + .addr = client->addr, > > + .flags = I2C_M_RD, > > + .len = EDID_LENGTH, > > + .buf = block, > > + } > > + }; > > + > > + if (!block) > > + return NULL; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID.\n"); > > + goto err; > > + } > > + > > + if (!drm_edid_block_valid(block, 0, false, NULL)) { > > + DRM_ERROR("Invalid EDID block\n"); > > + goto err; > > + } > > + > > + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; > > + if (total_size > EDID_LENGTH) { > > + kfree(block); > > + block = kmalloc(total_size, GFP_KERNEL); > > + if (!block) > > + return NULL; > > + > > + /* Yes, read the entire buffer, and do not skip the first > > + * EDID_LENGTH bytes. > > + */ > > + start = 0x00; > > + msgs[1].len = total_size; > > + msgs[1].buf = block; > > + > > + if (i2c_transfer(adapter, msgs, 2) != 2) { > > + DRM_ERROR("Unable to read EDID extension blocks.\n"); > > + goto err; > > + } > > + } > > + > > + return block; > > + > > +err: > > + kfree(block); > > + return NULL; > > +} > > + > > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge; > > + struct i2c_client *client; > > + int num_modes = 0; > > + > > + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); > > + client = ptn_bridge->edid_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + kfree(ptn_bridge->edid); > > + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); > > + > > + if (ptn_bridge->edid) { > > + drm_mode_connector_update_edid_property(connector, > > + ptn_bridge->edid); > > + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); > > + } > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + return num_modes; > > +} > > + > > + > > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( > > + struct drm_connector *connector, struct drm_display_mode *mode) > > +{ > > + if (mode->clock > MAX_PIXEL_CLOCK) { > > + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", > > + mode->name); > > + return MODE_CLOCK_HIGH; > > + } > > + > > + return MODE_OK; > > +} > > + > > +static const struct > > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { > > + .get_modes = ge_b850v3_lvds_dp_get_modes, > > + .mode_valid = ge_b850v3_lvds_dp_mode_valid, > > +}; > > + > > +static enum drm_connector_status ge_b850v3_lvds_dp_detect( > > + struct drm_connector *connector, bool force) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = > > + connector_to_ge_b850v3_lvds_dp(connector); > > + struct i2c_client *ge_b850v3_lvds_dp_i2c = > > + ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + s32 link_state; > > + > > + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_STS_REG); > > + > > + if (link_state == STDP4028_CON_STATE_CONNECTED) > > + return connector_status_connected; > > + > > + if (link_state == 0) > > + return connector_status_disconnected; > > + > > + return connector_status_unknown; > > +} > > + > > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { > > + .dpms = drm_atomic_helper_connector_dpms, > > + .fill_modes = drm_helper_probe_single_connector_modes, > > + .detect = ge_b850v3_lvds_dp_detect, > > + .destroy = drm_connector_cleanup, > > + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + > > + mutex_lock(&ptn_bridge->lock); > > + > > + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, > > + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); > > + > > + mutex_unlock(&ptn_bridge->lock); > > + > > + if (ptn_bridge->connector.dev) > > + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); > > + > > + return IRQ_HANDLED; > > +} > > + > > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) > > +{ > > + struct ge_b850v3_lvds_dp *ptn_bridge > > + = bridge_to_ge_b850v3_lvds_dp(bridge); > > + struct drm_connector *connector = &ptn_bridge->connector; > > + struct i2c_client *ge_b850v3_lvds_dp_i2c > > + = ptn_bridge->ge_b850v3_lvds_dp_i2c; > > + int ret; > > + > > + if (!bridge->encoder) { > > + DRM_ERROR("Parent encoder object not found"); > > + return -ENODEV; > > + } > > + > > + connector->polled = DRM_CONNECTOR_POLL_HPD; > > + > > + drm_connector_helper_add(connector, > > + &ge_b850v3_lvds_dp_connector_helper_funcs); > > + > > + ret = drm_connector_init(bridge->dev, connector, > > + &ge_b850v3_lvds_dp_connector_funcs, > > + DRM_MODE_CONNECTOR_DisplayPort); > > + if (ret) { > > + DRM_ERROR("Failed to initialize connector with drm\n"); > > + return ret; > > + } > > + > > + drm_connector_register(connector); > > Connectors shouldn't be registered in the bridge driver, they should > be registered by the kms driver after drm_dev_register is called. > The drm_connector_register_all() API is used for that. Hmm, I got this from: drivers/gpu/drm/bridge/nxp-ptn3460.c: drm_connector_register(&ptn_bridge->connector); drivers/gpu/drm/bridge/parade-ps8622.c: drm_connector_register(&ps8622->connector); drivers/gpu/drm/bridge/analogix-anx78xx.c: err = drm_connector_register(&anx78xx->connector); > > > + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); > > + if (ret) > > + return ret; > > + > > + drm_bridge_enable(bridge); > > This drm_bridge_enable() doesn't seem to serve any purpose here. It also > doesn't seem to make much sense to call drm_bridge_() funcs within a > bridge op itself. Yes, removing this line has no effect. I'll send V6. Thank you! > > > + if (ge_b850v3_lvds_dp_i2c->irq) { > > + drm_helper_hpd_irq_event(connector->dev); > > + > > + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, > > + ge_b850v3_lvds_dp_i2c->irq, NULL, > > + ge_b850v3_lvds_dp_irq_handler, > > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, > > + "ge-b850v3-lvds-dp", ptn_bridge); > > Is there a reason why we register the interrupt handler here and not in > probe? Yes, drm_bridge_funcs.attach() is called when drm decides it is time to initialize the bridge, which means the drm is already running. i2c_driver.probe() is likely to be called way before drm initializes, and if for some reason the bridge is not attached, there is no need for the interrupt handler. In this case the interrupt handler is mostly used for events related to display plugging/unplugging. ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge 2016-09-26 11:54 ` Peter Senna Tschudin (?) @ 2016-09-26 12:54 ` Archit Taneja -1 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 12:54 UTC (permalink / raw) To: Peter Senna Tschudin Cc: kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel, akpm, Rob Herring, javier, devicetree, martin.donnelly, robh+dt, linux-arm-kernel, eballetbo, pawel.moll, Peter Senna Tschudin, treding, heiko, thierry.reding, daniel.vetter, linux, Fabio Estevam, jslaby, dri-devel, martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk, ijc+devicetree, rmk+kernel, davem, mark.rutland On 09/26/2016 05:24 PM, Peter Senna Tschudin wrote: > > On Monday, September 26, 2016 12:29 CEST, Archit Taneja <architt@codeaurora.org> wrote: > >> Hi, >> >> Some comments. > > Thank you for the review! > >> >> On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: >>> Add a driver that create a drm_bridge and a drm_connector for the LVDS >>> to DP++ display bridge of the GE B850v3. >>> >>> There are two physical bridges on the video signal pipeline: a >>> STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and >>> firmware made it complicated for this binding to comprise two device >>> tree nodes, as the design goal is to configure both bridges based on >>> the LVDS signal, which leave the driver powerless to control the video >>> processing pipeline. The two bridges behaves as a single bridge, and >>> the driver is only needed for telling the host about EDID / HPD, and >>> for giving the host powers to ack interrupts. The video signal pipeline >>> is as follows: >>> >>> Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >>> >>> Cc: Martyn Welch <martyn.welch@collabora.co.uk> >>> Cc: Martin Donnelly <martin.donnelly@ge.com> >>> Cc: Daniel Vetter <daniel.vetter@ffwll.ch> >>> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> >>> Cc: Philipp Zabel <p.zabel@pengutronix.de> >>> Cc: Rob Herring <robh@kernel.org> >>> Cc: Fabio Estevam <fabio.estevam@nxp.com> >>> CC: David Airlie <airlied@linux.ie> >>> CC: Thierry Reding <treding@nvidia.com> >>> CC: Thierry Reding <thierry.reding@gmail.com> >>> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> >>> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> >>> --- >>> Changes from V4: >>> - Check the output of the first call to i2c_smbus_write_word_data() and return >>> it's error code for failing gracefully on i2c issues >>> - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to >>> remove the comma from the driver name >>> >>> Changes from V3: >>> - 3/4 instead of 4/5 >>> - Tested on next-20160804 >>> >>> Changes from V2: >>> - Made it atomic to be applied on next-20160729 on top of Liu Ying changes >>> that made imx-ldb atomic >>> >>> Changes from V1: >>> - New commit message >>> - Removed 3 empty entry points >>> - Removed memory leak from ge_b850v3_lvds_dp_get_modes() >>> - Added a lock for mode setting >>> - Removed a few blank lines >>> - Changed the order at Makefile and Kconfig >>> >>> MAINTAINERS | 8 + >>> drivers/gpu/drm/bridge/Kconfig | 11 + >>> drivers/gpu/drm/bridge/Makefile | 1 + >>> drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ >>> 4 files changed, 425 insertions(+) >>> create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >>> >>> diff --git a/MAINTAINERS b/MAINTAINERS >>> index a306795..e8d106a 100644 >>> --- a/MAINTAINERS >>> +++ b/MAINTAINERS >>> @@ -5142,6 +5142,14 @@ W: https://linuxtv.org >>> S: Maintained >>> F: drivers/media/radio/radio-gemtek* >>> >>> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE >>> +M: Peter Senna Tschudin <peter.senna@collabora.com> >>> +M: Martin Donnelly <martin.donnelly@ge.com> >>> +M: Martyn Welch <martyn.welch@collabora.co.uk> >>> +S: Maintained >>> +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c >>> +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt >>> + >>> GENERIC GPIO I2C DRIVER >>> M: Haavard Skinnemoen <hskinnemoen@gmail.com> >>> S: Supported >> >> Could you move the MAINTAINERS change to a different patch? It would >> make it easier to integrate separately. > > If needed, yes sure. > >> >>> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig >>> index b590e67..b4b70fb 100644 >>> --- a/drivers/gpu/drm/bridge/Kconfig >>> +++ b/drivers/gpu/drm/bridge/Kconfig >>> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO >>> Designware HDMI block. This is used in conjunction with >>> the i.MX6 HDMI driver. >>> >>> +config DRM_GE_B850V3_LVDS_DP >>> + tristate "GE B850v3 LVDS to DP++ display bridge" >>> + depends on OF >>> + select DRM_KMS_HELPER >>> + select DRM_PANEL >>> + ---help--- >>> + This is a driver for the display bridge of >>> + GE B850v3 that convert dual channel LVDS >>> + to DP++. This is used with the i.MX6 imx-ldb >>> + driver. >>> + >>> config DRM_NXP_PTN3460 >>> tristate "NXP PTN3460 DP/LVDS bridge" >>> depends on OF >>> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile >>> index efdb07e..b9606f3 100644 >>> --- a/drivers/gpu/drm/bridge/Makefile >>> +++ b/drivers/gpu/drm/bridge/Makefile >>> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm >>> obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o >>> obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o >>> obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o >>> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o >>> obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o >>> obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o >>> obj-$(CONFIG_DRM_SII902X) += sii902x.o >>> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >>> new file mode 100644 >>> index 0000000..81e9279 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >>> @@ -0,0 +1,405 @@ >>> +/* >>> + * Driver for GE B850v3 DP display bridge >>> + >>> + * Copyright (c) 2016, Collabora Ltd. >>> + * Copyright (c) 2016, General Electric Company >>> + >>> + * This program is free software; you can redistribute it and/or modify it >>> + * under the terms and conditions of the GNU General Public License, >>> + * version 2, as published by the Free Software Foundation. >>> + >>> + * This program is distributed in the hope it will be useful, but WITHOUT >>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or >>> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for >>> + * more details. >>> + >>> + * You should have received a copy of the GNU General Public License >>> + * along with this program. If not, see <http://www.gnu.org/licenses/>. >>> + >>> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ >>> + * display bridge of the GE B850v3. There are two physical bridges on the video >>> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However >>> + * the physical bridges are automatically configured by the input video signal, >>> + * and the driver has no access to the video processing pipeline. The driver is >>> + * only needed to read EDID from the STDP2690 and to handle HPD events from the >>> + * STDP4028. The driver communicates with both bridges over i2c. The video >>> + * signal pipeline is as follows: >>> + * >>> + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >>> + * >>> + */ >>> + >>> +#include <linux/gpio.h> >>> +#include <linux/i2c.h> >>> +#include <linux/module.h> >>> +#include <linux/of.h> >>> +#include <drm/drm_atomic.h> >>> +#include <drm/drm_atomic_helper.h> >>> +#include <drm/drm_crtc_helper.h> >>> +#include <drm/drm_edid.h> >>> +#include <drm/drmP.h> >>> + >>> +/* >>> + * 220Mhz is a limitation of the host, as the bridge is capable of up to >>> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications >>> + * Processor Reference Manual for more information about the 220Mhz limit. >>> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work >>> + * fine. >>> + */ >>> +#define MAX_PIXEL_CLOCK 220000 >>> + >>> +#define EDID_EXT_BLOCK_CNT 0x7E >>> + >>> +#define STDP4028_IRQ_OUT_CONF_REG 0x02 >>> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C >>> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D >>> +#define STDP4028_DPTX_STS_REG 0x3E >>> + >>> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 >>> + >>> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 >>> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 >>> +#define STDP4028_DPTX_IRQ_CONFIG \ >>> + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) >>> + >>> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 >>> +#define STDP4028_DPTX_LINK_STS 0x1000 >>> +#define STDP4028_CON_STATE_CONNECTED \ >>> + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) >>> + >>> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 >>> +#define STDP4028_DPTX_LINK_CH_STS 0x2000 >>> +#define STDP4028_DPTX_IRQ_CLEAR \ >>> + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) >>> + >>> +struct ge_b850v3_lvds_dp { >>> + struct drm_connector connector; >>> + struct drm_bridge bridge; >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c; >>> + struct i2c_client *edid_i2c; >>> + struct edid *edid; >>> + struct mutex lock; >>> +}; >>> + >>> +static inline struct ge_b850v3_lvds_dp * >>> + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) >>> +{ >>> + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); >>> +} >>> + >>> +static inline struct ge_b850v3_lvds_dp * >>> + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) >>> +{ >>> + return container_of(connector, struct ge_b850v3_lvds_dp, connector); >>> +} >>> + >>> +u8 *stdp2690_get_edid(struct i2c_client *client) >>> +{ >>> + struct i2c_adapter *adapter = client->adapter; >>> + unsigned char start = 0x00; >>> + unsigned int total_size; >>> + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); >>> + >>> + struct i2c_msg msgs[] = { >>> + { >>> + .addr = client->addr, >>> + .flags = 0, >>> + .len = 1, >>> + .buf = &start, >>> + }, { >>> + .addr = client->addr, >>> + .flags = I2C_M_RD, >>> + .len = EDID_LENGTH, >>> + .buf = block, >>> + } >>> + }; >>> + >>> + if (!block) >>> + return NULL; >>> + >>> + if (i2c_transfer(adapter, msgs, 2) != 2) { >>> + DRM_ERROR("Unable to read EDID.\n"); >>> + goto err; >>> + } >>> + >>> + if (!drm_edid_block_valid(block, 0, false, NULL)) { >>> + DRM_ERROR("Invalid EDID block\n"); >>> + goto err; >>> + } >>> + >>> + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; >>> + if (total_size > EDID_LENGTH) { >>> + kfree(block); >>> + block = kmalloc(total_size, GFP_KERNEL); >>> + if (!block) >>> + return NULL; >>> + >>> + /* Yes, read the entire buffer, and do not skip the first >>> + * EDID_LENGTH bytes. >>> + */ >>> + start = 0x00; >>> + msgs[1].len = total_size; >>> + msgs[1].buf = block; >>> + >>> + if (i2c_transfer(adapter, msgs, 2) != 2) { >>> + DRM_ERROR("Unable to read EDID extension blocks.\n"); >>> + goto err; >>> + } >>> + } >>> + >>> + return block; >>> + >>> +err: >>> + kfree(block); >>> + return NULL; >>> +} >>> + >>> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge; >>> + struct i2c_client *client; >>> + int num_modes = 0; >>> + >>> + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); >>> + client = ptn_bridge->edid_i2c; >>> + >>> + mutex_lock(&ptn_bridge->lock); >>> + >>> + kfree(ptn_bridge->edid); >>> + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); >>> + >>> + if (ptn_bridge->edid) { >>> + drm_mode_connector_update_edid_property(connector, >>> + ptn_bridge->edid); >>> + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); >>> + } >>> + >>> + mutex_unlock(&ptn_bridge->lock); >>> + >>> + return num_modes; >>> +} >>> + >>> + >>> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( >>> + struct drm_connector *connector, struct drm_display_mode *mode) >>> +{ >>> + if (mode->clock > MAX_PIXEL_CLOCK) { >>> + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", >>> + mode->name); >>> + return MODE_CLOCK_HIGH; >>> + } >>> + >>> + return MODE_OK; >>> +} >>> + >>> +static const struct >>> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { >>> + .get_modes = ge_b850v3_lvds_dp_get_modes, >>> + .mode_valid = ge_b850v3_lvds_dp_mode_valid, >>> +}; >>> + >>> +static enum drm_connector_status ge_b850v3_lvds_dp_detect( >>> + struct drm_connector *connector, bool force) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge = >>> + connector_to_ge_b850v3_lvds_dp(connector); >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c = >>> + ptn_bridge->ge_b850v3_lvds_dp_i2c; >>> + s32 link_state; >>> + >>> + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, >>> + STDP4028_DPTX_STS_REG); >>> + >>> + if (link_state == STDP4028_CON_STATE_CONNECTED) >>> + return connector_status_connected; >>> + >>> + if (link_state == 0) >>> + return connector_status_disconnected; >>> + >>> + return connector_status_unknown; >>> +} >>> + >>> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { >>> + .dpms = drm_atomic_helper_connector_dpms, >>> + .fill_modes = drm_helper_probe_single_connector_modes, >>> + .detect = ge_b850v3_lvds_dp_detect, >>> + .destroy = drm_connector_cleanup, >>> + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c >>> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >>> + >>> + mutex_lock(&ptn_bridge->lock); >>> + >>> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >>> + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); >>> + >>> + mutex_unlock(&ptn_bridge->lock); >>> + >>> + if (ptn_bridge->connector.dev) >>> + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); >>> + >>> + return IRQ_HANDLED; >>> +} >>> + >>> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge >>> + = bridge_to_ge_b850v3_lvds_dp(bridge); >>> + struct drm_connector *connector = &ptn_bridge->connector; >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c >>> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >>> + int ret; >>> + >>> + if (!bridge->encoder) { >>> + DRM_ERROR("Parent encoder object not found"); >>> + return -ENODEV; >>> + } >>> + >>> + connector->polled = DRM_CONNECTOR_POLL_HPD; >>> + >>> + drm_connector_helper_add(connector, >>> + &ge_b850v3_lvds_dp_connector_helper_funcs); >>> + >>> + ret = drm_connector_init(bridge->dev, connector, >>> + &ge_b850v3_lvds_dp_connector_funcs, >>> + DRM_MODE_CONNECTOR_DisplayPort); >>> + if (ret) { >>> + DRM_ERROR("Failed to initialize connector with drm\n"); >>> + return ret; >>> + } >>> + >>> + drm_connector_register(connector); >> >> Connectors shouldn't be registered in the bridge driver, they should >> be registered by the kms driver after drm_dev_register is called. >> The drm_connector_register_all() API is used for that. > > Hmm, I got this from: > > drivers/gpu/drm/bridge/nxp-ptn3460.c: drm_connector_register(&ptn_bridge->connector); > drivers/gpu/drm/bridge/parade-ps8622.c: drm_connector_register(&ps8622->connector); > drivers/gpu/drm/bridge/analogix-anx78xx.c: err = drm_connector_register(&anx78xx->connector); Yeah, we need to get rid of these too, eventually. The connectors should be registered only after the drm device has been registered. Relying on the drm core to do that ensures that. See drm_modeset_register_all() for more info. Also, btw, the imx driver shouldn't use the load() drm_driver ops anymore. You would want to change that too. See the comments in drm_dev_register() in drm_drv.c for more details. > > >> >>> + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); >>> + if (ret) >>> + return ret; >>> + >>> + drm_bridge_enable(bridge); >> >> This drm_bridge_enable() doesn't seem to serve any purpose here. It also >> doesn't seem to make much sense to call drm_bridge_() funcs within a >> bridge op itself. > > Yes, removing this line has no effect. I'll send V6. Thank you! > >> >>> + if (ge_b850v3_lvds_dp_i2c->irq) { >>> + drm_helper_hpd_irq_event(connector->dev); >>> + >>> + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, >>> + ge_b850v3_lvds_dp_i2c->irq, NULL, >>> + ge_b850v3_lvds_dp_irq_handler, >>> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, >>> + "ge-b850v3-lvds-dp", ptn_bridge); >> >> Is there a reason why we register the interrupt handler here and not in >> probe? > > Yes, drm_bridge_funcs.attach() is called when drm decides it is time to initialize the bridge, which means the drm is already running. i2c_driver.probe() is likely to be called way before drm initializes, and if for some reason the bridge is not attached, there is no need for the interrupt handler. In this case the interrupt handler is mostly used for events related to display plugging/unplugging. The i2c device can probe before drm, this should be fixed by making sure the device's interrupts are masked before we request the handler, and unmasking it when we know it is ready for use. If a bridge device exists and isn't attached, then we should question why it probed in the first place if it wasn't to be used. Besides, in the current scenario, consider the case where the imx driver's call to drm_bridge_attach() succeeds, but then later fails at some point. There would be no one to remove this interrupt handler. The devm_ api attaches the lifetime of the handler with the i2c device, not the drm device. Also, the call to drm_helper_hpd_irq_event(drm_dev) should go too. These should be called only when the drm device is registered. Thanks, Archit -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 12:54 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 12:54 UTC (permalink / raw) To: linux-arm-kernel On 09/26/2016 05:24 PM, Peter Senna Tschudin wrote: > > On Monday, September 26, 2016 12:29 CEST, Archit Taneja <architt@codeaurora.org> wrote: > >> Hi, >> >> Some comments. > > Thank you for the review! > >> >> On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: >>> Add a driver that create a drm_bridge and a drm_connector for the LVDS >>> to DP++ display bridge of the GE B850v3. >>> >>> There are two physical bridges on the video signal pipeline: a >>> STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and >>> firmware made it complicated for this binding to comprise two device >>> tree nodes, as the design goal is to configure both bridges based on >>> the LVDS signal, which leave the driver powerless to control the video >>> processing pipeline. The two bridges behaves as a single bridge, and >>> the driver is only needed for telling the host about EDID / HPD, and >>> for giving the host powers to ack interrupts. The video signal pipeline >>> is as follows: >>> >>> Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >>> >>> Cc: Martyn Welch <martyn.welch@collabora.co.uk> >>> Cc: Martin Donnelly <martin.donnelly@ge.com> >>> Cc: Daniel Vetter <daniel.vetter@ffwll.ch> >>> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> >>> Cc: Philipp Zabel <p.zabel@pengutronix.de> >>> Cc: Rob Herring <robh@kernel.org> >>> Cc: Fabio Estevam <fabio.estevam@nxp.com> >>> CC: David Airlie <airlied@linux.ie> >>> CC: Thierry Reding <treding@nvidia.com> >>> CC: Thierry Reding <thierry.reding@gmail.com> >>> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com> >>> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> >>> --- >>> Changes from V4: >>> - Check the output of the first call to i2c_smbus_write_word_data() and return >>> it's error code for failing gracefully on i2c issues >>> - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to >>> remove the comma from the driver name >>> >>> Changes from V3: >>> - 3/4 instead of 4/5 >>> - Tested on next-20160804 >>> >>> Changes from V2: >>> - Made it atomic to be applied on next-20160729 on top of Liu Ying changes >>> that made imx-ldb atomic >>> >>> Changes from V1: >>> - New commit message >>> - Removed 3 empty entry points >>> - Removed memory leak from ge_b850v3_lvds_dp_get_modes() >>> - Added a lock for mode setting >>> - Removed a few blank lines >>> - Changed the order at Makefile and Kconfig >>> >>> MAINTAINERS | 8 + >>> drivers/gpu/drm/bridge/Kconfig | 11 + >>> drivers/gpu/drm/bridge/Makefile | 1 + >>> drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ >>> 4 files changed, 425 insertions(+) >>> create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >>> >>> diff --git a/MAINTAINERS b/MAINTAINERS >>> index a306795..e8d106a 100644 >>> --- a/MAINTAINERS >>> +++ b/MAINTAINERS >>> @@ -5142,6 +5142,14 @@ W: https://linuxtv.org >>> S: Maintained >>> F: drivers/media/radio/radio-gemtek* >>> >>> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE >>> +M: Peter Senna Tschudin <peter.senna@collabora.com> >>> +M: Martin Donnelly <martin.donnelly@ge.com> >>> +M: Martyn Welch <martyn.welch@collabora.co.uk> >>> +S: Maintained >>> +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c >>> +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt >>> + >>> GENERIC GPIO I2C DRIVER >>> M: Haavard Skinnemoen <hskinnemoen@gmail.com> >>> S: Supported >> >> Could you move the MAINTAINERS change to a different patch? It would >> make it easier to integrate separately. > > If needed, yes sure. > >> >>> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig >>> index b590e67..b4b70fb 100644 >>> --- a/drivers/gpu/drm/bridge/Kconfig >>> +++ b/drivers/gpu/drm/bridge/Kconfig >>> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO >>> Designware HDMI block. This is used in conjunction with >>> the i.MX6 HDMI driver. >>> >>> +config DRM_GE_B850V3_LVDS_DP >>> + tristate "GE B850v3 LVDS to DP++ display bridge" >>> + depends on OF >>> + select DRM_KMS_HELPER >>> + select DRM_PANEL >>> + ---help--- >>> + This is a driver for the display bridge of >>> + GE B850v3 that convert dual channel LVDS >>> + to DP++. This is used with the i.MX6 imx-ldb >>> + driver. >>> + >>> config DRM_NXP_PTN3460 >>> tristate "NXP PTN3460 DP/LVDS bridge" >>> depends on OF >>> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile >>> index efdb07e..b9606f3 100644 >>> --- a/drivers/gpu/drm/bridge/Makefile >>> +++ b/drivers/gpu/drm/bridge/Makefile >>> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm >>> obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o >>> obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o >>> obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o >>> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o >>> obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o >>> obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o >>> obj-$(CONFIG_DRM_SII902X) += sii902x.o >>> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >>> new file mode 100644 >>> index 0000000..81e9279 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >>> @@ -0,0 +1,405 @@ >>> +/* >>> + * Driver for GE B850v3 DP display bridge >>> + >>> + * Copyright (c) 2016, Collabora Ltd. >>> + * Copyright (c) 2016, General Electric Company >>> + >>> + * This program is free software; you can redistribute it and/or modify it >>> + * under the terms and conditions of the GNU General Public License, >>> + * version 2, as published by the Free Software Foundation. >>> + >>> + * This program is distributed in the hope it will be useful, but WITHOUT >>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or >>> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for >>> + * more details. >>> + >>> + * You should have received a copy of the GNU General Public License >>> + * along with this program. If not, see <http://www.gnu.org/licenses/>. >>> + >>> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ >>> + * display bridge of the GE B850v3. There are two physical bridges on the video >>> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However >>> + * the physical bridges are automatically configured by the input video signal, >>> + * and the driver has no access to the video processing pipeline. The driver is >>> + * only needed to read EDID from the STDP2690 and to handle HPD events from the >>> + * STDP4028. The driver communicates with both bridges over i2c. The video >>> + * signal pipeline is as follows: >>> + * >>> + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >>> + * >>> + */ >>> + >>> +#include <linux/gpio.h> >>> +#include <linux/i2c.h> >>> +#include <linux/module.h> >>> +#include <linux/of.h> >>> +#include <drm/drm_atomic.h> >>> +#include <drm/drm_atomic_helper.h> >>> +#include <drm/drm_crtc_helper.h> >>> +#include <drm/drm_edid.h> >>> +#include <drm/drmP.h> >>> + >>> +/* >>> + * 220Mhz is a limitation of the host, as the bridge is capable of up to >>> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications >>> + * Processor Reference Manual for more information about the 220Mhz limit. >>> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work >>> + * fine. >>> + */ >>> +#define MAX_PIXEL_CLOCK 220000 >>> + >>> +#define EDID_EXT_BLOCK_CNT 0x7E >>> + >>> +#define STDP4028_IRQ_OUT_CONF_REG 0x02 >>> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C >>> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D >>> +#define STDP4028_DPTX_STS_REG 0x3E >>> + >>> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 >>> + >>> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 >>> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 >>> +#define STDP4028_DPTX_IRQ_CONFIG \ >>> + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) >>> + >>> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 >>> +#define STDP4028_DPTX_LINK_STS 0x1000 >>> +#define STDP4028_CON_STATE_CONNECTED \ >>> + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) >>> + >>> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 >>> +#define STDP4028_DPTX_LINK_CH_STS 0x2000 >>> +#define STDP4028_DPTX_IRQ_CLEAR \ >>> + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) >>> + >>> +struct ge_b850v3_lvds_dp { >>> + struct drm_connector connector; >>> + struct drm_bridge bridge; >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c; >>> + struct i2c_client *edid_i2c; >>> + struct edid *edid; >>> + struct mutex lock; >>> +}; >>> + >>> +static inline struct ge_b850v3_lvds_dp * >>> + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) >>> +{ >>> + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); >>> +} >>> + >>> +static inline struct ge_b850v3_lvds_dp * >>> + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) >>> +{ >>> + return container_of(connector, struct ge_b850v3_lvds_dp, connector); >>> +} >>> + >>> +u8 *stdp2690_get_edid(struct i2c_client *client) >>> +{ >>> + struct i2c_adapter *adapter = client->adapter; >>> + unsigned char start = 0x00; >>> + unsigned int total_size; >>> + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); >>> + >>> + struct i2c_msg msgs[] = { >>> + { >>> + .addr = client->addr, >>> + .flags = 0, >>> + .len = 1, >>> + .buf = &start, >>> + }, { >>> + .addr = client->addr, >>> + .flags = I2C_M_RD, >>> + .len = EDID_LENGTH, >>> + .buf = block, >>> + } >>> + }; >>> + >>> + if (!block) >>> + return NULL; >>> + >>> + if (i2c_transfer(adapter, msgs, 2) != 2) { >>> + DRM_ERROR("Unable to read EDID.\n"); >>> + goto err; >>> + } >>> + >>> + if (!drm_edid_block_valid(block, 0, false, NULL)) { >>> + DRM_ERROR("Invalid EDID block\n"); >>> + goto err; >>> + } >>> + >>> + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; >>> + if (total_size > EDID_LENGTH) { >>> + kfree(block); >>> + block = kmalloc(total_size, GFP_KERNEL); >>> + if (!block) >>> + return NULL; >>> + >>> + /* Yes, read the entire buffer, and do not skip the first >>> + * EDID_LENGTH bytes. >>> + */ >>> + start = 0x00; >>> + msgs[1].len = total_size; >>> + msgs[1].buf = block; >>> + >>> + if (i2c_transfer(adapter, msgs, 2) != 2) { >>> + DRM_ERROR("Unable to read EDID extension blocks.\n"); >>> + goto err; >>> + } >>> + } >>> + >>> + return block; >>> + >>> +err: >>> + kfree(block); >>> + return NULL; >>> +} >>> + >>> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge; >>> + struct i2c_client *client; >>> + int num_modes = 0; >>> + >>> + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); >>> + client = ptn_bridge->edid_i2c; >>> + >>> + mutex_lock(&ptn_bridge->lock); >>> + >>> + kfree(ptn_bridge->edid); >>> + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); >>> + >>> + if (ptn_bridge->edid) { >>> + drm_mode_connector_update_edid_property(connector, >>> + ptn_bridge->edid); >>> + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); >>> + } >>> + >>> + mutex_unlock(&ptn_bridge->lock); >>> + >>> + return num_modes; >>> +} >>> + >>> + >>> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( >>> + struct drm_connector *connector, struct drm_display_mode *mode) >>> +{ >>> + if (mode->clock > MAX_PIXEL_CLOCK) { >>> + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", >>> + mode->name); >>> + return MODE_CLOCK_HIGH; >>> + } >>> + >>> + return MODE_OK; >>> +} >>> + >>> +static const struct >>> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { >>> + .get_modes = ge_b850v3_lvds_dp_get_modes, >>> + .mode_valid = ge_b850v3_lvds_dp_mode_valid, >>> +}; >>> + >>> +static enum drm_connector_status ge_b850v3_lvds_dp_detect( >>> + struct drm_connector *connector, bool force) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge = >>> + connector_to_ge_b850v3_lvds_dp(connector); >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c = >>> + ptn_bridge->ge_b850v3_lvds_dp_i2c; >>> + s32 link_state; >>> + >>> + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, >>> + STDP4028_DPTX_STS_REG); >>> + >>> + if (link_state == STDP4028_CON_STATE_CONNECTED) >>> + return connector_status_connected; >>> + >>> + if (link_state == 0) >>> + return connector_status_disconnected; >>> + >>> + return connector_status_unknown; >>> +} >>> + >>> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { >>> + .dpms = drm_atomic_helper_connector_dpms, >>> + .fill_modes = drm_helper_probe_single_connector_modes, >>> + .detect = ge_b850v3_lvds_dp_detect, >>> + .destroy = drm_connector_cleanup, >>> + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c >>> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >>> + >>> + mutex_lock(&ptn_bridge->lock); >>> + >>> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >>> + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); >>> + >>> + mutex_unlock(&ptn_bridge->lock); >>> + >>> + if (ptn_bridge->connector.dev) >>> + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); >>> + >>> + return IRQ_HANDLED; >>> +} >>> + >>> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge >>> + = bridge_to_ge_b850v3_lvds_dp(bridge); >>> + struct drm_connector *connector = &ptn_bridge->connector; >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c >>> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >>> + int ret; >>> + >>> + if (!bridge->encoder) { >>> + DRM_ERROR("Parent encoder object not found"); >>> + return -ENODEV; >>> + } >>> + >>> + connector->polled = DRM_CONNECTOR_POLL_HPD; >>> + >>> + drm_connector_helper_add(connector, >>> + &ge_b850v3_lvds_dp_connector_helper_funcs); >>> + >>> + ret = drm_connector_init(bridge->dev, connector, >>> + &ge_b850v3_lvds_dp_connector_funcs, >>> + DRM_MODE_CONNECTOR_DisplayPort); >>> + if (ret) { >>> + DRM_ERROR("Failed to initialize connector with drm\n"); >>> + return ret; >>> + } >>> + >>> + drm_connector_register(connector); >> >> Connectors shouldn't be registered in the bridge driver, they should >> be registered by the kms driver after drm_dev_register is called. >> The drm_connector_register_all() API is used for that. > > Hmm, I got this from: > > drivers/gpu/drm/bridge/nxp-ptn3460.c: drm_connector_register(&ptn_bridge->connector); > drivers/gpu/drm/bridge/parade-ps8622.c: drm_connector_register(&ps8622->connector); > drivers/gpu/drm/bridge/analogix-anx78xx.c: err = drm_connector_register(&anx78xx->connector); Yeah, we need to get rid of these too, eventually. The connectors should be registered only after the drm device has been registered. Relying on the drm core to do that ensures that. See drm_modeset_register_all() for more info. Also, btw, the imx driver shouldn't use the load() drm_driver ops anymore. You would want to change that too. See the comments in drm_dev_register() in drm_drv.c for more details. > > >> >>> + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); >>> + if (ret) >>> + return ret; >>> + >>> + drm_bridge_enable(bridge); >> >> This drm_bridge_enable() doesn't seem to serve any purpose here. It also >> doesn't seem to make much sense to call drm_bridge_() funcs within a >> bridge op itself. > > Yes, removing this line has no effect. I'll send V6. Thank you! > >> >>> + if (ge_b850v3_lvds_dp_i2c->irq) { >>> + drm_helper_hpd_irq_event(connector->dev); >>> + >>> + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, >>> + ge_b850v3_lvds_dp_i2c->irq, NULL, >>> + ge_b850v3_lvds_dp_irq_handler, >>> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, >>> + "ge-b850v3-lvds-dp", ptn_bridge); >> >> Is there a reason why we register the interrupt handler here and not in >> probe? > > Yes, drm_bridge_funcs.attach() is called when drm decides it is time to initialize the bridge, which means the drm is already running. i2c_driver.probe() is likely to be called way before drm initializes, and if for some reason the bridge is not attached, there is no need for the interrupt handler. In this case the interrupt handler is mostly used for events related to display plugging/unplugging. The i2c device can probe before drm, this should be fixed by making sure the device's interrupts are masked before we request the handler, and unmasking it when we know it is ready for use. If a bridge device exists and isn't attached, then we should question why it probed in the first place if it wasn't to be used. Besides, in the current scenario, consider the case where the imx driver's call to drm_bridge_attach() succeeds, but then later fails at some point. There would be no one to remove this interrupt handler. The devm_ api attaches the lifetime of the handler with the i2c device, not the drm device. Also, the call to drm_helper_hpd_irq_event(drm_dev) should go too. These should be called only when the drm device is registered. Thanks, Archit -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 12:54 ` Archit Taneja 0 siblings, 0 replies; 172+ messages in thread From: Archit Taneja @ 2016-09-26 12:54 UTC (permalink / raw) To: Peter Senna Tschudin Cc: kernel-bIcnvbaLZ9MEGnE8C9+IrQ, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, tiwai-IBi9RG/b67k, linux-kernel-u79uwXL29TY76Z2rM5mHXA, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, Rob Herring, javier-0uQlZySMnqxg9hUCZPvPmw, devicetree-u79uwXL29TY76Z2rM5mHXA, martin.donnelly-JJi787mZWgc, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, pawel.moll-5wv7dgnIgG8, Peter Senna Tschudin, treding-DDmLM1+adcrQT0dZR+AlfA, heiko-4mtYJXux2i+zQB+pC5nmwQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, daniel.vetter-/w4YWyX8dFk, linux-0h96xk9xTtrk1uMJSBkQmQ, Fabio Estevam, jslaby-AlSwsSmVLrQ, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, linux-I+IVW8TIWO2tmTQ+vhA3Yw, galak-sgV2jX0FEOL9JmXXK+q4OQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, airlied On 09/26/2016 05:24 PM, Peter Senna Tschudin wrote: > > On Monday, September 26, 2016 12:29 CEST, Archit Taneja <architt-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org> wrote: > >> Hi, >> >> Some comments. > > Thank you for the review! > >> >> On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote: >>> Add a driver that create a drm_bridge and a drm_connector for the LVDS >>> to DP++ display bridge of the GE B850v3. >>> >>> There are two physical bridges on the video signal pipeline: a >>> STDP4028(LVDS to DP) and a STDP2690(DP to DP++). The hardware and >>> firmware made it complicated for this binding to comprise two device >>> tree nodes, as the design goal is to configure both bridges based on >>> the LVDS signal, which leave the driver powerless to control the video >>> processing pipeline. The two bridges behaves as a single bridge, and >>> the driver is only needed for telling the host about EDID / HPD, and >>> for giving the host powers to ack interrupts. The video signal pipeline >>> is as follows: >>> >>> Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >>> >>> Cc: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> >>> Cc: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> >>> Cc: Daniel Vetter <daniel.vetter-/w4YWyX8dFk@public.gmane.org> >>> Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> >>> Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> >>> Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> >>> Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> >>> CC: David Airlie <airlied-cv59FeDIM0c@public.gmane.org> >>> CC: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org> >>> CC: Thierry Reding <thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> >>> Reviewed-by: Enric Balletbo <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> >>> Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> >>> --- >>> Changes from V4: >>> - Check the output of the first call to i2c_smbus_write_word_data() and return >>> it's error code for failing gracefully on i2c issues >>> - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to >>> remove the comma from the driver name >>> >>> Changes from V3: >>> - 3/4 instead of 4/5 >>> - Tested on next-20160804 >>> >>> Changes from V2: >>> - Made it atomic to be applied on next-20160729 on top of Liu Ying changes >>> that made imx-ldb atomic >>> >>> Changes from V1: >>> - New commit message >>> - Removed 3 empty entry points >>> - Removed memory leak from ge_b850v3_lvds_dp_get_modes() >>> - Added a lock for mode setting >>> - Removed a few blank lines >>> - Changed the order at Makefile and Kconfig >>> >>> MAINTAINERS | 8 + >>> drivers/gpu/drm/bridge/Kconfig | 11 + >>> drivers/gpu/drm/bridge/Makefile | 1 + >>> drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++ >>> 4 files changed, 425 insertions(+) >>> create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >>> >>> diff --git a/MAINTAINERS b/MAINTAINERS >>> index a306795..e8d106a 100644 >>> --- a/MAINTAINERS >>> +++ b/MAINTAINERS >>> @@ -5142,6 +5142,14 @@ W: https://linuxtv.org >>> S: Maintained >>> F: drivers/media/radio/radio-gemtek* >>> >>> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE >>> +M: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> >>> +M: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> >>> +M: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> >>> +S: Maintained >>> +F: drivers/gpu/drm/bridge/ge_b850v3_dp2.c >>> +F: Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt >>> + >>> GENERIC GPIO I2C DRIVER >>> M: Haavard Skinnemoen <hskinnemoen-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> >>> S: Supported >> >> Could you move the MAINTAINERS change to a different patch? It would >> make it easier to integrate separately. > > If needed, yes sure. > >> >>> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig >>> index b590e67..b4b70fb 100644 >>> --- a/drivers/gpu/drm/bridge/Kconfig >>> +++ b/drivers/gpu/drm/bridge/Kconfig >>> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO >>> Designware HDMI block. This is used in conjunction with >>> the i.MX6 HDMI driver. >>> >>> +config DRM_GE_B850V3_LVDS_DP >>> + tristate "GE B850v3 LVDS to DP++ display bridge" >>> + depends on OF >>> + select DRM_KMS_HELPER >>> + select DRM_PANEL >>> + ---help--- >>> + This is a driver for the display bridge of >>> + GE B850v3 that convert dual channel LVDS >>> + to DP++. This is used with the i.MX6 imx-ldb >>> + driver. >>> + >>> config DRM_NXP_PTN3460 >>> tristate "NXP PTN3460 DP/LVDS bridge" >>> depends on OF >>> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile >>> index efdb07e..b9606f3 100644 >>> --- a/drivers/gpu/drm/bridge/Makefile >>> +++ b/drivers/gpu/drm/bridge/Makefile >>> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm >>> obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o >>> obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o >>> obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o >>> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o >>> obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o >>> obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o >>> obj-$(CONFIG_DRM_SII902X) += sii902x.o >>> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >>> new file mode 100644 >>> index 0000000..81e9279 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c >>> @@ -0,0 +1,405 @@ >>> +/* >>> + * Driver for GE B850v3 DP display bridge >>> + >>> + * Copyright (c) 2016, Collabora Ltd. >>> + * Copyright (c) 2016, General Electric Company >>> + >>> + * This program is free software; you can redistribute it and/or modify it >>> + * under the terms and conditions of the GNU General Public License, >>> + * version 2, as published by the Free Software Foundation. >>> + >>> + * This program is distributed in the hope it will be useful, but WITHOUT >>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or >>> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for >>> + * more details. >>> + >>> + * You should have received a copy of the GNU General Public License >>> + * along with this program. If not, see <http://www.gnu.org/licenses/>. >>> + >>> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++ >>> + * display bridge of the GE B850v3. There are two physical bridges on the video >>> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However >>> + * the physical bridges are automatically configured by the input video signal, >>> + * and the driver has no access to the video processing pipeline. The driver is >>> + * only needed to read EDID from the STDP2690 and to handle HPD events from the >>> + * STDP4028. The driver communicates with both bridges over i2c. The video >>> + * signal pipeline is as follows: >>> + * >>> + * Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output >>> + * >>> + */ >>> + >>> +#include <linux/gpio.h> >>> +#include <linux/i2c.h> >>> +#include <linux/module.h> >>> +#include <linux/of.h> >>> +#include <drm/drm_atomic.h> >>> +#include <drm/drm_atomic_helper.h> >>> +#include <drm/drm_crtc_helper.h> >>> +#include <drm/drm_edid.h> >>> +#include <drm/drmP.h> >>> + >>> +/* >>> + * 220Mhz is a limitation of the host, as the bridge is capable of up to >>> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications >>> + * Processor Reference Manual for more information about the 220Mhz limit. >>> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work >>> + * fine. >>> + */ >>> +#define MAX_PIXEL_CLOCK 220000 >>> + >>> +#define EDID_EXT_BLOCK_CNT 0x7E >>> + >>> +#define STDP4028_IRQ_OUT_CONF_REG 0x02 >>> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C >>> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D >>> +#define STDP4028_DPTX_STS_REG 0x3E >>> + >>> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000 >>> + >>> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400 >>> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000 >>> +#define STDP4028_DPTX_IRQ_CONFIG \ >>> + (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN) >>> + >>> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200 >>> +#define STDP4028_DPTX_LINK_STS 0x1000 >>> +#define STDP4028_CON_STATE_CONNECTED \ >>> + (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS) >>> + >>> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400 >>> +#define STDP4028_DPTX_LINK_CH_STS 0x2000 >>> +#define STDP4028_DPTX_IRQ_CLEAR \ >>> + (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS) >>> + >>> +struct ge_b850v3_lvds_dp { >>> + struct drm_connector connector; >>> + struct drm_bridge bridge; >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c; >>> + struct i2c_client *edid_i2c; >>> + struct edid *edid; >>> + struct mutex lock; >>> +}; >>> + >>> +static inline struct ge_b850v3_lvds_dp * >>> + bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge) >>> +{ >>> + return container_of(bridge, struct ge_b850v3_lvds_dp, bridge); >>> +} >>> + >>> +static inline struct ge_b850v3_lvds_dp * >>> + connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector) >>> +{ >>> + return container_of(connector, struct ge_b850v3_lvds_dp, connector); >>> +} >>> + >>> +u8 *stdp2690_get_edid(struct i2c_client *client) >>> +{ >>> + struct i2c_adapter *adapter = client->adapter; >>> + unsigned char start = 0x00; >>> + unsigned int total_size; >>> + u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL); >>> + >>> + struct i2c_msg msgs[] = { >>> + { >>> + .addr = client->addr, >>> + .flags = 0, >>> + .len = 1, >>> + .buf = &start, >>> + }, { >>> + .addr = client->addr, >>> + .flags = I2C_M_RD, >>> + .len = EDID_LENGTH, >>> + .buf = block, >>> + } >>> + }; >>> + >>> + if (!block) >>> + return NULL; >>> + >>> + if (i2c_transfer(adapter, msgs, 2) != 2) { >>> + DRM_ERROR("Unable to read EDID.\n"); >>> + goto err; >>> + } >>> + >>> + if (!drm_edid_block_valid(block, 0, false, NULL)) { >>> + DRM_ERROR("Invalid EDID block\n"); >>> + goto err; >>> + } >>> + >>> + total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH; >>> + if (total_size > EDID_LENGTH) { >>> + kfree(block); >>> + block = kmalloc(total_size, GFP_KERNEL); >>> + if (!block) >>> + return NULL; >>> + >>> + /* Yes, read the entire buffer, and do not skip the first >>> + * EDID_LENGTH bytes. >>> + */ >>> + start = 0x00; >>> + msgs[1].len = total_size; >>> + msgs[1].buf = block; >>> + >>> + if (i2c_transfer(adapter, msgs, 2) != 2) { >>> + DRM_ERROR("Unable to read EDID extension blocks.\n"); >>> + goto err; >>> + } >>> + } >>> + >>> + return block; >>> + >>> +err: >>> + kfree(block); >>> + return NULL; >>> +} >>> + >>> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge; >>> + struct i2c_client *client; >>> + int num_modes = 0; >>> + >>> + ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector); >>> + client = ptn_bridge->edid_i2c; >>> + >>> + mutex_lock(&ptn_bridge->lock); >>> + >>> + kfree(ptn_bridge->edid); >>> + ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client); >>> + >>> + if (ptn_bridge->edid) { >>> + drm_mode_connector_update_edid_property(connector, >>> + ptn_bridge->edid); >>> + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); >>> + } >>> + >>> + mutex_unlock(&ptn_bridge->lock); >>> + >>> + return num_modes; >>> +} >>> + >>> + >>> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid( >>> + struct drm_connector *connector, struct drm_display_mode *mode) >>> +{ >>> + if (mode->clock > MAX_PIXEL_CLOCK) { >>> + DRM_INFO("The pixel clock for the mode %s is too high, and not supported.", >>> + mode->name); >>> + return MODE_CLOCK_HIGH; >>> + } >>> + >>> + return MODE_OK; >>> +} >>> + >>> +static const struct >>> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = { >>> + .get_modes = ge_b850v3_lvds_dp_get_modes, >>> + .mode_valid = ge_b850v3_lvds_dp_mode_valid, >>> +}; >>> + >>> +static enum drm_connector_status ge_b850v3_lvds_dp_detect( >>> + struct drm_connector *connector, bool force) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge = >>> + connector_to_ge_b850v3_lvds_dp(connector); >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c = >>> + ptn_bridge->ge_b850v3_lvds_dp_i2c; >>> + s32 link_state; >>> + >>> + link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c, >>> + STDP4028_DPTX_STS_REG); >>> + >>> + if (link_state == STDP4028_CON_STATE_CONNECTED) >>> + return connector_status_connected; >>> + >>> + if (link_state == 0) >>> + return connector_status_disconnected; >>> + >>> + return connector_status_unknown; >>> +} >>> + >>> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = { >>> + .dpms = drm_atomic_helper_connector_dpms, >>> + .fill_modes = drm_helper_probe_single_connector_modes, >>> + .detect = ge_b850v3_lvds_dp_detect, >>> + .destroy = drm_connector_cleanup, >>> + .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 irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge = dev_id; >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c >>> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >>> + >>> + mutex_lock(&ptn_bridge->lock); >>> + >>> + i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c, >>> + STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR); >>> + >>> + mutex_unlock(&ptn_bridge->lock); >>> + >>> + if (ptn_bridge->connector.dev) >>> + drm_kms_helper_hotplug_event(ptn_bridge->connector.dev); >>> + >>> + return IRQ_HANDLED; >>> +} >>> + >>> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge) >>> +{ >>> + struct ge_b850v3_lvds_dp *ptn_bridge >>> + = bridge_to_ge_b850v3_lvds_dp(bridge); >>> + struct drm_connector *connector = &ptn_bridge->connector; >>> + struct i2c_client *ge_b850v3_lvds_dp_i2c >>> + = ptn_bridge->ge_b850v3_lvds_dp_i2c; >>> + int ret; >>> + >>> + if (!bridge->encoder) { >>> + DRM_ERROR("Parent encoder object not found"); >>> + return -ENODEV; >>> + } >>> + >>> + connector->polled = DRM_CONNECTOR_POLL_HPD; >>> + >>> + drm_connector_helper_add(connector, >>> + &ge_b850v3_lvds_dp_connector_helper_funcs); >>> + >>> + ret = drm_connector_init(bridge->dev, connector, >>> + &ge_b850v3_lvds_dp_connector_funcs, >>> + DRM_MODE_CONNECTOR_DisplayPort); >>> + if (ret) { >>> + DRM_ERROR("Failed to initialize connector with drm\n"); >>> + return ret; >>> + } >>> + >>> + drm_connector_register(connector); >> >> Connectors shouldn't be registered in the bridge driver, they should >> be registered by the kms driver after drm_dev_register is called. >> The drm_connector_register_all() API is used for that. > > Hmm, I got this from: > > drivers/gpu/drm/bridge/nxp-ptn3460.c: drm_connector_register(&ptn_bridge->connector); > drivers/gpu/drm/bridge/parade-ps8622.c: drm_connector_register(&ps8622->connector); > drivers/gpu/drm/bridge/analogix-anx78xx.c: err = drm_connector_register(&anx78xx->connector); Yeah, we need to get rid of these too, eventually. The connectors should be registered only after the drm device has been registered. Relying on the drm core to do that ensures that. See drm_modeset_register_all() for more info. Also, btw, the imx driver shouldn't use the load() drm_driver ops anymore. You would want to change that too. See the comments in drm_dev_register() in drm_drv.c for more details. > > >> >>> + ret = drm_mode_connector_attach_encoder(connector, bridge->encoder); >>> + if (ret) >>> + return ret; >>> + >>> + drm_bridge_enable(bridge); >> >> This drm_bridge_enable() doesn't seem to serve any purpose here. It also >> doesn't seem to make much sense to call drm_bridge_() funcs within a >> bridge op itself. > > Yes, removing this line has no effect. I'll send V6. Thank you! > >> >>> + if (ge_b850v3_lvds_dp_i2c->irq) { >>> + drm_helper_hpd_irq_event(connector->dev); >>> + >>> + ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev, >>> + ge_b850v3_lvds_dp_i2c->irq, NULL, >>> + ge_b850v3_lvds_dp_irq_handler, >>> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, >>> + "ge-b850v3-lvds-dp", ptn_bridge); >> >> Is there a reason why we register the interrupt handler here and not in >> probe? > > Yes, drm_bridge_funcs.attach() is called when drm decides it is time to initialize the bridge, which means the drm is already running. i2c_driver.probe() is likely to be called way before drm initializes, and if for some reason the bridge is not attached, there is no need for the interrupt handler. In this case the interrupt handler is mostly used for events related to display plugging/unplugging. The i2c device can probe before drm, this should be fixed by making sure the device's interrupts are masked before we request the handler, and unmasking it when we know it is ready for use. If a bridge device exists and isn't attached, then we should question why it probed in the first place if it wasn't to be used. Besides, in the current scenario, consider the case where the imx driver's call to drm_bridge_attach() succeeds, but then later fails at some point. There would be no one to remove this interrupt handler. The devm_ api attaches the lifetime of the handler with the i2c device, not the drm device. Also, the call to drm_helper_hpd_irq_event(drm_dev) should go too. These should be called only when the drm device is registered. Thanks, Archit -- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge 2016-08-09 16:41 ` Peter Senna Tschudin @ 2016-08-09 16:41 ` Peter Senna Tschudin -1 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk Cc: Rob Herring, Fabio Estevam Configures the GE B850v3 LVDS/DP++ bridge on the dts file. Cc: Martyn Welch <martyn.welch@collabora.co.uk> Cc: Martin Donnelly <martin.donnelly@ge.com> Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V4 Changes from V3: - 4/4 instead of 5/5 Unchanged from V2 Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to b850v3-lvds-dp-bridge arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..8db3bf2 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -72,6 +72,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port@4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -142,3 +149,26 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge@73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-08-09 16:41 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw) To: linux-arm-kernel Configures the GE B850v3 LVDS/DP++ bridge on the dts file. Cc: Martyn Welch <martyn.welch@collabora.co.uk> Cc: Martin Donnelly <martin.donnelly@ge.com> Cc: Javier Martinez Canillas <javier@dowhile0.org> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Rob Herring <robh@kernel.org> Cc: Fabio Estevam <fabio.estevam@nxp.com> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> --- Unchanged from V4 Changes from V3: - 4/4 instead of 5/5 Unchanged from V2 Changes from V1: - Replaced '_' by '-' in node names or compatible strings - Added missing @73 to b850v3-lvds-dp-bridge arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts index 167f744..8db3bf2 100644 --- a/arch/arm/boot/dts/imx6q-b850v3.dts +++ b/arch/arm/boot/dts/imx6q-b850v3.dts @@ -72,6 +72,13 @@ fsl,data-mapping = "spwg"; fsl,data-width = <24>; status = "okay"; + + port at 4 { + reg = <4>; + lvds0_out: endpoint { + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; + }; + }; }; }; @@ -142,3 +149,26 @@ reg = <0x4a>; }; }; + +&mux2_i2c2 { + status = "okay"; + clock-frequency = <100000>; + + b850v3-lvds-dp-bridge at 73 { + compatible = "ge,b850v3-lvds-dp"; + #address-cells = <1>; + #size-cells = <0>; + + reg = <0x73>; + interrupt-parent = <&gpio2>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + + edid-reg = <0x72>; + + port { + b850v3_lvds_dp_bridge_in: endpoint { + remote-endpoint = <&lvds0_out>; + }; + }; + }; +}; -- 2.5.5 ^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:27 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:27 UTC (permalink / raw) To: Peter Senna Tschudin Cc: linux-arm-kernel, eballetbo, pawel.moll, treding, linux, heiko, thierry.reding, daniel.vetter, Fabio Estevam, jslaby, dri-devel, martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk, ijc+devicetree, rmk+kernel, davem, mark.rutland, kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel, akpm, Rob Herring, javier, robh+dt, devicetree, martin.donnelly Patch 1/4 is already on linux-next, but what about this one? Ping? On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Configures the GE B850v3 LVDS/DP++ bridge on the dts file. > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Unchanged from V4 > > Changes from V3: > - 4/4 instead of 5/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to b850v3-lvds-dp-bridge > > arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ > 1 file changed, 30 insertions(+) > > diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts > index 167f744..8db3bf2 100644 > --- a/arch/arm/boot/dts/imx6q-b850v3.dts > +++ b/arch/arm/boot/dts/imx6q-b850v3.dts > @@ -72,6 +72,13 @@ > fsl,data-mapping = "spwg"; > fsl,data-width = <24>; > status = "okay"; > + > + port@4 { > + reg = <4>; > + lvds0_out: endpoint { > + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; > + }; > + }; > }; > }; > > @@ -142,3 +149,26 @@ > reg = <0x4a>; > }; > }; > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_lvds_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:27 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:27 UTC (permalink / raw) To: linux-arm-kernel Patch 1/4 is already on linux-next, but what about this one? Ping? On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: > Configures the GE B850v3 LVDS/DP++ bridge on the dts file. > > Cc: Martyn Welch <martyn.welch@collabora.co.uk> > Cc: Martin Donnelly <martin.donnelly@ge.com> > Cc: Javier Martinez Canillas <javier@dowhile0.org> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com> > Cc: Philipp Zabel <p.zabel@pengutronix.de> > Cc: Rob Herring <robh@kernel.org> > Cc: Fabio Estevam <fabio.estevam@nxp.com> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com> > --- > Unchanged from V4 > > Changes from V3: > - 4/4 instead of 5/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to b850v3-lvds-dp-bridge > > arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ > 1 file changed, 30 insertions(+) > > diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts > index 167f744..8db3bf2 100644 > --- a/arch/arm/boot/dts/imx6q-b850v3.dts > +++ b/arch/arm/boot/dts/imx6q-b850v3.dts > @@ -72,6 +72,13 @@ > fsl,data-mapping = "spwg"; > fsl,data-width = <24>; > status = "okay"; > + > + port at 4 { > + reg = <4>; > + lvds0_out: endpoint { > + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; > + }; > + }; > }; > }; > > @@ -142,3 +149,26 @@ > reg = <0x4a>; > }; > }; > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge at 73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_lvds_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-09-26 8:27 ` Peter Senna Tschudin 0 siblings, 0 replies; 172+ messages in thread From: Peter Senna Tschudin @ 2016-09-26 8:27 UTC (permalink / raw) To: Peter Senna Tschudin Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, pawel.moll-5wv7dgnIgG8, treding-DDmLM1+adcrQT0dZR+AlfA, linux-0h96xk9xTtrk1uMJSBkQmQ, heiko-4mtYJXux2i+zQB+pC5nmwQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, daniel.vetter-/w4YWyX8dFk, Fabio Estevam, jslaby-AlSwsSmVLrQ, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, shawnguo-DgEjT+Ai2ygdnm+yROfE0A, linux-I+IVW8TIWO2tmTQ+vhA3Yw, galak-sgV2jX0FEOL9JmXXK+q4OQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, airlied-cv59FeDIM0c, ykk-TNX95d0MmH7DzftRWevZcw, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, davem-fT/PcQaiUtIeIZ0/mPfg9Q, mark.rutland-5wv7dgnIgG8, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, tiwai-IBi9RG/b67k, linux-kernel-u79uwXL29TY76Z2rM5mHXA, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, Rob Herring, javier-0uQlZySMnqxg9hUCZPvPmw Patch 1/4 is already on linux-next, but what about this one? Ping? On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> wrote: > Configures the GE B850v3 LVDS/DP++ bridge on the dts file. > > Cc: Martyn Welch <martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ@public.gmane.org> > Cc: Martin Donnelly <martin.donnelly-JJi787mZWgc@public.gmane.org> > Cc: Javier Martinez Canillas <javier-0uQlZySMnqxg9hUCZPvPmw@public.gmane.org> > Cc: Enric Balletbo i Serra <enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > Cc: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> > Cc: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> > Cc: Fabio Estevam <fabio.estevam-3arQi8VN3Tc@public.gmane.org> > Signed-off-by: Peter Senna Tschudin <peter.senna-ZGY8ohtN/8qB+jHODAdFcQ@public.gmane.org> > --- > Unchanged from V4 > > Changes from V3: > - 4/4 instead of 5/5 > > Unchanged from V2 > > Changes from V1: > - Replaced '_' by '-' in node names or compatible strings > - Added missing @73 to b850v3-lvds-dp-bridge > > arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++ > 1 file changed, 30 insertions(+) > > diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts > index 167f744..8db3bf2 100644 > --- a/arch/arm/boot/dts/imx6q-b850v3.dts > +++ b/arch/arm/boot/dts/imx6q-b850v3.dts > @@ -72,6 +72,13 @@ > fsl,data-mapping = "spwg"; > fsl,data-width = <24>; > status = "okay"; > + > + port@4 { > + reg = <4>; > + lvds0_out: endpoint { > + remote-endpoint = <&b850v3_lvds_dp_bridge_in>; > + }; > + }; > }; > }; > > @@ -142,3 +149,26 @@ > reg = <0x4a>; > }; > }; > + > +&mux2_i2c2 { > + status = "okay"; > + clock-frequency = <100000>; > + > + b850v3-lvds-dp-bridge@73 { > + compatible = "ge,b850v3-lvds-dp"; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reg = <0x73>; > + interrupt-parent = <&gpio2>; > + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; > + > + edid-reg = <0x72>; > + > + port { > + b850v3_lvds_dp_bridge_in: endpoint { > + remote-endpoint = <&lvds0_out>; > + }; > + }; > + }; > +}; > -- > 2.5.5 > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge 2016-09-26 8:27 ` Peter Senna Tschudin (?) @ 2016-09-29 10:39 ` Shawn Guo -1 siblings, 0 replies; 172+ messages in thread From: Shawn Guo @ 2016-09-29 10:39 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Peter Senna Tschudin, linux-arm-kernel, eballetbo, pawel.moll, treding, linux, heiko, thierry.reding, daniel.vetter, Fabio Estevam, jslaby, dri-devel, martyn.welch, linux, galak, peter.senna, airlied, ykk, ijc+devicetree, rmk+kernel, davem, mark.rutland, kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel, akpm, Rob Herring, javier, robh+dt, devicetree, martin.donnelly On Mon, Sep 26, 2016 at 09:27:59AM +0100, Peter Senna Tschudin wrote: > Patch 1/4 is already on linux-next, but what about this one? Ping? Ping me after driver part (patch #3) lands on mainline. Shawn ^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-09-29 10:39 ` Shawn Guo 0 siblings, 0 replies; 172+ messages in thread From: Shawn Guo @ 2016-09-29 10:39 UTC (permalink / raw) To: linux-arm-kernel On Mon, Sep 26, 2016 at 09:27:59AM +0100, Peter Senna Tschudin wrote: > Patch 1/4 is already on linux-next, but what about this one? Ping? Ping me after driver part (patch #3) lands on mainline. Shawn ^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge @ 2016-09-29 10:39 ` Shawn Guo 0 siblings, 0 replies; 172+ messages in thread From: Shawn Guo @ 2016-09-29 10:39 UTC (permalink / raw) To: Peter Senna Tschudin Cc: Peter Senna Tschudin, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, eballetbo-Re5JQEeQqe8AvxtiuMwx3w, pawel.moll-5wv7dgnIgG8, treding-DDmLM1+adcrQT0dZR+AlfA, linux-0h96xk9xTtrk1uMJSBkQmQ, heiko-4mtYJXux2i+zQB+pC5nmwQ, thierry.reding-Re5JQEeQqe8AvxtiuMwx3w, daniel.vetter-/w4YWyX8dFk, Fabio Estevam, jslaby-AlSwsSmVLrQ, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, martyn.welch-ZGY8ohtN/8pPYcu2f3hruQ, linux-I+IVW8TIWO2tmTQ+vhA3Yw, galak-sgV2jX0FEOL9JmXXK+q4OQ, peter.senna-Re5JQEeQqe8AvxtiuMwx3w, airlied-cv59FeDIM0c, ykk-TNX95d0MmH7DzftRWevZcw, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg, rmk+kernel-I+IVW8TIWO2tmTQ+vhA3Yw, davem-fT/PcQaiUtIeIZ0/mPfg9Q, mark.rutland-5wv7dgnIgG8, kernel-bIcnvbaLZ9MEGnE8C9+IrQ, enric.balletbo-ZGY8ohtN/8qB+jHODAdFcQ, mchehab-JPH+aEBZ4P+UEJcrhfAQsw, tiwai-IBi9RG/b67k, linux-kernel-u79uwXL29TY76Z2rM5mHXA, gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r, p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ, akpm-de/tnXTf+JLsfHDXvbKv3WD2FQJk+8+b, Rob Herring On Mon, Sep 26, 2016 at 09:27:59AM +0100, Peter Senna Tschudin wrote: > Patch 1/4 is already on linux-next, but what about this one? Ping? Ping me after driver part (patch #3) lands on mainline. Shawn -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 172+ messages in thread
end of thread, other threads:[~2016-09-29 10:39 UTC | newest] Thread overview: 172+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin 2016-05-30 16:39 ` Peter Senna Tschudin 2016-05-30 16:39 ` [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin 2016-05-30 16:39 ` Peter Senna Tschudin 2016-06-02 13:09 ` Philipp Zabel 2016-06-02 13:09 ` Philipp Zabel 2016-06-02 13:09 ` Philipp Zabel 2016-05-30 16:39 ` [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin 2016-05-30 16:39 ` Peter Senna Tschudin 2016-05-30 16:49 ` Fabio Estevam 2016-05-30 16:49 ` Fabio Estevam 2016-05-30 16:49 ` Fabio Estevam 2016-06-02 12:55 ` Philipp Zabel 2016-06-02 12:55 ` Philipp Zabel 2016-06-02 12:55 ` Philipp Zabel 2016-05-30 16:39 ` [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp Peter Senna Tschudin 2016-05-30 16:39 ` Peter Senna Tschudin 2016-06-02 12:49 ` Philipp Zabel 2016-06-02 12:49 ` Philipp Zabel 2016-06-02 12:49 ` Philipp Zabel 2016-06-02 23:19 ` Peter Senna Tschudin 2016-06-02 23:19 ` Peter Senna Tschudin 2016-06-02 23:19 ` Peter Senna Tschudin 2016-06-02 22:57 ` Rob Herring 2016-06-02 22:57 ` Rob Herring 2016-05-30 16:39 ` [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin 2016-05-30 16:39 ` Peter Senna Tschudin 2016-05-31 7:48 ` Enric Balletbo Serra 2016-05-31 7:48 ` Enric Balletbo Serra 2016-05-31 7:48 ` Enric Balletbo Serra 2016-05-30 16:39 ` [PATCH 5/5] arm/dts/imx6q-b850v3: Use " Peter Senna Tschudin 2016-05-30 16:39 ` Peter Senna Tschudin 2016-05-30 16:54 ` Fabio Estevam 2016-05-30 16:54 ` Fabio Estevam 2016-05-30 16:54 ` Fabio Estevam 2016-06-09 16:25 ` [PATCH V2 0/5] Add driver for " Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-06-09 16:25 ` [PATCH V2 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-06-09 16:25 ` [PATCH V2 2/5] dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-06-09 16:25 ` [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-06-10 17:42 ` Rob Herring 2016-06-10 17:42 ` Rob Herring 2016-06-10 18:54 ` Javier Martinez Canillas 2016-06-10 18:54 ` Javier Martinez Canillas 2016-06-10 18:54 ` Javier Martinez Canillas 2016-06-09 16:25 ` [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-06-10 7:39 ` Enric Balletbo Serra 2016-06-10 7:39 ` Enric Balletbo Serra 2016-06-10 7:39 ` Enric Balletbo Serra 2016-06-10 9:44 ` Peter Senna Tschudin 2016-06-10 9:44 ` Peter Senna Tschudin 2016-06-10 9:44 ` Peter Senna Tschudin 2016-06-10 14:13 ` Daniel Vetter 2016-06-10 14:13 ` Daniel Vetter 2016-06-10 14:13 ` Daniel Vetter 2016-06-22 8:34 ` Archit Taneja 2016-06-22 8:34 ` Archit Taneja 2016-06-09 16:25 ` [PATCH V2 5/5] dts/imx6q-b850v3: Use " Peter Senna Tschudin 2016-06-09 16:25 ` Peter Senna Tschudin 2016-07-31 19:55 ` [PATCH V3 0/5] Add driver for " Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-07-31 19:55 ` [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-08-01 10:21 ` Philipp Zabel 2016-08-01 10:21 ` Philipp Zabel 2016-08-01 10:21 ` Philipp Zabel 2016-08-02 18:46 ` Peter Senna Tschudin 2016-08-02 18:46 ` Peter Senna Tschudin 2016-08-02 18:46 ` Peter Senna Tschudin 2016-07-31 19:55 ` [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-08-01 8:54 ` Lucas Stach 2016-08-01 8:54 ` Lucas Stach 2016-08-01 8:54 ` Lucas Stach 2016-08-01 12:30 ` Peter Senna Tschudin 2016-08-01 12:30 ` Peter Senna Tschudin 2016-08-01 12:30 ` Peter Senna Tschudin 2016-08-02 13:13 ` Daniel Vetter 2016-08-02 13:13 ` Daniel Vetter 2016-08-02 13:13 ` Daniel Vetter 2016-07-31 19:55 ` [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-08-01 16:59 ` Rob Herring 2016-08-01 16:59 ` Rob Herring 2016-08-01 16:59 ` Rob Herring 2016-07-31 19:55 ` [PATCH V3 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-07-31 19:55 ` [PATCH V3 5/5] dts/imx6q-b850v3: Use " Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-07-31 19:55 ` Peter Senna Tschudin 2016-08-04 22:36 ` [PATCH V4 0/4] Add driver for " Peter Senna Tschudin 2016-08-04 22:36 ` Peter Senna Tschudin 2016-08-04 22:36 ` Peter Senna Tschudin 2016-08-04 22:36 ` [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin 2016-08-04 22:36 ` Peter Senna Tschudin 2016-08-16 15:40 ` Martyn Welch 2016-08-16 15:40 ` Martyn Welch 2016-08-16 15:40 ` Martyn Welch 2016-08-04 22:36 ` [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin 2016-08-04 22:36 ` Peter Senna Tschudin 2016-08-05 7:28 ` Enric Balletbo Serra 2016-08-05 7:28 ` Enric Balletbo Serra 2016-08-05 7:28 ` Enric Balletbo Serra 2016-08-16 15:59 ` Martyn Welch 2016-08-16 15:59 ` Martyn Welch 2016-08-16 15:59 ` Martyn Welch 2016-08-04 22:37 ` [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin 2016-08-04 22:37 ` Peter Senna Tschudin 2016-08-05 7:38 ` Enric Balletbo Serra 2016-08-05 7:38 ` Enric Balletbo Serra 2016-08-05 7:38 ` Enric Balletbo Serra 2016-08-04 22:37 ` [PATCH V4 4/4] dts/imx6q-b850v3: Use " Peter Senna Tschudin 2016-08-04 22:37 ` Peter Senna Tschudin 2016-08-09 16:41 ` [PATCH V5 0/4] Add driver for " Peter Senna Tschudin 2016-08-09 16:41 ` Peter Senna Tschudin 2016-08-09 16:41 ` Peter Senna Tschudin 2016-08-09 16:41 ` [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin 2016-08-09 16:41 ` Peter Senna Tschudin 2016-08-11 9:38 ` Philipp Zabel 2016-08-11 9:38 ` Philipp Zabel 2016-08-11 9:38 ` Philipp Zabel 2016-08-09 16:41 ` [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin 2016-08-09 16:41 ` Peter Senna Tschudin 2016-09-26 8:26 ` Peter Senna Tschudin 2016-09-26 8:26 ` Peter Senna Tschudin 2016-09-26 8:26 ` Peter Senna Tschudin 2016-08-09 16:41 ` [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin 2016-08-09 16:41 ` Peter Senna Tschudin 2016-08-16 4:15 ` Archit Taneja 2016-08-16 4:15 ` Archit Taneja 2016-08-16 4:15 ` Archit Taneja 2016-09-26 8:27 ` Peter Senna Tschudin 2016-09-26 8:27 ` Peter Senna Tschudin 2016-09-26 8:27 ` Peter Senna Tschudin 2016-09-26 8:31 ` Archit Taneja 2016-09-26 8:31 ` Archit Taneja 2016-09-26 8:31 ` Archit Taneja 2016-09-26 8:58 ` Peter Senna Tschudin 2016-09-26 8:58 ` Peter Senna Tschudin 2016-09-26 8:58 ` Peter Senna Tschudin 2016-09-26 10:28 ` Archit Taneja 2016-09-26 10:28 ` Archit Taneja 2016-09-26 10:28 ` Archit Taneja 2016-09-26 10:29 ` Archit Taneja 2016-09-26 10:29 ` Archit Taneja 2016-09-26 10:29 ` Archit Taneja 2016-09-26 11:54 ` Peter Senna Tschudin 2016-09-26 11:54 ` Peter Senna Tschudin 2016-09-26 11:54 ` Peter Senna Tschudin 2016-09-26 12:54 ` Archit Taneja 2016-09-26 12:54 ` Archit Taneja 2016-09-26 12:54 ` Archit Taneja 2016-08-09 16:41 ` [PATCH V5 4/4] dts/imx6q-b850v3: Use " Peter Senna Tschudin 2016-08-09 16:41 ` Peter Senna Tschudin 2016-09-26 8:27 ` Peter Senna Tschudin 2016-09-26 8:27 ` Peter Senna Tschudin 2016-09-26 8:27 ` Peter Senna Tschudin 2016-09-29 10:39 ` Shawn Guo 2016-09-29 10:39 ` Shawn Guo 2016-09-29 10:39 ` Shawn Guo
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.