Re: [PATCH v3 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver

From: Jose Abreu
Date: Mon Jun 19 2017 - 05:37:30 EST


Hi Sylwester,


Thanks again for the feedback!


On 18-06-2017 19:04, Sylwester Nawrocki wrote:
> On 06/16/2017 06:38 PM, Jose Abreu wrote:
>> This is an initial submission for the Synopsys Designware HDMI RX
>> Controller Driver. This driver interacts with a phy driver so that
>> a communication between them is created and a video pipeline is
>> configured.
>>
>> The controller + phy pipeline can then be integrated into a fully
>> featured system that can be able to receive video up to 4k@60Hz
>> with deep color 48bit RGB, depending on the platform. Although,
>> this initial version does not yet handle deep color modes.
>> Signed-off-by: Jose Abreu <joabreu@xxxxxxxxxxxx>
>> +static int dw_hdmi_phy_init(struct dw_hdmi_dev *dw_dev)
>> +{
>> + struct dw_phy_pdata *phy = &dw_dev->phy_config;
>> + struct platform_device_info pdevinfo;
>> +
>> + memset(&pdevinfo, 0, sizeof(pdevinfo));
>> +
>> + phy->funcs = &dw_hdmi_phy_funcs;
>> + phy->funcs_arg = dw_dev;
>> +
>> + pdevinfo.parent = dw_dev->dev;
>> + pdevinfo.id = PLATFORM_DEVID_NONE;
>> + pdevinfo.name = dw_dev->phy_drv;
>> + pdevinfo.data = phy;
>> + pdevinfo.size_data = sizeof(*phy);
>> + pdevinfo.dma_mask = DMA_BIT_MASK(32);
>> +
>> + request_module(pdevinfo.name);
>> +
>> + dw_dev->phy_pdev = platform_device_register_full(&pdevinfo);
>> + if (IS_ERR(dw_dev->phy_pdev)) {
>> + dev_err(dw_dev->dev, "failed to register phy device\n");
>> + return PTR_ERR(dw_dev->phy_pdev);
>> + }
>> +
>> + if (!dw_dev->phy_pdev->dev.driver) {
>> + dev_err(dw_dev->dev, "failed to initialize phy driver\n");
>> + goto err;
>> + }
> I think this is not safe because there is nothing preventing unbinding
> or unloading the driver at this point.
>
>> + if (!try_module_get(dw_dev->phy_pdev->dev.driver->owner)) {
> So dw_dev->phy_pdev->dev.driver may be already NULL here.

How can I make sure it wont be NULL? Because I've seen other
media drivers do this and I don't think they do any kind of
locking, but they do this mainly for I2C subdevs.

>
>> + dev_err(dw_dev->dev, "failed to get phy module\n");
>> + goto err;
>> + }
>> +
>> + dw_dev->phy_sd = dev_get_drvdata(&dw_dev->phy_pdev->dev);
>> + if (!dw_dev->phy_sd) {
>> + dev_err(dw_dev->dev, "failed to get phy subdev\n");
>> + goto err_put;
>> + }
>> +
>> + if (v4l2_device_register_subdev(&dw_dev->v4l2_dev, dw_dev->phy_sd)) {
>> + dev_err(dw_dev->dev, "failed to register phy subdev\n");
>> + goto err_put;
>> + }
> I'd suggest usign v4l2-async API, so we use a common pattern for sub-device
> registration. And with recent change [1] you could handle this PHY subdev
> in a standard way. That might be more complicated than it is now but should
> make any future platform integration easier.
>
> [1] https://urldefense.proofpoint.com/v2/url?u=https-3A__patchwork.linuxtv.org_patch_41834&d=DwICaQ&c=DPL6_X_6JkXFx7AXWqB0tg&r=WHDsc6kcWAl4i96Vm5hJ_19IJiuxx_p_Rzo2g-uHDKw&m=uHTyp6WsEj6vN19Zc09HqSNUhCx62OI8u-tAgi-EVts&s=WjyPjIwN1uGvPoV7ZlcmzOgdptakzluHywuKRA8ZG8M&e=

So I will instantiate phy driver and then wait for phy driver to
register into v4l2 core?

>
>> + module_put(dw_dev->phy_pdev->dev.driver->owner);
>> + return 0;
>> +
>> +err_put:
>> + module_put(dw_dev->phy_pdev->dev.driver->owner);
>> +err:
>> + platform_device_unregister(dw_dev->phy_pdev);
>> + return -EINVAL;
>> +}
>> +
>> +static void dw_hdmi_phy_exit(struct dw_hdmi_dev *dw_dev)
>> +{
>> + if (!IS_ERR(dw_dev->phy_pdev))
>> + platform_device_unregister(dw_dev->phy_pdev);
>> +}
>> +static int dw_hdmi_config_hdcp(struct dw_hdmi_dev *dw_dev)
>> +{
>> + for (i = 0; i < DW_HDMI_HDCP14_KEYS_SIZE; i += 2) {
>> + for (j = 0; j < key_write_tries; j++) {
>> + if (is_hdcp14_key_write_allowed(dw_dev))
>> + break;
>> + mdelay(10);
> usleep_range()? I've seen more (busy waiting) mdelay() calls in this
> patch series.

I will change.

>
>
>> +static int __dw_hdmi_power_on(struct dw_hdmi_dev *dw_dev, u32 input)
>> +{
>> + unsigned long flags;
>> + int ret;
>> +
>> + ret = dw_hdmi_config(dw_dev, input);
>> +
>> + spin_lock_irqsave(&dw_dev->lock, flags);
>> + dw_dev->pending_config = false;
>> + spin_unlock_irqrestore(&dw_dev->lock, flags);
>> +
>> + return ret;
>> +}
>> +
>> +struct dw_hdmi_work_data {
>> + struct dw_hdmi_dev *dw_dev;
>> + struct work_struct work;
>> + u32 input;
>> +};
>> +
>> +static void dw_hdmi_work_handler(struct work_struct *work)
>> +{
>> + struct dw_hdmi_work_data *data = container_of(work,
>> + struct dw_hdmi_work_data, work);
>> +
>> + __dw_hdmi_power_on(data->dw_dev, data->input);
>> + devm_kfree(data->dw_dev->dev, data);
>> +}
>> +
>> +static int dw_hdmi_power_on(struct dw_hdmi_dev *dw_dev, u32 input)
>> +{
>> + struct dw_hdmi_work_data *data;
>> + unsigned long flags;
>> +
>> + data = devm_kzalloc(dw_dev->dev, sizeof(*data), GFP_KERNEL);
> Why use devm_{kzalloc, kfree} when dw_hdmi_power_on() is not only called
> in the device's probe() callback, but in other places, including interrupt
> handler? devm_* API is normally used when life time of a resource is more
> or less equal to life time of struct device or its matched driver. Were
> there any specific reasons to not just use kzalloc()/kfree() ?

No specific reason, I just thought it would be safer because if I
cancel a work before it started then memory will remain
allocated. But I will change to kzalloc().

>
>> + if (!data)
>> + return -ENOMEM;
>> +
>> + INIT_WORK(&data->work, dw_hdmi_work_handler);
>> + data->dw_dev = dw_dev;
>> + data->input = input;
>> +
>> + spin_lock_irqsave(&dw_dev->lock, flags);
>> + if (dw_dev->pending_config) {
>> + devm_kfree(dw_dev->dev, data);
>> + spin_unlock_irqrestore(&dw_dev->lock, flags);
>> + return 0;
>> + }
>> +
>> + queue_work(dw_dev->wq, &data->work);
>> + dw_dev->pending_config = true;
>> + spin_unlock_irqrestore(&dw_dev->lock, flags);
>> + return 0;
>> +}
>> +static irqreturn_t dw_hdmi_irq_handler(int irq, void *dev_data)
>> +{
>> + struct dw_hdmi_dev *dw_dev = dev_data;
>> + u32 hdmi_ists = dw_hdmi_get_int_val(dw_dev, HDMI_ISTS, HDMI_IEN);
>> + u32 md_ists = dw_hdmi_get_int_val(dw_dev, HDMI_MD_ISTS, HDMI_MD_IEN);
>> +
>> + dw_hdmi_clear_ints(dw_dev);
>> +
>> + if ((hdmi_ists & HDMI_ISTS_CLK_CHANGE) ||
>> + (hdmi_ists & HDMI_ISTS_PLL_LCK_CHG) || md_ists) {
>> + dw_hdmi_power_off(dw_dev);
>> + if (has_signal(dw_dev, dw_dev->configured_input))
>> + dw_hdmi_power_on(dw_dev, dw_dev->configured_input);
>> + }
>> + return IRQ_HANDLED;
>> +}
>> +static int dw_hdmi_registered(struct v4l2_subdev *sd)
>> +{
>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> + int ret;
>> +
>> + ret = cec_register_adapter(dw_dev->cec_adap, dw_dev->dev);
>> + if (ret) {
>> + dev_err(dw_dev->dev, "failed to register CEC adapter\n");
>> + cec_delete_adapter(dw_dev->cec_adap);
>> + return ret;
>> + }
>> +
>> + cec_register_cec_notifier(dw_dev->cec_adap, dw_dev->cec_notifier);
>> + dw_dev->registered = true;
>> + return ret;
>> +}
>> +
>> +static void dw_hdmi_unregistered(struct v4l2_subdev *sd)
>> +{
>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> +
>> + cec_unregister_adapter(dw_dev->cec_adap);
>> + cec_notifier_put(dw_dev->cec_notifier);
>> +}
>> +
>> +static const struct v4l2_subdev_core_ops dw_hdmi_sd_core_ops = {
>> + .log_status = dw_hdmi_log_status,
>> + .subscribe_event = dw_hdmi_subscribe_event,
>> +};
>> +
>> +static const struct v4l2_subdev_video_ops dw_hdmi_sd_video_ops = {
>> + .s_routing = dw_hdmi_s_routing,
>> + .g_input_status = dw_hdmi_g_input_status,
>> + .g_parm = dw_hdmi_g_parm,
>> + .g_dv_timings = dw_hdmi_g_dv_timings,
>> + .query_dv_timings = dw_hdmi_query_dv_timings,
>> +};
>> +
>> +static const struct v4l2_subdev_pad_ops dw_hdmi_sd_pad_ops = {
>> + .enum_mbus_code = dw_hdmi_enum_mbus_code,
>> + .get_fmt = dw_hdmi_get_fmt,
>> + .set_fmt = dw_hdmi_set_fmt,
>> + .dv_timings_cap = dw_hdmi_dv_timings_cap,
>> + .enum_dv_timings = dw_hdmi_enum_dv_timings,
>> +};
>> +
>> +static const struct v4l2_subdev_ops dw_hdmi_sd_ops = {
>> + .core = &dw_hdmi_sd_core_ops,
>> + .video = &dw_hdmi_sd_video_ops,
>> + .pad = &dw_hdmi_sd_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops dw_hdmi_internal_ops = {
>> + .registered = dw_hdmi_registered,
>> + .unregistered = dw_hdmi_unregistered,
>> +};
>> +
>> +static int dw_hdmi_parse_dt(struct dw_hdmi_dev *dw_dev)
>> +{
>> + struct device_node *notifier, *np = dw_dev->of_node;
>> + struct dw_phy_pdata *phy = &dw_dev->phy_config;
>> +
>> + if (!np) {
>> + dev_err(dw_dev->dev, "missing DT node\n");
>> + return -EINVAL;
>> + }
>> +
>> + /* PHY properties parsing */
>> + of_property_read_u8(np, "snps,hdmi-phy-jtag-addr",
>> + &dw_dev->phy_jtag_addr);
>> + if (!dw_dev->phy_jtag_addr) {
>> + dev_err(dw_dev->dev, "missing hdmi-phy-jtag-addr in DT\n");
>> + return -EINVAL;
>> + }
>> +
>> + of_property_read_u32(np, "snps,hdmi-phy-version", &phy->version);
>> + if (!phy->version) {
>> + dev_err(dw_dev->dev, "missing hdmi-phy-version in DT\n");
>> + return -EINVAL;
>> + }
>> +
>> + of_property_read_u32(np, "snps,hdmi-phy-cfg-clk", &phy->cfg_clk);
>> + if (!phy->cfg_clk) {
>> + dev_err(dw_dev->dev, "missing hdmi-phy-cfg-clk in DT\n");
>> + return -EINVAL;
>> + }
> With changes as proposed in comments to patch "4/4 dt-bindings: ..."
> you could use the common clk API for retrieving the clock rate, e.g.
> devm_clk_get(), clk_get_rate().
>
> When the HDMI RX IP block gets integrated within some SoC I'd expect
> the system clock controller to be already using the common clk DT
> bindings. Unless for some reason the platform doesn't support CCF.

Yes, I will change.

>
>
>> + if (of_property_read_string_index(np, "snps,hdmi-phy-driver", 0,
>> + &dw_dev->phy_drv) < 0) {
>> + dev_err(dw_dev->dev, "missing hdmi-phy-driver in DT\n");
> I don't think we can put Linux driver names in DT like this, it seems rather
> a serious abuse. With proposed changes to the DT binding you could reference
> the PHY device by DT phandle or child node.
>
>> + return -EINVAL;
>> + }
>> +
>> + /* Controller properties parsing */
>> + of_property_read_u32(np, "snps,hdmi-ctl-cfg-clk", &dw_dev->cfg_clk);
>> + if (!dw_dev->cfg_clk) {
>> + dev_err(dw_dev->dev, "missing hdmi-ctl-cfg-clk in DT\n");
>> + return -EINVAL;
>> + }
>> +
>> +#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
>> + /* Notifier device parsing */
>> + notifier = of_parse_phandle(np, "edid-phandle", 0);
>> + if (!notifier) {
>> + dev_err(dw_dev->dev, "missing edid-phandle in DT\n");
>> + return -EINVAL;
>> + }
>> +
>> + dw_dev->notifier_pdev = of_find_device_by_node(np);
> Shouldn't this be:
> dw_dev->notifier_pdev = of_find_device_by_node(notifier);
> ?
>
> The caller of dw_hdmi_parse_dt() already knows about the device
> associated with np.

Yeah, its a typo, it works by luck because in my setup np ==
notifier.

>
>> + if (!dw_dev->notifier_pdev)
>> + return -EPROBE_DEFER;
>> +#endif
>> +
>> + return 0;
>> +}
>> +
>> +static int dw_hdmi_rx_probe(struct platform_device *pdev)
>> +{
>> + /* Deferred work */
>> + dw_dev->wq = create_workqueue(DW_HDMI_RX_DRVNAME);
> Have you considered using create_singlethread_workqueue() ? create_workqueue()
> will spawn one thread per CPU.

Ok, will change.

>
>> + if (!dw_dev->wq)
>> + return -ENOMEM;
>> +
>> + /* Registers mapping */
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + if (!res) {
>> + ret = -ENXIO;
>> + goto err_wq;
>> + }
> You can drop res testing here, devm_ioremap_resource() verifies internally
> if res is valid and returns proper error code.

Ok.

>
>> + dw_dev->regs = devm_ioremap_resource(dev, res);
>> + if (IS_ERR(dw_dev->regs)) {
>> + ret = PTR_ERR(dw_dev->regs);
>> + goto err_wq;
>> + }
>
>> + /* V4L2 initialization */
>> + sd = &dw_dev->sd;
>> + v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
>> + strlcpy(sd->name, DW_HDMI_RX_DRVNAME, sizeof(sd->name));
> sd->name should be unique, you could, for instance, do something like
>
> strlcpy(sd->name, dev_name(&pdev->dev), sizeof(sd->name));

Ok.

>
>> + sd->internal_ops = &dw_hdmi_internal_ops;
>> + sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
>> +}
>> +
>> +static int dw_hdmi_rx_remove(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct v4l2_subdev *sd = dev_get_drvdata(dev);
>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> +
>> + dev_dbg(dev, "%s\n", __func__);
>> +
>> + dw_hdmi_disable_ints(dw_dev);
>> + dw_hdmi_disable_hpd(dw_dev);
>> + dw_hdmi_disable_scdc(dw_dev);
>> + dw_hdmi_power_off(dw_dev);
>> + dw_hdmi_phy_s_power(dw_dev, false);
>> + flush_workqueue(dw_dev->wq);
>> + destroy_workqueue(dw_dev->wq);
>> + v4l2_device_unregister(&dw_dev->v4l2_dev);
>> + dw_hdmi_phy_exit(dw_dev);> + dev_info(dev, "driver removed\n");
>> + return 0;
>> +}
>> +
>> +static struct platform_driver dw_hdmi_rx_driver = {
>> + .probe = dw_hdmi_rx_probe,
>> + .remove = dw_hdmi_rx_remove,
> I think we need also .of_match_table here.
>
>> + .driver = {
>> + .name = DW_HDMI_RX_DRVNAME,
>> + }
>> +};
>> +module_platform_driver(dw_hdmi_rx_driver);
>> +#endif /* __DW_HDMI_RX_H__ */
>> diff --git a/include/media/dwc/dw-hdmi-rx-pdata.h b/include/media/dwc/dw-hdmi-rx-pdata.h
>> new file mode 100644
>> index 0000000..ff8554d
>> --- /dev/null
>> +++ b/include/media/dwc/dw-hdmi-rx-pdata.h
>> @@ -0,0 +1,63 @@
>> +#ifndef __DW_HDMI_RX_PDATA_H__
>> +#define __DW_HDMI_RX_PDATA_H__
>> +
>> +#define DW_HDMI_RX_DRVNAME "dw-hdmi-rx"
>> +
>> +/* Notify events */
>> +#define DW_HDMI_NOTIFY_IS_OFF 1
>> +#define DW_HDMI_NOTIFY_INPUT_CHANGED 2
>> +#define DW_HDMI_NOTIFY_AUDIO_CHANGED 3
>> +#define DW_HDMI_NOTIFY_IS_STABLE 4
>> +
>> +/* HDCP 1.4 */
>> +#define DW_HDMI_HDCP14_BKSV_SIZE 2
>> +#define DW_HDMI_HDCP14_KEYS_SIZE (2 * 40)
>> +
>> +struct dw_hdmi_hdcp14_key {
>> + u32 seed;
>> + u32 bksv[DW_HDMI_HDCP14_BKSV_SIZE];
>> + u32 keys[DW_HDMI_HDCP14_KEYS_SIZE];
>> + bool keys_valid;
>> +};
>> +
>> +struct dw_hdmi_rx_pdata {
>> + /* Controller configuration */
>> + unsigned int iref_clk; /* MHz */
> Is this field unused?

Yes, left overs from previous versions.

>
>> + struct dw_hdmi_hdcp14_key hdcp14_keys;
>> + /* 5V sense interface */
>> + bool (*dw_5v_status)(void __iomem *regs, int input);
>> + void (*dw_5v_clear)(void __iomem *regs);
>> + void __iomem *dw_5v_arg;> + /* Zcal interface */
>> + void (*dw_zcal_reset)(void __iomem *regs);
>> + bool (*dw_zcal_done)(void __iomem *regs);
>> + void __iomem *dw_zcal_arg;
> I'm just wondering if these operations could be modeled with the regmap,
> so we could avoid callbacks in the platform data structure.

Hmm, I don't think that is safe because registers may not be
adjacent to each other. And maybe I was a little generous in
passing a __iomem argument, maybe it should be just void instead
because this can be not a regmap at all.

Best regards,
Jose Miguel Abreu

>
>> +};
>> +#endif /* __DW_HDMI_RX_PDATA_H__ */
> --
> Regards,
> Sylwester