Re: [PATCH v9 4/4] [media] platform: Add Synopsys DesignWare HDMI RX Controller Driver

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

 



Hi Hans,

On 07-12-2017 12:33, Hans Verkuil wrote:
> Hi Jose,
>
> Some (small) comments below:

Thanks for the review!

>
> On 12/07/17 10:47, 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.
>>
>> This driver was implemented as a standard V4L2 subdevice and its
>> main features are:
>> 	- Internal state machine that reconfigures phy until the
>> 	video is not stable
>> 	- JTAG communication with phy
>> 	- Inter-module communication with phy driver
>> 	- Debug write/read ioctls
>>
>> Some notes:
>> 	- RX sense controller (cable connection/disconnection) must
>> 	be handled by the platform wrapper as this is not integrated
>> 	into the controller RTL
>> 	- The same goes for EDID ROM's
>> 	- ZCAL calibration is needed only in FPGA platforms, in ASIC
>> 	this is not needed
>> 	- The state machine is not an ideal solution as it creates a
>> 	kthread but it is needed because some sources might not be
>> 	very stable at sending the video (i.e. we must react
>> 	accordingly).
>>
>> Signed-off-by: Jose Abreu <joabreu@xxxxxxxxxxxx>
>> Cc: Joao Pinto <jpinto@xxxxxxxxxxxx>
>> Cc: Mauro Carvalho Chehab <mchehab@xxxxxxxxxx>
>> Cc: Hans Verkuil <hans.verkuil@xxxxxxxxx>
>> Cc: Sylwester Nawrocki <snawrocki@xxxxxxxxxx>
>> Cc: Sakari Ailus <sakari.ailus@xxxxxx>
>> ---
>> Changes from v8:
>> 	- Incorporate Sakari's work on ASYNC subdevs
>> Changes from v6:
>> 	- edid-phandle now also looks for parent node (Sylwester)
>> 	- Fix kbuild build warnings
>> Changes from v5:
>> 	- Removed HDCP 1.4 support (Hans)
>> 	- Removed some CEC debug messages (Hans)
>> 	- Add s_dv_timings callback (Hans)
>> 	- Add V4L2_CID_DV_RX_POWER_PRESENT ctrl (Hans)
>> Changes from v4:
>> 	- Add flag V4L2_SUBDEV_FL_HAS_DEVNODE (Sylwester)
>> 	- Remove some comments and change some messages to dev_dbg (Sylwester)
>> 	- Use v4l2_async_subnotifier_register() (Sylwester)
>> Changes from v3:
>> 	- Use v4l2 async API (Sylwester)
>> 	- Do not block waiting for phy
>> 	- Do not use busy waiting delays (Sylwester)
>> 	- Simplify dw_hdmi_power_on (Sylwester)
>> 	- Use clock API (Sylwester)
>> 	- Use compatible string (Sylwester)
>> 	- Minor fixes (Sylwester)
>> Changes from v2:
>> 	- Address review comments from Hans regarding CEC
>> 	- Use CEC notifier
>> 	- Enable SCDC
>> Changes from v1:
>> 	- Add support for CEC
>> 	- Correct typo errors
>> 	- Correctly detect interlaced video modes
>> 	- Correct VIC parsing
>> Changes from RFC:
>> 	- Add support for HDCP 1.4
>> 	- Fixup HDMI_VIC not being parsed (Hans)
>> 	- Send source change signal when powering off (Hans)
>> 	- Add a "wait stable delay"
>> 	- Detect interlaced video modes (Hans)
>> 	- Restrain g/s_register from reading/writing to HDCP regs (Hans)
>> ---
>>  drivers/media/platform/dwc/Kconfig      |   15 +
>>  drivers/media/platform/dwc/Makefile     |    1 +
>>  drivers/media/platform/dwc/dw-hdmi-rx.c | 1834 +++++++++++++++++++++++++++++++
>>  drivers/media/platform/dwc/dw-hdmi-rx.h |  441 ++++++++
>>  include/media/dwc/dw-hdmi-rx-pdata.h    |   70 ++
>>  5 files changed, 2361 insertions(+)
>>  create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
>>  create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
>>  create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h
>>
> <snip>
>
>> +static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev *dw_dev, u32 stat)
>> +{
>> +	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
>> +		dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__);
>> +		return;
>> +	}
>> +
>> +	if (stat & HDMI_AUD_CEC_ISTS_ARBLST) {
>> +		cec_transmit_attempt_done(dw_dev->cec_adap,
>> +				CEC_TX_STATUS_ARB_LOST);
>> +		return;
>> +	}
>> +
>> +	if (stat & HDMI_AUD_CEC_ISTS_NACK) {
>> +		cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_NACK);
>> +		return;
>> +	}
>> +
>> +	if (stat & HDMI_AUD_CEC_ISTS_ERROR_INIT) {
>> +		dev_dbg(dw_dev->dev, "%s: got initiator error\n", __func__);
>> +		cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_ERROR);
> There is no separate 'low drive' interrupt? Do you know what happens if a low drive
> is received during a transmit?

