Re: [PATCH v9 3/4] [media] cxusb: add analog mode support for Medion MD95700

From: Hans Verkuil
Date: Wed Apr 24 2019 - 09:06:37 EST


On 4/16/19 1:40 AM, Maciej S. Szmigiero wrote:
> On 12.04.2019 11:22, Hans Verkuil wrote:
>> On 3/30/19 12:51 AM, Maciej S. Szmigiero wrote:
>>> This patch adds support for analog part of Medion 95700 in the cxusb
>>> driver.
>>>
>>> What works:
>>> * Video capture at various sizes with sequential fields,
>>> * Input switching (TV Tuner, Composite, S-Video),
>>> * TV and radio tuning,
>>> * Video standard switching and auto detection,
>>> * Radio mode switching (stereo / mono),
>>> * Unplugging while capturing,
>>> * DVB / analog coexistence.
>>>
>>> What does not work yet:
>>> * Audio,
>>> * VBI,
>>> * Picture controls.
>>>
>>> Signed-off-by: Maciej S. Szmigiero <mail@xxxxxxxxxxxxxxxxxxxxx>
>>> ---
> (..)
>>> +static int cxusb_medion_try_s_fmt_vid_cap(struct file *file,
>>> + struct v4l2_format *f,
>>> + bool isset)
>>> +{
>>> + struct dvb_usb_device *dvbdev = video_drvdata(file);
>>> + struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> + struct v4l2_subdev_format subfmt;
>>> + int ret;
>>> +
>>> + if (isset && (vb2_start_streaming_called(&cxdev->videoqueue) ||
>>> + cxdev->stop_streaming))
>>
>> This should be:
>>
>> if (isset && vb2_is_busy(&cxdev->videoqueue))
>>
>> As long as buffers are allocated you can no longer change the format.
>>
>>> + return -EBUSY;
>>> +
>>> + memset(&subfmt, 0, sizeof(subfmt));
>>> + subfmt.which = isset ? V4L2_SUBDEV_FORMAT_ACTIVE :
>>> + V4L2_SUBDEV_FORMAT_TRY;
>>> + subfmt.format.width = f->fmt.pix.width & ~1;
>>> + subfmt.format.height = f->fmt.pix.height & ~1;
>>> + subfmt.format.code = MEDIA_BUS_FMT_FIXED;
>>> + subfmt.format.field = V4L2_FIELD_SEQ_TB;
>>
>> Why FIELD_SEQ_TB? If memory serves the cx25840 always uses FIELD_INTERLACED.
>
> It looks like all the existing cx25840 users use an intelligent bridge
> chip (or a MPEG encoder), which normalize the video data received to
> a nice V4L2_FIELD_INTERLACED.
>
> However, this device uses a "dumb" USB bridge chip which does not do any
> video processing (other than dropping parts of it if the host PC is not
> receiving it fast enough), so the output data matches what the cx25840
> chip produces - and it produces a sequential fields format.

Ah. But in that case it is SEQ_BT for NTSC and SEQ_TB for all other standards.

>
>>> + subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
>>> +
>>> + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt);
>>> + if (ret != 0) {
>>> + if (ret != -ERANGE)
>>> + return ret;
>>> +
>>> + /* try some common formats */
>>> + subfmt.format.width = 720;
>>> + subfmt.format.height = 576;
>>> + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL,
>>> + &subfmt);
>>> + if (ret != 0) {
>>> + if (ret != -ERANGE)
>>> + return ret;
>>> +
>>> + subfmt.format.width = 640;
>>> + subfmt.format.height = 480;
>>> + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt,
>>> + NULL, &subfmt);
>>> + if (ret != 0)
>>> + return ret;
>>> + }
>>
>> Why these fallbacks? Is this really needed?
>
> V4L2 docs say that VIDIOC_{S,TRY}_FMT ioctls "should not return an error
> code unless the type field is invalid", that is, they should not return
> an error for invalid or unsupported image widths or heights.
> They should instead return something sensible for these image parameters.
>
> However, cx25840 driver set_fmt callback simply returns -ERANGE if it
> does not like the provided width or height.

I think the right approach here is to modify cx25840 so that it updates
the width/height to stay within the limits of the HW.

BTW, a quick scan shows that none of the drivers that call set_fmt actually
check the return value :-)

So changing the cx25840 behavior certainly won't make things worse for
existing drivers.

