Re: [GIT PULL] ViewCast O820E capture support added

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

 



Em 13-09-2012 21:59, Mauro Carvalho Chehab escreveu:
> Em Thu, 13 Sep 2012 20:23:42 -0300
> Mauro Carvalho Chehab <mchehab@xxxxxxxxxx> escreveu:
> 
>> Em 13-09-2012 20:19, Mauro Carvalho Chehab escreveu:
>>> Em Sat, 18 Aug 2012 11:48:52 -0400
>>> Steven Toth <stoth@xxxxxxxxxxxxxx> escreveu:
>>>
>>>> Mauro, please read below, a new set of patches I'm submitting for merge.
>>>>
>>>> On Thu, Aug 16, 2012 at 2:49 PM, Hans Verkuil <hverkuil@xxxxxxxxx> wrote:
>>>>> On Thu August 16 2012 19:39:51 Steven Toth wrote:
>>>>>>>> So, I've ran v4l2-compliance and it pointed out a few things that I've
>>>>>>>> fixed, but it also does a few things that (for some reason) I can't
>>>>>>>> seem to catch. One particular test is on (iirc) s_fmt. It attempts to
>>>>>>>> set ATSC but by ioctl callback never receives ATSC in the norm/id arg,
>>>>>>>> it actually receives 0x0. This feels more like a bug in the test.
>>>>>>>> Either way, I have some if (std & ATSC) return -EINVAL, but it still
>>>>>>>> appears to fail the test.
>>>>>>
>>>>>> Oddly enough. If I set tvnorms to something valid, then compliance
>>>>>> passes but gstreamer
>>>>>> fails to run, looks like some kind of confusion about either the
>>>>>> current established
>>>>>> norm, or a failure to establish a norm.
>>>>>>
>>>>>> For the time being I've set tvnorms to 0 (with a comment) and removed
>>>>>> current_norm.
>>>>>
>>>>> Well, this needs to be sorted, because something is clearly amiss.
>>>>
>>>> Agreed. I just can't see what's wrong. I may need your advise /
>>>> eyeballs on this. I'd be willing to provide logs that show gstreamer
>>>> accessing the driver and exiting. It needs fixed, I've tried, I just
>>>> can't see why gstreamer fails.
>>>>
>>>> On the main topic of merge.... As promised, I spent quite a bit of
>>>> time this week reworking the code based on the feedback. I also
>>>> flattened all of these patches into a single patchset and upgraded to
>>>> the latest re-org tree.
>>>>
>>>> The source notes describe in a little more detail the major changes:
>>>> http://git.kernellabs.com/?p=stoth/media_tree.git;a=commit;h=f295dd63e2f7027e327daad730eb86f2c17e3b2c
>>>>
>>>> Mauro, so, I hereby submit for your review/merge again, the updated
>>>> patchset. *** Please comment. ***
>>>
>>> I'll comment patch by patch. Let's hope the ML will get this email. Not sure,
>>> as it tends to discard big emails like that.
>>>
>>> This is the comment of patch 1/4.
>>>
>>
>> Patch 2 is trivial. It is obviously OK.
>>
>> Patch 3 also looked OK on my eyes.
> 
> Patch 4 will very likely be discarded by vger server, if everything is
> added there. So, I'll drop the parts that weren't commented.
> 
> Anyway:
> 
>> Subject: [media] vc8x0: Adding support for the ViewCast O820E Capture Card.
>> Cc: Linux Media Mailing List <linux-media@xxxxxxxxxxxxxxx>
>>
>> A dual channel 1920x1080p60 PCIe x4 capture card, two DVI
>> inputs capable of capturing DVI/HDMI, Component, Svideo, Composite
>> and some VGA resolutions.
> ...
> 
>> +#include "vc8x0.h"
>> +
>> +static unsigned int audio_debug;
>> +module_param(audio_debug, int, 0644);
>> +MODULE_PARM_DESC(audio_debug, "enable debug messages [audio]");
>> +
>> +static unsigned int audio_alsa_during_irq = 1;
>> +module_param(audio_alsa_during_irq, int, 0644);
>> +MODULE_PARM_DESC(audio_alsa_during_irq, "feed alsa during the irq handler, not via a dpc [audio]");
>> +
>> +#define dprintk(level, fmt, arg...)\
>> +	do {\
>> +		if (audio_debug >= level)\
>> +			pr_err("%s/0: " fmt, \
>> +				channel->dev->name, ## arg);\
>> +	} while (0)
>> +
>> +#define MIXER_RCA_JACKS 1
>> +
>> +/* Repack 24 bit audio samples (in 32bit alignment)
>> + * into 16bit samples within the same buffer, and
>> + * return the new buffer length in bytes.
>> + *
>> + * Input Sample:
>> + * LEFT         RIGHT
>> + * 00 B0 B1 B2  00 B1 B1 B2
>> +
>> + * Output Sample:
>> + * LEFT   RIGHT
>> + * B1 B2  B1 B2
>> + */
>> +static int repack_24_to_16(u8 *buf, int len)
>> +{
>> +	int i;
>> +	u8 *dst = buf;
>> +	u8 *src = buf + 2;
>> +
>> +	/* For each 24 bit sample */
>> +	for (i = 0; i < (len / 4); i++) {
>> +		*(dst) = *(src);
>> +		*(dst + 1) = *(src + 1);
>> +		dst += 2;
>> +		src += 4;
>> +	}
>> +
>> +	return (len / 4) * 2;
>> +}
> 
> Why is it needed? It would be better to let ALSA userspace to handle
> it.
> 
>> +	dprintk(3,
>> +		"%s() %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
>> +		__func__, *(buf->cpu + 0), *(buf->cpu + 1), *(buf->cpu + 2),
>> +		*(buf->cpu + 3), *(buf->cpu + 4), *(buf->cpu + 5),
>> +		*(buf->cpu + 6), *(buf->cpu + 7), *(buf->cpu + 8),
>> +		*(buf->cpu + 9), *(buf->cpu + 10), *(buf->cpu + 11),
>> +		*(buf->cpu + 12), *(buf->cpu + 13), *(buf->cpu + 14),
>> +		*(buf->cpu + 15)
>> +	    );
> 
> FYI, there's now a new printk syntax to print buffer dumps like
> that, where you pass the buffer and the length, and printk does the
> rest.
> 
>> +	spin_unlock(&channel->dma_buffers_full_lock);
>> +	spin_unlock_irqrestore(&channel->dma_buffers_dpc_lock, flags);
>> +
>> +	/* BAM! The interrupt handler is now free to move on */
>> +	/* BAM! The interrupt handler is now free to move on */
>> +	/* BAM! The interrupt handler is now free to move on */
>> +	/* BAM! The interrupt handler is now free to move on */
> 
> Wow! the above 4 lines won the prize of the weirdest comment I ever seen ;)
> Why you need to say the above 4 times? :)
> 
> Even saying it once seems overkill to me, as it just repeats what the
>  spin_unlock() just said ;)
> 
>> +
>> +	/* Now let's dequeue the full buffers */
>> +	/* For each full buffer, send it to user space */
>> +	spin_lock(&channel->dma_buffers_full_lock);
> 
> Huh? You just unlocked it... Also, it looks weird that you're using two spin
> locks on the above code, and just one here. Using more than one spin lock
> like that could cause dead locks.
> 
> Btw, this patch is too big! You should break it into some smaller
> pieces (one patch per file, for example) making life easier for reviewers and
> allowing people at the ML to see/comment the full code, as one of the requirements
> is that, before sending a pull request, you should be sending the patches to
> the ML.
> 
> In the specific case of the -alsa driver, it is mandatory to have it on a
> separate patch, as it should be copied also to the alsa ML, to allow alsa
> people to comment/review.
> 
> So, please split patch 4 into separate patches, doing the Kconfig/Makefile
> integration at the end of your series.
> 
>> +	buf->used_len = 3840;
>> +	buf->used_len = repack_24_to_16(buf->cpu, buf->used_len);
> 
> This repack thing looks weird on my eyes.
> 
>> +	spin_lock_irqsave(&channel->dma_buffers_busy_lock, flags);
>> +
>> +	/* Last, put the buffer on the DPC list for our deferred worker
>> +	 * to process */
>> +	spin_lock_irqsave(&channel->dma_buffers_dpc_lock, flags);
> 
> Again, double-locking.
> 
>> +static inline void handle_audio_data(struct vc8x0_dma_buffer *buf,
>> +	int *period_elapsed)
>> +{
>> +	struct vc8x0_dma_channel *channel = buf->channel;
>> +	struct vc8x0_audio_dev *chip = channel->audio_dev;
>> +	struct snd_pcm_runtime *runtime = chip->capture_pcm_substream->runtime;
>> +	int stride;
>> +	int len, rdb, cpsafe[3];
>> +	unsigned char *cp;
>> +	unsigned int oldptr;
>> +
>> +	stride = runtime->frame_bits >> 3;
>> +	if (stride == 0) {
>> +		pr_err("%s() divbyzero BUG\n", __func__);
>> +		stride = 4;
>> +	}
>> +
>> +	len = buf->used_len / stride;
> 
> Hmm... that looks weird on my eyes, as other drivers don't have
> such check. Why such logic is needed? Rounding it to 4 won't cause
> buffer overflows? Maybe a BUG_ON would apply better here.
> 
>> +#if ENABLE_ALSA_MIXER
> 
> Please use CONFIG_foo instead, it the user may opt to have it or not.
> 
>> diff --git a/drivers/media/pci/vc8x0/vc8x0-buffer.c b/drivers/media/pci/vc8x0/vc8x0-buffer.c
> 
> 
>> +DMA buffers per channel must be contigious, reside only in 32bit
> 
> typo: contiguous.
> 
>> +memory.
>> +
>> +The PCIe bridge (GN4124) supports up to 18 'fifos', essentially
>> +discrete DMA channels. The GN4124 uses a DMA Sequencer architecture
>> +to control which dma buffers are targets for which channel. The sequencer
>> +is a list of program instructions that effictivel handle the data modement
> 
> typo: effective
> 
>> +
>> +void vc8x0_buffer_analyze(u8 *buf, int len)
>> +{
>> +	int i;
>> +	u32 data[256];
>> +	memset(data, 0, sizeof(data));
>> +
>> +	for (i = 0; i < len; i++) {
>> +		data[*(buf + i)]++;
>> +	}
>> +
>> +	for (i = 0; i < 256; i++) {
>> +		if (data[i]) {
>> +			pr_err("%02x %x\n", i, data[i]);
>> +		}
>> +	}
> 
> use print_hex_dump() instead of the loop.
> 
> On a big driver, like that, it is hard to see how each module
> interacts with the others. Yet, it seemed, on my eyes, that
> vc8x0-buffer is doing something close to what vb2-contig is
> already doing.
> 
> If you need to use contiguous buffer memories for DMA transfers,
> I strongly suggest you to use vb2, as vb1 is known to have some
> serious issues with contiguous memories.
> 
>> diff --git a/drivers/media/pci/vc8x0/vc8x0-channel.c b/drivers/media/pci/vc8x0/vc8x0-channel.c
> 
> Again, it is hard to understand what is there at *-channel, in the context
> of the entire driver, but it seems part of a videobuf handling code.
> 
>> diff --git a/drivers/media/pci/vc8x0/vc8x0-core.c b/drivers/media/pci/vc8x0/vc8x0-core.c
> 
>> +
>> +/* 1 = Basic device statistics
>> + * 2 = PCIe register dump for entire device
>> + * 4 = AD9985 register dump
>> + * 8 = SIL9013 register dump
>> + */
>> +unsigned int vc8x0_thread_active = 1;
>> +module_param(vc8x0_thread_active, int, 0644);
>> +MODULE_PARM_DESC(vc8x0_thread_active, "should keep alive thread run");
> 
> Is it really needed? If so, I think you should better describe it as, the
> above description doesn't mean anything for me... What Thread? What happens
> if the thread doesn't run?
> 
>> +static int vc8x0_dev_setup(struct vc8x0_dev *dev)
>> +{
>> +	int i;
>> +
>> +	mutex_init(&dev->lock);
>> +
>> +	atomic_inc(&dev->refcount);
>> +
>> +	dev->nr = vc8x0_devcount++;
>> +	sprintf(dev->name, "vc8x0[%d]", dev->nr);
>> +
>> +	/* board config */
>> +	dev->board = UNSET;
>> +	if (card[dev->nr] < vc8x0_bcount)
>> +		dev->board = card[dev->nr];
>> +	for (i = 0; UNSET == dev->board  &&  i < vc8x0_idcount; i++)
>> +		if (dev->pci->subsystem_vendor == vc8x0_subids[i].subvendor &&
>> +		    dev->pci->subsystem_device == vc8x0_subids[i].subdevice)
>> +			dev->board = vc8x0_subids[i].card;
>> +	if (UNSET == dev->board) {
>> +		dev->board = VC8X0_BOARD_UNKNOWN;
>> +		vc8x0_card_list(dev);
>> +	}
>> +
>> +	/* The keepalive thread needs a mutex */
>> +	mutex_init(&dev->kthread_lock);
>> +
>> +	/* Main Master 0 Bus incl. eeprom */
>> +	mutex_init(&dev->i2c_bus.lock);
>> +	dev->i2c_bus.nr = 0;
>> +	dev->i2c_bus.dev = dev;
>> +	dev->i2c_bus.reg_base = 0xd80;
>> +
>> +	if (get_resources(dev) < 0) {
>> +		pr_err(
>> +		"CORE %s No more PCIe resources for subsystem: %04x:%04x\n",
>> +		       dev->name, dev->pci->subsystem_vendor,
>> +		       dev->pci->subsystem_device);
>> +
>> +		vc8x0_devcount--;
>> +		return -ENODEV;
>> +	}
>> +
>> +	/* PCIe stuff */
>> +	dev->lmmio032 = ioremap(pci_resource_start(dev->pci, BAR0),
>> +			     pci_resource_len(dev->pci, BAR0));
>> +	dev->lmmio064 = (u64 *)dev->lmmio032;
>> +	dev->bmmio = (u8 *)dev->lmmio032;
>> +	dev->lmmio4 = ioremap(pci_resource_start(dev->pci, BAR4),
>> +			     pci_resource_len(dev->pci, BAR4));
>> +
>> +	dev->m_nInterruptMask1 = 0;
>> +	dev->m_nInterruptMask2 = 0;
>> +
>> +	pr_info("CORE %s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n",
>> +	       dev->name, dev->pci->subsystem_vendor,
>> +	       dev->pci->subsystem_device, vc8x0_boards[dev->board].name,
>> +	       dev->board, card[dev->nr] == dev->board ?
>> +	       "insmod option" : "autodetected");
>> +
>> +	return 0;
>> +}
> 
> This is driver's author choice, but I would move driver init, register, unregister
> logic to be at *-cards.c. That balances a little more the code size on each
> .c file. We successfully did it on several drivers, and the end result reduced
> the number of exported functions.
> 
>> +#if ENABLE_ALSA
> ...
> 
>> +#if ENABLE_MONITOR_REGISTERS
> ...
>> +#if ENABLE_AUDIO_KEEPALIVE && ENABLE_ALSA
> ...
> 
> Why do you need those defines? If they're needed, please use CONFIG_foo.
> 
> If they're for debug purposes, please convert them on if (debug == FOO).
> 
>> +#if ENABLE_ALSA && ENABLE_AUDIO_KEEPALIVE
>> +		/* The PCM audio subsystem throws this messages:
>> +		 * ALSA sound/core/pcm_lib.c:1765: capture write error
>> +		 * (DMA or IRQ trouble?) when no audio is delivered for 10
>> +		 * seconds. It basically means it's worker thread didn't
>> +		 * receive a notification with 10 seconds. The message is poor.
>> +		 * In terms of the vc8x0 driver this message will appear by
>> +		 * default if the HDMI cable is disconnected for > 10 seconds,
>> +		 * and it will appear every 10 seconds. If you don't want
>> +		 * this IRQ message to appear then set ENABLE_AUDIO_KEEPLIVE=1
> 
> How to set it? It is not on Kconfig, nor it is a modprobe option.
> 
>> +		/* Other parts of the driver need to guarantee that
>> +		 * various 'keep alives' aren't happening. We'll
>> +		 * prevent race conditions by allowing the
>> +		 * rest of the driver to dictate when
>> +		 * this keepalives can occur.
>> +		 */
>> +		mutex_lock(&dev->kthread_lock);
>> +
>> +		mutex_unlock(&dev->kthread_lock);
> 
> Huh???? Lock/unlock here, without any code inside? That looks odd.
> 
>> +#if ENABLE_BAD_READS
> 
> Yet another define stuff, without a Kconfig item.
> 
>> diff --git a/drivers/media/pci/vc8x0/vc8x0-display.c b/drivers/media/pci/vc8x0/vc8x0-display.c
> 
>> +struct letter_t {
>> +	u8 *ptr;
>> +	u8 data[8];
>> +} charset[] = {
>> + /* ' ' */ [0x20] = { 0, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, },
>> + /* 00000000 */
>> + /* 00000000 */
>> + /* 00000000 */
>> + /* 00000000 */
>> + /* 00000000 */
>> + /* 00000000 */
>> + /* 00000000 */
>> + /* 00000000 */
>> + /* '!' */ [0x21] = { 0, { 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x04, 0x00 }, },
>> + /* 00000100 */
>> + /* 00000100 */
>> + /* 00000100 */
>> + /* 00000100 */
>> + /* 00000000 */
>> + /* 00000000 */
>> + /* 00000100 */
>> + /* 00000000 */
> 
> Charset???? No, please! If you really need a charset, take a look at the
> vivi driver. It uses an already-existent Kernel charset. See:
> 
> 	static int __init vivi_init(void)
> 	{
> 		const struct font_desc *font = find_font("VGA8x16");
> 
> Not sure about the rest of the code here at vc8x0-display.c, but maybe you'll
> find a similar code to it already coded. Where do you use it?
> 
>> diff --git a/drivers/media/pci/vc8x0/vc8x0-dma.c b/drivers/media/pci/vc8x0/vc8x0-dma.c
> 
>> +/* DMA SEQUENCER PROGRAM */
>> +
>> +static u32 vc8x0_FlexDMAProgram[] = {
>> +/*	0x0000	*/	VDMA_LOAD_RA(VD_1_STREAM_DISABLED),
> ...
>> +/*	0x02DA	*/	VDMA_JMP(VDMA_ALWAYS, 0, MAIN),
>> +};
>> +
> 
> Firmware? It is likely better to put it elsewhere, maybe at linux-firmware.
> There are some GPL'd firmwares there.
> 
> I'll comment the remaining files of patch 4 on a separate email
> (editing a 11.000 lines email is very hard... my emailer crashed a few
>  times).

> diff --git a/drivers/media/pci/vc8x0/vc8x0-video.c b/drivers/media/pci/vc8x0/vc8x0-video.c

> +static unsigned int video_1080p = 1;
> +module_param(video_1080p, int, 0644);
> +MODULE_PARM_DESC(video_1080p, "enable 1080i50/60 (0) or 1080p50/60 (1) handling [default 1, 1080p]");

DV timings API allows selecting each time. No need for modprobe
parameters.

> +
> +static struct vc8x0_format formats[] = {
> +	{
> +		.name     = "422packed,YUYV,640x480p60",
> +		.fourcc   = V4L2_PIX_FMT_YUYV,
> +		.width    = 640,
> +		.height   = 480,
> +		.depth    = 16,
> +		.flags    = ADV7441A_FORMAT_PROGRESSIVE,
> +		.id       = ADV7441A_FORMAT_640x480p60,

Again, please use the public standards at DV API. You'll only
need adv7441a-specific ID's it it supports non-standard timings.

> +static int vc8x0_video_generate_osd(struct vc8x0_dma_channel *channel, u8 *dst)
> +{
> +#if 1
> +	return 0;
> +#else
> +	/* Do some text rendering */
> +	struct vc8x0_format *fmt = channel->ad7441_ctx.detected_fmt;
> +	unsigned char tmp[256];
> +	int ret;
> +
> +	ret = vc8x0_display_render_reset(&channel->display_ctx, dst,
> +		channel->fmt->width);
> +	if (ret < 0)
> +		return ret;

Hmm... Are you using the *-display.c code for OSD? Not sure if it is
a good idea to handle it like that.

Hans,

What do you think?

Yet, the code here is commented, but there's a hole driver there in order
to implement OSD display, just bloating the driver's code... 
...

> +void vc8x0_video_timeout(unsigned long data)
> +{
> +	struct vc8x0_dma_channel *channel = (struct vc8x0_dma_channel *)data;
> +	struct vc8x0_buffer *buf;
> +	u8 *dst;
> +	unsigned long flags;
> +
> +	dprintk(1, "%s()\n", __func__);
> +
> +	if (channel->state != STATE_RUNNING) {
> +		/* Return without processing the buffer or restarting
> +		 * the timer
> +		 */
> +		pr_err("%s() channel stopped, aborting\n", __func__);
> +		return;
> +	}
> +
> +	/* Return all of the buffers in error state, so the vbi/vid inode
> +	 * can return from blocking.
> +	 */
> +	spin_lock_irqsave(&channel->v4l2_capture_lock, flags);
> +	while (!list_empty(&channel->v4l2_capture)) {
> +		buf = list_entry(channel->v4l2_capture.next,
> +			struct vc8x0_buffer, vb.queue);
> +
> +		/* See the notes in the video dequeue section related to
> +		 * generating colorbars */
> +		dst = videobuf_to_vmalloc(&buf->vb);

Double-buffering? Doesn't it be giving you some performance issues?

I suspect that converting it to VB2 will allow you to avoid the
memcpy you're likely doing with VB1.

> +
> +/* Linux VBI handling wants lines 5-21 in a single videobuf buffer in YUY2
> + * format. We'll skip the first 4 lines of the FPGA buffer, convert to 422
> + * and place the resulting pixeldata into a short VBI buffer. Unlikely
> + * video, once the single field of VBI data is processed, we'll hand it off
> + * to the user sometime after this function has created the content.
> + */

Hmm... doesn't it support sliced VBI? If so, I think the implementation will
be cleaner... there are lots of "magic stuff" on the code below.

> +void vc8x0_process_vbi_field(u8 *py, struct vc8x0_buffer *vb_buf, int nr,
> +	struct vc8x0_format *fmt)
> +{
> +	u8 *dst = 0;
> +	u8 *y;
> +	int i;
> +
> +	dst = videobuf_to_vmalloc(&vb_buf->vb);
> +
> +	/* VBI collected buy the PGA starts at line 2, so we need to put
> +	 * this in line 2 in the dest buffer.
> +	 */
> +	if (nr == 1) {
> +		dst += (vb_buf->vb.width);
> +		dst += (vb_buf->vb.width) * 21;
> +	}
> +
> +	y  = (py + ((fmt->width * (fmt->height / 2)) * nr));
> +	if (nr == 1)
> +		y += (fmt->width * fmt->vbi_field0_lines);
> +
> +	/* We're going to deinterlace in dwords */
> +	/* We're going to deinterlace 4 dwords at a time, 8 pixels per cycle */
> +
> +	/* Process a single field of vbi data, 17 lines max */
> +	for (i = 0; i < 22; i++)
> +		memcpy(dst + (i * 1440), y + (i * fmt->width), 720);
> +}
> +
> +void vc8x0_process_video_field(u8 *py, u8 *pu, u8 *pv,
> +	struct vc8x0_buffer *vb_buf, int nr,
> +	u32 vbi_enabled, struct vc8x0_format *fmt)
> +{
> +	u32 px[4];
> +	u32 yp, up, vp;
> +	u8 *dst = 0;
> +	u32 *ddst = 0;
> +	u32 *y, *u, *v;
> +	u32 ymax;
> +	int i, pixelcount;
> +
> +	dst = videobuf_to_vmalloc(&vb_buf->vb);
> +	dst += ((vb_buf->vb.width * 2) * nr);
> +	ddst = (u32 *)dst;
> +
> +	y  = (u32 *)(py + ((vb_buf->vb.width * (vb_buf->vb.height / 2)) * nr));
> +	u  = (u32 *)(pu + (((vb_buf->vb.width / 2) *
> +		(vb_buf->vb.height / 2)) * nr));
> +	v  = (u32 *)(pv + (((vb_buf->vb.width / 2) *
> +		(vb_buf->vb.height / 2)) * nr));
> +
> +	if (vbi_enabled) {
> +		/* VBI */
> +		if (nr == 0) {
> +			y += ((fmt->width * fmt->vbi_field0_lines) /
> +				sizeof(u32));
> +			u += (((fmt->width / 2) * fmt->vbi_field0_lines) /
> +				sizeof(u32));
> +			v += (((fmt->width / 2) * fmt->vbi_field0_lines) /
> +				sizeof(u32));
> +		} else {
> +			y += ((fmt->width * fmt->vbi_field0_lines) /
> +				sizeof(u32));
> +			u += (((fmt->width / 2) * fmt->vbi_field0_lines) /
> +				sizeof(u32));
> +			v += (((fmt->width / 2) * fmt->vbi_field0_lines) /
> +				sizeof(u32));
> +
> +			y += ((fmt->width * fmt->vbi_field1_lines) /
> +				sizeof(u32));
> +			u += (((fmt->width / 2) * fmt->vbi_field1_lines) /
> +				sizeof(u32));
> +			v += (((fmt->width / 2) * fmt->vbi_field1_lines) /
> +				sizeof(u32));
> +		}
> +	}
> +
> +	/* We're going to deinterlace in dwords */
> +	/* We're going to deinterlace 4 dwords at a time, 8 pixels per cycle */
> +
> +	/* Process a single field (height / 2) of width ant 8 pixels per time */
> +	ymax = ((vb_buf->vb.height / 2) * vb_buf->vb.width) / 8;
> +	for (i = 0, pixelcount = 0; i < ymax; i++, pixelcount += 8) {
> +
> +		if (pixelcount == (vb_buf->vb.width)) {
> +			ddst += (vb_buf->vb.width / 2);
> +			pixelcount = 0;
> +		}
> +
> +		/* highly optimized for intel i686 little endian, which is
> +		 * what the SOW calls for. */
> +		/* Input data in ram looks like:
> +		 * y_plane: Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8
> +		 * u_plane: U0 U1 U2 U3
> +		 * v_plane: V0 V1 V2 V3
> +		 *
> +		 * We shuffle these bytes into dwords ...
> +		 * px[0] = 0xV0Y1U0Y0
> +		 * px[1] = 0xV1Y3U1Y2
> +		 * px[2] = 0xV2Y5U2Y4
> +		 * px[3] = 0xV3Y7U3Y6
> +		 *
> +		 * and the dwords in little ending are stored back into a
> +		 * byte buffer,
> +		 * which then looks like:
> +		 * bytes 0 - 15 ->
> +		 * Y0 U0 Y1 V0  Y2 U1 Y3 V1  Y3 U2 Y4 V2  Y5 U3 Y6 V3
> +		 *
> +		 */
> +
> +		yp = *(y++);
> +		up = *(u++);
> +		vp = *(v++);
> +
> +		/* Pixels 1, 2 */
> +		/* Y0 xx Y1 xx */
> +		px[0] = (yp & 0x000000ff) | ((yp & 0x0000ff00) << 8);
> +
> +		/* xx U0 xx xx */
> +		px[0] |= ((up & 0x000000ff) << 8);
> +
> +		/* xx xx xx V0 */
> +		px[0] |= ((vp & 0x000000ff) << 24);
> +
> +		/* Pixels 3, 4 */
> +		/* Y0 xx Y1 xx */
> +		px[1] = ((yp & 0x00ff0000) >> 16) | ((yp & 0xff000000) >> 8);
> +
> +		/* xx U0 xx xx */
> +		px[1] |= (up & 0x0000ff00);
> +
> +		/* xx xx xx V0 */
> +		px[1] |= ((vp & 0x0000ff00) << 16);
> +
> +
> +		yp = *(y++);
> +
> +		/* Pixels 5, 6 */
> +		/* Y0 xx Y1 xx */
> +		px[2] = (yp & 0x000000ff) | ((yp & 0x0000ff00) << 8);
> +
> +		/* xx U0 xx xx */
> +		px[2] |= ((up & 0x00ff0000) >> 8);
> +
> +		/* xx xx xx V0 */
> +		px[2] |= ((vp & 0x00ff0000) << 8);
> +
> +
> +		/* Pixels 7, 8 */
> +		/* Y0 xx Y1 xx */
> +		px[3] = ((yp & 0x00ff0000) >> 16) | ((yp & 0xff000000) >> 8);
> +
> +		/* xx U0 xx xx */
> +		px[3] |= ((up & 0xff000000) >> 16);
> +
> +		/* xx xx xx V0 */
> +		px[3] |= (vp & 0xff000000);
> +
> +		/* clk the 8 pixels (4 dwords) into the videobuf image, now
> +		 * as YUYV422 */
> +
> +		*(ddst++) = px[0];
> +		*(ddst++) = px[1];
> +		*(ddst++) = px[2];
> +		*(ddst++) = px[3];
> +	}
> +}
> +

The above are format conversions!!! It should be at libv4l, and not on
Kernelspace.

> +			if (fmt->flags == ADV7441A_FORMAT_INTERLACED) {
> +
> +				vc8x0_process_video_field(
> +					buf->cpu_y_plane,
> +					buf->cpu_u_plane,
> +					buf->cpu_v_plane,
> +					vb_buf,
> +					0,
> +					channel->vbi_enabled,
> +					fmt);
> +				vc8x0_process_video_field(
> +					buf->cpu_y_plane,
> +					buf->cpu_u_plane,
> +					buf->cpu_v_plane,
> +					vb_buf,
> +					1,
> +					channel->vbi_enabled,
> +					fmt);

Those in-kernel software format changing logic is not allowed.
Instead, add it video formats parsing into libv4l and output 
the format here as provided by the hardware.

> +
> +				if (channel->vbi_enabled) {
> +					spin_lock(&channel->vbi_capture_lock);
> +					do {
> +						if (list_empty(&channel->vbi_capture)) {
> +							break;
> +						}
> +
> +						vbi_buf = list_entry(channel->vbi_capture.next,
> +							struct vc8x0_buffer, vb.queue);
> +						dst = videobuf_to_vmalloc(&vbi_buf->vb);
> +						if (!dst)
> +							break;
> +						vc8x0_process_vbi_field(buf->cpu_y_plane, vbi_buf, 0, fmt);
> +						vc8x0_process_vbi_field(buf->cpu_y_plane, vbi_buf, 1, fmt);

libv4l doesn't handle weird vbi formats, so, while this is ugly,
we might accept it if there isn't any other clean way of doing it.

> +
> +						do_gettimeofday(&vbi_buf->vb.ts);

We're not using gettimeofday() anymore, as the time is not
monotonic (e. g. it is affected by TZ). See the KS/2012 notes.

> +						list_del(&vbi_buf->vb.queue);
> +
> +						vbi_buf->vb.state = VIDEOBUF_DONE;
> +						wake_up(&vbi_buf->vb.done);
> +
> +						/* re-set the buffer timeout */
> +						mod_timer(&channel->vbi_timeout,
> +							jiffies + (HZ / 2));
> +
> +					} while (0);
> +					spin_unlock(&channel->vbi_capture_lock);
> +				}
> +
> +			} else
> +			if (fmt->flags == ADV7441A_FORMAT_PROGRESSIVE) {
> +				for (i = 0, j = 0; i < (vb_buf->vb.height *
> +					vb_buf->vb.width); i++, j += 2) {
> +					crc += *(buf->cpu_y_plane + i);
> +					*(pdst + j + 0) =
> +						*(buf->cpu_y_plane + i);
> +				}
> +				for (i = 0, j = 1; i < ((vb_buf->vb.height *
> +					vb_buf->vb.width) / 2); i++, j += 4) {
> +					*(pdst + j + 0) =
> +						*(buf->cpu_u_plane + i);
> +					*(pdst + j + 2) =
> +						*(buf->cpu_v_plane + i);
> +				}

> +	spin_unlock_irqrestore(&channel->dma_buffers_dpc_lock, flags);
> +
> +	/* BAM! The interrupt handler is now free to move on */
> +	/* BAM! The interrupt handler is now free to move on */
> +	/* BAM! The interrupt handler is now free to move on */
> +	/* BAM! The interrupt handler is now free to move on */
> +
> +	/* Now let's dequeue the full buffers */
> +	/* For each full buffer, send it to user space */
> +	spin_lock(&channel->dma_buffers_full_lock);

Oh: That bambambambam comments again.... Looks weird, and I bet it will
cause dead locks as well.

> +	list_for_each_safe(p, q, &channel->dma_buffers_full) {
> +
> +		buf = list_entry(p, struct vc8x0_dma_buffer, list);
> +
> +		vc8x0_video_buffer_dequeue(buf);
> +
> +		spin_lock_irqsave(&channel->dma_buffers_busy_lock, flags);
> +		buf->state = STATE_BUSY;
> +		list_move_tail(&buf->list, &channel->dma_buffers_busy);
> +		spin_unlock_irqrestore(&channel->dma_buffers_busy_lock, flags);
> +
> +	}
> +	spin_unlock(&channel->dma_buffers_full_lock);
> +}
> +
> +int vc8x0_video_irqhandler(struct vc8x0_dma_channel *channel)
> +{
> +	struct vc8x0_dev *dev = channel->dev;
> +	struct vc8x0_dma_buffer *buf, *ts_buf;
> +	int handled = 0;
> +
> +	u32 lastidx = 0, vid_pciaddr = 0, ts_pciaddr = 0;
> +	u32 buflen, reg;
> +	unsigned long flags;
> +
> +	/* Process the interrupt */
> +
> +	channel->buffers_processed++;
> +	buflen = channel->fmt->width * channel->fmt->height * 2;
> +	if (buflen > MAX_USER_VIDEO_BUFFER_SIZE) {
> +		pr_err("%s() buffer length is corrupt (%x).\n",
> +			__func__, buflen);
> +		goto badaddr;
> +	}
> +
> +	/* Find the finished frame and locate it's pci address */
> +	lastidx = vc_read32(channel->regs.m_nLastFrameIndex);
> +	if ((lastidx < 1) || (lastidx > MAX_USER_VIDEO_BUFFERS)) {
> +		pr_err("%s() lastidx out of range, critical (%x).\n",
> +			__func__, lastidx);
> +		goto badaddr;
> +	}
> +
> +	if (channel->lastidx == lastidx) {
> +		/* We've already processed this frame, skip it,
> +		 * duplicate interrupt.
> +		 */
> +		goto out;
> +	}
> +	channel->lastidx = lastidx;
> +
> +	if (lastidx == 9)
> +		lastidx = 1;
> +	else
> +		lastidx++;
> +
> +	lastidx--;
> +
> +	reg = channel->regs.m_nYDmaAddrL + (lastidx * 6 * sizeof(u32));
> +	vid_pciaddr = vc_read32(reg);
> +
> +	reg = channel->regs.m_nYTSDmaAddrL + (lastidx * 2 * sizeof(u32));
> +	ts_pciaddr = vc_read32(reg);
> +
> +	dprintk(2, "%s() buf completed, reg=%x, vid_physical 0x%x\n",
> +		__func__, reg, vid_pciaddr);
> +	dprintk(2, "%s() ts_physical 0x%x, lastidx=%d\n",
> +		__func__, ts_pciaddr, lastidx);
> +
> +	/* Find the video buffer by address */
> +	buf = vc8x0_buffer_busy_get_by_address(channel,
> +		vid_pciaddr, TYPE_VIDEO);
> +	if (buf == 0) {
> +		dprintk(1, "%s() No busy video dma buffer for address 0x%x\n",
> +			__func__, vid_pciaddr);
> +		channel->buffers_bad_address++;
> +		goto badaddr;
> +	}
> +	buf->used_len = buflen;
> +
> +	/* Find the timestamp buffer by address */
> +	ts_buf = vc8x0_buffer_busy_get_by_address(channel,
> +		ts_pciaddr, TYPE_TIMESTAMP);
> +	if (ts_buf == 0) {
> +		dprintk(1,
> +		"%s() No busy timestamp dma buffer for address 0x%x\n",
> +			__func__, ts_pciaddr);
> +		channel->buffers_bad_address++;
> +		goto badaddr;
> +	}
> +	ts_buf->used_len = 0;
> +	dprintk(2, "%s() bufs completed vid %p nr=%d, ts %p nr=%d\n",
> +		__func__, buf, buf->nr, ts_buf, ts_buf->nr);
> +
> +	vc8x0_timestamp_validate(ts_buf, lastidx);
> +
> +	/* Put the buffer on the DPC list. This list is spinlock held very
> +	 * briefly so we're not going to be held up.
> +	 * The only person that holds the DPC spinlock is the deferred work
> +	 * queue. The only person that holds the busy spinlock is the
> +	 * interrupt handler.
> +	 * These spinlocks are also held briefly for reporting purposes in other
> +	 * parts of the driver but they are very timely.
> +	 */
> +	spin_lock_irqsave(&channel->dma_buffers_busy_lock, flags);
> +
> +	/* Last, put the buffer on the DPC list for our deferred
> +	 * worker to process */
> +	spin_lock_irqsave(&channel->dma_buffers_dpc_lock, flags);

How many locks are you using on those IRQ code? 3 locks? more?

I would try to rewrite the entire code to use just one, as otherwise,
you'll get dead locks.

> +	buf->state = STATE_DPC;
> +	list_move_tail(&buf->list, &channel->dma_buffers_dpc);
> +	spin_unlock_irqrestore(&channel->dma_buffers_dpc_lock, flags);
> +
> +	spin_unlock_irqrestore(&channel->dma_buffers_busy_lock, flags);
> +
> +	handled++;
> +badaddr:
> +	if (handled) {
> +		if (video_during_irq == 1) {
> +			/* Process video now, zero latency */
> +			vc8x0_video_work_handler(&channel->workhandler);
> +		} else {
> +			/* Process video via a deferred queue, with
> +			 * possible latencies */
> +			schedule_work(&channel->workhandler);
> +		}
> +	}
> +out:
> +	return handled;
> +}
> +


> +
> +static const struct v4l2_queryctrl no_ctl = {
> +	.name  = "42",
> +	.flags = V4L2_CTRL_FLAG_DISABLED,
> +};
> +
> +static struct vc8x0_ctrl vc8x0_ctls[] = {
> +	{
> +		.v = {
> +			.id            = V4L2_CID_BRIGHTNESS,
> +			.name          = "Brightness",
> +			.minimum       = -127,
> +			.maximum       = 128,
> +			.step          = 1,
> +			.default_value = 128,
> +			.type          = V4L2_CTRL_TYPE_INTEGER,
> +		},
> +	}, {
> +		.v = {
> +			.id            = V4L2_CID_CONTRAST,
> +			.name          = "Contrast",
> +			.minimum       = 0,
> +			.maximum       = 255,
> +			.step          = 1,
> +			.default_value = 128,
> +			.type          = V4L2_CTRL_TYPE_INTEGER,
> +		},
> +	}, {
> +		.v = {
> +			.id            = V4L2_CID_SATURATION,
> +			.name          = "Saturation",
> +			.minimum       = 0,
> +			.maximum       = 255,
> +			.step          = 1,
> +			.default_value = 128,
> +			.type          = V4L2_CTRL_TYPE_INTEGER,
> +		},
> +	}, {
> +		.v = {
> +			.id            = V4L2_CID_HUE,
> +			.name          = "Hue",
> +			.minimum       = 0,
> +			.maximum       = 255,
> +			.step          = 1,
> +			.default_value = 0,
> +			.type          = V4L2_CTRL_TYPE_INTEGER,
> +		},
> +	}, {
> +		.v = {
> +			.id            = V4L2_CID_AUDIO_MUTE,
> +			.name          = "Mute",
> +			.minimum       = 0,
> +			.maximum       = 1,
> +			.step          = 1,
> +			.default_value = 0,
> +			.type          = V4L2_CTRL_TYPE_BOOLEAN,
> +		},
> +	}, {
> +		.v = {
> +			.id            = V4L2_CID_AUDIO_VOLUME,
> +			.name          = "Volume",
> +			.minimum       = -4,
> +			.maximum       = 20,
> +			.step          = 1,
> +			.default_value = 5,
> +			.type          = V4L2_CTRL_TYPE_INTEGER,
> +		},
> +	}
> +};
> +static const int VC8X0_CTLS = ARRAY_SIZE(vc8x0_ctls);
> +
> +/* Must be sorted from low to high control ID! */
> +static const u32 vc8x0_user_ctrls[] = {
> +	V4L2_CID_USER_CLASS,
> +	V4L2_CID_BRIGHTNESS,
> +	V4L2_CID_CONTRAST,
> +	V4L2_CID_SATURATION,
> +	V4L2_CID_HUE,
> +	V4L2_CID_AUDIO_VOLUME,
> +	V4L2_CID_AUDIO_MUTE,
> +	0
> +};
> +
> +static const u32 *ctrl_classes[] = {
> +	vc8x0_user_ctrls,
> +	NULL
> +};
> +
> +static int vc8x0_set_tvnorm(struct vc8x0_dma_channel *channel, v4l2_std_id norm)
> +{
> +	dprintk(1, "%s(norm = 0x%08x) name: [%s]\n",
> +		__func__,
> +		(unsigned int)norm,
> +		v4l2_norm_to_name(norm));
> +
> +	if (norm & V4L2_STD_ATSC)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +

> +static int vc8x0_ctrl_query(struct v4l2_queryctrl *qctrl)
> +{
> +	int i;
> +
> +	if (qctrl->id < V4L2_CID_BASE ||
> +	    qctrl->id >= V4L2_CID_LASTP1)
> +		return -EINVAL;
> +	for (i = 0; i < VC8X0_CTLS; i++)
> +		if (vc8x0_ctls[i].v.id == qctrl->id)
> +			break;
> +	if (i == VC8X0_CTLS) {
> +		*qctrl = no_ctl;
> +		return 0;
> +	}
> +	*qctrl = vc8x0_ctls[i].v;
> +	return 0;
> +}
> +




> +static int vidioc_enum_frameintervals(struct file *file, void *priv,
> +	struct v4l2_frmivalenum *f)
> +{
> +	struct vc8x0_fh *fh = priv;
> +	struct vc8x0_dma_channel *channel  = fh->channel;
> +	int framerates[4] = { 25, 30, 50, 60 };

You'll need to add some logic here to handle 60Hz and 59.97Hz difference.

> +
> +	dprintk(1, "%s(index=%d)\n", __func__, f->index);
> +
> +	if ((f->index < 0) || (f->index >= 4))
> +		return -EINVAL;
> +
> +	f->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> +	f->discrete.numerator = 1;
> +	f->discrete.denominator = framerates[f->index];
> +
> +	dprintk(1, "%s(index=%d) rate = %d/%d\n", __func__, f->index,
> +		f->discrete.numerator, f->discrete.denominator);
> +
> +	return 0;
> +}
> +


> +
> +static int vc8x0_querymenu(struct file *file, void *priv,
> +	struct v4l2_querymenu *m)
> +{
> +	return -EINVAL;

I think v4l2-compliance will not like it. Had you validate this driver
with the compliance tool? Could you please post the results?

> +}
> +
> +static int vc8x0_g_priority(struct file *file, void *priv,
> +	enum v4l2_priority *p)
> +{
> +	return -EINVAL;

Priority is handled by the V4L framework. Please implement it.

> +}
> +
> +static int vc8x0_log_status(struct file *file, void *priv)
> +{
> +	struct vc8x0_dma_channel *channel = ((struct vc8x0_fh *)priv)->channel;
> +	struct vc8x0_dev *dev = channel->dev;
> +
> +	v4l2_subdev_call(channel->sd_adv7441a, core, log_status);
> +	v4l2_subdev_call(dev->dma_channel[DMA_CHANNEL9].sd_pcm3052,
> +		core, log_status);
> +
> +	return 0;
> +}

I think this is only available with advanced debug.

> +
> +static const struct v4l2_file_operations video_fops = {
> +	.owner	       = THIS_MODULE,
> +	.open	       = vc8x0_video_open,
> +	.release       = vc8x0_video_close,
> +	.read	       = vc8x0_video_read,
> +	.poll          = vc8x0_video_poll,
> +	.mmap	       = vc8x0_video_mmap,
> +	.unlocked_ioctl = video_ioctl2,
> +};
> +
> +static const struct v4l2_ioctl_ops video_ioctl_ops = {
> +	.vidioc_querycap      = vidioc_querycap,
> +	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
> +	.vidioc_enum_frameintervals  = vidioc_enum_frameintervals,
> +	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
> +	.vidioc_try_fmt_vid_cap   = vc8x0_try_fmt_vid_cap,
> +	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
> +	.vidioc_reqbufs       = vidioc_reqbufs,
> +	.vidioc_querybuf      = vidioc_querybuf,
> +	.vidioc_qbuf          = vidioc_qbuf,
> +	.vidioc_dqbuf         = vidioc_dqbuf,
> +	.vidioc_s_std         = vc8x0_s_std,
> +	.vidioc_enum_input    = vidioc_enum_input,
> +	.vidioc_g_input       = vidioc_g_input,
> +	.vidioc_s_input       = vidioc_s_input,
> +	.vidioc_queryctrl     = vidioc_queryctrl,
> +	.vidioc_g_ctrl        = vidioc_g_ctrl,
> +	.vidioc_s_ctrl        = vidioc_s_ctrl,
> +	.vidioc_streamon      = vidioc_streamon,
> +	.vidioc_streamoff     = vidioc_streamoff,
> +	.vidioc_s_parm        = vc8x0_video_s_parm,
> +	.vidioc_g_parm        = vc8x0_video_g_parm,
> +/* Need this for VBI */
> +	.vidioc_g_fmt_vbi_cap   = vidioc_g_fmt_vbi_cap,
> +	.vidioc_g_std           = vc8x0_g_std,
> +	.vidioc_enumaudio	= vc8x0_g_audio,
> +	.vidioc_g_audio		= vc8x0_g_audio,
> +	.vidioc_s_audio		= vc8x0_s_audio,
> +	.vidioc_querymenu	= vc8x0_querymenu,
> +	.vidioc_g_priority	= vc8x0_g_priority,
> +	.vidioc_log_status	= vc8x0_log_status,

DV timings ioctl's are missing.

> +};
> +
> +static struct video_device vc8x0_vbi_template;
> +static struct video_device vc8x0_video_template = {
> +	.name		= "vc8x0-video",
> +	.fops		= &video_fops,
> +	.ioctl_ops	= &video_ioctl_ops,
> +#if 0
> +	/* If I set this to something valid then gstreamer fails
> +	 * to negotiate a reasonable std/fmt, but it fixes
> +	 * the v4l2 ATSC compliance test.
> +	 */
> +	.tvnorms	= V4L2_STD_NTSC_M,
> +#else
> +	/* We'll fail the ATSC compliance test but gstreamer will work. */
> +	.tvnorms	= 0,
> +#endif

That looks really weird. Ok, when HDMI is used, "NTSC" doesn't
make sense, but for the other inputs, it should be accepting it.


> +/* Enable this to enable bad register reads and verification */
> +#define ENABLE_BAD_READS 0
> +
> +/* Read 12 registers from the FPGA during interrupt handling */
> +#define ENABLE_MONITOR_REGISTERS 1
> +
> +/* ALSA adjustments */
> +#define ENABLE_ALSA 1
> +#define ENABLE_AUDIO_KEEPALIVE 1
> +#define ENABLE_AUDIO_DMA 1
> +#define ENABLE_ALSA_MIXER 1
> +#define ENABLE_ALSA_WORKAROUNDS 1

Oh... that defines appeared, finally! The best is to convert the
ones that actually make sense into CONFIG_foo, and maybe discarding
the others or convert them into debug stuff.

With that, I finished the review.

Please, next time you submit it, make sure to break the patch series
into something that makes easier for us to review. A big patch like
that is _really_ hard for review as, on the next step, I'll need to
re-read everything.

Thanks,
Mauro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[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