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