I think it launches this interrupt, i.e. Initiator Error.

>
> FYI: I've been working on error injection support for my cec-gpio driver, allowing
> me to test all these nasty little corner cases. And that includes Arbitration Lost.
> It's available here:
>
> https://urldefense.proofpoint.com/v2/url?u=https-3A__git.linuxtv.org_hverkuil_media-5Ftree.git_log_-3Fh-3Dcec-2Derror-2Dinj&d=DwIDaQ&c=DPL6_X_6JkXFx7AXWqB0tg&r=WHDsc6kcWAl4i96Vm5hJ_19IJiuxx_p_Rzo2g-uHDKw&m=lM9aIhXqx9F8VZg9CADcx_qJbK_B2NTyHn8ZNIEpAEo&s=x0PASA3DhAHKXPrpj2G31D5xOFu0ERI3ewWb-yyTLMg&e=
>
> It works like a charm with my Rpi3.

Nice! But for this I would need to have access to the physical
pin for cec, right? I don't have that ...

>
>> +
>> +static int dw_hdmi_query_dv_timings(struct v4l2_subdev *sd,
>> +		struct v4l2_dv_timings *timings)
>> +{
>> +	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> +	struct v4l2_bt_timings *bt = &timings->bt;
>> +	bool is_hdmi_vic;
>> +	u32 htot, hofs;
>> +	u32 vtot;
>> +	u8 vic;
>> +
>> +	dev_dbg(dw_dev->dev, "%s\n", __func__);
>> +
>> +	memset(timings, 0, sizeof(*timings));
>> +
>> +	timings->type = V4L2_DV_BT_656_1120;
>> +	bt->width = hdmi_readl(dw_dev, HDMI_MD_HACT_PX);
>> +	bt->height = hdmi_readl(dw_dev, HDMI_MD_VAL);
>> +	bt->interlaced = hdmi_readl(dw_dev, HDMI_MD_STS) & HDMI_MD_STS_ILACE;
>> +
>> +	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_VS_POL_ADJ)
>> +		bt->polarities |= V4L2_DV_VSYNC_POS_POL;
>> +	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_HS_POL_ADJ)
>> +		bt->polarities |= V4L2_DV_HSYNC_POS_POL;
>> +
>> +	bt->pixelclock = dw_hdmi_get_pixelclk(dw_dev);
>> +
>> +	/* HTOT = HACT + HFRONT + HSYNC + HBACK */
>> +	htot = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
>> +			HDMI_MD_HT1_HTOT_PIX_OFFSET,
>> +			HDMI_MD_HT1_HTOT_PIX_MASK);
>> +	/* HOFS = HSYNC + HBACK */
>> +	hofs = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
>> +			HDMI_MD_HT1_HOFS_PIX_OFFSET,
>> +			HDMI_MD_HT1_HOFS_PIX_MASK);
>> +
>> +	bt->hfrontporch = htot - hofs - bt->width;
>> +	bt->hsync = hdmi_mask_readl(dw_dev, HDMI_MD_HT0,
>> +			HDMI_MD_HT0_HS_CLK_OFFSET,
>> +			HDMI_MD_HT0_HS_CLK_MASK);
>> +	bt->hbackporch = hofs - bt->hsync;
>> +
>> +	/* VTOT = VACT + VFRONT + VSYNC + VBACK */
>> +	vtot = hdmi_readl(dw_dev, HDMI_MD_VTL);
>> +
>> +	hdmi_mask_writel(dw_dev, 0x1, HDMI_MD_VCTRL,
>> +			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_OFFSET,
>> +			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_MASK);
>> +	msleep(50);
>> +	bt->vsync = hdmi_readl(dw_dev, HDMI_MD_VOL);
>> +
>> +	hdmi_mask_writel(dw_dev, 0x0, HDMI_MD_VCTRL,
>> +			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_OFFSET,
>> +			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_MASK);
>> +	msleep(50);
>> +	bt->vbackporch = hdmi_readl(dw_dev, HDMI_MD_VOL);
>> +	bt->vfrontporch = vtot - bt->height - bt->vsync - bt->vbackporch;
> For interlaced formats the bt->il_* fields should also be filled in.