> In this case the code above simply tries first the bigger PAL capture
> resolution then the smaller NTSC one.
> Which one will be accepted by the cx25840 depends on the currently set
> broadcast standard and parameters of the last signal that was received,
> at least one of these resolutions seem to work even without any
> signal being received since the chip was powered up.
>
> This way the API guarantees should be kept by the driver.

This is basically a work-around for a cx25840 bug.

In any case, since the driver initializes to PAL the 720x576 resolution
should be fine.

BTW, I noticed that cxdev->norm is initialized to 0. Why isn't that
PAL?

>
>>> +int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev)
>>> +{
>>> + struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> + u8 tuner_analog_msg_data[] = { 0x9c, 0x60, 0x85, 0x54 };
>>> + struct i2c_msg tuner_analog_msg = { .addr = 0x61, .flags = 0,
>>> + .buf = tuner_analog_msg_data,
>>> + .len =
>>> + sizeof(tuner_analog_msg_data) };
>>> + struct v4l2_subdev_format subfmt;
>>> + int ret;
>>> +
>>> + /* switch tuner to analog mode so IF demod will become accessible */
>>> + ret = i2c_transfer(&dvbdev->i2c_adap, &tuner_analog_msg, 1);
>>> + if (ret != 1)
>>> + dev_warn(&dvbdev->udev->dev,
>>> + "tuner analog switch failed (%d)\n", ret);
>>> +
>>> + /*
>>> + * cx25840 might have lost power during mode switching so we need
>>> + * to set it again
>>> + */
>>> + ret = v4l2_subdev_call(cxdev->cx25840, core, reset, 0);
>>> + if (ret != 0)
>>> + dev_warn(&dvbdev->udev->dev,
>>> + "cx25840 reset failed (%d)\n", ret);
>>> +
>>> + ret = v4l2_subdev_call(cxdev->cx25840, video, s_routing,
>>> + CX25840_COMPOSITE1, 0, 0);
>>> + if (ret != 0)
>>> + dev_warn(&dvbdev->udev->dev,
>>> + "cx25840 initial input setting failed (%d)\n", ret);
>>> +
>>> + /* composite */
>>> + cxdev->input = 1;
>>> + cxdev->norm = 0;
>>> +
>>> + /* TODO: setup audio samples insertion */
>>> +
>>> + ret = v4l2_subdev_call(cxdev->cx25840, core, s_io_pin_config,
>>> + sizeof(cxusub_medion_pin_config) /
>>> + sizeof(cxusub_medion_pin_config[0]),
>>> + cxusub_medion_pin_config);
>>> + if (ret != 0)
>>> + dev_warn(&dvbdev->udev->dev,
>>> + "cx25840 pin config failed (%d)\n", ret);
>>> +
>>> + /* make sure that we aren't in radio mode */
>>> + v4l2_subdev_call(cxdev->tda9887, video, s_std, V4L2_STD_PAL);
>>> + v4l2_subdev_call(cxdev->tuner, video, s_std, V4L2_STD_PAL);
>>> + v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
>>> +
>>> + memset(&subfmt, 0, sizeof(subfmt));
>>> + subfmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
>>> + subfmt.format.width = cxdev->width;
>>> + subfmt.format.height = cxdev->height;
>>> + subfmt.format.code = MEDIA_BUS_FMT_FIXED;
>>> + subfmt.format.field = V4L2_FIELD_SEQ_TB;
>>> + subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
>>> +
>>> + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt);
>>> + if (ret != 0) {
>>> + subfmt.format.width = 640;
>>> + subfmt.format.height = 480;
>>
>> Why the fallback to 640x480?
>
> This resolution seems to work even without any signal being received,
> and it is a sensible NTSC-like resolution so it makes a good fallback
> if restoring the previously used resolution has failed.

But it is really a work-around for a cx25840 bug. Just fix set_fmt
and you should not need this anymore.

Regards.

Hans

>
>>> + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL,
>>> + &subfmt);
>>> + if (ret != 0)
>>> + dev_warn(&dvbdev->udev->dev,
>>> + "cx25840 format set failed (%d)\n", ret);
>>> + }
>>> +
>>> + if (ret == 0) {
>>> + cxdev->width = subfmt.format.width;
>>> + cxdev->height = subfmt.format.height;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>
>>
>> Regards,
>>
>> Hans
>>
>
> Thanks and best regards,
> Maciej
>