Message ID | ad2d43d590302b67121338cfd4f9349a45942104.1395397665.git.moinejf@free.fr (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi Jean-François, Thank you for the patch. On Friday 21 March 2014 09:17:32 Jean-Francois Moine wrote: > The 'slave encoder' structure of the tda998x driver asks for glue > between the DRM driver and the encoder/connector structures. > > This patch changes the driver to a normal DRM encoder/connector > thanks to the infrastructure for componentised subsystems. I like the idea, but I'm not really happy with the implementation. Let me try to explain why below. > Signed-off-by: Jean-Francois Moine <moinejf@free.fr> > --- > drivers/gpu/drm/i2c/tda998x_drv.c | 323 +++++++++++++++++++---------------- > 1 file changed, 188 insertions(+), 135 deletions(-) > > diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c > b/drivers/gpu/drm/i2c/tda998x_drv.c index fd6751c..1c25e40 100644 > --- a/drivers/gpu/drm/i2c/tda998x_drv.c > +++ b/drivers/gpu/drm/i2c/tda998x_drv.c [snip] > @@ -44,10 +45,14 @@ struct tda998x_priv { > > wait_queue_head_t wq_edid; > volatile int wq_edid_wait; > - struct drm_encoder *encoder; > + struct drm_encoder encoder; > + struct drm_connector connector; > }; [snip] > -static int > -tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id) > +static int tda_bind(struct device *dev, struct device *master, void *data) > { > + struct drm_device *drm = data; This is the part that bothers me. You're making two assumptions here, that the DRM driver will pass a struct drm_device pointer to the bind operation, and that the I2C encoder driver can take control of DRM encoder and connector creation. Although it could become problematic later, the first assumption isn't too much of an issue for now. I'll thus focus on the second one. The component framework isolate the encoder and DRM master drivers as far as component creation and binding is concerned, but doesn't provide a way for the two drivers to communicate together (nor should it). You're solving this by passing a pointer to the DRM device to the encoder bind operation, making the encoder driver create a DRM encoder and connector, and relying on the DRM core to orchestrate CRTCs, encoders and connectors. You thus assume that the encoder hardware should be represented by a DRM encoder object, and that its output is connected to a connector that should be represented by a DRM connector object. While this can work in your use case, that won't always hold true. Hardware encoders can be chained together, while DRM encoders can't. The DRM core has recently received support for bridge objects to overcome that limitation. Depending on the hardware topology, a given hardware encoder should be modeled as a DRM encoder or as a DRM bridge. That decision shouldn't be taken by the encoder driver but by the DRM master driver. The I2C encoder driver thus shouldn't create the DRM encoder and DRM connector itself. I believe the encoder/master communication problem should be solved differently. Instead of passing a pointer to the DRM device to the encoder driver and making the encoder driver control DRM encoder and connector creation, the encoder driver should instead create an object not visible to userspace that can be retrieved by the DRM master driver (possibly through registration with the DRM core, or by going through drvdata in the encoder's struct device). The DRM master could use that object to communicate with the encoder, and would register the DRM encoder and DRM connector itself based on hardware topology. > + struct i2c_client *i2c_client = to_i2c_client(dev); > + struct tda998x_priv *priv = i2c_get_clientdata(i2c_client); > + struct drm_connector *connector = &priv->connector; > + struct drm_encoder *encoder = &priv->encoder; > + int ret; > + > + if (!try_module_get(THIS_MODULE)) { > + dev_err(dev, "cannot get module %s\n", THIS_MODULE->name); > + return -EINVAL; > + } > + > + ret = drm_connector_init(drm, connector, > + &connector_funcs, > + DRM_MODE_CONNECTOR_HDMIA); This is one example of the shortcomings I've explained above. An encoder driver can't always know what connector type it is connected to. If I'm not mistaken possible options here are DVII, DVID, HDMIA and HDMIB. It should be up to the master driver to select the connector type based on its overall view of the hardware, or even to a connector driver that would be bound to a connector DT node (as proposed in https://www.mail-archive.com/devicetree@vger.kernel.org/msg16585.html). > + if (ret < 0) > + return ret; > + drm_connector_helper_add(connector, &connector_helper_funcs); > + > + ret = drm_encoder_init(drm, encoder, > + &encoder_funcs, > + DRM_MODE_ENCODER_TMDS); > + > + encoder->possible_crtcs = 1; // 1 << lcd_num > + > + if (ret < 0) > + goto err; > + drm_encoder_helper_add(encoder, &encoder_helper_funcs); > + > + ret = drm_mode_connector_attach_encoder(connector, encoder); > + if (ret < 0) > + goto err; > + connector->encoder = encoder; > + > + drm_sysfs_connector_add(connector); > + > + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); > + ret = drm_object_property_set_value(&connector->base, > + drm->mode_config.dpms_property, > + DRM_MODE_DPMS_OFF); > + > + if (priv->hdmi->irq) > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + else > + connector->polled = DRM_CONNECTOR_POLL_CONNECT | > + DRM_CONNECTOR_POLL_DISCONNECT; > return 0; > + > +err: > + if (encoder->dev) > + drm_encoder_cleanup(encoder); > + if (connector->dev) > + drm_connector_cleanup(connector); > + return ret; > } > > -static int > -tda998x_remove(struct i2c_client *client) > +static void tda_unbind(struct device *dev, struct device *master, void > *data) { > - return 0; > + module_put(THIS_MODULE); > } > > +static const struct component_ops comp_ops = { > + .bind = tda_bind, > + .unbind = tda_unbind, > +}; > + > static int > -tda998x_encoder_init(struct i2c_client *client, > - struct drm_device *dev, > - struct drm_encoder_slave *encoder_slave) > +tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id) > { > struct tda998x_priv *priv; > struct device_node *np = client->dev.of_node; > @@ -1239,6 +1291,8 @@ tda998x_encoder_init(struct i2c_client *client, > if (!priv) > return -ENOMEM; > > + i2c_set_clientdata(client, priv); > + > priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3); > priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1); > priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5); > @@ -1250,13 +1304,8 @@ tda998x_encoder_init(struct i2c_client *client, > kfree(priv); > return -ENODEV; > } > - > - priv->encoder = &encoder_slave->base; > priv->dpms = DRM_MODE_DPMS_OFF; > > - encoder_slave->slave_priv = priv; > - encoder_slave->slave_funcs = &tda998x_encoder_funcs; > - > /* wake up the device: */ > cec_write(priv, REG_CEC_ENAMODS, > CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI); > @@ -1340,31 +1389,55 @@ tda998x_encoder_init(struct i2c_client *client, > /* enable EDID read irq: */ > reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); > > - if (!np) > - return 0; /* non-DT */ > + if (np) { /* if DT */ > > - /* get the optional video properties */ > - ret = of_property_read_u32(np, "video-ports", &video); > - if (ret == 0) { > - priv->vip_cntrl_0 = video >> 16; > - priv->vip_cntrl_1 = video >> 8; > - priv->vip_cntrl_2 = video; > + /* get the optional video properties */ > + ret = of_property_read_u32(np, "video-ports", &video); > + if (ret == 0) { > + priv->vip_cntrl_0 = video >> 16; > + priv->vip_cntrl_1 = video >> 8; > + priv->vip_cntrl_2 = video; > + } > + } else { > + struct tda998x_encoder_params *params = > + (struct tda998x_encoder_params) > + client->dev.platform_data; > + > + if (params) > + tda998x_encoder_set_config(priv, params); > } > > + /* tda998x video component ready */ > + component_add(&client->dev, &comp_ops); > + > return 0; > > fail: > - /* if encoder_init fails, the encoder slave is never registered, > - * so cleanup here: > - */ > if (priv->cec) > i2c_unregister_device(priv->cec); > kfree(priv); > - encoder_slave->slave_priv = NULL; > - encoder_slave->slave_funcs = NULL; > return -ENXIO; > } > > +static int > +tda998x_remove(struct i2c_client *client) > +{ > + struct tda998x_priv *priv = i2c_get_clientdata(client); > + > + /* disable all IRQs and free the IRQ handler */ > + cec_write(priv, REG_CEC_RXSHPDINTENA, 0); > + reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); > + if (priv->hdmi->irq) > + free_irq(priv->hdmi->irq, priv); > + > + component_del(&client->dev, &comp_ops); > + > + if (priv->cec) > + i2c_unregister_device(priv->cec); > + kfree(priv); > + return 0; > +} > + > #ifdef CONFIG_OF > static const struct of_device_id tda998x_dt_ids[] = { > { .compatible = "nxp,tda9989", }, > @@ -1381,38 +1454,18 @@ static struct i2c_device_id tda998x_ids[] = { > }; > MODULE_DEVICE_TABLE(i2c, tda998x_ids); > > -static struct drm_i2c_encoder_driver tda998x_driver = { > - .i2c_driver = { > - .probe = tda998x_probe, > - .remove = tda998x_remove, > - .driver = { > - .name = "tda998x", > - .of_match_table = of_match_ptr(tda998x_dt_ids), > - }, > - .id_table = tda998x_ids, > +static struct i2c_driver tda998x_driver = { > + .probe = tda998x_probe, > + .remove = tda998x_remove, > + .driver = { > + .name = "tda998x", > + .of_match_table = of_match_ptr(tda998x_dt_ids), > }, > - .encoder_init = tda998x_encoder_init, > + .id_table = tda998x_ids, > };
On Mon, 24 Mar 2014 23:39:01 +0100 Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote: > Hi Jean-François, Hi Laurent, > Thank you for the patch. > > On Friday 21 March 2014 09:17:32 Jean-Francois Moine wrote: > > The 'slave encoder' structure of the tda998x driver asks for glue > > between the DRM driver and the encoder/connector structures. > > > > This patch changes the driver to a normal DRM encoder/connector > > thanks to the infrastructure for componentised subsystems. > > I like the idea, but I'm not really happy with the implementation. Let me try > to explain why below. > > > Signed-off-by: Jean-Francois Moine <moinejf@free.fr> > > --- > > drivers/gpu/drm/i2c/tda998x_drv.c | 323 +++++++++++++++++++---------------- > > 1 file changed, 188 insertions(+), 135 deletions(-) > > > > diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c > > b/drivers/gpu/drm/i2c/tda998x_drv.c index fd6751c..1c25e40 100644 > > --- a/drivers/gpu/drm/i2c/tda998x_drv.c > > +++ b/drivers/gpu/drm/i2c/tda998x_drv.c > > [snip] > > > @@ -44,10 +45,14 @@ struct tda998x_priv { > > > > wait_queue_head_t wq_edid; > > volatile int wq_edid_wait; > > - struct drm_encoder *encoder; > > + struct drm_encoder encoder; > > + struct drm_connector connector; > > }; > > [snip] > > > -static int > > -tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id) > > +static int tda_bind(struct device *dev, struct device *master, void *data) > > { > > + struct drm_device *drm = data; > > This is the part that bothers me. You're making two assumptions here, that the > DRM driver will pass a struct drm_device pointer to the bind operation, and > that the I2C encoder driver can take control of DRM encoder and connector > creation. Although it could become problematic later, the first assumption > isn't too much of an issue for now. I'll thus focus on the second one. > > The component framework isolate the encoder and DRM master drivers as far as > component creation and binding is concerned, but doesn't provide a way for the > two drivers to communicate together (nor should it). You're solving this by > passing a pointer to the DRM device to the encoder bind operation, making the > encoder driver create a DRM encoder and connector, and relying on the DRM core > to orchestrate CRTCs, encoders and connectors. You thus assume that the > encoder hardware should be represented by a DRM encoder object, and that its > output is connected to a connector that should be represented by a DRM > connector object. While this can work in your use case, that won't always hold > true. Hardware encoders can be chained together, while DRM encoders can't. The > DRM core has recently received support for bridge objects to overcome that > limitation. Depending on the hardware topology, a given hardware encoder > should be modeled as a DRM encoder or as a DRM bridge. That decision shouldn't > be taken by the encoder driver but by the DRM master driver. The I2C encoder > driver thus shouldn't create the DRM encoder and DRM connector itself. > > I believe the encoder/master communication problem should be solved > differently. Instead of passing a pointer to the DRM device to the encoder > driver and making the encoder driver control DRM encoder and connector > creation, the encoder driver should instead create an object not visible to > userspace that can be retrieved by the DRM master driver (possibly through > registration with the DRM core, or by going through drvdata in the encoder's > struct device). The DRM master could use that object to communicate with the > encoder, and would register the DRM encoder and DRM connector itself based on > hardware topology. > > > + struct i2c_client *i2c_client = to_i2c_client(dev); > > + struct tda998x_priv *priv = i2c_get_clientdata(i2c_client); > > + struct drm_connector *connector = &priv->connector; > > + struct drm_encoder *encoder = &priv->encoder; > > + int ret; > > + > > + if (!try_module_get(THIS_MODULE)) { > > + dev_err(dev, "cannot get module %s\n", THIS_MODULE->name); > > + return -EINVAL; > > + } > > + > > + ret = drm_connector_init(drm, connector, > > + &connector_funcs, > > + DRM_MODE_CONNECTOR_HDMIA); > > This is one example of the shortcomings I've explained above. An encoder > driver can't always know what connector type it is connected to. If I'm not > mistaken possible options here are DVII, DVID, HDMIA and HDMIB. It should be > up to the master driver to select the connector type based on its overall view > of the hardware, or even to a connector driver that would be bound to a > connector DT node (as proposed in https://www.mail-archive.com/devicetree@vger.kernel.org/msg16585.html). [snip] The tda998x, as a HDMI transmitter, has to deal with both video and audio. Whereas the hardware connection schemes are the same in both worlds, the way they are translated to computer objects are very different: - video DRM card -> CRTCs -> encoders -> (bridges) -> connectors - audio ALSA card -> CPUs -> (CODECs) -> CODECs and it would be nice to have a common layout. Actually, the tda998x is a slave encoder, that is, it plays the roles of both encoder and connector. In the 2 DRM drivers (armada and tilcdc) which use it, yes, the encoders and connectors are created by the main DRM drivers, but, there is no notion of bridge, and, also, the encoder is DRM_MODE_ENCODER_TMDS and the connector is DRM_MODE_CONNECTOR_HDMIA. Then, nothing is changed in the global system working. About the connector, yes, I let its type as hard-coded, but this could be changed by configuration in the platform data or in the DT. Anyway, there is nothing as such in the proposed patch 'Add DT binding documentation for HDMI Connector' I also dislike this patch because it adds a device which is of no use. I had a same remark from Mark Brown about a tda998x CODEC proposal of mine: hdmi_codec: hdmi-codec { compatible = "nxp,tda998x-codec"; audio-ports = <0x03>, <0x04>; }; So, the next tda998x CODEC will be directly included in the tda998x driver, the audio output being the HDMI connector. Here is the DT definition I have for the Cubox: &i2c0 { hdmi: hdmi-encoder { compatible = "nxp,tda9989"; reg = <0x70>; interrupt-parent = <&gpio0>; interrupts = <27 IRQ_TYPE_EDGE_FALLING>; pinctrl-0 = <&pmx_camera>; pinctrl-names = "default"; audio-ports = <0x03>, <0x04>; /* 2 audio input ports */ audio-port-names = "i2s", "spdif"; #sound-dai-cells = <1>; port { /* 1 video input port */ hdmi_0: endpoint@0 { remote-endpoint = <&lcd0_0>; }; }; }; }; Back to the DRM device pointer given to the tda998x driver, as the tda998x is an encoder/connector, it has no bridge function, so, there is no need to add a complex API for information exchange between both drivers. Anyway, there is a big lack in my proposal: the tda998x encoder is hard-coded to the first CRTC. This could be solved by a scan of the DT and of the encoder list by the DRM driver, but I think that the actual definitions as proposed by media/video-interfaces.txt are not easy to use. Do you think that a description as done for ALSA could work? A sound card creation is done by a global sound configuration and a description of the links between the card elements. Here is the tda998x part of the Cubox audio card ('audio1' is the audio device): sound { compatible = "simple-audio-card"; simple-audio-card,name = "Cubox Audio"; simple-audio-card,dai-link@0 { /* I2S - HDMI */ format = "i2s"; cpu { sound-dai = <&audio1 0>; /* I2S output */ }; codec { sound-dai = <&hdmi 0>; /* I2S input */ }; }; simple-audio-card,dai-link@1 { /* S/PDIF - HDMI */ cpu { sound-dai = <&audio1 1>; /* S/PDIF output */ }; codec { sound-dai = <&hdmi 1>; /* S/PDIF input */ }; }; }; Using the same elements, here is what could be the video card of the Armada 510 with a panel, the tda998x and the display controller: video { compatible = "simple-video-card"; simple-video-card,dvi-link { crtc { dvi = <&lcd0>; }; encoder { dvi = <&panel>; connector-type = 7; /* LVDS */ }; }; simple-video-card,dvi-link { crtc { dvi = <&lcd0>; }; encoder { dvi = <&hdmi>; connector-type = 11; /* HDMI-A */ }; }; }; lcd0: lcd-controller@820000 { compatible = "marvell,armada-510-lcd"; ... hardware definitions ... }; hdmi : hdmi-encoder { .. same as above, but without the video input port .. }; panel: panel { .. panel parameters .. }; Then, the generic 'simple-video-card' has all elements to create the DRM device.
Hi Jean-François, On Tuesday 25 March 2014 16:55:48 Jean-Francois Moine wrote: > On Mon, 24 Mar 2014 23:39:01 +0100 Laurent Pinchart wrote: > > On Friday 21 March 2014 09:17:32 Jean-Francois Moine wrote: > > > The 'slave encoder' structure of the tda998x driver asks for glue > > > between the DRM driver and the encoder/connector structures. > > > > > > This patch changes the driver to a normal DRM encoder/connector > > > thanks to the infrastructure for componentised subsystems. > > > > I like the idea, but I'm not really happy with the implementation. Let me > > try to explain why below. > > > > > Signed-off-by: Jean-Francois Moine <moinejf@free.fr> > > > --- > > > > > > drivers/gpu/drm/i2c/tda998x_drv.c | 323 ++++++++++++++++--------------- > > > 1 file changed, 188 insertions(+), 135 deletions(-) > > > > > > diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c > > > b/drivers/gpu/drm/i2c/tda998x_drv.c index fd6751c..1c25e40 100644 > > > --- a/drivers/gpu/drm/i2c/tda998x_drv.c > > > +++ b/drivers/gpu/drm/i2c/tda998x_drv.c > > > > [snip] > > > > > @@ -44,10 +45,14 @@ struct tda998x_priv { > > > wait_queue_head_t wq_edid; > > > volatile int wq_edid_wait; > > > > > > - struct drm_encoder *encoder; > > > + struct drm_encoder encoder; > > > + struct drm_connector connector; > > > > > > }; > > > > [snip] > > > > > -static int > > > -tda998x_probe(struct i2c_client *client, const struct i2c_device_id > > > *id) > > > +static int tda_bind(struct device *dev, struct device *master, void > > > *data) > > > { > > > + struct drm_device *drm = data; > > > > This is the part that bothers me. You're making two assumptions here, that > > the DRM driver will pass a struct drm_device pointer to the bind > > operation, and that the I2C encoder driver can take control of DRM > > encoder and connector creation. Although it could become problematic > > later, the first assumption isn't too much of an issue for now. I'll thus > > focus on the second one. > > > > The component framework isolate the encoder and DRM master drivers as far > > as component creation and binding is concerned, but doesn't provide a way > > for the two drivers to communicate together (nor should it). You're > > solving this by passing a pointer to the DRM device to the encoder bind > > operation, making the encoder driver create a DRM encoder and connector, > > and relying on the DRM core to orchestrate CRTCs, encoders and > > connectors. You thus assume that the encoder hardware should be > > represented by a DRM encoder object, and that its output is connected to > > a connector that should be represented by a DRM connector object. While > > this can work in your use case, that won't always hold true. Hardware > > encoders can be chained together, while DRM encoders can't. The DRM core > > has recently received support for bridge objects to overcome that > > limitation. Depending on the hardware topology, a given hardware encoder > > should be modeled as a DRM encoder or as a DRM bridge. That decision > > shouldn't be taken by the encoder driver but by the DRM master driver. > > The I2C encoder driver thus shouldn't create the DRM encoder and DRM > > connector itself. > > > > I believe the encoder/master communication problem should be solved > > differently. Instead of passing a pointer to the DRM device to the encoder > > driver and making the encoder driver control DRM encoder and connector > > creation, the encoder driver should instead create an object not visible > > to userspace that can be retrieved by the DRM master driver (possibly > > through registration with the DRM core, or by going through drvdata in the > > encoder's struct device). The DRM master could use that object to > > communicate with the encoder, and would register the DRM encoder and DRM > > connector itself based on hardware topology. > > > > > + struct i2c_client *i2c_client = to_i2c_client(dev); > > > + struct tda998x_priv *priv = i2c_get_clientdata(i2c_client); > > > + struct drm_connector *connector = &priv->connector; > > > + struct drm_encoder *encoder = &priv->encoder; > > > + int ret; > > > + > > > + if (!try_module_get(THIS_MODULE)) { > > > + dev_err(dev, "cannot get module %s\n", THIS_MODULE->name); > > > + return -EINVAL; > > > + } > > > + > > > + ret = drm_connector_init(drm, connector, > > > + &connector_funcs, > > > + DRM_MODE_CONNECTOR_HDMIA); > > > > This is one example of the shortcomings I've explained above. An encoder > > driver can't always know what connector type it is connected to. If I'm > > not mistaken possible options here are DVII, DVID, HDMIA and HDMIB. It > > should be up to the master driver to select the connector type based on > > its overall view of the hardware, or even to a connector driver that would > > be bound to a connector DT node (as proposed in > > https://www.mail-archive.com/devicetree@vger.kernel.org/msg16585.html). > [snip] > > The tda998x, as a HDMI transmitter, has to deal with both video and > audio. > > Whereas the hardware connection schemes are the same in both worlds, > the way they are translated to computer objects are very different: > > - video > DRM card -> CRTCs -> encoders -> (bridges) -> connectors > > - audio > ALSA card -> CPUs -> (CODECs) -> CODECs > > and it would be nice to have a common layout. > > Actually, the tda998x is a slave encoder, that is, it plays the roles of > both encoder and connector. In the 2 DRM drivers (armada and tilcdc) which > use it, yes, the encoders and connectors are created by the main DRM > drivers, but, there is no notion of bridge, and, also, the encoder is > DRM_MODE_ENCODER_TMDS and the connector is DRM_MODE_CONNECTOR_HDMIA. Then, > nothing is changed in the global system working. > > About the connector, yes, I let its type as hard-coded, but this could be > changed by configuration in the platform data or in the DT. Anyway, there is > nothing as such in the proposed patch > > 'Add DT binding documentation for HDMI Connector' > > I also dislike this patch because it adds a device which is of no use. I had > a same remark from Mark Brown about a tda998x CODEC proposal of mine: > > hdmi_codec: hdmi-codec { > compatible = "nxp,tda998x-codec"; > audio-ports = <0x03>, <0x04>; > }; > > So, the next tda998x CODEC will be directly included in the tda998x driver, > the audio output being the HDMI connector. Here is the DT definition I have > for the Cubox: > > &i2c0 { > hdmi: hdmi-encoder { > compatible = "nxp,tda9989"; > reg = <0x70>; > interrupt-parent = <&gpio0>; > interrupts = <27 IRQ_TYPE_EDGE_FALLING>; > pinctrl-0 = <&pmx_camera>; > pinctrl-names = "default"; > > audio-ports = <0x03>, <0x04>; /* 2 audio input ports */ > audio-port-names = "i2s", "spdif"; > #sound-dai-cells = <1>; > > port { /* 1 video input port */ > hdmi_0: endpoint@0 { > remote-endpoint = <&lcd0_0>; > }; > }; > }; > }; > > Back to the DRM device pointer given to the tda998x driver, as the tda998x > is an encoder/connector, it has no bridge function, so, there is no need to > add a complex API for information exchange between both drivers. > > Anyway, there is a big lack in my proposal: the tda998x encoder is hard- >coded to the first CRTC. This could be solved by a scan of the DT and of the > encoder list by the DRM driver, but I think that the actual definitions as > proposed by media/video-interfaces.txt are not easy to use. > > Do you think that a description as done for ALSA could work? > > A sound card creation is done by a global sound configuration and a > description of the links between the card elements. Here is the tda998x > part of the Cubox audio card ('audio1' is the audio device): > > sound { > compatible = "simple-audio-card"; > simple-audio-card,name = "Cubox Audio"; > > simple-audio-card,dai-link@0 { /* I2S - HDMI */ > format = "i2s"; > cpu { > sound-dai = <&audio1 0>; /* I2S output */ > }; > codec { > sound-dai = <&hdmi 0>; /* I2S input */ > }; > }; > > simple-audio-card,dai-link@1 { /* S/PDIF - HDMI */ > cpu { > sound-dai = <&audio1 1>; /* S/PDIF output */ > }; > codec { > sound-dai = <&hdmi 1>; /* S/PDIF input */ > }; > }; > }; > > Using the same elements, here is what could be the video card of the > Armada 510 with a panel, the tda998x and the display controller: > > video { > compatible = "simple-video-card"; > > simple-video-card,dvi-link { > crtc { > dvi = <&lcd0>; > }; > encoder { > dvi = <&panel>; > connector-type = 7; /* LVDS */ > }; > }; > simple-video-card,dvi-link { > crtc { > dvi = <&lcd0>; > }; > encoder { > dvi = <&hdmi>; > connector-type = 11; /* HDMI-A */ > }; > }; > }; > > lcd0: lcd-controller@820000 { > compatible = "marvell,armada-510-lcd"; > ... hardware definitions ... > }; > > hdmi : hdmi-encoder { > .. same as above, but without the video input port .. > }; > > panel: panel { > .. panel parameters .. > }; > > Then, the generic 'simple-video-card' has all elements to create the DRM > device. That could work in your case, but I don't really like that. We need to describe the hardware topology, that might be the only point we all agree on. There are various hardware topologies we need to support with different levels of complexity, and several ways to describe them, also with a wide range of complexities and features. The more complex the hardware topology, the more complex its description needs to be, and the more complex the code that will parse the description and handle the hardware will be. I don't think there's any doubt here. A given device can be integrated in a wide variety of hardware with different complexities. A driver can't thus always assume that the whole composite display device will match a given class of topologies. This is especially true for encoders and connectors, they can be hooked up directly at the output of a very simple display controller, or can be part of a very complex graph. Encoder and connector drivers can't assume much about how they will be integrated. I want to avoid a situation where using an HDMI encoder already supported in mainline with a different SoC will result in having to rewrite the HDMI encoder driver. The story is a bit different for display controllers. While the hardware topology outside (and sometimes inside as well) of the SoC can vary, a display controller often approximately implies a given level of complexity. A cheap SoC with a simple display controller will likely not be used to drive a 4k display requiring 4 encoders running in parallel for instance. While I also want to avoid having to make significant changes to a display controller driver when using the SoC on a different board, I believe the requirement can be slightly relaxed here, and the display controller driver(s) can assume more about the hardware topology than the encoder drivers. I've asked myself whether we needed a single, one-size-fits-them-all DT representation of the hardware topology. The view of the world from an external encoder point of view must not depend on the SoC it is hooked up to (this would prevent using a single encoder driver with multiple SoCs), which calls for at least some form of standardization. The topology representation on the display controller side may vary from display controller to display controller, but I believe this would just result in code duplication and having to solve the same problem in multiple drivers. For those reasons I believe that the OF graph proposal to represent the display hardware topology would be a good choice. The bindings are flexible enough to represent both simple and complex hardware. Now, I don't want to force all display device drivers to implement complex code when they only need to support simple hardware and simple hardware topologies. Not only would that be rightfully rejected, I would be among the ones nack'ing that approach. My opinion is that this calls for the creation of helpers to handle common cases. Several (possibly many) display systems only need (or want) to support linear pipelines at their output(s), made of a single encoder and a single connector. There's no point in duplicating DT parsing or encoder/connector instantiation code in several drivers in that case where helpers could be reused. Several sets of helpers could support different kinds of topologies, with the driver author selecting a set of helpers depending on the expected hardware topology complexity. We also need to decide on who (as in which driver) will be responsible for binding all devices together. As DRM exposes a single device to userspace, there needs to be a single driver that will perform front line handling of the userspace API and delegate calls to the other drivers involved. I believe it would be logical for that driver to also be in charge of bindings the components together, as it will be the driver that delegate calls to the components. For a similar reason I also believe that that driver should also be the one in control of creating DRM encoders and DRM connectors. The component drivers having only a narrow view of the device they handle, they can't perform that task in a generic way and would thus get tied to specific master drivers because of the assumptions they would make. Whether the master driver is the CRTC driver or a separate driver that binds standalone CRTCs, connectors and encoders doesn't in my opinion change my above conclusions. Some SoCs could use a simple-video-card driver like the one you've proposed, and others could implement a display controller driver that would perform the same tasks for more complex pipelines in addition to controlling the display controller's CRTC(s). The simple-video-card driver would perform that same tasks as the helpers I've previously talked about, so the two solutions are pretty similar, and I don't see much added value in the general case in having a simple-video-card driver compared to using helpers in the display controller driver. The point that matters to me is that encoders DT bindings and drivers must be identical regardless of whether the master driver is the display controller driver or a driver for a logical composite device. For all those reasons we should use the OF graph DT bindings for the simple-video-card driver should we decide to implement it, and we should create DRM encoders and connectors in the master driver, not in the encoder drivers.
Hi Laurent, On Wed, 26 Mar 2014 18:33:09 +0100 Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote: > That could work in your case, but I don't really like that. > > We need to describe the hardware topology, that might be the only point we all > agree on. There are various hardware topologies we need to support with > different levels of complexity, and several ways to describe them, also with a > wide range of complexities and features. > > The more complex the hardware topology, the more complex its description needs > to be, and the more complex the code that will parse the description and > handle the hardware will be. I don't think there's any doubt here. But also, the simpler is the topology and the simpler should be its description. In your approach, the connections between endpoints are described in the components themselves, which makes hard for the DT maintainers to have a global understanding of the video subsystem. Having the topology described in the master device is clearer, even if it is complex. > A given device can be integrated in a wide variety of hardware with different > complexities. A driver can't thus always assume that the whole composite > display device will match a given class of topologies. This is especially true > for encoders and connectors, they can be hooked up directly at the output of a > very simple display controller, or can be part of a very complex graph. > Encoder and connector drivers can't assume much about how they will be > integrated. I want to avoid a situation where using an HDMI encoder already > supported in mainline with a different SoC will result in having to rewrite > the HDMI encoder driver. The tda998x chips are simple enough for being used in many boards: one video input and one serial digital output. I don't see in which circumstance the driver should be rewritten. > The story is a bit different for display controllers. While the hardware > topology outside (and sometimes inside as well) of the SoC can vary, a display > controller often approximately implies a given level of complexity. A cheap > SoC with a simple display controller will likely not be used to drive a 4k > display requiring 4 encoders running in parallel for instance. While I also > want to avoid having to make significant changes to a display controller > driver when using the SoC on a different board, I believe the requirement can > be slightly relaxed here, and the display controller driver(s) can assume more > about the hardware topology than the encoder drivers. I don't think that the display controllers or the encoders have to know about the topology. They have well-knwon specific functions and the way they are interconnected should not impact these functions. If that would be the case, they should offer a particular configuration interface to the master driver. > I've asked myself whether we needed a single, one-size-fits-them-all DT > representation of the hardware topology. The view of the world from an > external encoder point of view must not depend on the SoC it is hooked up to > (this would prevent using a single encoder driver with multiple SoCs), which > calls for at least some form of standardization. The topology representation > on the display controller side may vary from display controller to display > controller, but I believe this would just result in code duplication and > having to solve the same problem in multiple drivers. For those reasons I > believe that the OF graph proposal to represent the display hardware topology > would be a good choice. The bindings are flexible enough to represent both > simple and complex hardware. Your OF graph proposal is rather complex, and it does not even indicate which way the data flows. > Now, I don't want to force all display device drivers to implement complex > code when they only need to support simple hardware and simple hardware > topologies. Not only would that be rightfully rejected, I would be among the > ones nack'ing that approach. My opinion is that this calls for the creation of > helpers to handle common cases. Several (possibly many) display systems only > need (or want) to support linear pipelines at their output(s), made of a > single encoder and a single connector. There's no point in duplicating DT > parsing or encoder/connector instantiation code in several drivers in that > case where helpers could be reused. Several sets of helpers could support > different kinds of topologies, with the driver author selecting a set of > helpers depending on the expected hardware topology complexity. I don't like this 'helper' notion. See below. > We also need to decide on who (as in which driver) will be responsible for > binding all devices together. As DRM exposes a single device to userspace, > there needs to be a single driver that will perform front line handling of the > userspace API and delegate calls to the other drivers involved. I believe it > would be logical for that driver to also be in charge of bindings the > components together, as it will be the driver that delegate calls to the > components. For a similar reason I also believe that that driver should also > be the one in control of creating DRM encoders and DRM connectors. The > component drivers having only a narrow view of the device they handle, they > can't perform that task in a generic way and would thus get tied to specific > master drivers because of the assumptions they would make. I don't see why the encoders and connectors should be created by some external entity. They are declared in the DT or created by the board init, so, they exist at system startup time. > Whether the master driver is the CRTC driver or a separate driver that binds > standalone CRTCs, connectors and encoders doesn't in my opinion change my > above conclusions. Some SoCs could use a simple-video-card driver like the one > you've proposed, and others could implement a display controller driver that > would perform the same tasks for more complex pipelines in addition to > controlling the display controller's CRTC(s). The simple-video-card driver > would perform that same tasks as the helpers I've previously talked about, so > the two solutions are pretty similar, and I don't see much added value in the > general case in having a simple-video-card driver compared to using helpers in > the display controller driver. In fact, I wonder why there is not only one DRM driver. The global logic is always the same, and, actually, it is duplicated in each specific driver. I had this approach in the gspca driver: one main driver and many specific subdrivers. Once, I even had the idea to convert your UVC driver into a gspca subdriver, but, at this time, I had too much work with the other webcams. Nevertheless, it would have been easy: all the required interfaces were (are still) present in the main gspca driver. In the same order of idea, there is only one ALSA SoC driver, and you cannot say that the audio topology is less complex than the video one. When thinking about the unique DRM driver, there would no helper anymore: the driver would offer to each component the functions they need for working correctly. > The point that matters to me is that encoders DT bindings and drivers must be > identical regardless of whether the master driver is the display controller > driver or a driver for a logical composite device. For all those reasons we > should use the OF graph DT bindings for the simple-video-card driver should we > decide to implement it, and we should create DRM encoders and connectors in > the master driver, not in the encoder drivers. Sorry, but I think that a DRM encoder is the hardware encoder. What else could it be? At initialization time, the master driver has only to manage the relation between the video components. It has not to create entities which already exist. So my preferred scheme is: - at starting time, each video component initializes its software and hardware, and then, plugs itself into the DRM driver. - from the central video topology, the DRM device waits for all components to be plugged and then it links the components together, creating the user's view of the system. If you create the encoders and connectors in the DRM master, the hardware encoder and connector devices are just like zombies. They must put something in the system to say they are here and they must also have a callback function for them to know to which video subsystem they belong. On the contrary, if the DRM encoders and connectors are created by the hardware devices, they are immediately alive and operational.
Hi Jean-François, Sorry for the late reply. On Thursday 27 March 2014 12:34:49 Jean-Francois Moine wrote: > On Wed, 26 Mar 2014 18:33:09 +0100 Laurent Pinchart wrote: > > That could work in your case, but I don't really like that. > > > > We need to describe the hardware topology, that might be the only point we > > all agree on. There are various hardware topologies we need to support > > with different levels of complexity, and several ways to describe them, > > also with a wide range of complexities and features. > > > > The more complex the hardware topology, the more complex its description > > needs to be, and the more complex the code that will parse the > > description and handle the hardware will be. I don't think there's any > > doubt here. > > But also, the simpler is the topology and the simpler should be its > description. I wouldn't put it so strongly. I believe we can slightly relax our requirements regarding DT bindings complexity as long as drivers remain simple. There's of course no reason to use more complex bindings just for the sake of it. > In your approach, the connections between endpoints are described in the > components themselves, which makes hard for the DT maintainers to have a > global understanding of the video subsystem. > > Having the topology described in the master device is clearer, even if it is > complex. First of all, let's clarify what a "master device" is. In the "simple-video- card" example you've proposed the master device is a logical device (with a DT node that has no corresponding hardware device). The second approach I can think of is to make the IP core that implements the CRTC (which I usually call display controller) be the master device. Let's note that the second case makes both the link and "global description" DT binding styles possible. My concern with the "global description" bindings style is that the approach only applies to simple hardware and can't be generalized. Now, I'm not too concerned about using that approach for simple hardware and a link-based approach for more complex hardware. The "slave" components, however, need to use a single DT bindings style, regardless of the DT bindings used by the master device. That's why I believe we should try to standardize one DT bindings style for master devices, even if it results in a slightly more complex DT description than strictly needed in some cases. > > A given device can be integrated in a wide variety of hardware with > > different complexities. A driver can't thus always assume that the whole > > composite display device will match a given class of topologies. This is > > especially true for encoders and connectors, they can be hooked up > > directly at the output of a very simple display controller, or can be > > part of a very complex graph. Encoder and connector drivers can't assume > > much about how they will be integrated. I want to avoid a situation where > > using an HDMI encoder already supported in mainline with a different SoC > > will result in having to rewrite the HDMI encoder driver. > > The tda998x chips are simple enough for being used in many boards: one video > input and one serial digital output. I don't see in which circumstance the > driver should be rewritten. It shouldn't, that's the whole point ;-) I wasn't talking about the tda998x only, but about encoder drivers in general. I have a display controller in a Renesas SoC that has two LVDS encoders that output LVDS signals out of the SoC. One LVDS port is connected to an LVDS panel (a connector from a DRM point of view), the second one to an LVDS receiver that outputs parallel RGB data to an HDMI encoder. The LVDS encoder can't thus assume any particular downstream topology and its driver thus can't create DRM encoder and connector instances. The same could be true for an HDMI encoder in theory, although an HDMI encoder connected on the same board directly to an HDMI decoder that outputs RGB data to a panel is probably a case that will be rarely seen in practice. In the general case an encoder driver can't assume any specific upstream or downstream topology. As long as DRM can't expose the real hardware topology to userspace, someone needs to decide on how to map the hardware topology to the DRM topology exposed to userspace. Encoder drivers can't take that role and thus shouldn't create DRM encoder and connector instances themselves. > > The story is a bit different for display controllers. While the hardware > > topology outside (and sometimes inside as well) of the SoC can vary, a > > display controller often approximately implies a given level of > > complexity. A cheap SoC with a simple display controller will likely not > > be used to drive a 4k display requiring 4 encoders running in parallel > > for instance. While I also want to avoid having to make significant > > changes to a display controller driver when using the SoC on a different > > board, I believe the requirement can be slightly relaxed here, and the > > display controller driver(s) can assume more about the hardware topology > > than the encoder drivers. > > I don't think that the display controllers or the encoders have to know > about the topology. They have well-knwon specific functions and the way they > are interconnected should not impact these functions. If that would be the > case, they should offer a particular configuration interface to the master > driver. As explained above, part of the problem comes from the fact that we need to expose a logical topology to userspace that doesn't map 1:1 to the hardware topology. We can discuss whether or not this should be changed, but I believe that's out of scope. As we'll need to map logical encoders and connectors to real hardware in the foreseeable future we need a solution to that problem, and I believe the display controller should be in control of the mapping (possibly using helpers). > > I've asked myself whether we needed a single, one-size-fits-them-all DT > > representation of the hardware topology. The view of the world from an > > external encoder point of view must not depend on the SoC it is hooked up > > to (this would prevent using a single encoder driver with multiple SoCs), > > which calls for at least some form of standardization. The topology > > representation on the display controller side may vary from display > > controller to display controller, but I believe this would just result in > > code duplication and having to solve the same problem in multiple > > drivers. For those reasons I believe that the OF graph proposal to > > represent the display hardware topology would be a good choice. The > > bindings are flexible enough to represent both simple and complex > > hardware. > > Your OF graph proposal is rather complex, I agree it's slightly more complex than your simple-video-card proposal for simple hardware, but I don't see it as overly complex, as long as we can provide helper functions that moves the parsing complexity out of drivers (at least drivers for simple hardware). > and it does not even indicate which way the data flows. Please note that the OF graph DT bindings are not set in stone. If we need to indicate the data flow direction (and I assume we will need to at some point) that should just be discussed and implemented. > > Now, I don't want to force all display device drivers to implement complex > > code when they only need to support simple hardware and simple hardware > > topologies. Not only would that be rightfully rejected, I would be among > > the ones nack'ing that approach. My opinion is that this calls for the > > creation of helpers to handle common cases. Several (possibly many) > > display systems only need (or want) to support linear pipelines at their > > output(s), made of a single encoder and a single connector. There's no > > point in duplicating DT parsing or encoder/connector instantiation code > > in several drivers in that case where helpers could be reused. Several > > sets of helpers could support different kinds of topologies, with the > > driver author selecting a set of helpers depending on the expected > > hardware topology complexity. > > I don't like this 'helper' notion. See below. > > > We also need to decide on who (as in which driver) will be responsible for > > binding all devices together. As DRM exposes a single device to userspace, > > there needs to be a single driver that will perform front line handling of > > the userspace API and delegate calls to the other drivers involved. I > > believe it would be logical for that driver to also be in charge of > > bindings the components together, as it will be the driver that delegate > > calls to the components. For a similar reason I also believe that that > > driver should also be the one in control of creating DRM encoders and DRM > > connectors. The component drivers having only a narrow view of the device > > they handle, they can't perform that task in a generic way and would thus > > get tied to specific master drivers because of the assumptions they would > > make. > > I don't see why the encoders and connectors should be created by some > external entity. They are declared in the DT or created by the board init, > so, they exist at system startup time. As explained above, the hardware encoders and connectors are declared in DT, but they don't map 1:1 to the logical encoders and connectors exposed by DRM to userspace. Obviously, in some cases (and probably a majority of cases today), the mapping will be 1:1, and the DRM encoders and connectors could be created by the encoder drivers, but that won't scale to more complex hardware. The transition to DT is a disruption that I'd like to take as an opportunity to model things correctly. We *could* transition to DT by creating encoders and connectors in encoder drivers, but a second disruption will then be needed in the near future to transition to a model that can handle most hardware. The drm_bridge API was a first step in such a direction, and I think we should take it into account. > > Whether the master driver is the CRTC driver or a separate driver that > > binds standalone CRTCs, connectors and encoders doesn't in my opinion > > change my above conclusions. Some SoCs could use a simple-video-card > > driver like the one you've proposed, and others could implement a display > > controller driver that would perform the same tasks for more complex > > pipelines in addition to controlling the display controller's CRTC(s). > > The simple-video-card driver would perform that same tasks as the helpers > > I've previously talked about, so the two solutions are pretty similar, > > and I don't see much added value in the general case in having a > > simple-video-card driver compared to using helpers in the display > > controller driver. > > In fact, I wonder why there is not only one DRM driver. The global logic is > always the same, and, actually, it is duplicated in each specific driver. That's quite a good question, and I agree that several DRM drivers, especially drivers for embedded systems, are pretty similar in architecture. The CRTC helpers are a pretty good example of an attempt to share common code between drivers. The Intel DRM driver used them, developers then realized that it made their life more difficult, and decided to implement the DRM API directly instead of using the helpers. The devil is in the details. Architectures can seem similar, but subtle difference can make a common implementation not only complex and difficult to maintain (as changing it will impact all drivers), but also useless when it tries to cater for the needs of everybody. An abstraction for something that can't be abstracted is just pointless. I'm all for sharing code. This should, in my opinion, be done by providing optional helpers that drivers can use. That's how the CRTC helpers work, that's how the V4L2 control framework and videobuf2 library work, and that allows drivers to provide their own implementation in the rare cases when the generic helpers are not good enough. I think simple helpers that work in 90% of the cases are much better than really complex helpers that would achieve a 95% coverage only anyway. Given that several embedded DRM drivers duplicate code it might be that a different set of CRTC helpers could be better for that class of hardware. If you want to experiment with you I'd be glad to discuss requirements and architectures. > I had this approach in the gspca driver: one main driver and many specific > subdrivers. Once, I even had the idea to convert your UVC driver into a > gspca subdriver, but, at this time, I had too much work with the other > webcams. Nevertheless, it would have been easy: all the required interfaces > were (are still) present in the main gspca driver. If you look into the details of the uvcvideo driver you'll find that it wouldn't be that easy. UVC is a special case in that its architecture is pretty different from the other webcam drivers. For instance the driver doesn't use the V4L2 control framework, as its needs regarding controls are quite special. I've discussed this with Hans Verkuil and we've concluded (at least for the time being) that implementing the features uvcvideo requires in the control framework would make the framework way too complex, just because of a single driver. This is just a counterexample of course. It doesn't mean that code shouldn't be shared, quite the contrary. > In the same order of idea, there is only one ALSA SoC driver, and you cannot > say that the audio topology is less complex than the video one. I can't really comment on that, I don't have enough knowledge of ALSA. > When thinking about the unique DRM driver, there would no helper anymore: > the driver would offer to each component the functions they need for working > correctly. That's where things get hairy. There will be one driver that won't fit in the model. There will then be two options: filling the framework with exceptions and corner cases for just one user, create a spaghetti mess in common code, or allowing drivers to opt-out. I'm a strong believer in the second option. For the sake of completeness I want to mention the third option, which is to make the framework so thin that in becomes a wrapper that mostly translates function calls 1:1, which is pretty useless. We're getting out of scope here though. I'd be happy to continue discussing the single DRM driver with you, but let's do so in a separate mail thread then. > > The point that matters to me is that encoders DT bindings and drivers must > > be identical regardless of whether the master driver is the display > > controller driver or a driver for a logical composite device. For all > > those reasons we should use the OF graph DT bindings for the > > simple-video-card driver should we decide to implement it, and we should > > create DRM encoders and connectors in the master driver, not in the > > encoder drivers. > > Sorry, but I think that a DRM encoder is the hardware encoder. What else > could it be? As explained above, a DRM encoder is a logical entity that usually corresponds to a hardware encoder, but that can also correspond to several encoders chained (sometimes using the drm_bridge abstraction, but not always). > At initialization time, the master driver has only to manage the relation > between the video components. It has not to create entities which already > exist. So my preferred scheme is: > > - at starting time, each video component initializes its software and > hardware, and then, plugs itself into the DRM driver. > > - from the central video topology, the DRM device waits for all > components to be plugged and then it links the components together, > creating the user's view of the system. So far I agree with you, but I believe this calls for an in-kernel view of the components possibly different than the drm_encoder and drm_connector objects. > If you create the encoders and connectors in the DRM master, the hardware > encoder and connector devices are just like zombies. They must put something > in the system to say they are here and they must also have a callback > function for them to know to which video subsystem they belong. > > On the contrary, if the DRM encoders and connectors are created by the > hardware devices, they are immediately alive and operational.
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index fd6751c..1c25e40 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -20,11 +20,12 @@ #include <linux/hdmi.h> #include <linux/module.h> #include <linux/irq.h> +#include <linux/of_platform.h> +#include <linux/component.h> #include <sound/asoundef.h> #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> -#include <drm/drm_encoder_slave.h> #include <drm/drm_edid.h> #include <drm/i2c/tda998x.h> @@ -44,10 +45,14 @@ struct tda998x_priv { wait_queue_head_t wq_edid; volatile int wq_edid_wait; - struct drm_encoder *encoder; + struct drm_encoder encoder; + struct drm_connector connector; }; -#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv) +#define connector_priv(e) \ + container_of(connector, struct tda998x_priv, connector) +#define encoder_priv(e) \ + container_of(encoder, struct tda998x_priv, encoder) /* The TDA9988 series of devices use a paged register scheme.. to simplify * things we encode the page # in upper bits of the register #. To read/ @@ -559,9 +564,8 @@ static irqreturn_t tda998x_irq_thread(int irq, void *data) if ((flag2 & INT_FLAGS_2_EDID_BLK_RD) && priv->wq_edid_wait) { priv->wq_edid_wait = 0; wake_up(&priv->wq_edid); - } else if (cec != 0) { /* HPD change */ - if (priv->encoder && priv->encoder->dev) - drm_helper_hpd_irq_event(priv->encoder->dev); + } else if (cec != 0 && priv->encoder.dev) { /* HPD change */ + drm_helper_hpd_irq_event(priv->encoder.dev); } return IRQ_HANDLED; } @@ -731,9 +735,8 @@ tda998x_configure_audio(struct tda998x_priv *priv, /* DRM encoder functions */ static void -tda998x_encoder_set_config(struct drm_encoder *encoder, void *params) +tda998x_encoder_set_config(struct tda998x_priv *priv, void *params) { - struct tda998x_priv *priv = to_tda998x_priv(encoder); struct tda998x_encoder_params *p = params; priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(p->swap_a) | @@ -755,7 +758,7 @@ tda998x_encoder_set_config(struct drm_encoder *encoder, void *params) static void tda998x_encoder_dpms(struct drm_encoder *encoder, int mode) { - struct tda998x_priv *priv = to_tda998x_priv(encoder); + struct tda998x_priv *priv = encoder_priv(encoder); /* we only care about on or off: */ if (mode != DRM_MODE_DPMS_ON) @@ -786,18 +789,6 @@ tda998x_encoder_dpms(struct drm_encoder *encoder, int mode) priv->dpms = mode; } -static void -tda998x_encoder_save(struct drm_encoder *encoder) -{ - DBG(""); -} - -static void -tda998x_encoder_restore(struct drm_encoder *encoder) -{ - DBG(""); -} - static bool tda998x_encoder_mode_fixup(struct drm_encoder *encoder, const struct drm_display_mode *mode, @@ -806,11 +797,14 @@ tda998x_encoder_mode_fixup(struct drm_encoder *encoder, return true; } -static int -tda998x_encoder_mode_valid(struct drm_encoder *encoder, - struct drm_display_mode *mode) +static void tda998x_encoder_prepare(struct drm_encoder *encoder) { - return MODE_OK; + tda998x_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void tda998x_encoder_commit(struct drm_encoder *encoder) +{ + tda998x_encoder_dpms(encoder, DRM_MODE_DPMS_ON); } static void @@ -818,7 +812,7 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { - struct tda998x_priv *priv = to_tda998x_priv(encoder); + struct tda998x_priv *priv = encoder_priv(encoder); uint16_t ref_pix, ref_line, n_pix, n_line; uint16_t hs_pix_s, hs_pix_e; uint16_t vs1_pix_s, vs1_pix_e, vs1_line_s, vs1_line_e; @@ -1006,10 +1000,9 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder, } static enum drm_connector_status -tda998x_encoder_detect(struct drm_encoder *encoder, - struct drm_connector *connector) +tda998x_connector_detect(struct drm_connector *connector, bool force) { - struct tda998x_priv *priv = to_tda998x_priv(encoder); + struct tda998x_priv *priv = connector_priv(connector); uint8_t val = cec_read(priv, REG_CEC_RXSHPDLEV); return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected : @@ -1017,9 +1010,8 @@ tda998x_encoder_detect(struct drm_encoder *encoder, } static int -read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk) +read_edid_block(struct tda998x_priv *priv, uint8_t *buf, int blk) { - struct tda998x_priv *priv = to_tda998x_priv(encoder); uint8_t offset, segptr; int ret, i; @@ -1073,10 +1065,8 @@ read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk) return 0; } -static uint8_t * -do_get_edid(struct drm_encoder *encoder) +static uint8_t *do_get_edid(struct tda998x_priv *priv) { - struct tda998x_priv *priv = to_tda998x_priv(encoder); int j, valid_extensions = 0; uint8_t *block, *new; bool print_bad_edid = drm_debug & DRM_UT_KMS; @@ -1088,7 +1078,7 @@ do_get_edid(struct drm_encoder *encoder) reg_clear(priv, REG_TX4, TX4_PD_RAM); /* base block fetch */ - if (read_edid_block(encoder, block, 0)) + if (read_edid_block(priv, block, 0)) goto fail; if (!drm_edid_block_valid(block, 0, print_bad_edid)) @@ -1105,7 +1095,7 @@ do_get_edid(struct drm_encoder *encoder) for (j = 1; j <= block[0x7e]; j++) { uint8_t *ext_block = block + (valid_extensions + 1) * EDID_LENGTH; - if (read_edid_block(encoder, ext_block, j)) + if (read_edid_block(priv, ext_block, j)) goto fail; if (!drm_edid_block_valid(ext_block, j, print_bad_edid)) @@ -1137,12 +1127,28 @@ fail: return NULL; } +/* DRM connector functions */ + +static struct drm_encoder * +tda998x_connector_best_encoder(struct drm_connector *connector) +{ + struct tda998x_priv *priv = connector_priv(connector); + + return &priv->encoder; +} + +static int +tda998x_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + static int -tda998x_encoder_get_modes(struct drm_encoder *encoder, - struct drm_connector *connector) +tda998x_connector_get_modes(struct drm_connector *connector) { - struct tda998x_priv *priv = to_tda998x_priv(encoder); - struct edid *edid = (struct edid *)do_get_edid(encoder); + struct tda998x_priv *priv = connector_priv(connector); + struct edid *edid = (struct edid *) do_get_edid(priv); int n = 0; if (edid) { @@ -1156,22 +1162,7 @@ tda998x_encoder_get_modes(struct drm_encoder *encoder, } static int -tda998x_encoder_create_resources(struct drm_encoder *encoder, - struct drm_connector *connector) -{ - struct tda998x_priv *priv = to_tda998x_priv(encoder); - - if (priv->hdmi->irq) - connector->polled = DRM_CONNECTOR_POLL_HPD; - else - connector->polled = DRM_CONNECTOR_POLL_CONNECT | - DRM_CONNECTOR_POLL_DISCONNECT; - return 0; -} - -static int -tda998x_encoder_set_property(struct drm_encoder *encoder, - struct drm_connector *connector, +tda998x_connector_set_property(struct drm_connector *connector, struct drm_property *property, uint64_t val) { @@ -1179,56 +1170,117 @@ tda998x_encoder_set_property(struct drm_encoder *encoder, return 0; } -static void -tda998x_encoder_destroy(struct drm_encoder *encoder) +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { + .dpms = tda998x_encoder_dpms, + .mode_fixup = tda998x_encoder_mode_fixup, + .prepare = tda998x_encoder_prepare, + .commit = tda998x_encoder_commit, + .mode_set = tda998x_encoder_mode_set, +}; + +static void tda998x_encoder_destroy(struct drm_encoder *encoder) { - struct tda998x_priv *priv = to_tda998x_priv(encoder); - drm_i2c_encoder_destroy(encoder); + drm_encoder_cleanup(encoder); +} - /* disable all IRQs and free the IRQ handler */ - cec_write(priv, REG_CEC_RXSHPDINTENA, 0); - reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); - if (priv->hdmi->irq) - free_irq(priv->hdmi->irq, priv); +static const struct drm_encoder_funcs encoder_funcs = { + .destroy = tda998x_encoder_destroy, +}; - if (priv->cec) - i2c_unregister_device(priv->cec); - kfree(priv); +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = tda998x_connector_get_modes, + .mode_valid = tda998x_connector_mode_valid, + .best_encoder = tda998x_connector_best_encoder, +}; + +static void tda998x_connector_destroy(struct drm_connector *connector) +{ + if (!connector->dev) + return; + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); } -static struct drm_encoder_slave_funcs tda998x_encoder_funcs = { - .set_config = tda998x_encoder_set_config, - .destroy = tda998x_encoder_destroy, - .dpms = tda998x_encoder_dpms, - .save = tda998x_encoder_save, - .restore = tda998x_encoder_restore, - .mode_fixup = tda998x_encoder_mode_fixup, - .mode_valid = tda998x_encoder_mode_valid, - .mode_set = tda998x_encoder_mode_set, - .detect = tda998x_encoder_detect, - .get_modes = tda998x_encoder_get_modes, - .create_resources = tda998x_encoder_create_resources, - .set_property = tda998x_encoder_set_property, +static const struct drm_connector_funcs connector_funcs = { + .detect = tda998x_connector_detect, + .set_property = tda998x_connector_set_property, + .fill_modes = drm_helper_probe_single_connector_modes, + .dpms = drm_helper_connector_dpms, + .destroy = tda998x_connector_destroy, }; /* I2C driver functions */ -static int -tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id) +static int tda_bind(struct device *dev, struct device *master, void *data) { + struct drm_device *drm = data; + struct i2c_client *i2c_client = to_i2c_client(dev); + struct tda998x_priv *priv = i2c_get_clientdata(i2c_client); + struct drm_connector *connector = &priv->connector; + struct drm_encoder *encoder = &priv->encoder; + int ret; + + if (!try_module_get(THIS_MODULE)) { + dev_err(dev, "cannot get module %s\n", THIS_MODULE->name); + return -EINVAL; + } + + ret = drm_connector_init(drm, connector, + &connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret < 0) + return ret; + drm_connector_helper_add(connector, &connector_helper_funcs); + + ret = drm_encoder_init(drm, encoder, + &encoder_funcs, + DRM_MODE_ENCODER_TMDS); + + encoder->possible_crtcs = 1; // 1 << lcd_num + + if (ret < 0) + goto err; + drm_encoder_helper_add(encoder, &encoder_helper_funcs); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret < 0) + goto err; + connector->encoder = encoder; + + drm_sysfs_connector_add(connector); + + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); + ret = drm_object_property_set_value(&connector->base, + drm->mode_config.dpms_property, + DRM_MODE_DPMS_OFF); + + if (priv->hdmi->irq) + connector->polled = DRM_CONNECTOR_POLL_HPD; + else + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; return 0; + +err: + if (encoder->dev) + drm_encoder_cleanup(encoder); + if (connector->dev) + drm_connector_cleanup(connector); + return ret; } -static int -tda998x_remove(struct i2c_client *client) +static void tda_unbind(struct device *dev, struct device *master, void *data) { - return 0; + module_put(THIS_MODULE); } +static const struct component_ops comp_ops = { + .bind = tda_bind, + .unbind = tda_unbind, +}; + static int -tda998x_encoder_init(struct i2c_client *client, - struct drm_device *dev, - struct drm_encoder_slave *encoder_slave) +tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct tda998x_priv *priv; struct device_node *np = client->dev.of_node; @@ -1239,6 +1291,8 @@ tda998x_encoder_init(struct i2c_client *client, if (!priv) return -ENOMEM; + i2c_set_clientdata(client, priv); + priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3); priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1); priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5); @@ -1250,13 +1304,8 @@ tda998x_encoder_init(struct i2c_client *client, kfree(priv); return -ENODEV; } - - priv->encoder = &encoder_slave->base; priv->dpms = DRM_MODE_DPMS_OFF; - encoder_slave->slave_priv = priv; - encoder_slave->slave_funcs = &tda998x_encoder_funcs; - /* wake up the device: */ cec_write(priv, REG_CEC_ENAMODS, CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI); @@ -1340,31 +1389,55 @@ tda998x_encoder_init(struct i2c_client *client, /* enable EDID read irq: */ reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); - if (!np) - return 0; /* non-DT */ + if (np) { /* if DT */ - /* get the optional video properties */ - ret = of_property_read_u32(np, "video-ports", &video); - if (ret == 0) { - priv->vip_cntrl_0 = video >> 16; - priv->vip_cntrl_1 = video >> 8; - priv->vip_cntrl_2 = video; + /* get the optional video properties */ + ret = of_property_read_u32(np, "video-ports", &video); + if (ret == 0) { + priv->vip_cntrl_0 = video >> 16; + priv->vip_cntrl_1 = video >> 8; + priv->vip_cntrl_2 = video; + } + } else { + struct tda998x_encoder_params *params = + (struct tda998x_encoder_params) + client->dev.platform_data; + + if (params) + tda998x_encoder_set_config(priv, params); } + /* tda998x video component ready */ + component_add(&client->dev, &comp_ops); + return 0; fail: - /* if encoder_init fails, the encoder slave is never registered, - * so cleanup here: - */ if (priv->cec) i2c_unregister_device(priv->cec); kfree(priv); - encoder_slave->slave_priv = NULL; - encoder_slave->slave_funcs = NULL; return -ENXIO; } +static int +tda998x_remove(struct i2c_client *client) +{ + struct tda998x_priv *priv = i2c_get_clientdata(client); + + /* disable all IRQs and free the IRQ handler */ + cec_write(priv, REG_CEC_RXSHPDINTENA, 0); + reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + if (priv->hdmi->irq) + free_irq(priv->hdmi->irq, priv); + + component_del(&client->dev, &comp_ops); + + if (priv->cec) + i2c_unregister_device(priv->cec); + kfree(priv); + return 0; +} + #ifdef CONFIG_OF static const struct of_device_id tda998x_dt_ids[] = { { .compatible = "nxp,tda9989", }, @@ -1381,38 +1454,18 @@ static struct i2c_device_id tda998x_ids[] = { }; MODULE_DEVICE_TABLE(i2c, tda998x_ids); -static struct drm_i2c_encoder_driver tda998x_driver = { - .i2c_driver = { - .probe = tda998x_probe, - .remove = tda998x_remove, - .driver = { - .name = "tda998x", - .of_match_table = of_match_ptr(tda998x_dt_ids), - }, - .id_table = tda998x_ids, +static struct i2c_driver tda998x_driver = { + .probe = tda998x_probe, + .remove = tda998x_remove, + .driver = { + .name = "tda998x", + .of_match_table = of_match_ptr(tda998x_dt_ids), }, - .encoder_init = tda998x_encoder_init, + .id_table = tda998x_ids, }; -/* Module initialization */ - -static int __init -tda998x_init(void) -{ - DBG(""); - return drm_i2c_encoder_register(THIS_MODULE, &tda998x_driver); -} - -static void __exit -tda998x_exit(void) -{ - DBG(""); - drm_i2c_encoder_unregister(&tda998x_driver); -} +module_i2c_driver(tda998x_driver); MODULE_AUTHOR("Rob Clark <robdclark@gmail.com"); MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder"); MODULE_LICENSE("GPL"); - -module_init(tda998x_init); -module_exit(tda998x_exit);
The 'slave encoder' structure of the tda998x driver asks for glue between the DRM driver and the encoder/connector structures. This patch changes the driver to a normal DRM encoder/connector thanks to the infrastructure for componentised subsystems. Signed-off-by: Jean-Francois Moine <moinejf@free.fr> --- drivers/gpu/drm/i2c/tda998x_drv.c | 323 ++++++++++++++++++++++---------------- 1 file changed, 188 insertions(+), 135 deletions(-)