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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux