[PATCH v2] [media] at91: add Atmel Image Sensor Interface (ISI) support

Wu, Josh Josh.wu at atmel.com
Fri Jun 3 04:28:27 EDT 2011


Hi, Guennadi

Guennadi Liakhovetski wrote on May 29, 2011 4:25 AM

> Hi Josh

> Thanks for the update. A general note: I much prefer the new IO accessors and > register names and values - thanks for changing that!

Thank you for the reviewing. I also think this version is clearer. :)
Base on the suggestion from you and J.C, I will send out the version 3 soon.

> On Fri, 27 May 2011, Josh Wu wrote:

>> This patch is to enable Atmel Image Sensor Interface (ISI) driver support.
>> - Using soc-camera framework with videobuf2 dma-contig allocator
>> - Supporting video streaming of YUV packed format
>> - Tested on AT91SAM9M10G45-EK with OV2640
>> 
>> Signed-off-by: Josh Wu <josh.wu at atmel.com>
>> ---
>> Modified in V2 patch:
>> [snip]
>> +
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/fs.h>
>> +#include <linux/init.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/kernel.h>

> You had <linux/module.h> here in "v1," I think, it is needed for various
> MODULE_AUTHOR() etc. macros. Please, re-add.

I will add it.

>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +#include <linux/delay.h>
>> +
>> +#include <mach/board.h>
>> +#include <mach/cpu.h>

> I still don't understand, why you need these two. If unneeded, please, remove.

I forgot to remove it. I'll remove this.

>> +
>> +#include <media/videobuf2-dma-contig.h> #include <media/soc_camera.h> 
>> +#include <media/soc_mediabus.h> #include <media/atmel-isi.h>

> Alphabetic order here too, please.

I will change the order.

>> +
>> +#define MAX_BUFFER_NUMS			32

> "NUM" above doesn't need an "S" at the end - it's just a "number of buffers," > not "numbers."

sure, I will fix it.

>> +#define MAX_SUPPORT_WIDTH		2048
>> +#define MAX_SUPPORT_HEIGHT		2048
>> +#define VID_LIMIT_BYTES			(16 * 1024 * 1024)
>> +#define MIN_FRAME_RATE			15
>> +#define FRAME_INTERVAL_MILLI_SEC	(1000 / MIN_FRAME_RATE)
>> +
>> +/* ISI states */
>> +enum {
>> +	ISI_STATE_IDLE = 0,
>> +	ISI_STATE_READY,
>> +	ISI_STATE_WAIT_SOF,
>> +};
>> +
>> +/* Frame buffer descriptor */
>> +struct fbd {
>> +	/* Physical address of the frame buffer */
>> +	u32 fb_address;
>> +	/* DMA Control Register(only in HISI2) */
>> +	u32 dma_ctrl;

> Ok, several reviewers, including myself, asked you to remove #ifdef here, but > at least I didn't realise at that moment, that this struct describes >hareware-specific memory layout. So, how is it supposed to work now on platforms, >that don't have this field, i.e., !CONFIG_ARCH_AT91SAM9G45 >and !CONFIG_ARCH_AT91SAM9X5? Or are they not supported yet? Maybe you could >define two structs - later, when you also support other CPUs, and use a union >or something else?

This dma_ctrl is hardware related memory layout. In the ISI_V1 hardware (AT91SAM9263, AVR32, etc), there is no dma_ctrl in the descriptor. In the future I prefer to add another ISI_V1 fbd structure and a function to handle these two structures according to hardware. But in this version I think ISI_V1 hardware platform is not supported.

>> +	/* Physical address of the next fbd */
>> +	u32 next_fbd_address;
>> +};
>> +
>> +static void set_dma_ctrl(struct fbd *fb_desc, u32 ctrl) {
>> +	fb_desc->dma_ctrl = ctrl;
>> +}
>> +
>> +/* Frame buffer data */
>> +struct frame_buffer {
>> +	struct vb2_buffer vb;
>> +	struct fbd *p_fb_desc;
>> +	u32 fb_desc_phys;
>> +	struct list_head list;
>> +};
>> +
>> +struct atmel_isi {
>> +	/* Protects the access of variables shared with the ISR */
>> +	spinlock_t			lock;
>> +	void __iomem			*regs;
>> +
>> +	int				sequence;
>> +	/* State of the ISI module in capturing mode */
>> +	int				state;
>> +
>> +	/* Capture/streaming wait queue for waiting for SOF */
>> +	wait_queue_head_t		capture_wq;
>> +
>> +	struct vb2_alloc_ctx		*alloc_ctx;
>> +
>> +	/* Allocate descriptors for dma buffer use */
>> +	struct fbd			*p_fb_descriptors;
>> +	u32				fb_descriptors_phys;
>> +	int				index_fb_desc;
>> +
>> +	struct completion		isi_complete;

> You don't need to prefix members in structs, especially since you don't do this > with others. Just leave "complete" or something similar.

I will change the name.

>> [snip]
>> +
>> +	if (!buf->p_fb_desc) {
>> +		/* Initialize the dma descriptor */
>> +		buf->p_fb_desc = &isi->p_fb_descriptors[isi->index_fb_desc];
>> +		buf->fb_desc_phys = isi->fb_descriptors_phys +
>> +				isi->index_fb_desc * sizeof(struct fbd);
>> +
>> +		buf->p_fb_desc->fb_address = vb2_dma_contig_plane_paddr(vb, 0);
>> +		buf->p_fb_desc->next_fbd_address = 0;
>> +		set_dma_ctrl(buf->p_fb_desc, ISI_DMA_CTRL_WB);
>> +		isi->index_fb_desc++;
>> +		if (isi->index_fb_desc > MAX_BUFFER_NUMS) {
>> +			dev_err(icd->dev.parent, "Not enough dma descriptors.\n");

> Don't you have to check overflow _before_ using the index? Pointing the hardware > to an invalid location doesn't seem like a very good idea.

You are right. I will fix this.

>> +			return -EINVAL;
>> +		}
>> +	}
>> +	return 0;
>> +}
>> +
>> +static void buffer_cleanup(struct vb2_buffer *vb) {
>> +	struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
>> +	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
>> +	struct atmel_isi *isi = ici->priv;
>> +	struct frame_buffer *buf = container_of(vb, struct frame_buffer, vb);
>> +	unsigned long flags;
>> +
>> +	spin_lock_irqsave(&isi->lock, flags);
>> +	if (buf->p_fb_desc) {
>> +		buf->p_fb_desc = NULL;
>> +		buf->fb_desc_phys = 0;
>> +		isi->index_fb_desc--;
>> +		if (isi->index_fb_desc < 0)
>> +			dev_err(icd->dev.parent, "Invalid descriptors index.\n");

> ...and this would break, if you start initialising and cleaning up buffers out of order. Maybe you could just extend your private data

> struct isi_dmadesc {
> 	struct list_head	list;
>	struct fbd		*fbd;
> };

> struct atmel_isi {
> 	...
>	struct list_head	dmadesc_head;
>	struct isi_dmadesc	dmadesc[MAX_BUFFER_NUM];
>};

> and then in your atmel_isi_probe()

>	kzalloc(...);
>	dma_alloc_coherent(...);
>	for (i = 0; i < MAX_BUFFER_NUM; i++) {
>		isi->dmadesc[i].fbd = isi->p_fb_descriptors + i;
>		list_add(&isi->dmadesc[i].list, &isi->dmadesc_head);
>	}

> etc.

It seems no problem if the initializing buffer numbers is the same as cleaning buffers numbers. For example, if we initialized 4 buffers for video buffer queue. It should be ok as long as we cleaned 4 buffers. No matter the order of the buffers to be cleaned up.

And yes, add a list to track the buffer descriptor is better way, I will change this. The list is look like following:
struct isi_dma_desc {
	struct list_head list;
	struct fbd *p_fbd;
	u32 fbd_phys;
};
The fbd_phys is physical address of the structure fbd.

>> +	}
>> +	spin_unlock_irqrestore(&isi->lock, flags); }
>> +
>> [snip]
>> +	spin_lock_irq(&isi->lock);
>> +	isi->active = NULL;
>> +	/* Release all active buffers */
>> +	list_for_each_safe(pos, node, &isi->video_buffer_list) {
>> +		buf = list_entry(pos, struct frame_buffer, list);
>> +		list_del_init(&buf->list);
>> +		vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);

> list_for_entry_safe() would be even better;)

oh, I don't notice this function yet. I'll use it.

>> +	}
>> +	spin_unlock_irq(&isi->lock);
>> +
>> +	timeout = jiffies + FRAME_INTERVAL_MILLI_SEC * HZ;
>> +	/* Wait until the end of the current frame. */
>> +	while ((isi_readl(isi, ISI_STATUS) & ISI_CTRL_CDC) &&
>> [snip]
>> +	if (isi->icd)
>> +		return -EBUSY;
>> +
>> +	if (clk_enable(isi->pclk))
>> +		return -EIO;

> Better propagate error code from clk_enable().

Sure. I will fix it

>> [snip]
>> +
>> +	bus_flags = make_bus_param(isi);
>> +	common_flags = soc_camera_bus_param_compatible(camera_flags, bus_flags);
>> +	dev_dbg(icd->dev.parent, "Flags cam: 0x%lx host: 0x%lx common: 0x%lx\n",
>> +		camera_flags, bus_flags, common_flags);
>> +	if (!common_flags)
>> +		return -EINVAL;

> Below seems overengineered;) In general, I think, your ISI controller supports > both active low and high HSYNC and VSYNC and falling and rising PCLK. That's > what your make_bus_param() is suggesting and those are the flags, that you send > to the client. On top of that your platform data flags hsync_act_low, >vsync_act_low, and pclk_act_falling are only recommendations for ambiguous >cases, when the client also supports both polarities, right? 

Right. That is exact what I want to do.

> Otherwise, if the client only supports, say, PCLK rising, it has precedence >over the platform preference. Now, below you do something different

>> +
>> +	/* Make choises, based on platform preferences */
>> +	if ((common_flags & SOCAM_HSYNC_ACTIVE_HIGH) &&
>> +	    (common_flags & SOCAM_HSYNC_ACTIVE_LOW)) {
>> +		if (isi->pdata->hsync_act_low)
>> +			common_flags &= ~SOCAM_HSYNC_ACTIVE_HIGH;
>> +		else
>> +			common_flags &= ~SOCAM_HSYNC_ACTIVE_LOW;
>> +	}
>> +
>> +	if ((common_flags & SOCAM_VSYNC_ACTIVE_HIGH) &&
>> +	    (common_flags & SOCAM_VSYNC_ACTIVE_LOW)) {
>> +		if (isi->pdata->vsync_act_low)
>> +			common_flags &= ~SOCAM_VSYNC_ACTIVE_HIGH;
>> +		else
>> +			common_flags &= ~SOCAM_VSYNC_ACTIVE_LOW;
>> +	}
>> +
>> +	if ((common_flags & SOCAM_PCLK_SAMPLE_RISING) &&
>> +	    (common_flags & SOCAM_PCLK_SAMPLE_FALLING)) {
>> +		if (isi->pdata->pclk_act_falling)
>> +			common_flags &= ~SOCAM_PCLK_SAMPLE_RISING;
>> +		else
>> +			common_flags &= ~SOCAM_PCLK_SAMPLE_FALLING;
>> +	}

> The three constructs above _already_ select all three polarities in the optimal > way - in ambiguous cases they use platform preferences to select one of the > two polarities. Now, all you need to do is use your common_flags to configure > the ISI:

>> +
>> +	ret = icd->ops->set_bus_param(icd, common_flags);
>> +	if (ret < 0) {
>> +		dev_dbg(icd->dev.parent, "Camera set_bus_param(%lx) returned %d\n",
>> +			common_flags, ret);
>> +		return ret;
>> +	}
>> +
>> +	/* set bus param for ISI */
>> +	hsync_act_low = isi->pdata->hsync_act_low;
>> +	vsync_act_low = isi->pdata->vsync_act_low;
>> +	pclk_act_falling = isi->pdata->pclk_act_falling;

> These variables are not needed at all.

I will remove this.