See [1].

>
>> +	bt->standards = V4L2_DV_BT_STD_CEA861;
>> +
>> +	vic = dw_hdmi_get_curr_vic(dw_dev, &is_hdmi_vic);
>> +	if (vic) {
>> +		if (is_hdmi_vic) {
>> +			bt->flags |= V4L2_DV_FL_HAS_HDMI_VIC;
>> +			bt->hdmi_vic = vic;
>> +			bt->cea861_vic = 0;
>> +		} else {
>> +			bt->flags |= V4L2_DV_FL_HAS_CEA861_VIC;
>> +			bt->hdmi_vic = 0;
>> +			bt->cea861_vic = vic;
>> +		}
>> +	}
> You can simplify this. We have this function in v4l2-dv-timings.c:
> v4l2_find_dv_timings_cea861_vic(). If you read a CEA861 vic code,
> then you can call it to find the corresponding timings and just return
> that.
>
> I thought I made a v4l2_find_dv_timings_hdmi_vic as well, but apparently
> I didn't. It's trivial to add it, though.

Ok, but how will then we handle 59.94 vs 60Hz video modes?
Because the vic remains the same in these modes.

>
>> +
>> +	return 0;
>> +}
>> +
>> +static int dw_hdmi_enum_mbus_code(struct v4l2_subdev *sd,
>> +		struct v4l2_subdev_pad_config *cfg,
>> +		struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> +
>> +	dev_dbg(dw_dev->dev, "%s\n", __func__);
>> +	if (code->index != 0)
>> +		return -EINVAL;
>> +
>> +	code->code = dw_dev->mbus_code;
>> +	return 0;
>> +}
>> +
>> +static int dw_hdmi_fill_format(struct dw_hdmi_dev *dw_dev,
>> +		struct v4l2_mbus_framefmt *format)
>> +{
>> +	memset(format, 0, sizeof(*format));
>> +
>> +	format->width = dw_dev->timings.bt.width;
>> +	format->height = dw_dev->timings.bt.height;
>> +	format->colorspace = V4L2_COLORSPACE_SRGB;
>> +	format->code = dw_dev->mbus_code;
>> +	if (dw_dev->timings.bt.interlaced)
>> +		format->field = V4L2_FIELD_ALTERNATE;
> Were interlaced formats tested? (Apologies if I have asked this before)
>
> Interlaced is tricky and my recommendation is to only add support for it
> to a driver if you have been able to test it.
>
> I see that dw_hdmi_timings_cap only supports progressive, so I conclude
> that this hasn't been tested. It's better to just fix field to NONE in
> that case.

[1] Ok, but then I will still need to fill bt->il_* fields ?

>> +
>> +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 0;
>> +	/*return v4l2_async_subdev_notifier_register(&dw_dev->sd,
>> +			&dw_dev->v4l2_notifier);*/
> Comment can be dropped, I guess.

Yeah, leftovers from the previous version...

>
>> +
>> +static int dw_hdmi_rx_probe(struct platform_device *pdev)
>> +{
>> +	const struct v4l2_dv_timings timings_def = HDMI_DEFAULT_TIMING;
>> +	struct dw_hdmi_rx_pdata *pdata = pdev->dev.platform_data;
>> +	struct device *dev = &pdev->dev;
>> +	struct v4l2_ctrl_handler *hdl;
>> +	struct dw_hdmi_dev *dw_dev;
>> +	struct v4l2_subdev *sd;
>> +	struct resource *res;
>> +	int ret, irq;
>> +
>> +	dev_dbg(dev, "%s\n", __func__);
>> +
>> +	dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
>> +	if (!dw_dev)
>> +		return -ENOMEM;
>> +
>> +	if (!pdata) {
>> +		dev_err(dev, "missing platform data\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	dw_dev->dev = dev;
>> +	dw_dev->config = pdata;
>> +	dw_dev->state = HDMI_STATE_NO_INIT;
>> +	dw_dev->of_node = dev->of_node;
>> +	spin_lock_init(&dw_dev->lock);
>> +
>> +	ret = dw_hdmi_parse_dt(dw_dev);
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* Deferred work */
>> +	dw_dev->wq = create_singlethread_workqueue(DW_HDMI_RX_DRVNAME);
>> +	if (!dw_dev->wq) {
>> +		dev_err(dev, "failed to create workqueue\n");
>> +		return -ENOMEM;
>> +	}
>> +
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +	dw_dev->regs = devm_ioremap_resource(dev, res);
>> +	if (IS_ERR(dw_dev->regs)) {
>> +		dev_err(dev, "failed to remap resource\n");
>> +		ret = PTR_ERR(dw_dev->regs);
>> +		goto err_wq;
>> +	}
>> +
>> +	/* Disable HPD as soon as posssible */
>> +	dw_hdmi_disable_hpd(dw_dev);
>> +	/* Prevent HDCP from tampering video */
>> +	dw_hdmi_config_hdcp(dw_dev);
>> +
>> +	irq = platform_get_irq(pdev, 0);
>> +	if (irq < 0) {
>> +		ret = irq;
>> +		goto err_wq;
>> +	}
>> +
>> +	ret = devm_request_threaded_irq(dev, irq, NULL, dw_hdmi_irq_handler,
>> +			IRQF_ONESHOT, DW_HDMI_RX_DRVNAME, dw_dev);
>> +	if (ret)
>> +		goto err_wq;
>> +
>> +	/* V4L2 initialization */
>> +	sd = &dw_dev->sd;
>> +	v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
>> +	strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
>> +	sd->dev = dev;
>> +	sd->internal_ops = &dw_hdmi_internal_ops;
>> +	sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +
>> +	/* Control handlers */
>> +	hdl = &dw_dev->hdl;
>> +	v4l2_ctrl_handler_init(hdl, 1);
>> +	dw_dev->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL,
>> +			V4L2_CID_DV_RX_POWER_PRESENT, 0, BIT(4) - 1, 0, 0);
>> +
>> +	sd->ctrl_handler = hdl;
>> +	if (hdl->error) {
>> +		ret = hdl->error;
>> +		goto err_hdl;
>> +	}
>> +
>> +	/* Wait for ctrl handler register before requesting 5v interrupt */
>> +	irq = platform_get_irq(pdev, 1);
>> +	if (irq < 0) {
>> +		ret = irq;
>> +		goto err_hdl;
>> +	}
>> +
>> +	ret = devm_request_threaded_irq(dev, irq, dw_hdmi_5v_hard_irq_handler,
>> +			dw_hdmi_5v_irq_handler, IRQF_ONESHOT,
>> +			DW_HDMI_RX_DRVNAME "-5v-handler", dw_dev);
>> +	if (ret)
>> +		goto err_hdl;
>> +
>> +	/* Notifier for subdev binding */
>> +	ret = dw_hdmi_v4l2_init_notifier(dw_dev);
>> +	if (ret) {
>> +		dev_err(dev, "failed to init v4l2 notifier\n");
>> +		goto err_hdl;
>> +	}
>> +
>> +	/* PHY loading */
>> +	ret = dw_hdmi_phy_init(dw_dev);
>> +	if (ret)
>> +		goto err_hdl;
>> +
>> +	/* CEC */
>> +#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
>> +	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
>> +			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
>> +			CEC_CAP_LOG_ADDRS | CEC_CAP_RC | CEC_CAP_PASSTHROUGH,
> Use CEC_CAP_DEFAULTS instead of specifying these caps separately.

Ok.

Thanks and Best Regards,
Jose Miguel Abreu

>
> Regards,
>
> 	Hans




[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