>> +
>> +	if (common_flags & SOCAM_HSYNC_ACTIVE_LOW)
>> +		hsync_act_low = 1;
>> +	if (common_flags & SOCAM_VSYNC_ACTIVE_LOW)
>> +		vsync_act_low = 1;
>> +	if (common_flags & SOCAM_PCLK_SAMPLE_FALLING)
>> +		pclk_act_falling = 1;
>> +
>> +	if (hsync_act_low)
>> +		cfg1 |= ISI_CFG1_HSYNC_POL_ACTIVE_LOW;

> This should become

> +	if (common_flags & SOCAM_HSYNC_ACTIVE_LOW)
> +		cfg1 |= ISI_CFG1_HSYNC_POL_ACTIVE_LOW;

> similar for the other two.

You are right. I will change the code.

>> [snip]
>> +---------------------------------------------------------------------
>> +--*/ static int __exit atmel_isi_remove(struct platform_device *pdev)

> __devexit

I will fix it.

>> [snip]
>> +	if (dev->platform_data) {
>> +		pdata = (struct isi_platform_data *) dev->platform_data;

> superfluous cast

I will remove it.

>> +	} else {
>> +		/* Set default ISI platform data */
>> +		isi_default_data.frate		= ISI_CFG1_FRATE_CAPTURE_ALL;
>> +		isi_default_data.has_emb_sync	= 0;
>> +		isi_default_data.emb_crc_sync	= 0;
>> +		isi_default_data.hsync_act_low	= 0;
>> +		isi_default_data.vsync_act_low	= 0;
>> +		isi_default_data.pclk_act_falling = 0;
>> +		isi_default_data.isi_full_mode	= 1;
>> +		isi_default_data.data_width_flags = ISI_DATAWIDTH_8 |
>> +				ISI_DATAWIDTH_10;

> Well, I don't like it. Let's put it straight: please, remove this default >platform data and make platform data compulsory. I don't think we're asking too >much from platform developers with this. Maybe Oopsing without platform data, >like some drivers do, is not very nice, but we can definitely just bail out with >an error.

Yes. I will do this way, if no platform_data provide, I will return an error directly.

>> +
>> +		pdata = &isi_default_data;
>> +		dev_info(&pdev->dev,
>> +			"No config available using default values\n");
>> +	}
>> +
>> +	isi->pdata = pdata;
>> +	isi->platform_flags = pdata->data_width_flags;
>> +	if (isi->platform_flags == 0)
>> +		isi->platform_flags = ISI_DATAWIDTH_8;

> Same here. Datawidth is only known to the platform, so, it _must_ be set. 
> And you don't need this extra field isi->platform_flags - you have the 
> complete pdata always with you.

ditto. And I will remove the isi->platform_flags too.

>> [snip]
>> +static struct platform_driver atmel_isi_driver = {
>> +	.probe		= atmel_isi_probe,
>> +	.remove		= __exit_p(atmel_isi_remove),

> __devexit_p()

I will fix it.

>> [snip]
>> +
>> +/* Definition for isi_platform_data */
>> +#define ISI_DATAWIDTH_8		0x40
>> +#define ISI_DATAWIDTH_10	0x80

> Hm, why don't you begin with 0x01, 0x02?

I'll fix it.

>> +struct isi_platform_data {
>> +	u8 has_emb_sync;
>> +	u8 emb_crc_sync;
>> +	u8 hsync_act_low;
>> +	u8 vsync_act_low;
>> +	u8 pclk_act_falling;
>> +	u8 isi_full_mode;
>> +	u32 data_width_flags;
>> +	/* Using for ISI_CFG1 */
>> +	u32 frate;
>> +
>> +	/* TODO: not support yet */
>> +	u8 gs_mode;
>> +	u8 pixfmt;
>> +	u8 sfd;
>> +	u8 sld;
>> +	u8 thmask;

> Please, remove these, if unused.

Ok, I will remove it.

>> +};
>> +
>> +#endif /* __ATMEL_ISI_H__ */
>> -- 
>> 1.6.3.3
>> 

> Looks much better now!

Thanks you again.

Best Regards,
Josh Wu





More information about the linux-arm-kernel mailing list