This set consists of the following patches:
1/6 SoC Camera: add driver for OMAP1 camera interface 2/6 OMAP1: Add support for SoC camera interface 3/6 SoC Camera: add driver for OV6650 sensor 4/6 SoC Camera: add support for g_parm / s_parm operations 5/6 OMAP1: Amstrad Delta: add support for on-board camera 6/6 OMAP1: Amstrad Delta: add camera controlled LEDS trigger
arch/arm/mach-omap1/board-ams-delta.c | 69 + arch/arm/mach-omap1/devices.c | 43 arch/arm/mach-omap1/include/mach/camera.h | 8 drivers/media/video/Kconfig | 14 drivers/media/video/Makefile | 2 drivers/media/video/omap1_camera.c | 1781 ++++++++++++++++++++++++++++++ drivers/media/video/ov6650.c | 1242 ++++++++++++++++++++ drivers/media/video/soc_camera.c | 18 include/media/omap1_camera.h | 35 include/media/v4l2-chip-ident.h | 1 10 files changed, 3213 insertions(+)
This is a V4L2 driver for TI OMAP1 SoC camera interface.
Both videobuf-dma versions are supported, contig and sg, selectable with a module option. The former uses less processing power, but often fails to allocate contignuous buffer memory. The latter is free of this problem, but generates tens of DMA interrupts per frame. If contig memory allocation ever fails, the driver falls back to sg automatically on next open, but still can be switched back to contig manually. Both paths work stable for me, even under heavy load, on my OMAP1510 based Amstrad Delta videophone, that is the oldest, least powerfull OMAP1 implementation.
The interface generally works in pass-through mode. Since input data byte endianess can be swapped, it provides up to two v4l2 pixel formats per each of several soc_mbus formats that have their swapped endian counterparts.
Boards using this driver can provide it with the followning information: - if and what freqency clock is expected by an on-board camera sensor, - what is the maximum pixel clock that should be accepted from the sensor, - what is the polarity of the sensor provided pixel clock, - if the interface GPIO line is connected to a sensor reset/powerdown input and what is the input polarity.
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl --- Friday 30 July 2010 13:07:42 Guennadi Liakhovetski wrote:
So, I think, a very welcome improvement to the driver would be a cleaner separation between the two cases. Don't try that much to reuse the code as much as possible. Would be much better to have clean separation between the two implementations - whether dynamically switchable at runtime or at config time - would be best to separate the two implementations to the point, where each of them becomes understandable and maintainable.
Guennadi, I've tried to rearrange them spearated, as you requested, but finally decided to keep them together, as before, only better documented and cleaned up as much as possible. I'm rather satisfied with the result, but if you think it is still not enough understandable and maintainable, I'll take one more iteration and split both paths.
Besides, there are a few my not yet answered questions or suggestions (see http://www.spinics.net/lists/linux-media/msg21615.html for original message):
Friday 30 July 2010 20:49:05 Janusz Krzysztofik wrote:
Friday 30 July 2010 13:07:42 Guennadi Liakhovetski wrote:
On Sun, 18 Jul 2010, Janusz Krzysztofik wrote:
This is a V4L2 driver for TI OMAP1 SoC camera interface.
...
- u32 reg_cache[OMAP1_CAMERA_IOSIZE / sizeof(u32)];
Don't think you'd lose much performance without cache, for that the code would become less error-prone.
The reason is my bad experience with my hardware audio interface. For my rationale regarding OMAP McBSP register caching, please look at this commit: http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdif... or the foregoing discussions (links both to my initial and final submitions): http://thread.gmane.org/gmane.linux.alsa.devel/65675 http://thread.gmane.org/gmane.linux.ports.arm.omap/29708
However, I haven't noticed any similiar flaws in camera interface yet, so if you think that caching would not be helpful in any possible future (context save/restore) enhancements here, then I can drop it.
...
Don't you have to update the cache on a direct read?
If I do it unconditionally, I'd loose the read error prevention function of register caching idea.
I've kept register caching for now, only replaced a few not intentional reads from hardware with more sensible reads from cache. Please confirm if you prefere me dropping register caching anyways.
...
+static void videobuf_done(struct omap1_cam_dev *pcdev,
enum videobuf_state result)
+{
- ...
- if (waitqueue_active(&vb->done)) {
I'm not sure this is a good idea. Why are you reusing the buffer, if noone is waiting for it _now_? It can well be, that the task is doing something else atm. Like processing the previous frame. Or it has been preempted by another process, before it called poll() or select() on your device?
I've introduced this way of videobuf handling after learning, form the Documentation/video4linux/videobuf, what the first step of filling a buffer should be:
"- Obtain the next available buffer and make sure that somebody is actually waiting for it."
Since I had no idea how I could implement it, I decided to do it a bit differently: start filling a buffer unconditionally, then drop its contents if not awaited by any user when done
...
- } else if (pcdev->ready) {
dev_dbg(dev, "%s: nobody waiting on videobuf, swap with next\n",
__func__);
Not sure this is a wise decision... If there are more buffers in the queue, they stay there waiting. Best remove this waitqueue_active() check entirely, just complete buffers and wake up the user regardless. If no more buffers - go to sleep.
If you think I don't have to follow the above mentioned requirement for not servicing a buffer unless someone is waiting for it, then yes, I'll drop these overcomplicated bits. Please confirm.
One more reference to Documentation/video4linux/videobuf:
"It is equally possible that nobody is yet interested in the buffer; the driver should not remove it from the list or fill it until a process is waiting on it."
Please review my now better commented code again, and confirm if you still expect me to rearrange it, ignoring the documentation provided requirements.
...
+static int omap1_cam_set_crop(struct soc_camera_device *icd,
struct v4l2_crop *crop)
+{ ...
- icd->user_width = rect->width;
- icd->user_height = rect->height;
No, this is wrong. user_width and user_height are _user_ sizes, i.e., a result sensor cropping and scaling and host cropping and scaling. Whereas set_crop sets sensor input rectangle.
I'm not sure if I understand what you mean. Should I drop these assignments? Or correct them? If yes, how they should be calculated?
In Documentation/video4linux/soc-camera.txt, you say: "The core updates these fields upon successful completion of a .s_fmt() call, but if these fields change elsewhere, e.g., during .s_crop() processing, the host driver is responsible for updating them."
I thought this is the case we're dealing with here.
Please confirm if I my understanding of what the documentation says is wrong and I should really correct the above.
Thanks, Janusz
v1->v2 changes:
requested by Guennadi Liakhovetski (thanks!): - first try contig, and if it fails - fall back to sg; invalidates next two, - invalidated: Kconfig: VIDEO_OMAP1_SG: not need "if", the "depends on" should suffice, - invalidated: include both <media/videobuf-dma-contig.h> and <media/videobuf-dma-sg.h> headers, - extensively document buffer manipulations, better yet clean it up, - a copyright / licence header was missing form a header file, - need to #include <linux/bitops.h> if using BIT() macro, - don't need macros representing frequencies - use numbers directly, - add a few missing "static" qualifiers, - use u32 type for register content handling, - some cached registers were unnecessarily read from the hardware directly, - use true/false constants instead of 0/1 for booleans, - avoid assigning variables inside other constructs, - don't need to test if RAZ_FIFO is cleared, - no need to split "\n" to a new line, don't worry about > 80 characters, - don't increment field_count in case of a VIDEOBUF_ERROR, - adjust mbus format codes to the new names, - make is_dma_aligned() return value a bool, - no need to align frame line lenghts to integer number of DMA elements, - drop a few superflous whitespace, braces, etc., - use correct multiline comment style, - follow some good-style rules when defining a multi-line function-like macro, - drop tests for impossible bool < 0, - that's not an error when S_FMT sets a format different from what the user has requested, just use whatever you managed to configure, - as a general rule, try_fmt shouldn't fail, - use sensor_reset() instead of duplicating its code, - first shut down the hardware, and only then unconfigure software,
suggested by Ralph Corderoy (thanks!): - include vb->state's value in the debug messages instead of just "unknown", - correct a few print formats, - don't use goto if return is all that needs to be done,
other: - correct a few issues reported with "checkpatch.pl --strict". - refreshed against linux-2.6.36-rc3
drivers/media/video/Kconfig | 8 drivers/media/video/Makefile | 1 drivers/media/video/omap1_camera.c | 1781 +++++++++++++++++++++++++++++++++++++ include/media/omap1_camera.h | 35 4 files changed, 1825 insertions(+)
diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Kconfig linux-2.6.36-rc3/drivers/media/video/Kconfig --- linux-2.6.36-rc3.orig/drivers/media/video/Kconfig 2010-09-03 22:29:37.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/Kconfig 2010-09-09 17:55:52.000000000 +0200 @@ -890,6 +890,14 @@ config VIDEO_SH_MOBILE_CEU ---help--- This is a v4l2 driver for the SuperH Mobile CEU Interface
+config VIDEO_OMAP1 + tristate "OMAP1 Camera Interface driver" + depends on VIDEO_DEV && ARCH_OMAP1 && SOC_CAMERA + select VIDEOBUF_DMA_CONTIG + select VIDEOBUF_DMA_SG + ---help--- + This is a v4l2 driver for the TI OMAP1 camera interface + config VIDEO_OMAP2 tristate "OMAP2 Camera Capture Interface driver" depends on VIDEO_DEV && ARCH_OMAP2 diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Makefile linux-2.6.36-rc3/drivers/media/video/Makefile --- linux-2.6.36-rc3.orig/drivers/media/video/Makefile 2010-09-03 22:29:37.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/Makefile 2010-09-09 17:55:52.000000000 +0200 @@ -163,6 +163,7 @@ obj-$(CONFIG_VIDEO_MX3) += mx3_camera. obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o +obj-$(CONFIG_VIDEO_OMAP1) += omap1_camera.o obj-$(CONFIG_VIDEO_SAMSUNG_S5P_FIMC) += s5p-fimc/
obj-$(CONFIG_ARCH_DAVINCI) += davinci/ diff -upr linux-2.6.36-rc3.orig/drivers/media/video/omap1_camera.c linux-2.6.36-rc3/drivers/media/video/omap1_camera.c --- linux-2.6.36-rc3.orig/drivers/media/video/omap1_camera.c 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/omap1_camera.c 2010-09-09 15:58:22.000000000 +0200 @@ -0,0 +1,1781 @@ +/* + * V4L2 SoC Camera driver for OMAP1 Camera Interface + * + * Copyright (C) 2010, Janusz Krzysztofik jkrzyszt@tis.icnet.pl + * + * Based on V4L2 Driver for i.MXL/i.MXL camera (CSI) host + * Copyright (C) 2008, Paulius Zaleckas paulius.zaleckas@teltonika.lt + * Copyright (C) 2009, Darius Augulis augulis.darius@gmail.com + * + * Based on PXA SoC camera driver + * Copyright (C) 2006, Sascha Hauer, Pengutronix + * Copyright (C) 2008, Guennadi Liakhovetski kernel@pengutronix.de + * + * Hardware specific bits initialy based on former work by Matt Callow + * drivers/media/video/omap/omap1510cam.c + * Copyright (C) 2006 Matt Callow + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/version.h> + +#include <media/omap1_camera.h> +#include <media/soc_camera.h> +#include <media/soc_mediabus.h> +#include <media/videobuf-dma-contig.h> +#include <media/videobuf-dma-sg.h> + +#include <plat/dma.h> + + +#define DRIVER_NAME "omap1-camera" +#define VERSION_CODE KERNEL_VERSION(0, 0, 1) + + +/* + * --------------------------------------------------------------------------- + * OMAP1 Camera Interface registers + * --------------------------------------------------------------------------- + */ + +#define REG_CTRLCLOCK 0x00 +#define REG_IT_STATUS 0x04 +#define REG_MODE 0x08 +#define REG_STATUS 0x0C +#define REG_CAMDATA 0x10 +#define REG_GPIO 0x14 +#define REG_PEAK_COUNTER 0x18 + +/* CTRLCLOCK bit shifts */ +#define LCLK_EN BIT(7) +#define DPLL_EN BIT(6) +#define MCLK_EN BIT(5) +#define CAMEXCLK_EN BIT(4) +#define POLCLK BIT(3) +#define FOSCMOD_SHIFT 0 +#define FOSCMOD_MASK (0x7 << FOSCMOD_SHIFT) +#define FOSCMOD_12MHz 0x0 +#define FOSCMOD_6MHz 0x2 +#define FOSCMOD_9_6MHz 0x4 +#define FOSCMOD_24MHz 0x5 +#define FOSCMOD_8MHz 0x6 + +/* IT_STATUS bit shifts */ +#define DATA_TRANSFER BIT(5) +#define FIFO_FULL BIT(4) +#define H_DOWN BIT(3) +#define H_UP BIT(2) +#define V_DOWN BIT(1) +#define V_UP BIT(0) + +/* MODE bit shifts */ +#define RAZ_FIFO BIT(18) +#define EN_FIFO_FULL BIT(17) +#define EN_NIRQ BIT(16) +#define THRESHOLD_SHIFT 9 +#define THRESHOLD_MASK (0x7f << THRESHOLD_SHIFT) +#define DMA BIT(8) +#define EN_H_DOWN BIT(7) +#define EN_H_UP BIT(6) +#define EN_V_DOWN BIT(5) +#define EN_V_UP BIT(4) +#define ORDERCAMD BIT(3) + +#define IRQ_MASK (EN_V_UP | EN_V_DOWN | EN_H_UP | EN_H_DOWN | \ + EN_NIRQ | EN_FIFO_FULL) + +/* STATUS bit shifts */ +#define HSTATUS BIT(1) +#define VSTATUS BIT(0) + +/* GPIO bit shifts */ +#define CAM_RST BIT(0) + +/* end of OMAP1 Camera Interface registers */ + + +#define SOCAM_BUS_FLAGS (SOCAM_MASTER | \ + SOCAM_HSYNC_ACTIVE_HIGH | SOCAM_VSYNC_ACTIVE_HIGH | \ + SOCAM_PCLK_SAMPLE_RISING | SOCAM_PCLK_SAMPLE_FALLING | \ + SOCAM_DATA_ACTIVE_HIGH | SOCAM_DATAWIDTH_8) + + +#define FIFO_SIZE ((THRESHOLD_MASK >> THRESHOLD_SHIFT) + 1) +#define FIFO_SHIFT __fls(FIFO_SIZE) + +#define DMA_BURST_SHIFT (1 + OMAP_DMA_DATA_BURST_4) +#define DMA_BURST_SIZE (1 << DMA_BURST_SHIFT) + +#define DMA_ELEMENT_SHIFT OMAP_DMA_DATA_TYPE_S32 +#define DMA_ELEMENT_SIZE (1 << DMA_ELEMENT_SHIFT) + +#define DMA_FRAME_SHIFT_CONTIG (FIFO_SHIFT - 1) +#define DMA_FRAME_SHIFT_SG DMA_BURST_SHIFT + +#define DMA_FRAME_SHIFT(x) (x ? DMA_FRAME_SHIFT_SG : \ + DMA_FRAME_SHIFT_CONTIG) +#define DMA_FRAME_SIZE(x) (1 << DMA_FRAME_SHIFT(x)) +#define DMA_SYNC OMAP_DMA_SYNC_FRAME +#define THRESHOLD_LEVEL DMA_FRAME_SIZE + + +#define MAX_VIDEO_MEM 4 /* arbitrary video memory limit in MB */ + + +/* + * Structures + */ + +/* buffer for one video frame */ +struct omap1_cam_buf { + struct videobuf_buffer vb; + enum v4l2_mbus_pixelcode code; + int inwork; + struct scatterlist *sgbuf; + int sgcount; + int bytes_left; + enum videobuf_state result; +}; + +struct omap1_cam_dev { + struct soc_camera_host soc_host; + struct soc_camera_device *icd; + struct clk *clk; + + unsigned int irq; + void __iomem *base; + + int dma_ch; + + struct omap1_cam_platform_data *pdata; + struct resource *res; + unsigned long pflags; + unsigned long camexclk; + + struct list_head capture; + + /* lock used to protect videobuf */ + spinlock_t lock; + + u32 reg_cache[OMAP1_CAMERA_IOSIZE / + sizeof(u32)]; + + /* Pointers to DMA buffers */ + struct omap1_cam_buf *active; + struct omap1_cam_buf *ready; + + enum omap1_cam_vb_mode vb_mode; + int (*mmap_mapper)(struct videobuf_queue *q, + struct videobuf_buffer *buf, + struct vm_area_struct *vma); +}; + + +static void cam_write(struct omap1_cam_dev *pcdev, u16 reg, u32 val) +{ + pcdev->reg_cache[reg / sizeof(u32)] = val; + __raw_writel(val, pcdev->base + reg); +} + +static u32 cam_read(struct omap1_cam_dev *pcdev, u16 reg, bool from_cache) +{ + return !from_cache ? __raw_readl(pcdev->base + reg) : + pcdev->reg_cache[reg / sizeof(u32)]; +} + +#define CAM_READ(pcdev, reg) \ + cam_read(pcdev, REG_##reg, false) +#define CAM_WRITE(pcdev, reg, val) \ + cam_write(pcdev, REG_##reg, val) +#define CAM_READ_CACHE(pcdev, reg) \ + cam_read(pcdev, REG_##reg, true) + +/* + * Videobuf operations + */ +static int omap1_videobuf_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct soc_camera_device *icd = vq->priv_data; + int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, + icd->current_fmt->host_fmt); + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + + if (bytes_per_line < 0) + return bytes_per_line; + + *size = bytes_per_line * icd->user_height; + + if (!*count || *count < OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode)) + *count = OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode); + + if (*size * *count > MAX_VIDEO_MEM * 1024 * 1024) + *count = (MAX_VIDEO_MEM * 1024 * 1024) / *size; + + dev_dbg(icd->dev.parent, + "%s: count=%d, size=%d\n", __func__, *count, *size); + + return 0; +} + +static void free_buffer(struct videobuf_queue *vq, struct omap1_cam_buf *buf, + enum omap1_cam_vb_mode vb_mode) +{ + struct videobuf_buffer *vb = &buf->vb; + + BUG_ON(in_interrupt()); + + videobuf_waiton(vb, 0, 0); + + if (vb_mode == CONTIG) { + videobuf_dma_contig_free(vq, vb); + } else { + struct soc_camera_device *icd = vq->priv_data; + struct device *dev = icd->dev.parent; + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + videobuf_dma_unmap(dev, dma); + videobuf_dma_free(dma); + } + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static int omap1_videobuf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct soc_camera_device *icd = vq->priv_data; + struct omap1_cam_buf *buf = container_of(vb, struct omap1_cam_buf, vb); + int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, + icd->current_fmt->host_fmt); + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + int ret; + + if (bytes_per_line < 0) + return bytes_per_line; + + WARN_ON(!list_empty(&vb->queue)); + + BUG_ON(NULL == icd->current_fmt); + + buf->inwork = 1; + + if (buf->code != icd->current_fmt->code || vb->field != field || + vb->width != icd->user_width || + vb->height != icd->user_height) { + buf->code = icd->current_fmt->code; + vb->width = icd->user_width; + vb->height = icd->user_height; + vb->field = field; + vb->state = VIDEOBUF_NEEDS_INIT; + } + + vb->size = bytes_per_line * vb->height; + + if (vb->baddr && vb->bsize < vb->size) { + ret = -EINVAL; + goto out; + } + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + ret = videobuf_iolock(vq, vb, NULL); + if (ret) + goto fail; + + vb->state = VIDEOBUF_PREPARED; + } + buf->inwork = 0; + + return 0; +fail: + free_buffer(vq, buf, pcdev->vb_mode); +out: + buf->inwork = 0; + return ret; +} + +static void set_dma_dest_params(int dma_ch, struct omap1_cam_buf *buf, + enum omap1_cam_vb_mode vb_mode) +{ + dma_addr_t dma_addr; + unsigned int block_size; + + if (vb_mode == CONTIG) { + dma_addr = videobuf_to_dma_contig(&buf->vb); + block_size = buf->vb.size; + } else { + if (WARN_ON(!buf->sgbuf)) { + buf->result = VIDEOBUF_ERROR; + return; + } + dma_addr = sg_dma_address(buf->sgbuf); + if (WARN_ON(!dma_addr)) { + buf->sgbuf = NULL; + buf->result = VIDEOBUF_ERROR; + return; + } + block_size = sg_dma_len(buf->sgbuf); + if (WARN_ON(!block_size)) { + buf->sgbuf = NULL; + buf->result = VIDEOBUF_ERROR; + return; + } + if (unlikely(buf->bytes_left < block_size)) + block_size = buf->bytes_left; + if (WARN_ON(dma_addr & (DMA_FRAME_SIZE(vb_mode) * + DMA_ELEMENT_SIZE - 1))) { + dma_addr = ALIGN(dma_addr, DMA_FRAME_SIZE(vb_mode) * + DMA_ELEMENT_SIZE); + block_size &= ~(DMA_FRAME_SIZE(vb_mode) * + DMA_ELEMENT_SIZE - 1); + } + buf->bytes_left -= block_size; + buf->sgcount++; + } + + omap_set_dma_dest_params(dma_ch, + OMAP_DMA_PORT_EMIFF, OMAP_DMA_AMODE_POST_INC, dma_addr, 0, 0); + omap_set_dma_transfer_params(dma_ch, + OMAP_DMA_DATA_TYPE_S32, DMA_FRAME_SIZE(vb_mode), + block_size >> (DMA_FRAME_SHIFT(vb_mode) + DMA_ELEMENT_SHIFT), + DMA_SYNC, 0, 0); +} + +static struct omap1_cam_buf *prepare_next_vb(struct omap1_cam_dev *pcdev) +{ + struct omap1_cam_buf *buf; + + /* + * If there is already a buffer pointed out by the pcdev->ready, + * (re)use it, otherwise try to fetch and configure a new one. + */ + buf = pcdev->ready; + if (!buf) { + if (list_empty(&pcdev->capture)) + return buf; + buf = list_entry(pcdev->capture.next, + struct omap1_cam_buf, vb.queue); + buf->vb.state = VIDEOBUF_ACTIVE; + pcdev->ready = buf; + list_del_init(&buf->vb.queue); + } + + if (pcdev->vb_mode == CONTIG) { + /* + * In CONTIG mode, we can safely enter next buffer parameters + * into the DMA programming register set after the DMA + * has already been activated on the previous buffer + */ + set_dma_dest_params(pcdev->dma_ch, buf, pcdev->vb_mode); + } else { + /* + * In SG mode, the above is not safe since there are probably + * a bunch of sgbufs from previous sglist still pending. + * Instead, mark the sglist fresh for the upcoming + * try_next_sgbuf(). + */ + buf->sgbuf = NULL; + } + + return buf; +} + +static struct scatterlist *try_next_sgbuf(int dma_ch, struct omap1_cam_buf *buf) +{ + struct scatterlist *sgbuf; + + if (likely(buf->sgbuf)) { + /* current sglist is active */ + if (unlikely(!buf->bytes_left)) { + /* indicate sglist complete */ + sgbuf = NULL; + } else { + /* process next sgbuf */ + sgbuf = sg_next(buf->sgbuf); + if (WARN_ON(!sgbuf)) { + buf->result = VIDEOBUF_ERROR; + } else if (WARN_ON(!sg_dma_len(sgbuf))) { + sgbuf = NULL; + buf->result = VIDEOBUF_ERROR; + } + } + buf->sgbuf = sgbuf; + } else { + /* sglist is fresh, initialize it before using */ + struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb); + + sgbuf = dma->sglist; + if (!(WARN_ON(!sgbuf))) { + buf->sgbuf = sgbuf; + buf->sgcount = 0; + buf->bytes_left = buf->vb.size; + buf->result = VIDEOBUF_DONE; + } + } + if (sgbuf) + /* + * Put our next sgbuf parameters (address, size) + * into the DMA programming register set. + */ + set_dma_dest_params(dma_ch, buf, SG); + + return sgbuf; +} + +static void start_capture(struct omap1_cam_dev *pcdev) +{ + struct omap1_cam_buf *buf = pcdev->active; + u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); + u32 mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN; + + if (WARN_ON(!buf)) + return; + + /* + * Enable start of frame interrupt, which we will use for activating + * our end of frame watchdog when capture actually starts. + */ + mode |= EN_V_UP; + + if (unlikely(ctrlclock & LCLK_EN)) + /* stop pixel clock before FIFO reset */ + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); + /* reset FIFO */ + CAM_WRITE(pcdev, MODE, mode | RAZ_FIFO); + + omap_start_dma(pcdev->dma_ch); + + if (pcdev->vb_mode == SG) { + /* + * In SG mode, it's a good moment for fetching next sgbuf + * from the current sglist and, if available, already putting + * its parameters into the DMA programming register set. + */ + try_next_sgbuf(pcdev->dma_ch, buf); + } + + /* (re)enable pixel clock */ + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | LCLK_EN); + /* release FIFO reset */ + CAM_WRITE(pcdev, MODE, mode); +} + +static void suspend_capture(struct omap1_cam_dev *pcdev) +{ + u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); + + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); + omap_stop_dma(pcdev->dma_ch); +} + +static void disable_capture(struct omap1_cam_dev *pcdev) +{ + u32 mode = CAM_READ_CACHE(pcdev, MODE); + + CAM_WRITE(pcdev, MODE, mode & ~(IRQ_MASK | DMA)); +} + +static void omap1_videobuf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct soc_camera_device *icd = vq->priv_data; + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + struct omap1_cam_buf *buf; + u32 mode; + + list_add_tail(&vb->queue, &pcdev->capture); + vb->state = VIDEOBUF_QUEUED; + + if (pcdev->active) + return; + + WARN_ON(pcdev->ready); + + buf = prepare_next_vb(pcdev); + if (WARN_ON(!buf)) + return; + + pcdev->active = buf; + pcdev->ready = NULL; + + dev_dbg(icd->dev.parent, + "%s: capture not active, setup FIFO, start DMA\n", __func__); + mode = CAM_READ_CACHE(pcdev, MODE) & ~THRESHOLD_MASK; + mode |= THRESHOLD_LEVEL(pcdev->vb_mode) << THRESHOLD_SHIFT; + CAM_WRITE(pcdev, MODE, mode | EN_FIFO_FULL | DMA); + + if (pcdev->vb_mode == SG) { + /* + * In SG mode, the above prepare_next_vb() didn't actually + * put anything into the DMA programming register set, + * so we have to do it now, before activating DMA. + */ + try_next_sgbuf(pcdev->dma_ch, buf); + } + + start_capture(pcdev); +} + +static void omap1_videobuf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct omap1_cam_buf *buf = + container_of(vb, struct omap1_cam_buf, vb); + struct soc_camera_device *icd = vq->priv_data; + struct device *dev = icd->dev.parent; + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + + switch (vb->state) { + case VIDEOBUF_DONE: + dev_dbg(dev, "%s (done)\n", __func__); + break; + case VIDEOBUF_ACTIVE: + dev_dbg(dev, "%s (active)\n", __func__); + break; + case VIDEOBUF_QUEUED: + dev_dbg(dev, "%s (queued)\n", __func__); + break; + case VIDEOBUF_PREPARED: + dev_dbg(dev, "%s (prepared)\n", __func__); + break; + default: + dev_dbg(dev, "%s (unknown %d)\n", __func__, vb->state); + break; + } + + free_buffer(vq, buf, pcdev->vb_mode); +} + +static void videobuf_done(struct omap1_cam_dev *pcdev, + enum videobuf_state result) +{ + struct omap1_cam_buf *buf = pcdev->active; + struct videobuf_buffer *vb; + struct device *dev = pcdev->icd->dev.parent; + + if (WARN_ON(!buf)) { + suspend_capture(pcdev); + disable_capture(pcdev); + return; + } + + if (result == VIDEOBUF_ERROR) + suspend_capture(pcdev); + + vb = &buf->vb; + if (waitqueue_active(&vb->done)) { + if (!pcdev->ready && result != VIDEOBUF_ERROR) + /* + * No next buffer has been entered into the DMA + * programming register set on time, so best we can do + * is stopping the capture before last DMA block, + * whether our CONTIG mode whole buffer or its last + * sgbuf in SG mode, gets overwritten by next frame. + */ + suspend_capture(pcdev); + vb->state = result; + do_gettimeofday(&vb->ts); + if (result != VIDEOBUF_ERROR) + vb->field_count++; + wake_up(&vb->done); + + /* shift in next buffer */ + buf = pcdev->ready; + pcdev->active = buf; + pcdev->ready = NULL; + + if (!buf) { + /* + * No next buffer was ready on time (see above), so + * indicate error condition to force capture restart or + * stop, depending on next buffer already queued or not. + */ + result = VIDEOBUF_ERROR; + prepare_next_vb(pcdev); + + buf = pcdev->ready; + pcdev->active = buf; + pcdev->ready = NULL; + } + } else if (pcdev->ready) { + /* + * In both CONTIG and SG mode, the DMA engine has (might) + * already been autoreinitialized with the preprogrammed + * pcdev->ready buffer. We can either accept this fact + * and just swap the buffers, or provoke an error condition + * and restart capture. The former seems less intrusive. + */ + dev_dbg(dev, "%s: nobody waiting on videobuf, swap with next\n", + __func__); + pcdev->active = pcdev->ready; + + if (pcdev->vb_mode == SG) { + /* + * In SG mode, we have to make sure that the buffer we + * are putting back into the pcdev->ready is marked + * fresh. + */ + buf->sgbuf = NULL; + } + pcdev->ready = buf; + + buf = pcdev->active; + } else { + /* + * No next buffer has been entered into + * the DMA programming register set on time. + */ + if (pcdev->vb_mode == CONTIG) { + /* + * In CONTIG mode, the DMA engine has already been + * reinitialized with the current buffer. Best we can do + * is not touching it. + */ + dev_dbg(dev, + "%s: nobody waiting on videobuf, reuse it\n", + __func__); + } else { + /* + * In SG mode, the DMA engine has just been + * autoreinitialized with the last sgbuf from the + * current list. Restart capture in order to transfer + * next frame start into the first sgbuf, not the last + * one. + */ + if (result != VIDEOBUF_ERROR) { + suspend_capture(pcdev); + result = VIDEOBUF_ERROR; + } + } + } + + if (!buf) { + dev_dbg(dev, "%s: no more videobufs, stop capture\n", __func__); + disable_capture(pcdev); + return; + } + + if (pcdev->vb_mode == CONTIG) { + /* + * In CONTIG mode, the current buffer parameters had already + * been entered into the DMA programming register set while the + * buffer was fetched with prepare_next_vb(), they may have also + * been transfered into the runtime set and already active if + * the DMA still running. + */ + } else { + /* In SG mode, extra steps are required */ + if (result == VIDEOBUF_ERROR) + /* make sure we (re)use sglist from start on error */ + buf->sgbuf = NULL; + + /* + * In any case, enter the next sgbuf parameters into the DMA + * programming register set. They will be used either during + * nearest DMA autoreinitialization or, in case of an error, + * on DMA startup below. + */ + try_next_sgbuf(pcdev->dma_ch, buf); + } + + if (result == VIDEOBUF_ERROR) { + dev_dbg(dev, "%s: videobuf error; reset FIFO, restart DMA\n", + __func__); + start_capture(pcdev); + /* + * In SG mode, the above also resulted in the next sgbuf + * parameters being entered into the DMA programming register + * set, making them ready for next DMA autoreinitialization. + */ + } + + /* + * Finally, try fetching next buffer. In CONTIG mode, it will also + * get entered it into the DMA programming register set, + * making it ready for next DMA autoreinitialization. + */ + prepare_next_vb(pcdev); +} + +static void dma_isr(int channel, unsigned short status, void *data) +{ + struct omap1_cam_dev *pcdev = data; + struct omap1_cam_buf *buf = pcdev->active; + unsigned long flags; + + spin_lock_irqsave(&pcdev->lock, flags); + + if (WARN_ON(!buf)) { + suspend_capture(pcdev); + disable_capture(pcdev); + goto out; + } + + if (pcdev->vb_mode == CONTIG) { + /* + * In CONTIG mode, assume we have just managed to collect the + * whole frame, hopefully before our end of frame watchdog is + * triggered. Then, all we have to do is disabling the watchdog + * for this frame, and calling videobuf_done() with success + * indicated. + */ + CAM_WRITE(pcdev, MODE, + CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN); + videobuf_done(pcdev, VIDEOBUF_DONE); + } else { + /* + * In SG mode, we have to process every sgbuf from the current + * sglist, one after another. + */ + if (buf->sgbuf) { + /* + * Current sglist not completed yet, try fetching next + * sgbuf, hopefully putting it into the DMA programming + * register set, making it ready for next DMA + * autoreinitialization. + */ + try_next_sgbuf(pcdev->dma_ch, buf); + if (buf->sgbuf) + goto out; + + /* no more sgbufs in the current sglist */ + if (buf->result != VIDEOBUF_ERROR) { + /* + * Video frame collected without errors, we can + * prepare for collecting a next one as soon as + * DMA gets autoreinitialized after the currennt + * (last) sgbuf is completed. + */ + buf = prepare_next_vb(pcdev); + if (!buf) + goto out; + + try_next_sgbuf(pcdev->dma_ch, buf); + goto out; + } + } + /* end of videobuf */ + videobuf_done(pcdev, buf->result); + } + +out: + spin_unlock_irqrestore(&pcdev->lock, flags); +} + +static irqreturn_t cam_isr(int irq, void *data) +{ + struct omap1_cam_dev *pcdev = data; + struct device *dev = pcdev->icd->dev.parent; + struct omap1_cam_buf *buf = pcdev->active; + u32 it_status; + unsigned long flags; + + it_status = CAM_READ(pcdev, IT_STATUS); + if (!it_status) + return IRQ_NONE; + + spin_lock_irqsave(&pcdev->lock, flags); + + if (WARN_ON(!buf)) { + dev_warn(dev, "%s: unhandled camera interrupt, status == " + "0x%0x\n", __func__, it_status); + suspend_capture(pcdev); + disable_capture(pcdev); + goto out; + } + + if (unlikely(it_status & FIFO_FULL)) { + dev_warn(dev, "%s: FIFO overflow\n", __func__); + + } else if (it_status & V_DOWN) { + /* end of video frame watchdog */ + if (pcdev->vb_mode == CONTIG) { + /* + * In CONTIG mode, the watchdog is disabled with + * successful DMA end of block interrupt, and reenabled + * on next frame start. If we get here, there is nothing + * to check, we must be out of sync. + */ + } else { + if (buf->sgcount == 2) { + /* + * If exactly 2 sgbufs from the next sglist has + * been programmed into the DMA engine (the + * frist one already transfered into the DMA + * runtime register set, the second one still + * in the programming set), then we are in sync. + */ + goto out; + } + } + dev_notice(dev, "%s: unexpected end of video frame\n", + __func__); + + } else if (it_status & V_UP) { + u32 mode; + + if (pcdev->vb_mode == CONTIG) { + /* + * In CONTIG mode, we need this interrupt every frame + * in oredr to reenable our end of frame watchdog. + */ + mode = CAM_READ_CACHE(pcdev, MODE); + } else { + /* + * In SG mode, the below enabled end of frame watchdog + * is kept on permanently, so we can turn this one shot + * setup off. + */ + mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_UP; + } + + if (!(mode & EN_V_DOWN)) { + /* (re)enable end of frame watchdog interrupt */ + mode |= EN_V_DOWN; + } + CAM_WRITE(pcdev, MODE, mode); + goto out; + + } else { + dev_warn(dev, "%s: unhandled camera interrupt, status == " + "0x%0x\n", __func__, it_status); + goto out; + } + + videobuf_done(pcdev, VIDEOBUF_ERROR); +out: + spin_unlock_irqrestore(&pcdev->lock, flags); + return IRQ_HANDLED; +} + +static struct videobuf_queue_ops omap1_videobuf_ops = { + .buf_setup = omap1_videobuf_setup, + .buf_prepare = omap1_videobuf_prepare, + .buf_queue = omap1_videobuf_queue, + .buf_release = omap1_videobuf_release, +}; + + +/* + * SOC Camera host operations + */ + +static void sensor_reset(struct omap1_cam_dev *pcdev, bool reset) +{ + /* apply/release camera sensor reset if requested by platform data */ + if (pcdev->pflags & OMAP1_CAMERA_RST_HIGH) + CAM_WRITE(pcdev, GPIO, reset); + else if (pcdev->pflags & OMAP1_CAMERA_RST_LOW) + CAM_WRITE(pcdev, GPIO, !reset); +} + +/* + * The following two functions absolutely depend on the fact, that + * there can be only one camera on OMAP1 camera sensor interface + */ +static int omap1_cam_add_device(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + u32 ctrlclock; + + if (pcdev->icd) + return -EBUSY; + + clk_enable(pcdev->clk); + + /* setup sensor clock */ + ctrlclock = CAM_READ(pcdev, CTRLCLOCK); + ctrlclock &= ~(CAMEXCLK_EN | MCLK_EN | DPLL_EN); + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + + ctrlclock &= ~FOSCMOD_MASK; + switch (pcdev->camexclk) { + case 6000000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_6MHz; + break; + case 8000000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_8MHz | DPLL_EN; + break; + case 9600000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_9_6MHz | DPLL_EN; + break; + case 12000000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_12MHz; + break; + case 24000000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_24MHz | DPLL_EN; + default: + break; + } + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~DPLL_EN); + + /* enable internal clock */ + ctrlclock |= MCLK_EN; + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + + sensor_reset(pcdev, false); + + pcdev->icd = icd; + + dev_info(icd->dev.parent, "OMAP1 Camera driver attached to camera %d\n", + icd->devnum); + return 0; +} + +static void omap1_cam_remove_device(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + u32 ctrlclock; + + BUG_ON(icd != pcdev->icd); + + suspend_capture(pcdev); + disable_capture(pcdev); + + sensor_reset(pcdev, true); + + /* disable and release system clocks */ + ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); + ctrlclock &= ~(MCLK_EN | DPLL_EN | CAMEXCLK_EN); + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + + ctrlclock = (ctrlclock & ~FOSCMOD_MASK) | FOSCMOD_12MHz; + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | MCLK_EN); + + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~MCLK_EN); + + clk_disable(pcdev->clk); + + pcdev->icd = NULL; + + dev_info(icd->dev.parent, + "OMAP1 Camera driver detached from camera %d\n", icd->devnum); +} + +/* Duplicate standard formats based on host capability of byte swapping */ +static const struct soc_mbus_pixelfmt omap1_cam_formats[] = { + [V4L2_MBUS_FMT_UYVY8_2X8] = { + .fourcc = V4L2_PIX_FMT_YUYV, + .name = "YUYV", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + }, + [V4L2_MBUS_FMT_VYUY8_2X8] = { + .fourcc = V4L2_PIX_FMT_YVYU, + .name = "YVYU", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + }, + [V4L2_MBUS_FMT_YUYV8_2X8] = { + .fourcc = V4L2_PIX_FMT_UYVY, + .name = "UYVY", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + }, + [V4L2_MBUS_FMT_YVYU8_2X8] = { + .fourcc = V4L2_PIX_FMT_VYUY, + .name = "VYUY", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + }, + [V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE] = { + .fourcc = V4L2_PIX_FMT_RGB555, + .name = "RGB555", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + }, + [V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE] = { + .fourcc = V4L2_PIX_FMT_RGB555X, + .name = "RGB555X", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + }, + [V4L2_MBUS_FMT_RGB565_2X8_BE] = { + .fourcc = V4L2_PIX_FMT_RGB565, + .name = "RGB565", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + }, + [V4L2_MBUS_FMT_RGB565_2X8_LE] = { + .fourcc = V4L2_PIX_FMT_RGB565X, + .name = "RGB565X", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + }, +}; + +static int omap1_cam_get_formats(struct soc_camera_device *icd, + unsigned int idx, struct soc_camera_format_xlate *xlate) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->dev.parent; + int formats = 0, ret; + enum v4l2_mbus_pixelcode code; + const struct soc_mbus_pixelfmt *fmt; + + ret = v4l2_subdev_call(sd, video, enum_mbus_fmt, idx, &code); + if (ret < 0) + /* No more formats */ + return 0; + + fmt = soc_mbus_get_fmtdesc(code); + if (!fmt) { + dev_err(dev, "%s: invalid format code #%d: %d\n", __func__, + idx, code); + return 0; + } + + /* Check support for the requested bits-per-sample */ + if (fmt->bits_per_sample != 8) + return 0; + + switch (code) { + case V4L2_MBUS_FMT_YUYV8_2X8: + case V4L2_MBUS_FMT_YVYU8_2X8: + case V4L2_MBUS_FMT_UYVY8_2X8: + case V4L2_MBUS_FMT_VYUY8_2X8: + case V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE: + case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE: + case V4L2_MBUS_FMT_RGB565_2X8_BE: + case V4L2_MBUS_FMT_RGB565_2X8_LE: + formats++; + if (xlate) { + xlate->host_fmt = &omap1_cam_formats[code]; + xlate->code = code; + xlate++; + dev_dbg(dev, "%s: providing format %s " + "as byte swapped code #%d\n", __func__, + omap1_cam_formats[code].name, code); + } + default: + if (xlate) + dev_dbg(dev, "%s: providing format %s " + "in pass-through mode\n", __func__, + fmt->name); + } + formats++; + if (xlate) { + xlate->host_fmt = fmt; + xlate->code = code; + xlate++; + } + + return formats; +} + +static bool is_dma_aligned(s32 bytes_per_line, unsigned int height) +{ + int size = bytes_per_line * height; + + return IS_ALIGNED(bytes_per_line, DMA_ELEMENT_SIZE) && + IS_ALIGNED(size, DMA_FRAME_SIZE(CONTIG) * + DMA_ELEMENT_SIZE); +} + +static int dma_align(int *width, int *height, + const struct soc_mbus_pixelfmt *fmt, bool enlarge) +{ + s32 bytes_per_line = soc_mbus_bytes_per_line(*width, fmt); + + if (bytes_per_line < 0) + return bytes_per_line; + + if (!is_dma_aligned(bytes_per_line, *height)) { + unsigned int pxalign = __fls(bytes_per_line / *width); + unsigned int salign = DMA_FRAME_SHIFT(CONTIG) + + DMA_ELEMENT_SHIFT - pxalign; + unsigned int incr = enlarge << salign; + + v4l_bound_align_image(width, 1, *width + incr, 0, + height, 1, *height + incr, 0, salign); + return 0; + } + return 1; +} + +/* returns 1 on g_crop() success, 0 on cropcap() success, <0 on error */ +static int get_crop(struct soc_camera_device *icd, struct v4l2_rect *rect) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->dev.parent; + struct v4l2_crop crop; + int ret; + + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = v4l2_subdev_call(sd, video, g_crop, &crop); + if (ret == -ENOIOCTLCMD) { + struct v4l2_cropcap cc; + + dev_dbg(dev, "%s: g_crop() missing, trying cropcap() instead\n", + __func__); + cc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = v4l2_subdev_call(sd, video, cropcap, &cc); + if (ret < 0) + return ret; + *rect = cc.defrect; + return 0; + } else if (ret < 0) { + return ret; + } + *rect = crop.c; + return 1; +} + +/* + * returns 1 on g_mbus_fmt() or g_crop() success, 0 on cropcap() success, + * <0 on error + */ +static int get_geometry(struct soc_camera_device *icd, struct v4l2_rect *rect, + enum v4l2_mbus_pixelcode code) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->dev.parent; + struct v4l2_mbus_framefmt mf; + int ret; + + ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf); + if (ret == -ENOIOCTLCMD) { + struct v4l2_rect c; + + dev_dbg(dev, + "%s: g_mbus_fmt() missing, trying g_crop() instead\n", + __func__); + ret = get_crop(icd, &c); + if (ret < 0) + return ret; + + if (ret) { + *rect = c; /* use g_crop() result */ + } else { + dev_warn(dev, "%s: current geometry not available\n", + __func__); + return 0; + } + } else if (ret < 0) { + return ret; + } else if (mf.code != code) { + return -EINVAL; + } else { + rect->width = mf.width; + rect->height = mf.height; + } + return 1; +} + +#define subdev_call_with_sense(pcdev, dev, icd, sd, function, args...) \ +({ \ + struct soc_camera_sense sense = { \ + .master_clock = pcdev->camexclk, \ + .pixel_clock_max = 0, \ + }; \ + int __ret; \ + \ + if (pcdev->pdata) \ + sense.pixel_clock_max = pcdev->pdata->lclk_khz_max * 1000; \ + icd->sense = &sense; \ + __ret = v4l2_subdev_call(sd, video, function, ##args); \ + icd->sense = NULL; \ + \ + if (sense.flags & SOCAM_SENSE_PCLK_CHANGED) { \ + if (sense.pixel_clock > sense.pixel_clock_max) { \ + dev_err(dev, "%s: pixel clock %lu " \ + "set by the camera too high!\n", \ + __func__, sense.pixel_clock); \ + __ret = -EINVAL; \ + } \ + } \ + __ret; \ +}) + +static int omap1_cam_set_crop(struct soc_camera_device *icd, + struct v4l2_crop *crop) +{ + struct v4l2_rect *rect = &crop->c; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + struct device *dev = icd->dev.parent; + s32 bytes_per_line; + int ret; + + ret = dma_align(&rect->width, &rect->height, icd->current_fmt->host_fmt, + false); + if (ret < 0) { + dev_err(dev, "%s: failed to align %ux%u %s with DMA\n", + __func__, rect->width, rect->height, + icd->current_fmt->host_fmt->name); + return ret; + } + + subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, crop); + if (ret < 0) { + dev_warn(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__, + rect->width, rect->height, rect->left, rect->top); + return ret; + } + + ret = get_geometry(icd, rect, icd->current_fmt->code); + if (ret < 0) { + dev_err(dev, "%s: get_geometry() failed\n", __func__); + return ret; + } + if (!ret) + dev_warn(dev, "%s: unable to verify s_crop() results\n", + __func__); + + bytes_per_line = soc_mbus_bytes_per_line(rect->width, + icd->current_fmt->host_fmt); + if (bytes_per_line < 0) { + dev_err(dev, "%s: soc_mbus_bytes_per_line() failed\n", + __func__); + return bytes_per_line; + } + + ret = is_dma_aligned(bytes_per_line, rect->height); + if (!ret) { + dev_err(dev, "%s: resulting geometry %ux%u not DMA aligned\n", + __func__, rect->width, rect->height); + return -EINVAL; + } + + icd->user_width = rect->width; + icd->user_height = rect->height; + + return ret; +} + +static int omap1_cam_set_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + const struct soc_camera_format_xlate *xlate; + struct device *dev = icd->dev.parent; + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_mbus_framefmt mf; + struct v4l2_crop crop; + struct v4l2_rect *rect = &crop.c; + int bytes_per_line; + int ret; + + xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); + if (!xlate) { + dev_warn(dev, "%s: format %#x not found\n", __func__, + pix->pixelformat); + return -EINVAL; + } + + mf.width = pix->width; + mf.height = pix->height; + mf.field = pix->field; + mf.colorspace = pix->colorspace; + mf.code = xlate->code; + + ret = subdev_call_with_sense(pcdev, dev, icd, sd, s_mbus_fmt, &mf); + if (ret < 0) { + dev_err(dev, "%s: failed to set format\n", __func__); + return ret; + } + + if (mf.code != xlate->code) { + dev_err(dev, "%s: unexpected pixel code change\n", __func__); + return -EINVAL; + } + + pix->width = mf.width; + pix->height = mf.height; + pix->field = mf.field; + pix->colorspace = mf.colorspace; + icd->current_fmt = xlate; + + ret = dma_align(&pix->width, &pix->height, xlate->host_fmt, false); + if (ret < 0) { + dev_err(dev, "%s: failed to align %ux%u %s with DMA\n", + __func__, pix->width, pix->height, + xlate->host_fmt->name); + return ret; + } + + if (ret == 1) { + /* sensor returned geometry was already DMA aligned */ + return 0; + } + + dev_notice(dev, "%s: sensor geometry not DMA aligned, trying to crop to" + " %ux%u\n", __func__, pix->width, pix->height); + ret = get_crop(icd, rect); + if (ret < 0) { + dev_err(dev, "%s: get_crop() failed\n", __func__); + return ret; + } + + rect->width = pix->width; + rect->height = pix->height; + + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, &crop); + if (ret < 0) { + dev_err(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__, + rect->width, rect->height, rect->left, rect->top); + return ret; + } + + ret = get_geometry(icd, rect, xlate->code); + if (ret < 0) { + dev_err(dev, "%s: get_geometry() failed\n", __func__); + return ret; + } + + if (!ret) + dev_warn(dev, "%s: s_crop() results not confirmed\n", __func__); + + bytes_per_line = soc_mbus_bytes_per_line(rect->width, xlate->host_fmt); + if (bytes_per_line < 0) { + dev_err(dev, "%s: soc_mbus_bytes_per_line() failed\n", + __func__); + return bytes_per_line; + } + + ret = is_dma_aligned(bytes_per_line, rect->height); + if (!ret) { + dev_err(dev, "%s: resulting geometry %ux%u not DMA aligned\n", + __func__, rect->width, rect->height); + return -EINVAL; + } + + pix->width = rect->width; + pix->height = rect->height; + + return 0; +} + +static int omap1_cam_try_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + const struct soc_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_mbus_framefmt mf; + int ret; + /* TODO: limit to mx1 hardware capabilities */ + + xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); + if (!xlate) { + dev_warn(icd->dev.parent, "Format %#x not found\n", + pix->pixelformat); + return -EINVAL; + } + + mf.width = pix->width; + mf.height = pix->height; + mf.field = pix->field; + mf.colorspace = pix->colorspace; + mf.code = xlate->code; + + /* limit to sensor capabilities */ + ret = v4l2_subdev_call(sd, video, try_mbus_fmt, &mf); + if (ret < 0) + return ret; + + pix->width = mf.width; + pix->height = mf.height; + pix->field = mf.field; + pix->colorspace = mf.colorspace; + + return 0; +} + +static bool sg_mode; + +/* + * Local mmap_mapper wrapper, + * used for detecting videobuf-dma-contig buffer allocation failures + * and switching to videobuf-dma-sg automatically for future attempts. + */ +static int omap1_cam_mmap_mapper(struct videobuf_queue *q, + struct videobuf_buffer *buf, + struct vm_area_struct *vma) +{ + struct soc_camera_device *icd = q->priv_data; + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + int ret; + + ret = pcdev->mmap_mapper(q, buf, vma); + + if (ret == -ENOMEM) + sg_mode = 1; + + return ret; +} + +static void omap1_cam_init_videobuf(struct videobuf_queue *q, + struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + + if (!sg_mode) + videobuf_queue_dma_contig_init(q, &omap1_videobuf_ops, + icd->dev.parent, &pcdev->lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, + sizeof(struct omap1_cam_buf), icd); + else + videobuf_queue_sg_init(q, &omap1_videobuf_ops, + icd->dev.parent, &pcdev->lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, + sizeof(struct omap1_cam_buf), icd); + + /* use videobuf mode (auto)selected with the module parameter */ + pcdev->vb_mode = sg_mode ? SG : CONTIG; + + /* + * Ensure we substitute the videobuf-dma-contig version of the + * mmap_mapper() callback with our own wrapper, used for switching + * automatically to videobuf-dma-sg on buffer allocation failure. + */ + if (!sg_mode && q->int_ops->mmap_mapper != omap1_cam_mmap_mapper) { + pcdev->mmap_mapper = q->int_ops->mmap_mapper; + q->int_ops->mmap_mapper = omap1_cam_mmap_mapper; + } +} + +static int omap1_cam_reqbufs(struct soc_camera_file *icf, + struct v4l2_requestbuffers *p) +{ + int i; + + /* + * This is for locking debugging only. I removed spinlocks and now I + * check whether .prepare is ever called on a linked buffer, or whether + * a dma IRQ can occur for an in-work or unlinked buffer. Until now + * it hadn't triggered + */ + for (i = 0; i < p->count; i++) { + struct omap1_cam_buf *buf = container_of(icf->vb_vidq.bufs[i], + struct omap1_cam_buf, vb); + buf->inwork = 0; + INIT_LIST_HEAD(&buf->vb.queue); + } + + return 0; +} + +static int omap1_cam_querycap(struct soc_camera_host *ici, + struct v4l2_capability *cap) +{ + /* cap->name is set by the friendly caller:-> */ + strlcpy(cap->card, "OMAP1 Camera", sizeof(cap->card)); + cap->version = VERSION_CODE; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + + return 0; +} + +static int omap1_cam_set_bus_param(struct soc_camera_device *icd, + __u32 pixfmt) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct omap1_cam_dev *pcdev = ici->priv; + struct device *dev = icd->dev.parent; + const struct soc_camera_format_xlate *xlate; + const struct soc_mbus_pixelfmt *fmt; + unsigned long camera_flags, common_flags; + u32 ctrlclock, mode; + int ret; + + camera_flags = icd->ops->query_bus_param(icd); + + common_flags = soc_camera_bus_param_compatible(camera_flags, + SOCAM_BUS_FLAGS); + if (!common_flags) + return -EINVAL; + + /* Make choices, possibly based on platform configuration */ + if ((common_flags & SOCAM_PCLK_SAMPLE_RISING) && + (common_flags & SOCAM_PCLK_SAMPLE_FALLING)) { + if (!pcdev->pdata || + pcdev->pdata->flags & OMAP1_CAMERA_LCLK_RISING) + common_flags &= ~SOCAM_PCLK_SAMPLE_FALLING; + else + common_flags &= ~SOCAM_PCLK_SAMPLE_RISING; + } + + ret = icd->ops->set_bus_param(icd, common_flags); + if (ret < 0) + return ret; + + ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); + if (ctrlclock & LCLK_EN) + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); + + if (common_flags & SOCAM_PCLK_SAMPLE_RISING) { + dev_dbg(dev, "CTRLCLOCK_REG |= POLCLK\n"); + ctrlclock |= POLCLK; + } else { + dev_dbg(dev, "CTRLCLOCK_REG &= ~POLCLK\n"); + ctrlclock &= ~POLCLK; + } + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); + + if (ctrlclock & LCLK_EN) + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + + /* select bus endianess */ + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + fmt = xlate->host_fmt; + + mode = CAM_READ(pcdev, MODE) & ~(RAZ_FIFO | IRQ_MASK | DMA); + if (fmt->order == SOC_MBUS_ORDER_LE) { + dev_dbg(dev, "MODE_REG &= ~ORDERCAMD\n"); + CAM_WRITE(pcdev, MODE, mode & ~ORDERCAMD); + } else { + dev_dbg(dev, "MODE_REG |= ORDERCAMD\n"); + CAM_WRITE(pcdev, MODE, mode | ORDERCAMD); + } + + return 0; +} + +static unsigned int omap1_cam_poll(struct file *file, poll_table *pt) +{ + struct soc_camera_file *icf = file->private_data; + struct omap1_cam_buf *buf; + + buf = list_entry(icf->vb_vidq.stream.next, struct omap1_cam_buf, + vb.stream); + + poll_wait(file, &buf->vb.done, pt); + + if (buf->vb.state == VIDEOBUF_DONE || + buf->vb.state == VIDEOBUF_ERROR) + return POLLIN | POLLRDNORM; + + return 0; +} + +static struct soc_camera_host_ops omap1_host_ops = { + .owner = THIS_MODULE, + .add = omap1_cam_add_device, + .remove = omap1_cam_remove_device, + .get_formats = omap1_cam_get_formats, + .set_crop = omap1_cam_set_crop, + .set_fmt = omap1_cam_set_fmt, + .try_fmt = omap1_cam_try_fmt, + .init_videobuf = omap1_cam_init_videobuf, + .reqbufs = omap1_cam_reqbufs, + .querycap = omap1_cam_querycap, + .set_bus_param = omap1_cam_set_bus_param, + .poll = omap1_cam_poll, +}; + +static int __init omap1_cam_probe(struct platform_device *pdev) +{ + struct omap1_cam_dev *pcdev; + struct resource *res; + struct clk *clk; + void __iomem *base; + unsigned int irq; + int err = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || (int)irq <= 0) { + err = -ENODEV; + goto exit; + } + + clk = clk_get(&pdev->dev, "armper_ck"); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + goto exit; + } + + pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL); + if (!pcdev) { + dev_err(&pdev->dev, "Could not allocate pcdev\n"); + err = -ENOMEM; + goto exit_put_clk; + } + + pcdev->res = res; + pcdev->clk = clk; + + pcdev->pdata = pdev->dev.platform_data; + pcdev->pflags = pcdev->pdata->flags; + + if (pcdev->pdata) + pcdev->camexclk = pcdev->pdata->camexclk_khz * 1000; + + switch (pcdev->camexclk) { + case 6000000: + case 8000000: + case 9600000: + case 12000000: + case 24000000: + break; + default: + dev_warn(&pdev->dev, + "Incorrect sensor clock frequency %ld kHz, " + "should be one of 0, 6, 8, 9.6, 12 or 24 MHz, " + "please correct your platform data\n", + pcdev->pdata->camexclk_khz); + pcdev->camexclk = 0; + case 0: + dev_info(&pdev->dev, + "Not providing sensor clock\n"); + } + + INIT_LIST_HEAD(&pcdev->capture); + spin_lock_init(&pcdev->lock); + + /* + * Request the region. + */ + if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) { + err = -EBUSY; + goto exit_kfree; + } + + base = ioremap(res->start, resource_size(res)); + if (!base) { + err = -ENOMEM; + goto exit_release; + } + pcdev->irq = irq; + pcdev->base = base; + + sensor_reset(pcdev, true); + + err = omap_request_dma(OMAP_DMA_CAMERA_IF_RX, DRIVER_NAME, + dma_isr, (void *)pcdev, &pcdev->dma_ch); + if (err < 0) { + dev_err(&pdev->dev, "Can't request DMA for OMAP1 Camera\n"); + err = -EBUSY; + goto exit_iounmap; + } + dev_dbg(&pdev->dev, "got DMA channel %d\n", pcdev->dma_ch); + + /* preconfigure DMA */ + omap_set_dma_src_params(pcdev->dma_ch, OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, res->start + REG_CAMDATA, + 0, 0); + omap_set_dma_dest_burst_mode(pcdev->dma_ch, OMAP_DMA_DATA_BURST_4); + /* setup DMA autoinitialization */ + omap_dma_link_lch(pcdev->dma_ch, pcdev->dma_ch); + + err = request_irq(pcdev->irq, cam_isr, 0, DRIVER_NAME, pcdev); + if (err) { + dev_err(&pdev->dev, "Camera interrupt register failed\n"); + goto exit_free_dma; + } + + pcdev->soc_host.drv_name = DRIVER_NAME; + pcdev->soc_host.ops = &omap1_host_ops; + pcdev->soc_host.priv = pcdev; + pcdev->soc_host.v4l2_dev.dev = &pdev->dev; + pcdev->soc_host.nr = pdev->id; + + err = soc_camera_host_register(&pcdev->soc_host); + if (err) + goto exit_free_irq; + + dev_info(&pdev->dev, "OMAP1 Camera Interface driver loaded\n"); + + return 0; + +exit_free_irq: + free_irq(pcdev->irq, pcdev); +exit_free_dma: + omap_free_dma(pcdev->dma_ch); +exit_iounmap: + iounmap(base); +exit_release: + release_mem_region(res->start, resource_size(res)); +exit_kfree: + kfree(pcdev); +exit_put_clk: + clk_put(clk); +exit: + return err; +} + +static int __exit omap1_cam_remove(struct platform_device *pdev) +{ + struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev); + struct omap1_cam_dev *pcdev = container_of(soc_host, + struct omap1_cam_dev, soc_host); + struct resource *res; + + free_irq(pcdev->irq, pcdev); + + omap_free_dma(pcdev->dma_ch); + + soc_camera_host_unregister(soc_host); + + iounmap(pcdev->base); + + res = pcdev->res; + release_mem_region(res->start, resource_size(res)); + + kfree(pcdev); + + clk_put(pcdev->clk); + + dev_info(&pdev->dev, "OMAP1 Camera Interface driver unloaded\n"); + + return 0; +} + +static struct platform_driver omap1_cam_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = omap1_cam_probe, + .remove = __exit_p(omap1_cam_remove), +}; + +static int __init omap1_cam_init(void) +{ + return platform_driver_register(&omap1_cam_driver); +} +module_init(omap1_cam_init); + +static void __exit omap1_cam_exit(void) +{ + platform_driver_unregister(&omap1_cam_driver); +} +module_exit(omap1_cam_exit); + +module_param(sg_mode, bool, 0644); +MODULE_PARM_DESC(sg_mode, "videobuf mode, 0: dma-contig (default), 1: dma-sg"); + +MODULE_DESCRIPTION("OMAP1 Camera Interface driver"); +MODULE_AUTHOR("Janusz Krzysztofik jkrzyszt@tis.icnet.pl"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff -upr linux-2.6.36-rc3.orig/include/media/omap1_camera.h linux-2.6.36-rc3/include/media/omap1_camera.h --- linux-2.6.36-rc3.orig/include/media/omap1_camera.h 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/include/media/omap1_camera.h 2010-09-08 23:41:12.000000000 +0200 @@ -0,0 +1,35 @@ +/* + * Header for V4L2 SoC Camera driver for OMAP1 Camera Interface + * + * Copyright (C) 2010, Janusz Krzysztofik jkrzyszt@tis.icnet.pl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __MEDIA_OMAP1_CAMERA_H_ +#define __MEDIA_OMAP1_CAMERA_H_ + +#include <linux/bitops.h> + +#define OMAP1_CAMERA_IOSIZE 0x1c + +enum omap1_cam_vb_mode { + CONTIG = 0, + SG, +}; + +#define OMAP1_CAMERA_MIN_BUF_COUNT(x) ((x) == CONTIG ? 3 : 2) + +struct omap1_cam_platform_data { + unsigned long camexclk_khz; + unsigned long lclk_khz_max; + unsigned long flags; +}; + +#define OMAP1_CAMERA_LCLK_RISING BIT(0) +#define OMAP1_CAMERA_RST_LOW BIT(1) +#define OMAP1_CAMERA_RST_HIGH BIT(2) + +#endif /* __MEDIA_OMAP1_CAMERA_H_ */
On 10 Sep 2010, at 18:21, Janusz Krzysztofik wrote:
Both paths work stable for me, even under heavy load, on my OMAP1510 based Amstrad Delta videophone, that is the oldest, least powerfull OMAP1 implementation.
You say that, but the ARM925 in OMAP1510 is known not to exhibit the bug in OMAP1610, which causes severe slowdown to DRAM writes when the first address of an STM instruction is not aligned to a d-cache line boundary. This means that at least last time I looked, the Linux ARM memcpy() implementation is often faster on 1510 than 1610.
-J.
Saturday 11 September 2010 04:06:02 Jasmine Strong wrote:
On 10 Sep 2010, at 18:21, Janusz Krzysztofik wrote:
Both paths work stable for me, even under heavy load, on my OMAP1510 based Amstrad Delta videophone, that is the oldest, least powerfull OMAP1 implementation.
You say that, but the ARM925 in OMAP1510 is known not to exhibit the bug
Then, lucky me ;)
in OMAP1610, which causes severe slowdown to DRAM writes when the first address of an STM instruction is not aligned to a d-cache line boundary. This means that at least last time I looked, the Linux ARM memcpy() implementation is often faster on 1510 than 1610.
This sounds like a problem that should be solved at a machine support level rather than a camera driver. I don't follow general OMAP development closely enough to know if this bug has ever been addressed or is going to be.
Unfortunatelly, I have no access to any OMAP machine other than Amstrad Delta, so I'm not able to test the driver, including its performance, on other OMAP1 implementations.
Anyways, I think there is always a room for improvement, either in my omap1_camera or maybe in the omap24xxcam driver, if someone tries to add support for a camera to an OMAP1 board other than 1510, and identifies a more optimal, 1610 or higher specific way of handling the OMAP camera interface.
Do you think I should rename the driver to something like "omap1510cam" rather than "omap1_camera" then?
Thanks, Janusz
-J. _______________________________________________ e3-hacking mailing list e3-hacking@earth.li http://www.earth.li/cgi-bin/mailman/listinfo/e3-hacking
Hi Janusz,
This is a V4L2 driver for TI OMAP1 SoC camera interface.
"0x%0x\n", __func__, it_status);
"0x%0x\n", __func__, it_status);
dev_warn(dev, "%s: format %#x not found\n", __func__,
dev_warn(icd->dev.parent, "Format %#x not found\n",
Two bytes could be saved with consistent use of %#x. :-) Does it make sense to say `%0x' without giving a field width to pad to? That's another two bytes saved. A veritable word!
Cheers, Ralph.
Sunday 19 September 2010 17:06:58 Ralph Corderoy wrote:
Hi Janusz,
Hi Ralph, Thanks for your time.
This is a V4L2 driver for TI OMAP1 SoC camera interface.
"0x%0x\n", __func__, it_status);
"0x%0x\n", __func__, it_status);
dev_warn(dev, "%s: format %#x not found\n", __func__,
dev_warn(icd->dev.parent, "Format %#x not found\n",
Two bytes could be saved with consistent use of %#x. :-)
My "man 3 printf" says:
"The flag characters The character % is followed by zero or more of the following flags:
# The value should be converted to an "alternate form"... For x and X conversions, a non-zero result has the string "0x" (or "0X" for X conversions) prepended to it."
In the first two cases, I intended to keep the "0x" prepended even if 0. Am I missing something?
Does it make sense to say `%0x' without giving a field width to pad to?
My bad.
In next iteration, I'll probably use "0x%.8x" when printing 32-bit register values. What do you think?
Cheers, Janusz
That's another two bytes saved. A veritable word!
Cheers, Ralph.
e3-hacking mailing list e3-hacking@earth.li http://www.earth.li/cgi-bin/mailman/listinfo/e3-hacking
Hi Janusz,
"0x%0x\n", __func__, it_status);
"0x%0x\n", __func__, it_status);
dev_warn(dev, "%s: format %#x not found\n", __func__,
dev_warn(icd->dev.parent, "Format %#x not found\n",
Two bytes could be saved with consistent use of %#x. :-)
...
In the first two cases, I intended to keep the "0x" prepended even if 0. Am I missing something?
No, I didn't realise that was the intent. I agree, if you want zero to come out as "0x0" then you have to supply your own "0x".
In next iteration, I'll probably use "0x%.8x" when printing 32-bit register values. What do you think?
I'm probably more used to seeing "0x%08x", which is what I used to use myself, but either gives the same result. I stopped using it because I found that the shape of the word is as relevant with hex as it is with English.
0 0x80 0x800080 0x00000000 0x00000080 0x00800080
With the first line I can instantly see it's
Zero. 128 with the top-three bytes all zero. Three bytes with the top byte zero.
With the second line I have to scan the 0 digits carefully, looking for non-zeroes.
Cheers, Ralph.
Tuesday 21 September 2010 15:48:33 Ralph Corderoy napisał(a):
Hi Janusz,
"0x%0x\n", __func__, it_status);
"0x%0x\n", __func__, it_status);
dev_warn(dev, "%s: format %#x not found\n", __func__,
dev_warn(icd->dev.parent, "Format %#x not found\n",
Two bytes could be saved with consistent use of %#x. :-)
...
In the first two cases, I intended to keep the "0x" prepended even if 0. Am I missing something?
No, I didn't realise that was the intent. I agree, if you want zero to come out as "0x0" then you have to supply your own "0x".
In next iteration, I'll probably use "0x%.8x" when printing 32-bit register values. What do you think?
I'm probably more used to seeing "0x%08x", which is what I used to use myself, but either gives the same result. I stopped using it because I found that the shape of the word is as relevant with hex as it is with English.
0 0x80 0x800080 0x00000000 0x00000080 0x00800080
With the first line I can instantly see it's
Zero. 128 with the top-three bytes all zero. Three bytes with the top byte zero.
With the second line I have to scan the 0 digits carefully, looking for non-zeroes.
Fair enough, I'll convert them to "%#x".
Thanks, Janusz
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
This is a V4L2 driver for TI OMAP1 SoC camera interface.
Both videobuf-dma versions are supported, contig and sg, selectable with a module option. The former uses less processing power, but often fails to allocate contignuous buffer memory. The latter is free of this problem, but generates tens of DMA interrupts per frame. If contig memory allocation ever fails, the driver falls back to sg automatically on next open, but still can be switched back to contig manually. Both paths work stable for me, even under heavy load, on my OMAP1510 based Amstrad Delta videophone, that is the oldest, least powerfull OMAP1 implementation.
The interface generally works in pass-through mode. Since input data byte endianess can be swapped, it provides up to two v4l2 pixel formats per each of several soc_mbus formats that have their swapped endian counterparts.
Boards using this driver can provide it with the followning information:
- if and what freqency clock is expected by an on-board camera sensor,
- what is the maximum pixel clock that should be accepted from the sensor,
- what is the polarity of the sensor provided pixel clock,
- if the interface GPIO line is connected to a sensor reset/powerdown input and what is the input polarity.
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl
Friday 30 July 2010 13:07:42 Guennadi Liakhovetski wrote:
So, I think, a very welcome improvement to the driver would be a cleaner separation between the two cases. Don't try that much to reuse the code as much as possible. Would be much better to have clean separation between the two implementations - whether dynamically switchable at runtime or at config time - would be best to separate the two implementations to the point, where each of them becomes understandable and maintainable.
Guennadi, I've tried to rearrange them spearated, as you requested, but finally decided to keep them together, as before, only better documented and cleaned up as much as possible. I'm rather satisfied with the result, but if you think it is still not enough understandable and maintainable, I'll take one more iteration and split both paths.
Well, I think, I'll move a bit towards the "if it breaks - someone gets to fix it, or it gets dropped" policy, i.e., I'll give you more freedom (don't know what's wrong with me today;))
Besides, there are a few my not yet answered questions or suggestions (see http://www.spinics.net/lists/linux-media/msg21615.html for original message):
Friday 30 July 2010 20:49:05 Janusz Krzysztofik wrote:
Friday 30 July 2010 13:07:42 Guennadi Liakhovetski wrote:
On Sun, 18 Jul 2010, Janusz Krzysztofik wrote:
This is a V4L2 driver for TI OMAP1 SoC camera interface.
...
- u32 reg_cache[OMAP1_CAMERA_IOSIZE / sizeof(u32)];
Don't think you'd lose much performance without cache, for that the code would become less error-prone.
The reason is my bad experience with my hardware audio interface. For my rationale regarding OMAP McBSP register caching, please look at this commit: http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdif... or the foregoing discussions (links both to my initial and final submitions): http://thread.gmane.org/gmane.linux.alsa.devel/65675 http://thread.gmane.org/gmane.linux.ports.arm.omap/29708
However, I haven't noticed any similiar flaws in camera interface yet, so if you think that caching would not be helpful in any possible future (context save/restore) enhancements here, then I can drop it.
...
Don't you have to update the cache on a direct read?
If I do it unconditionally, I'd loose the read error prevention function of register caching idea.
I've kept register caching for now, only replaced a few not intentional reads from hardware with more sensible reads from cache. Please confirm if you prefere me dropping register caching anyways.
...
+static void videobuf_done(struct omap1_cam_dev *pcdev,
enum videobuf_state result)
+{
- ...
- if (waitqueue_active(&vb->done)) {
I'm not sure this is a good idea. Why are you reusing the buffer, if noone is waiting for it _now_? It can well be, that the task is doing something else atm. Like processing the previous frame. Or it has been preempted by another process, before it called poll() or select() on your device?
I've introduced this way of videobuf handling after learning, form the Documentation/video4linux/videobuf, what the first step of filling a buffer should be:
"- Obtain the next available buffer and make sure that somebody is actually waiting for it."
Since I had no idea how I could implement it, I decided to do it a bit differently: start filling a buffer unconditionally, then drop its contents if not awaited by any user when done
...
- } else if (pcdev->ready) {
dev_dbg(dev, "%s: nobody waiting on videobuf, swap with next\n",
__func__);
Not sure this is a wise decision... If there are more buffers in the queue, they stay there waiting. Best remove this waitqueue_active() check entirely, just complete buffers and wake up the user regardless. If no more buffers - go to sleep.
If you think I don't have to follow the above mentioned requirement for not servicing a buffer unless someone is waiting for it, then yes, I'll drop these overcomplicated bits. Please confirm.
One more reference to Documentation/video4linux/videobuf:
"It is equally possible that nobody is yet interested in the buffer; the driver should not remove it from the list or fill it until a process is waiting on it."
Please review my now better commented code again, and confirm if you still expect me to rearrange it, ignoring the documentation provided requirements.
...
+static int omap1_cam_set_crop(struct soc_camera_device *icd,
struct v4l2_crop *crop)
+{ ...
- icd->user_width = rect->width;
- icd->user_height = rect->height;
No, this is wrong. user_width and user_height are _user_ sizes, i.e., a result sensor cropping and scaling and host cropping and scaling. Whereas set_crop sets sensor input rectangle.
I'm not sure if I understand what you mean. Should I drop these assignments? Or correct them? If yes, how they should be calculated?
In Documentation/video4linux/soc-camera.txt, you say: "The core updates these fields upon successful completion of a .s_fmt() call, but if these fields change elsewhere, e.g., during .s_crop() processing, the host driver is responsible for updating them."
I thought this is the case we're dealing with here.
Please confirm if I my understanding of what the documentation says is wrong and I should really correct the above.
Thanks, Janusz
v1->v2 changes:
requested by Guennadi Liakhovetski (thanks!):
- first try contig, and if it fails - fall back to sg; invalidates next two,
- invalidated: Kconfig: VIDEO_OMAP1_SG: not need "if", the "depends on" should suffice,
- invalidated: include both <media/videobuf-dma-contig.h> and <media/videobuf-dma-sg.h> headers,
- extensively document buffer manipulations, better yet clean it up,
- a copyright / licence header was missing form a header file,
- need to #include <linux/bitops.h> if using BIT() macro,
- don't need macros representing frequencies - use numbers directly,
- add a few missing "static" qualifiers,
- use u32 type for register content handling,
- some cached registers were unnecessarily read from the hardware directly,
- use true/false constants instead of 0/1 for booleans,
- avoid assigning variables inside other constructs,
- don't need to test if RAZ_FIFO is cleared,
- no need to split "\n" to a new line, don't worry about > 80 characters,
- don't increment field_count in case of a VIDEOBUF_ERROR,
- adjust mbus format codes to the new names,
- make is_dma_aligned() return value a bool,
- no need to align frame line lenghts to integer number of DMA elements,
- drop a few superflous whitespace, braces, etc.,
- use correct multiline comment style,
- follow some good-style rules when defining a multi-line function-like macro,
- drop tests for impossible bool < 0,
- that's not an error when S_FMT sets a format different from what the user has requested, just use whatever you managed to configure,
- as a general rule, try_fmt shouldn't fail,
- use sensor_reset() instead of duplicating its code,
- first shut down the hardware, and only then unconfigure software,
suggested by Ralph Corderoy (thanks!):
- include vb->state's value in the debug messages instead of just "unknown",
- correct a few print formats,
- don't use goto if return is all that needs to be done,
other:
- correct a few issues reported with "checkpatch.pl --strict".
- refreshed against linux-2.6.36-rc3
drivers/media/video/Kconfig | 8 drivers/media/video/Makefile | 1 drivers/media/video/omap1_camera.c | 1781 +++++++++++++++++++++++++++++++++++++ include/media/omap1_camera.h | 35 4 files changed, 1825 insertions(+)
diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Kconfig linux-2.6.36-rc3/drivers/media/video/Kconfig --- linux-2.6.36-rc3.orig/drivers/media/video/Kconfig 2010-09-03 22:29:37.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/Kconfig 2010-09-09 17:55:52.000000000 +0200 @@ -890,6 +890,14 @@ config VIDEO_SH_MOBILE_CEU ---help--- This is a v4l2 driver for the SuperH Mobile CEU Interface
+config VIDEO_OMAP1
- tristate "OMAP1 Camera Interface driver"
- depends on VIDEO_DEV && ARCH_OMAP1 && SOC_CAMERA
- select VIDEOBUF_DMA_CONTIG
- select VIDEOBUF_DMA_SG
- ---help---
This is a v4l2 driver for the TI OMAP1 camera interface
config VIDEO_OMAP2 tristate "OMAP2 Camera Capture Interface driver" depends on VIDEO_DEV && ARCH_OMAP2 diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Makefile linux-2.6.36-rc3/drivers/media/video/Makefile --- linux-2.6.36-rc3.orig/drivers/media/video/Makefile 2010-09-03 22:29:37.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/Makefile 2010-09-09 17:55:52.000000000 +0200 @@ -163,6 +163,7 @@ obj-$(CONFIG_VIDEO_MX3) += mx3_camera. obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o +obj-$(CONFIG_VIDEO_OMAP1) += omap1_camera.o obj-$(CONFIG_VIDEO_SAMSUNG_S5P_FIMC) += s5p-fimc/
obj-$(CONFIG_ARCH_DAVINCI) += davinci/ diff -upr linux-2.6.36-rc3.orig/drivers/media/video/omap1_camera.c linux-2.6.36-rc3/drivers/media/video/omap1_camera.c --- linux-2.6.36-rc3.orig/drivers/media/video/omap1_camera.c 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/omap1_camera.c 2010-09-09 15:58:22.000000000 +0200 @@ -0,0 +1,1781 @@ +/*
- V4L2 SoC Camera driver for OMAP1 Camera Interface
- Copyright (C) 2010, Janusz Krzysztofik jkrzyszt@tis.icnet.pl
- Based on V4L2 Driver for i.MXL/i.MXL camera (CSI) host
- Copyright (C) 2008, Paulius Zaleckas paulius.zaleckas@teltonika.lt
- Copyright (C) 2009, Darius Augulis augulis.darius@gmail.com
- Based on PXA SoC camera driver
- Copyright (C) 2006, Sascha Hauer, Pengutronix
- Copyright (C) 2008, Guennadi Liakhovetski kernel@pengutronix.de
- Hardware specific bits initialy based on former work by Matt Callow
- drivers/media/video/omap/omap1510cam.c
- Copyright (C) 2006 Matt Callow
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- */
+#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/version.h>
+#include <media/omap1_camera.h> +#include <media/soc_camera.h> +#include <media/soc_mediabus.h> +#include <media/videobuf-dma-contig.h> +#include <media/videobuf-dma-sg.h>
+#include <plat/dma.h>
+#define DRIVER_NAME "omap1-camera" +#define VERSION_CODE KERNEL_VERSION(0, 0, 1)
+/*
- OMAP1 Camera Interface registers
- */
+#define REG_CTRLCLOCK 0x00 +#define REG_IT_STATUS 0x04 +#define REG_MODE 0x08 +#define REG_STATUS 0x0C +#define REG_CAMDATA 0x10 +#define REG_GPIO 0x14 +#define REG_PEAK_COUNTER 0x18
+/* CTRLCLOCK bit shifts */ +#define LCLK_EN BIT(7) +#define DPLL_EN BIT(6) +#define MCLK_EN BIT(5) +#define CAMEXCLK_EN BIT(4) +#define POLCLK BIT(3) +#define FOSCMOD_SHIFT 0 +#define FOSCMOD_MASK (0x7 << FOSCMOD_SHIFT) +#define FOSCMOD_12MHz 0x0 +#define FOSCMOD_6MHz 0x2 +#define FOSCMOD_9_6MHz 0x4 +#define FOSCMOD_24MHz 0x5 +#define FOSCMOD_8MHz 0x6
+/* IT_STATUS bit shifts */ +#define DATA_TRANSFER BIT(5) +#define FIFO_FULL BIT(4) +#define H_DOWN BIT(3) +#define H_UP BIT(2) +#define V_DOWN BIT(1) +#define V_UP BIT(0)
+/* MODE bit shifts */ +#define RAZ_FIFO BIT(18) +#define EN_FIFO_FULL BIT(17) +#define EN_NIRQ BIT(16) +#define THRESHOLD_SHIFT 9 +#define THRESHOLD_MASK (0x7f << THRESHOLD_SHIFT) +#define DMA BIT(8) +#define EN_H_DOWN BIT(7) +#define EN_H_UP BIT(6) +#define EN_V_DOWN BIT(5) +#define EN_V_UP BIT(4) +#define ORDERCAMD BIT(3)
+#define IRQ_MASK (EN_V_UP | EN_V_DOWN | EN_H_UP | EN_H_DOWN | \
EN_NIRQ | EN_FIFO_FULL)
+/* STATUS bit shifts */ +#define HSTATUS BIT(1) +#define VSTATUS BIT(0)
+/* GPIO bit shifts */ +#define CAM_RST BIT(0)
+/* end of OMAP1 Camera Interface registers */
+#define SOCAM_BUS_FLAGS (SOCAM_MASTER | \
SOCAM_HSYNC_ACTIVE_HIGH | SOCAM_VSYNC_ACTIVE_HIGH | \
SOCAM_PCLK_SAMPLE_RISING | SOCAM_PCLK_SAMPLE_FALLING | \
SOCAM_DATA_ACTIVE_HIGH | SOCAM_DATAWIDTH_8)
+#define FIFO_SIZE ((THRESHOLD_MASK >> THRESHOLD_SHIFT) + 1) +#define FIFO_SHIFT __fls(FIFO_SIZE)
+#define DMA_BURST_SHIFT (1 + OMAP_DMA_DATA_BURST_4) +#define DMA_BURST_SIZE (1 << DMA_BURST_SHIFT)
+#define DMA_ELEMENT_SHIFT OMAP_DMA_DATA_TYPE_S32 +#define DMA_ELEMENT_SIZE (1 << DMA_ELEMENT_SHIFT)
+#define DMA_FRAME_SHIFT_CONTIG (FIFO_SHIFT - 1) +#define DMA_FRAME_SHIFT_SG DMA_BURST_SHIFT
+#define DMA_FRAME_SHIFT(x) (x ? DMA_FRAME_SHIFT_SG : \
DMA_FRAME_SHIFT_CONTIG)
Don't you want to compare (x) against CONTIG and you want to put x in parenthesis in the DMA_FRAME_SHIFT macro. Besides, CONTIG and SG are not good enough names to be defined in a header under include/... Looks like you don't need them at all in the header? You only use them in this file, so, just move them inside.
+#define DMA_FRAME_SIZE(x) (1 << DMA_FRAME_SHIFT(x)) +#define DMA_SYNC OMAP_DMA_SYNC_FRAME +#define THRESHOLD_LEVEL DMA_FRAME_SIZE
+#define MAX_VIDEO_MEM 4 /* arbitrary video memory limit in MB */
+/*
- Structures
- */
+/* buffer for one video frame */ +struct omap1_cam_buf {
- struct videobuf_buffer vb;
- enum v4l2_mbus_pixelcode code;
- int inwork;
- struct scatterlist *sgbuf;
- int sgcount;
- int bytes_left;
- enum videobuf_state result;
+};
+struct omap1_cam_dev {
- struct soc_camera_host soc_host;
- struct soc_camera_device *icd;
- struct clk *clk;
- unsigned int irq;
- void __iomem *base;
- int dma_ch;
- struct omap1_cam_platform_data *pdata;
- struct resource *res;
- unsigned long pflags;
- unsigned long camexclk;
- struct list_head capture;
- /* lock used to protect videobuf */
- spinlock_t lock;
- u32 reg_cache[OMAP1_CAMERA_IOSIZE /
sizeof(u32)];
- /* Pointers to DMA buffers */
- struct omap1_cam_buf *active;
- struct omap1_cam_buf *ready;
- enum omap1_cam_vb_mode vb_mode;
- int (*mmap_mapper)(struct videobuf_queue *q,
struct videobuf_buffer *buf,
struct vm_area_struct *vma);
+};
+static void cam_write(struct omap1_cam_dev *pcdev, u16 reg, u32 val) +{
- pcdev->reg_cache[reg / sizeof(u32)] = val;
- __raw_writel(val, pcdev->base + reg);
+}
+static u32 cam_read(struct omap1_cam_dev *pcdev, u16 reg, bool from_cache) +{
- return !from_cache ? __raw_readl(pcdev->base + reg) :
pcdev->reg_cache[reg / sizeof(u32)];
+}
+#define CAM_READ(pcdev, reg) \
cam_read(pcdev, REG_##reg, false)
+#define CAM_WRITE(pcdev, reg, val) \
cam_write(pcdev, REG_##reg, val)
+#define CAM_READ_CACHE(pcdev, reg) \
cam_read(pcdev, REG_##reg, true)
+/*
- Videobuf operations
- */
+static int omap1_videobuf_setup(struct videobuf_queue *vq, unsigned int *count,
unsigned int *size)
+{
- struct soc_camera_device *icd = vq->priv_data;
- int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width,
icd->current_fmt->host_fmt);
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- if (bytes_per_line < 0)
return bytes_per_line;
- *size = bytes_per_line * icd->user_height;
- if (!*count || *count < OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode))
*count = OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode);
- if (*size * *count > MAX_VIDEO_MEM * 1024 * 1024)
*count = (MAX_VIDEO_MEM * 1024 * 1024) / *size;
- dev_dbg(icd->dev.parent,
"%s: count=%d, size=%d\n", __func__, *count, *size);
- return 0;
+}
+static void free_buffer(struct videobuf_queue *vq, struct omap1_cam_buf *buf,
enum omap1_cam_vb_mode vb_mode)
+{
- struct videobuf_buffer *vb = &buf->vb;
- BUG_ON(in_interrupt());
- videobuf_waiton(vb, 0, 0);
- if (vb_mode == CONTIG) {
videobuf_dma_contig_free(vq, vb);
- } else {
struct soc_camera_device *icd = vq->priv_data;
struct device *dev = icd->dev.parent;
struct videobuf_dmabuf *dma = videobuf_to_dma(vb);
videobuf_dma_unmap(dev, dma);
videobuf_dma_free(dma);
- }
- vb->state = VIDEOBUF_NEEDS_INIT;
+}
+static int omap1_videobuf_prepare(struct videobuf_queue *vq,
struct videobuf_buffer *vb, enum v4l2_field field)
+{
- struct soc_camera_device *icd = vq->priv_data;
- struct omap1_cam_buf *buf = container_of(vb, struct omap1_cam_buf, vb);
- int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width,
icd->current_fmt->host_fmt);
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- int ret;
- if (bytes_per_line < 0)
return bytes_per_line;
- WARN_ON(!list_empty(&vb->queue));
- BUG_ON(NULL == icd->current_fmt);
- buf->inwork = 1;
- if (buf->code != icd->current_fmt->code || vb->field != field ||
vb->width != icd->user_width ||
vb->height != icd->user_height) {
buf->code = icd->current_fmt->code;
vb->width = icd->user_width;
vb->height = icd->user_height;
vb->field = field;
vb->state = VIDEOBUF_NEEDS_INIT;
- }
- vb->size = bytes_per_line * vb->height;
- if (vb->baddr && vb->bsize < vb->size) {
ret = -EINVAL;
goto out;
- }
- if (vb->state == VIDEOBUF_NEEDS_INIT) {
ret = videobuf_iolock(vq, vb, NULL);
if (ret)
goto fail;
vb->state = VIDEOBUF_PREPARED;
- }
- buf->inwork = 0;
- return 0;
+fail:
- free_buffer(vq, buf, pcdev->vb_mode);
+out:
- buf->inwork = 0;
- return ret;
+}
+static void set_dma_dest_params(int dma_ch, struct omap1_cam_buf *buf,
enum omap1_cam_vb_mode vb_mode)
+{
- dma_addr_t dma_addr;
- unsigned int block_size;
- if (vb_mode == CONTIG) {
dma_addr = videobuf_to_dma_contig(&buf->vb);
block_size = buf->vb.size;
- } else {
if (WARN_ON(!buf->sgbuf)) {
buf->result = VIDEOBUF_ERROR;
return;
}
dma_addr = sg_dma_address(buf->sgbuf);
if (WARN_ON(!dma_addr)) {
buf->sgbuf = NULL;
buf->result = VIDEOBUF_ERROR;
return;
}
block_size = sg_dma_len(buf->sgbuf);
if (WARN_ON(!block_size)) {
buf->sgbuf = NULL;
buf->result = VIDEOBUF_ERROR;
return;
}
if (unlikely(buf->bytes_left < block_size))
block_size = buf->bytes_left;
if (WARN_ON(dma_addr & (DMA_FRAME_SIZE(vb_mode) *
DMA_ELEMENT_SIZE - 1))) {
dma_addr = ALIGN(dma_addr, DMA_FRAME_SIZE(vb_mode) *
DMA_ELEMENT_SIZE);
block_size &= ~(DMA_FRAME_SIZE(vb_mode) *
DMA_ELEMENT_SIZE - 1);
}
buf->bytes_left -= block_size;
buf->sgcount++;
- }
- omap_set_dma_dest_params(dma_ch,
OMAP_DMA_PORT_EMIFF, OMAP_DMA_AMODE_POST_INC, dma_addr, 0, 0);
- omap_set_dma_transfer_params(dma_ch,
OMAP_DMA_DATA_TYPE_S32, DMA_FRAME_SIZE(vb_mode),
block_size >> (DMA_FRAME_SHIFT(vb_mode) + DMA_ELEMENT_SHIFT),
DMA_SYNC, 0, 0);
+}
+static struct omap1_cam_buf *prepare_next_vb(struct omap1_cam_dev *pcdev) +{
- struct omap1_cam_buf *buf;
- /*
* If there is already a buffer pointed out by the pcdev->ready,
* (re)use it, otherwise try to fetch and configure a new one.
*/
- buf = pcdev->ready;
- if (!buf) {
if (list_empty(&pcdev->capture))
return buf;
buf = list_entry(pcdev->capture.next,
struct omap1_cam_buf, vb.queue);
buf->vb.state = VIDEOBUF_ACTIVE;
pcdev->ready = buf;
list_del_init(&buf->vb.queue);
- }
- if (pcdev->vb_mode == CONTIG) {
/*
* In CONTIG mode, we can safely enter next buffer parameters
* into the DMA programming register set after the DMA
* has already been activated on the previous buffer
*/
set_dma_dest_params(pcdev->dma_ch, buf, pcdev->vb_mode);
- } else {
/*
* In SG mode, the above is not safe since there are probably
* a bunch of sgbufs from previous sglist still pending.
* Instead, mark the sglist fresh for the upcoming
* try_next_sgbuf().
*/
buf->sgbuf = NULL;
- }
- return buf;
+}
+static struct scatterlist *try_next_sgbuf(int dma_ch, struct omap1_cam_buf *buf) +{
- struct scatterlist *sgbuf;
- if (likely(buf->sgbuf)) {
/* current sglist is active */
if (unlikely(!buf->bytes_left)) {
/* indicate sglist complete */
sgbuf = NULL;
} else {
/* process next sgbuf */
sgbuf = sg_next(buf->sgbuf);
if (WARN_ON(!sgbuf)) {
buf->result = VIDEOBUF_ERROR;
} else if (WARN_ON(!sg_dma_len(sgbuf))) {
sgbuf = NULL;
buf->result = VIDEOBUF_ERROR;
}
}
buf->sgbuf = sgbuf;
- } else {
/* sglist is fresh, initialize it before using */
struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb);
sgbuf = dma->sglist;
if (!(WARN_ON(!sgbuf))) {
buf->sgbuf = sgbuf;
buf->sgcount = 0;
buf->bytes_left = buf->vb.size;
buf->result = VIDEOBUF_DONE;
}
- }
- if (sgbuf)
/*
* Put our next sgbuf parameters (address, size)
* into the DMA programming register set.
*/
set_dma_dest_params(dma_ch, buf, SG);
- return sgbuf;
+}
+static void start_capture(struct omap1_cam_dev *pcdev) +{
- struct omap1_cam_buf *buf = pcdev->active;
- u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK);
- u32 mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN;
- if (WARN_ON(!buf))
return;
- /*
* Enable start of frame interrupt, which we will use for activating
* our end of frame watchdog when capture actually starts.
*/
- mode |= EN_V_UP;
- if (unlikely(ctrlclock & LCLK_EN))
/* stop pixel clock before FIFO reset */
CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
- /* reset FIFO */
- CAM_WRITE(pcdev, MODE, mode | RAZ_FIFO);
- omap_start_dma(pcdev->dma_ch);
- if (pcdev->vb_mode == SG) {
/*
* In SG mode, it's a good moment for fetching next sgbuf
* from the current sglist and, if available, already putting
* its parameters into the DMA programming register set.
*/
try_next_sgbuf(pcdev->dma_ch, buf);
- }
- /* (re)enable pixel clock */
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | LCLK_EN);
- /* release FIFO reset */
- CAM_WRITE(pcdev, MODE, mode);
+}
+static void suspend_capture(struct omap1_cam_dev *pcdev) +{
- u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK);
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
- omap_stop_dma(pcdev->dma_ch);
+}
+static void disable_capture(struct omap1_cam_dev *pcdev) +{
- u32 mode = CAM_READ_CACHE(pcdev, MODE);
- CAM_WRITE(pcdev, MODE, mode & ~(IRQ_MASK | DMA));
+}
+static void omap1_videobuf_queue(struct videobuf_queue *vq,
struct videobuf_buffer *vb)
+{
- struct soc_camera_device *icd = vq->priv_data;
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- struct omap1_cam_buf *buf;
- u32 mode;
- list_add_tail(&vb->queue, &pcdev->capture);
- vb->state = VIDEOBUF_QUEUED;
- if (pcdev->active)
return;
- WARN_ON(pcdev->ready);
- buf = prepare_next_vb(pcdev);
- if (WARN_ON(!buf))
return;
- pcdev->active = buf;
- pcdev->ready = NULL;
- dev_dbg(icd->dev.parent,
"%s: capture not active, setup FIFO, start DMA\n", __func__);
- mode = CAM_READ_CACHE(pcdev, MODE) & ~THRESHOLD_MASK;
- mode |= THRESHOLD_LEVEL(pcdev->vb_mode) << THRESHOLD_SHIFT;
- CAM_WRITE(pcdev, MODE, mode | EN_FIFO_FULL | DMA);
- if (pcdev->vb_mode == SG) {
/*
* In SG mode, the above prepare_next_vb() didn't actually
* put anything into the DMA programming register set,
* so we have to do it now, before activating DMA.
*/
try_next_sgbuf(pcdev->dma_ch, buf);
- }
- start_capture(pcdev);
+}
+static void omap1_videobuf_release(struct videobuf_queue *vq,
struct videobuf_buffer *vb)
+{
- struct omap1_cam_buf *buf =
container_of(vb, struct omap1_cam_buf, vb);
- struct soc_camera_device *icd = vq->priv_data;
- struct device *dev = icd->dev.parent;
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- switch (vb->state) {
- case VIDEOBUF_DONE:
dev_dbg(dev, "%s (done)\n", __func__);
break;
- case VIDEOBUF_ACTIVE:
dev_dbg(dev, "%s (active)\n", __func__);
break;
- case VIDEOBUF_QUEUED:
dev_dbg(dev, "%s (queued)\n", __func__);
break;
- case VIDEOBUF_PREPARED:
dev_dbg(dev, "%s (prepared)\n", __func__);
break;
- default:
dev_dbg(dev, "%s (unknown %d)\n", __func__, vb->state);
break;
- }
- free_buffer(vq, buf, pcdev->vb_mode);
+}
+static void videobuf_done(struct omap1_cam_dev *pcdev,
enum videobuf_state result)
+{
- struct omap1_cam_buf *buf = pcdev->active;
- struct videobuf_buffer *vb;
- struct device *dev = pcdev->icd->dev.parent;
- if (WARN_ON(!buf)) {
suspend_capture(pcdev);
disable_capture(pcdev);
return;
- }
- if (result == VIDEOBUF_ERROR)
suspend_capture(pcdev);
- vb = &buf->vb;
- if (waitqueue_active(&vb->done)) {
if (!pcdev->ready && result != VIDEOBUF_ERROR)
/*
* No next buffer has been entered into the DMA
* programming register set on time, so best we can do
* is stopping the capture before last DMA block,
* whether our CONTIG mode whole buffer or its last
* sgbuf in SG mode, gets overwritten by next frame.
*/
Hm, why do you think it's a good idea? This specific buffer completed successfully, but you want to fail it just because the next buffer is missing? Any specific reason for this? Besides, you seem to also be considering the possibility of your ->ready == NULL, but the queue non-empty, in which case you just take the next buffer from the queue and continue with it. Why error out in this case? And even if also the queue is empty - still not sure, why.
suspend_capture(pcdev);
vb->state = result;
do_gettimeofday(&vb->ts);
if (result != VIDEOBUF_ERROR)
vb->field_count++;
wake_up(&vb->done);
/* shift in next buffer */
buf = pcdev->ready;
pcdev->active = buf;
pcdev->ready = NULL;
if (!buf) {
/*
* No next buffer was ready on time (see above), so
* indicate error condition to force capture restart or
* stop, depending on next buffer already queued or not.
*/
result = VIDEOBUF_ERROR;
prepare_next_vb(pcdev);
buf = pcdev->ready;
pcdev->active = buf;
pcdev->ready = NULL;
}
- } else if (pcdev->ready) {
/*
* In both CONTIG and SG mode, the DMA engine has (might)
s/might/possibly/
* already been autoreinitialized with the preprogrammed
* pcdev->ready buffer. We can either accept this fact
* and just swap the buffers, or provoke an error condition
* and restart capture. The former seems less intrusive.
*/
dev_dbg(dev, "%s: nobody waiting on videobuf, swap with next\n",
__func__);
pcdev->active = pcdev->ready;
if (pcdev->vb_mode == SG) {
/*
* In SG mode, we have to make sure that the buffer we
* are putting back into the pcdev->ready is marked
* fresh.
*/
buf->sgbuf = NULL;
}
pcdev->ready = buf;
buf = pcdev->active;
- } else {
/*
* No next buffer has been entered into
* the DMA programming register set on time.
*/
if (pcdev->vb_mode == CONTIG) {
/*
* In CONTIG mode, the DMA engine has already been
* reinitialized with the current buffer. Best we can do
* is not touching it.
*/
dev_dbg(dev,
"%s: nobody waiting on videobuf, reuse it\n",
__func__);
} else {
/*
* In SG mode, the DMA engine has just been
* autoreinitialized with the last sgbuf from the
* current list. Restart capture in order to transfer
* next frame start into the first sgbuf, not the last
* one.
*/
if (result != VIDEOBUF_ERROR) {
suspend_capture(pcdev);
result = VIDEOBUF_ERROR;
}
}
- }
- if (!buf) {
dev_dbg(dev, "%s: no more videobufs, stop capture\n", __func__);
disable_capture(pcdev);
return;
- }
- if (pcdev->vb_mode == CONTIG) {
/*
* In CONTIG mode, the current buffer parameters had already
* been entered into the DMA programming register set while the
* buffer was fetched with prepare_next_vb(), they may have also
* been transfered into the runtime set and already active if
* the DMA still running.
*/
- } else {
/* In SG mode, extra steps are required */
if (result == VIDEOBUF_ERROR)
/* make sure we (re)use sglist from start on error */
buf->sgbuf = NULL;
/*
* In any case, enter the next sgbuf parameters into the DMA
* programming register set. They will be used either during
* nearest DMA autoreinitialization or, in case of an error,
* on DMA startup below.
*/
try_next_sgbuf(pcdev->dma_ch, buf);
- }
- if (result == VIDEOBUF_ERROR) {
dev_dbg(dev, "%s: videobuf error; reset FIFO, restart DMA\n",
__func__);
start_capture(pcdev);
/*
* In SG mode, the above also resulted in the next sgbuf
* parameters being entered into the DMA programming register
* set, making them ready for next DMA autoreinitialization.
*/
- }
- /*
* Finally, try fetching next buffer. In CONTIG mode, it will also
* get entered it into the DMA programming register set,
s/get entered/enter/
* making it ready for next DMA autoreinitialization.
*/
- prepare_next_vb(pcdev);
+}
+static void dma_isr(int channel, unsigned short status, void *data) +{
- struct omap1_cam_dev *pcdev = data;
- struct omap1_cam_buf *buf = pcdev->active;
- unsigned long flags;
- spin_lock_irqsave(&pcdev->lock, flags);
- if (WARN_ON(!buf)) {
suspend_capture(pcdev);
disable_capture(pcdev);
goto out;
- }
- if (pcdev->vb_mode == CONTIG) {
/*
* In CONTIG mode, assume we have just managed to collect the
* whole frame, hopefully before our end of frame watchdog is
* triggered. Then, all we have to do is disabling the watchdog
* for this frame, and calling videobuf_done() with success
* indicated.
*/
CAM_WRITE(pcdev, MODE,
CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN);
videobuf_done(pcdev, VIDEOBUF_DONE);
- } else {
/*
* In SG mode, we have to process every sgbuf from the current
* sglist, one after another.
*/
if (buf->sgbuf) {
/*
* Current sglist not completed yet, try fetching next
* sgbuf, hopefully putting it into the DMA programming
* register set, making it ready for next DMA
* autoreinitialization.
*/
try_next_sgbuf(pcdev->dma_ch, buf);
if (buf->sgbuf)
goto out;
/* no more sgbufs in the current sglist */
if (buf->result != VIDEOBUF_ERROR) {
/*
* Video frame collected without errors, we can
* prepare for collecting a next one as soon as
* DMA gets autoreinitialized after the currennt
* (last) sgbuf is completed.
*/
buf = prepare_next_vb(pcdev);
if (!buf)
goto out;
try_next_sgbuf(pcdev->dma_ch, buf);
goto out;
Uh, sorry, maybe I cannot quite follow the logic here, but it doesn't seem quite correct to me. Looks like you do not complete the current (successfully completed) buffer, if you failed to prepare the next one?
}
}
/* end of videobuf */
videobuf_done(pcdev, buf->result);
- }
+out:
- spin_unlock_irqrestore(&pcdev->lock, flags);
+}
+static irqreturn_t cam_isr(int irq, void *data) +{
- struct omap1_cam_dev *pcdev = data;
- struct device *dev = pcdev->icd->dev.parent;
- struct omap1_cam_buf *buf = pcdev->active;
- u32 it_status;
- unsigned long flags;
- it_status = CAM_READ(pcdev, IT_STATUS);
- if (!it_status)
return IRQ_NONE;
- spin_lock_irqsave(&pcdev->lock, flags);
- if (WARN_ON(!buf)) {
dev_warn(dev, "%s: unhandled camera interrupt, status == "
"0x%0x\n", __func__, it_status);
suspend_capture(pcdev);
disable_capture(pcdev);
goto out;
- }
- if (unlikely(it_status & FIFO_FULL)) {
dev_warn(dev, "%s: FIFO overflow\n", __func__);
- } else if (it_status & V_DOWN) {
/* end of video frame watchdog */
if (pcdev->vb_mode == CONTIG) {
/*
* In CONTIG mode, the watchdog is disabled with
* successful DMA end of block interrupt, and reenabled
* on next frame start. If we get here, there is nothing
* to check, we must be out of sync.
*/
} else {
if (buf->sgcount == 2) {
/*
* If exactly 2 sgbufs from the next sglist has
s/has/have/
* been programmed into the DMA engine (the
* frist one already transfered into the DMA
* runtime register set, the second one still
* in the programming set), then we are in sync.
*/
goto out;
}
}
dev_notice(dev, "%s: unexpected end of video frame\n",
__func__);
- } else if (it_status & V_UP) {
u32 mode;
if (pcdev->vb_mode == CONTIG) {
/*
* In CONTIG mode, we need this interrupt every frame
* in oredr to reenable our end of frame watchdog.
*/
mode = CAM_READ_CACHE(pcdev, MODE);
} else {
/*
* In SG mode, the below enabled end of frame watchdog
* is kept on permanently, so we can turn this one shot
* setup off.
*/
mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_UP;
}
if (!(mode & EN_V_DOWN)) {
/* (re)enable end of frame watchdog interrupt */
mode |= EN_V_DOWN;
}
CAM_WRITE(pcdev, MODE, mode);
goto out;
- } else {
dev_warn(dev, "%s: unhandled camera interrupt, status == "
"0x%0x\n", __func__, it_status);
Please, don't split strings
goto out;
- }
- videobuf_done(pcdev, VIDEOBUF_ERROR);
+out:
- spin_unlock_irqrestore(&pcdev->lock, flags);
- return IRQ_HANDLED;
+}
+static struct videobuf_queue_ops omap1_videobuf_ops = {
- .buf_setup = omap1_videobuf_setup,
- .buf_prepare = omap1_videobuf_prepare,
- .buf_queue = omap1_videobuf_queue,
- .buf_release = omap1_videobuf_release,
+};
+/*
- SOC Camera host operations
- */
+static void sensor_reset(struct omap1_cam_dev *pcdev, bool reset) +{
- /* apply/release camera sensor reset if requested by platform data */
- if (pcdev->pflags & OMAP1_CAMERA_RST_HIGH)
CAM_WRITE(pcdev, GPIO, reset);
- else if (pcdev->pflags & OMAP1_CAMERA_RST_LOW)
CAM_WRITE(pcdev, GPIO, !reset);
+}
+/*
- The following two functions absolutely depend on the fact, that
- there can be only one camera on OMAP1 camera sensor interface
- */
+static int omap1_cam_add_device(struct soc_camera_device *icd) +{
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- u32 ctrlclock;
- if (pcdev->icd)
return -EBUSY;
- clk_enable(pcdev->clk);
- /* setup sensor clock */
- ctrlclock = CAM_READ(pcdev, CTRLCLOCK);
- ctrlclock &= ~(CAMEXCLK_EN | MCLK_EN | DPLL_EN);
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
- ctrlclock &= ~FOSCMOD_MASK;
- switch (pcdev->camexclk) {
- case 6000000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_6MHz;
break;
- case 8000000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_8MHz | DPLL_EN;
break;
- case 9600000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_9_6MHz | DPLL_EN;
break;
- case 12000000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_12MHz;
break;
- case 24000000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_24MHz | DPLL_EN;
- default:
This default doesn't make much sense here, maybe put it to one of these values, that you think is a reasonable fall back. Ah, right, you wanted to check, whether this can work...
break;
- }
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~DPLL_EN);
- /* enable internal clock */
- ctrlclock |= MCLK_EN;
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
- sensor_reset(pcdev, false);
- pcdev->icd = icd;
- dev_info(icd->dev.parent, "OMAP1 Camera driver attached to camera %d\n",
icd->devnum);
- return 0;
+}
+static void omap1_cam_remove_device(struct soc_camera_device *icd) +{
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- u32 ctrlclock;
- BUG_ON(icd != pcdev->icd);
- suspend_capture(pcdev);
- disable_capture(pcdev);
- sensor_reset(pcdev, true);
- /* disable and release system clocks */
- ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK);
- ctrlclock &= ~(MCLK_EN | DPLL_EN | CAMEXCLK_EN);
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
- ctrlclock = (ctrlclock & ~FOSCMOD_MASK) | FOSCMOD_12MHz;
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | MCLK_EN);
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~MCLK_EN);
- clk_disable(pcdev->clk);
- pcdev->icd = NULL;
- dev_info(icd->dev.parent,
"OMAP1 Camera driver detached from camera %d\n", icd->devnum);
+}
+/* Duplicate standard formats based on host capability of byte swapping */ +static const struct soc_mbus_pixelfmt omap1_cam_formats[] = {
- [V4L2_MBUS_FMT_UYVY8_2X8] = {
.fourcc = V4L2_PIX_FMT_YUYV,
.name = "YUYV",
.bits_per_sample = 8,
.packing = SOC_MBUS_PACKING_2X8_PADHI,
.order = SOC_MBUS_ORDER_BE,
- },
- [V4L2_MBUS_FMT_VYUY8_2X8] = {
.fourcc = V4L2_PIX_FMT_YVYU,
.name = "YVYU",
.bits_per_sample = 8,
.packing = SOC_MBUS_PACKING_2X8_PADHI,
.order = SOC_MBUS_ORDER_BE,
- },
- [V4L2_MBUS_FMT_YUYV8_2X8] = {
.fourcc = V4L2_PIX_FMT_UYVY,
.name = "UYVY",
.bits_per_sample = 8,
.packing = SOC_MBUS_PACKING_2X8_PADHI,
.order = SOC_MBUS_ORDER_BE,
- },
- [V4L2_MBUS_FMT_YVYU8_2X8] = {
.fourcc = V4L2_PIX_FMT_VYUY,
.name = "VYUY",
.bits_per_sample = 8,
.packing = SOC_MBUS_PACKING_2X8_PADHI,
.order = SOC_MBUS_ORDER_BE,
- },
- [V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE] = {
.fourcc = V4L2_PIX_FMT_RGB555,
.name = "RGB555",
.bits_per_sample = 8,
.packing = SOC_MBUS_PACKING_2X8_PADHI,
.order = SOC_MBUS_ORDER_BE,
- },
- [V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE] = {
.fourcc = V4L2_PIX_FMT_RGB555X,
.name = "RGB555X",
.bits_per_sample = 8,
.packing = SOC_MBUS_PACKING_2X8_PADHI,
.order = SOC_MBUS_ORDER_BE,
- },
- [V4L2_MBUS_FMT_RGB565_2X8_BE] = {
.fourcc = V4L2_PIX_FMT_RGB565,
.name = "RGB565",
.bits_per_sample = 8,
.packing = SOC_MBUS_PACKING_2X8_PADHI,
.order = SOC_MBUS_ORDER_BE,
- },
- [V4L2_MBUS_FMT_RGB565_2X8_LE] = {
.fourcc = V4L2_PIX_FMT_RGB565X,
.name = "RGB565X",
.bits_per_sample = 8,
.packing = SOC_MBUS_PACKING_2X8_PADHI,
.order = SOC_MBUS_ORDER_BE,
- },
+};
+static int omap1_cam_get_formats(struct soc_camera_device *icd,
unsigned int idx, struct soc_camera_format_xlate *xlate)
+{
- struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
- struct device *dev = icd->dev.parent;
- int formats = 0, ret;
- enum v4l2_mbus_pixelcode code;
- const struct soc_mbus_pixelfmt *fmt;
- ret = v4l2_subdev_call(sd, video, enum_mbus_fmt, idx, &code);
- if (ret < 0)
/* No more formats */
return 0;
- fmt = soc_mbus_get_fmtdesc(code);
- if (!fmt) {
dev_err(dev, "%s: invalid format code #%d: %d\n", __func__,
idx, code);
return 0;
- }
- /* Check support for the requested bits-per-sample */
- if (fmt->bits_per_sample != 8)
return 0;
- switch (code) {
- case V4L2_MBUS_FMT_YUYV8_2X8:
- case V4L2_MBUS_FMT_YVYU8_2X8:
- case V4L2_MBUS_FMT_UYVY8_2X8:
- case V4L2_MBUS_FMT_VYUY8_2X8:
- case V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE:
- case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE:
- case V4L2_MBUS_FMT_RGB565_2X8_BE:
- case V4L2_MBUS_FMT_RGB565_2X8_LE:
formats++;
if (xlate) {
xlate->host_fmt = &omap1_cam_formats[code];
xlate->code = code;
xlate++;
dev_dbg(dev, "%s: providing format %s "
"as byte swapped code #%d\n", __func__,
omap1_cam_formats[code].name, code);
}
- default:
if (xlate)
dev_dbg(dev, "%s: providing format %s "
"in pass-through mode\n", __func__,
fmt->name);
- }
- formats++;
- if (xlate) {
xlate->host_fmt = fmt;
xlate->code = code;
xlate++;
- }
- return formats;
+}
+static bool is_dma_aligned(s32 bytes_per_line, unsigned int height) +{
- int size = bytes_per_line * height;
- return IS_ALIGNED(bytes_per_line, DMA_ELEMENT_SIZE) &&
IS_ALIGNED(size, DMA_FRAME_SIZE(CONTIG) *
DMA_ELEMENT_SIZE);
+}
+static int dma_align(int *width, int *height,
const struct soc_mbus_pixelfmt *fmt, bool enlarge)
+{
- s32 bytes_per_line = soc_mbus_bytes_per_line(*width, fmt);
- if (bytes_per_line < 0)
return bytes_per_line;
- if (!is_dma_aligned(bytes_per_line, *height)) {
unsigned int pxalign = __fls(bytes_per_line / *width);
unsigned int salign = DMA_FRAME_SHIFT(CONTIG) +
DMA_ELEMENT_SHIFT - pxalign;
unsigned int incr = enlarge << salign;
v4l_bound_align_image(width, 1, *width + incr, 0,
height, 1, *height + incr, 0, salign);
return 0;
- }
- return 1;
+}
+/* returns 1 on g_crop() success, 0 on cropcap() success, <0 on error */ +static int get_crop(struct soc_camera_device *icd, struct v4l2_rect *rect) +{
- struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
- struct device *dev = icd->dev.parent;
- struct v4l2_crop crop;
- int ret;
- crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ret = v4l2_subdev_call(sd, video, g_crop, &crop);
- if (ret == -ENOIOCTLCMD) {
struct v4l2_cropcap cc;
dev_dbg(dev, "%s: g_crop() missing, trying cropcap() instead\n",
__func__);
cc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = v4l2_subdev_call(sd, video, cropcap, &cc);
if (ret < 0)
return ret;
*rect = cc.defrect;
return 0;
- } else if (ret < 0) {
return ret;
- }
- *rect = crop.c;
- return 1;
+}
+/*
- returns 1 on g_mbus_fmt() or g_crop() success, 0 on cropcap() success,
- <0 on error
- */
+static int get_geometry(struct soc_camera_device *icd, struct v4l2_rect *rect,
enum v4l2_mbus_pixelcode code)
+{
- struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
- struct device *dev = icd->dev.parent;
- struct v4l2_mbus_framefmt mf;
- int ret;
- ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf);
- if (ret == -ENOIOCTLCMD) {
struct v4l2_rect c;
dev_dbg(dev,
"%s: g_mbus_fmt() missing, trying g_crop() instead\n",
__func__);
ret = get_crop(icd, &c);
if (ret < 0)
return ret;
if (ret) {
*rect = c; /* use g_crop() result */
} else {
dev_warn(dev, "%s: current geometry not available\n",
__func__);
return 0;
}
- } else if (ret < 0) {
return ret;
- } else if (mf.code != code) {
return -EINVAL;
- } else {
rect->width = mf.width;
rect->height = mf.height;
- }
- return 1;
+}
+#define subdev_call_with_sense(pcdev, dev, icd, sd, function, args...) \ +({ \
- struct soc_camera_sense sense = { \
.master_clock = pcdev->camexclk, \
.pixel_clock_max = 0, \
- }; \
- int __ret; \
\
- if (pcdev->pdata) \
sense.pixel_clock_max = pcdev->pdata->lclk_khz_max * 1000; \
- icd->sense = &sense; \
- __ret = v4l2_subdev_call(sd, video, function, ##args); \
- icd->sense = NULL; \
\
- if (sense.flags & SOCAM_SENSE_PCLK_CHANGED) { \
if (sense.pixel_clock > sense.pixel_clock_max) { \
dev_err(dev, "%s: pixel clock %lu " \
"set by the camera too high!\n", \
__func__, sense.pixel_clock); \
__ret = -EINVAL; \
} \
- } \
- __ret; \
+})
+static int omap1_cam_set_crop(struct soc_camera_device *icd,
struct v4l2_crop *crop)
+{
- struct v4l2_rect *rect = &crop->c;
- struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- struct device *dev = icd->dev.parent;
- s32 bytes_per_line;
- int ret;
- ret = dma_align(&rect->width, &rect->height, icd->current_fmt->host_fmt,
false);
- if (ret < 0) {
dev_err(dev, "%s: failed to align %ux%u %s with DMA\n",
__func__, rect->width, rect->height,
icd->current_fmt->host_fmt->name);
return ret;
- }
- subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, crop);
Missing "ret = "?
- if (ret < 0) {
dev_warn(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__,
rect->width, rect->height, rect->left, rect->top);
return ret;
- }
- ret = get_geometry(icd, rect, icd->current_fmt->code);
- if (ret < 0) {
dev_err(dev, "%s: get_geometry() failed\n", __func__);
return ret;
- }
- if (!ret)
dev_warn(dev, "%s: unable to verify s_crop() results\n",
__func__);
- bytes_per_line = soc_mbus_bytes_per_line(rect->width,
icd->current_fmt->host_fmt);
- if (bytes_per_line < 0) {
dev_err(dev, "%s: soc_mbus_bytes_per_line() failed\n",
__func__);
return bytes_per_line;
- }
- ret = is_dma_aligned(bytes_per_line, rect->height);
- if (!ret) {
dev_err(dev, "%s: resulting geometry %ux%u not DMA aligned\n",
__func__, rect->width, rect->height);
return -EINVAL;
- }
- icd->user_width = rect->width;
- icd->user_height = rect->height;
- return ret;
+}
+static int omap1_cam_set_fmt(struct soc_camera_device *icd,
struct v4l2_format *f)
+{
- struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
- const struct soc_camera_format_xlate *xlate;
- struct device *dev = icd->dev.parent;
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- struct v4l2_pix_format *pix = &f->fmt.pix;
- struct v4l2_mbus_framefmt mf;
- struct v4l2_crop crop;
- struct v4l2_rect *rect = &crop.c;
- int bytes_per_line;
- int ret;
- xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
- if (!xlate) {
dev_warn(dev, "%s: format %#x not found\n", __func__,
pix->pixelformat);
return -EINVAL;
- }
- mf.width = pix->width;
- mf.height = pix->height;
- mf.field = pix->field;
- mf.colorspace = pix->colorspace;
- mf.code = xlate->code;
- ret = subdev_call_with_sense(pcdev, dev, icd, sd, s_mbus_fmt, &mf);
- if (ret < 0) {
dev_err(dev, "%s: failed to set format\n", __func__);
return ret;
- }
- if (mf.code != xlate->code) {
dev_err(dev, "%s: unexpected pixel code change\n", __func__);
return -EINVAL;
- }
- pix->width = mf.width;
- pix->height = mf.height;
- pix->field = mf.field;
- pix->colorspace = mf.colorspace;
- icd->current_fmt = xlate;
- ret = dma_align(&pix->width, &pix->height, xlate->host_fmt, false);
- if (ret < 0) {
dev_err(dev, "%s: failed to align %ux%u %s with DMA\n",
__func__, pix->width, pix->height,
xlate->host_fmt->name);
return ret;
- }
- if (ret == 1) {
/* sensor returned geometry was already DMA aligned */
return 0;
- }
- dev_notice(dev, "%s: sensor geometry not DMA aligned, trying to crop to"
" %ux%u\n", __func__, pix->width, pix->height);
One line
- ret = get_crop(icd, rect);
- if (ret < 0) {
dev_err(dev, "%s: get_crop() failed\n", __func__);
return ret;
- }
- rect->width = pix->width;
- rect->height = pix->height;
- crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, &crop);
'ret = '?
- if (ret < 0) {
dev_err(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__,
rect->width, rect->height, rect->left, rect->top);
return ret;
- }
- ret = get_geometry(icd, rect, xlate->code);
- if (ret < 0) {
dev_err(dev, "%s: get_geometry() failed\n", __func__);
return ret;
- }
- if (!ret)
dev_warn(dev, "%s: s_crop() results not confirmed\n", __func__);
- bytes_per_line = soc_mbus_bytes_per_line(rect->width, xlate->host_fmt);
- if (bytes_per_line < 0) {
dev_err(dev, "%s: soc_mbus_bytes_per_line() failed\n",
__func__);
return bytes_per_line;
- }
- ret = is_dma_aligned(bytes_per_line, rect->height);
- if (!ret) {
dev_err(dev, "%s: resulting geometry %ux%u not DMA aligned\n",
__func__, rect->width, rect->height);
return -EINVAL;
- }
- pix->width = rect->width;
- pix->height = rect->height;
- return 0;
+}
+static int omap1_cam_try_fmt(struct soc_camera_device *icd,
struct v4l2_format *f)
+{
- struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
- const struct soc_camera_format_xlate *xlate;
- struct v4l2_pix_format *pix = &f->fmt.pix;
- struct v4l2_mbus_framefmt mf;
- int ret;
- /* TODO: limit to mx1 hardware capabilities */
- xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
- if (!xlate) {
dev_warn(icd->dev.parent, "Format %#x not found\n",
pix->pixelformat);
return -EINVAL;
- }
- mf.width = pix->width;
- mf.height = pix->height;
- mf.field = pix->field;
- mf.colorspace = pix->colorspace;
- mf.code = xlate->code;
- /* limit to sensor capabilities */
- ret = v4l2_subdev_call(sd, video, try_mbus_fmt, &mf);
- if (ret < 0)
return ret;
- pix->width = mf.width;
- pix->height = mf.height;
- pix->field = mf.field;
- pix->colorspace = mf.colorspace;
- return 0;
+}
+static bool sg_mode;
+/*
- Local mmap_mapper wrapper,
- used for detecting videobuf-dma-contig buffer allocation failures
- and switching to videobuf-dma-sg automatically for future attempts.
- */
+static int omap1_cam_mmap_mapper(struct videobuf_queue *q,
struct videobuf_buffer *buf,
struct vm_area_struct *vma)
+{
- struct soc_camera_device *icd = q->priv_data;
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- int ret;
- ret = pcdev->mmap_mapper(q, buf, vma);
- if (ret == -ENOMEM)
sg_mode = 1;
"true"
- return ret;
+}
+static void omap1_cam_init_videobuf(struct videobuf_queue *q,
struct soc_camera_device *icd)
+{
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- if (!sg_mode)
videobuf_queue_dma_contig_init(q, &omap1_videobuf_ops,
icd->dev.parent, &pcdev->lock,
V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE,
sizeof(struct omap1_cam_buf), icd);
- else
videobuf_queue_sg_init(q, &omap1_videobuf_ops,
icd->dev.parent, &pcdev->lock,
V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE,
sizeof(struct omap1_cam_buf), icd);
- /* use videobuf mode (auto)selected with the module parameter */
- pcdev->vb_mode = sg_mode ? SG : CONTIG;
- /*
* Ensure we substitute the videobuf-dma-contig version of the
* mmap_mapper() callback with our own wrapper, used for switching
* automatically to videobuf-dma-sg on buffer allocation failure.
*/
- if (!sg_mode && q->int_ops->mmap_mapper != omap1_cam_mmap_mapper) {
pcdev->mmap_mapper = q->int_ops->mmap_mapper;
q->int_ops->mmap_mapper = omap1_cam_mmap_mapper;
- }
+}
+static int omap1_cam_reqbufs(struct soc_camera_file *icf,
struct v4l2_requestbuffers *p)
+{
- int i;
- /*
* This is for locking debugging only. I removed spinlocks and now I
* check whether .prepare is ever called on a linked buffer, or whether
* a dma IRQ can occur for an in-work or unlinked buffer. Until now
* it hadn't triggered
*/
- for (i = 0; i < p->count; i++) {
struct omap1_cam_buf *buf = container_of(icf->vb_vidq.bufs[i],
struct omap1_cam_buf, vb);
buf->inwork = 0;
INIT_LIST_HEAD(&buf->vb.queue);
- }
- return 0;
+}
+static int omap1_cam_querycap(struct soc_camera_host *ici,
struct v4l2_capability *cap)
+{
- /* cap->name is set by the friendly caller:-> */
- strlcpy(cap->card, "OMAP1 Camera", sizeof(cap->card));
- cap->version = VERSION_CODE;
- cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
- return 0;
+}
+static int omap1_cam_set_bus_param(struct soc_camera_device *icd,
__u32 pixfmt)
+{
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- struct device *dev = icd->dev.parent;
- const struct soc_camera_format_xlate *xlate;
- const struct soc_mbus_pixelfmt *fmt;
- unsigned long camera_flags, common_flags;
- u32 ctrlclock, mode;
- int ret;
- camera_flags = icd->ops->query_bus_param(icd);
- common_flags = soc_camera_bus_param_compatible(camera_flags,
SOCAM_BUS_FLAGS);
- if (!common_flags)
return -EINVAL;
- /* Make choices, possibly based on platform configuration */
- if ((common_flags & SOCAM_PCLK_SAMPLE_RISING) &&
(common_flags & SOCAM_PCLK_SAMPLE_FALLING)) {
if (!pcdev->pdata ||
pcdev->pdata->flags & OMAP1_CAMERA_LCLK_RISING)
Out of curiosity - do you need to force a specific clock polarity on your platform or do you know of any, where this is necessary (beyond the natural restriction, coming from the sensor - if any). I.e., I'm not at all sure you need this flag, but if you do - I would apply it earlier - to mask the respective bit from SOCAM_BUS_FLAGS.
common_flags &= ~SOCAM_PCLK_SAMPLE_FALLING;
else
common_flags &= ~SOCAM_PCLK_SAMPLE_RISING;
- }
- ret = icd->ops->set_bus_param(icd, common_flags);
- if (ret < 0)
return ret;
- ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK);
- if (ctrlclock & LCLK_EN)
CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
- if (common_flags & SOCAM_PCLK_SAMPLE_RISING) {
dev_dbg(dev, "CTRLCLOCK_REG |= POLCLK\n");
ctrlclock |= POLCLK;
- } else {
dev_dbg(dev, "CTRLCLOCK_REG &= ~POLCLK\n");
ctrlclock &= ~POLCLK;
- }
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
- if (ctrlclock & LCLK_EN)
CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
- /* select bus endianess */
- xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
- fmt = xlate->host_fmt;
- mode = CAM_READ(pcdev, MODE) & ~(RAZ_FIFO | IRQ_MASK | DMA);
- if (fmt->order == SOC_MBUS_ORDER_LE) {
dev_dbg(dev, "MODE_REG &= ~ORDERCAMD\n");
CAM_WRITE(pcdev, MODE, mode & ~ORDERCAMD);
- } else {
dev_dbg(dev, "MODE_REG |= ORDERCAMD\n");
CAM_WRITE(pcdev, MODE, mode | ORDERCAMD);
- }
- return 0;
+}
+static unsigned int omap1_cam_poll(struct file *file, poll_table *pt) +{
- struct soc_camera_file *icf = file->private_data;
- struct omap1_cam_buf *buf;
- buf = list_entry(icf->vb_vidq.stream.next, struct omap1_cam_buf,
vb.stream);
- poll_wait(file, &buf->vb.done, pt);
- if (buf->vb.state == VIDEOBUF_DONE ||
buf->vb.state == VIDEOBUF_ERROR)
return POLLIN | POLLRDNORM;
- return 0;
+}
+static struct soc_camera_host_ops omap1_host_ops = {
- .owner = THIS_MODULE,
- .add = omap1_cam_add_device,
- .remove = omap1_cam_remove_device,
- .get_formats = omap1_cam_get_formats,
- .set_crop = omap1_cam_set_crop,
- .set_fmt = omap1_cam_set_fmt,
- .try_fmt = omap1_cam_try_fmt,
- .init_videobuf = omap1_cam_init_videobuf,
- .reqbufs = omap1_cam_reqbufs,
- .querycap = omap1_cam_querycap,
- .set_bus_param = omap1_cam_set_bus_param,
- .poll = omap1_cam_poll,
+};
+static int __init omap1_cam_probe(struct platform_device *pdev) +{
- struct omap1_cam_dev *pcdev;
- struct resource *res;
- struct clk *clk;
- void __iomem *base;
- unsigned int irq;
- int err = 0;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- irq = platform_get_irq(pdev, 0);
- if (!res || (int)irq <= 0) {
err = -ENODEV;
goto exit;
- }
- clk = clk_get(&pdev->dev, "armper_ck");
- if (IS_ERR(clk)) {
err = PTR_ERR(clk);
goto exit;
- }
- pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL);
- if (!pcdev) {
dev_err(&pdev->dev, "Could not allocate pcdev\n");
err = -ENOMEM;
goto exit_put_clk;
- }
- pcdev->res = res;
- pcdev->clk = clk;
- pcdev->pdata = pdev->dev.platform_data;
- pcdev->pflags = pcdev->pdata->flags;
- if (pcdev->pdata)
pcdev->camexclk = pcdev->pdata->camexclk_khz * 1000;
- switch (pcdev->camexclk) {
- case 6000000:
- case 8000000:
- case 9600000:
- case 12000000:
- case 24000000:
break;
- default:
dev_warn(&pdev->dev,
"Incorrect sensor clock frequency %ld kHz, "
"should be one of 0, 6, 8, 9.6, 12 or 24 MHz, "
"please correct your platform data\n",
pcdev->pdata->camexclk_khz);
pcdev->camexclk = 0;
- case 0:
dev_info(&pdev->dev,
"Not providing sensor clock\n");
- }
- INIT_LIST_HEAD(&pcdev->capture);
- spin_lock_init(&pcdev->lock);
- /*
* Request the region.
*/
- if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) {
err = -EBUSY;
goto exit_kfree;
- }
- base = ioremap(res->start, resource_size(res));
- if (!base) {
err = -ENOMEM;
goto exit_release;
- }
- pcdev->irq = irq;
- pcdev->base = base;
- sensor_reset(pcdev, true);
- err = omap_request_dma(OMAP_DMA_CAMERA_IF_RX, DRIVER_NAME,
dma_isr, (void *)pcdev, &pcdev->dma_ch);
- if (err < 0) {
dev_err(&pdev->dev, "Can't request DMA for OMAP1 Camera\n");
err = -EBUSY;
goto exit_iounmap;
- }
- dev_dbg(&pdev->dev, "got DMA channel %d\n", pcdev->dma_ch);
- /* preconfigure DMA */
- omap_set_dma_src_params(pcdev->dma_ch, OMAP_DMA_PORT_TIPB,
OMAP_DMA_AMODE_CONSTANT, res->start + REG_CAMDATA,
0, 0);
- omap_set_dma_dest_burst_mode(pcdev->dma_ch, OMAP_DMA_DATA_BURST_4);
- /* setup DMA autoinitialization */
- omap_dma_link_lch(pcdev->dma_ch, pcdev->dma_ch);
- err = request_irq(pcdev->irq, cam_isr, 0, DRIVER_NAME, pcdev);
- if (err) {
dev_err(&pdev->dev, "Camera interrupt register failed\n");
goto exit_free_dma;
- }
- pcdev->soc_host.drv_name = DRIVER_NAME;
- pcdev->soc_host.ops = &omap1_host_ops;
- pcdev->soc_host.priv = pcdev;
- pcdev->soc_host.v4l2_dev.dev = &pdev->dev;
- pcdev->soc_host.nr = pdev->id;
- err = soc_camera_host_register(&pcdev->soc_host);
- if (err)
goto exit_free_irq;
- dev_info(&pdev->dev, "OMAP1 Camera Interface driver loaded\n");
- return 0;
+exit_free_irq:
- free_irq(pcdev->irq, pcdev);
+exit_free_dma:
- omap_free_dma(pcdev->dma_ch);
+exit_iounmap:
- iounmap(base);
+exit_release:
- release_mem_region(res->start, resource_size(res));
+exit_kfree:
- kfree(pcdev);
+exit_put_clk:
- clk_put(clk);
+exit:
- return err;
+}
+static int __exit omap1_cam_remove(struct platform_device *pdev) +{
- struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev);
- struct omap1_cam_dev *pcdev = container_of(soc_host,
struct omap1_cam_dev, soc_host);
- struct resource *res;
- free_irq(pcdev->irq, pcdev);
- omap_free_dma(pcdev->dma_ch);
- soc_camera_host_unregister(soc_host);
- iounmap(pcdev->base);
- res = pcdev->res;
- release_mem_region(res->start, resource_size(res));
- kfree(pcdev);
- clk_put(pcdev->clk);
- dev_info(&pdev->dev, "OMAP1 Camera Interface driver unloaded\n");
- return 0;
+}
+static struct platform_driver omap1_cam_driver = {
- .driver = {
.name = DRIVER_NAME,
- },
- .probe = omap1_cam_probe,
- .remove = __exit_p(omap1_cam_remove),
+};
+static int __init omap1_cam_init(void) +{
- return platform_driver_register(&omap1_cam_driver);
+} +module_init(omap1_cam_init);
+static void __exit omap1_cam_exit(void) +{
- platform_driver_unregister(&omap1_cam_driver);
+} +module_exit(omap1_cam_exit);
+module_param(sg_mode, bool, 0644); +MODULE_PARM_DESC(sg_mode, "videobuf mode, 0: dma-contig (default), 1: dma-sg");
+MODULE_DESCRIPTION("OMAP1 Camera Interface driver"); +MODULE_AUTHOR("Janusz Krzysztofik jkrzyszt@tis.icnet.pl"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff -upr linux-2.6.36-rc3.orig/include/media/omap1_camera.h linux-2.6.36-rc3/include/media/omap1_camera.h --- linux-2.6.36-rc3.orig/include/media/omap1_camera.h 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/include/media/omap1_camera.h 2010-09-08 23:41:12.000000000 +0200 @@ -0,0 +1,35 @@ +/*
- Header for V4L2 SoC Camera driver for OMAP1 Camera Interface
- Copyright (C) 2010, Janusz Krzysztofik jkrzyszt@tis.icnet.pl
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- */
+#ifndef __MEDIA_OMAP1_CAMERA_H_ +#define __MEDIA_OMAP1_CAMERA_H_
+#include <linux/bitops.h>
+#define OMAP1_CAMERA_IOSIZE 0x1c
+enum omap1_cam_vb_mode {
- CONTIG = 0,
- SG,
+};
See above - are these needed here?
+#define OMAP1_CAMERA_MIN_BUF_COUNT(x) ((x) == CONTIG ? 3 : 2)
ditto
+struct omap1_cam_platform_data {
- unsigned long camexclk_khz;
- unsigned long lclk_khz_max;
- unsigned long flags;
+};
+#define OMAP1_CAMERA_LCLK_RISING BIT(0) +#define OMAP1_CAMERA_RST_LOW BIT(1) +#define OMAP1_CAMERA_RST_HIGH BIT(2)
+#endif /* __MEDIA_OMAP1_CAMERA_H_ */
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Hi,
Am Mittwoch, den 22.09.2010, 01:23 +0200 schrieb Guennadi Liakhovetski:
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
This is a V4L2 driver for TI OMAP1 SoC camera interface.
[snip]
- } else {
dev_warn(dev, "%s: unhandled camera interrupt, status == "
"0x%0x\n", __func__, it_status);
Please, don't split strings
sorry for any OT interference.
But, are there any new rules out?
Maybe I missed them.
Either way, the above was forced during the last three years.
Not at all ?
Cheers, Hermann
On Wed, 22 Sep 2010, hermann pitton wrote:
Am Mittwoch, den 22.09.2010, 01:23 +0200 schrieb Guennadi Liakhovetski:
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
This is a V4L2 driver for TI OMAP1 SoC camera interface.
[snip]
- } else {
dev_warn(dev, "%s: unhandled camera interrupt, status == "
"0x%0x\n", __func__, it_status);
Please, don't split strings
sorry for any OT interference.
But, are there any new rules out?
Maybe I missed them.
Either way, the above was forced during the last three years.
Not at all ?
No. Splitting print strings has always been discouraged, because it makes tracking back kernel logs difficult. The reason for this has been the 80 character line length limit, which has now been effectively lifted. I'm sure you can find enough links on the internet for any of these topics.
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Am Mittwoch, den 22.09.2010, 08:08 +0200 schrieb Guennadi Liakhovetski:
On Wed, 22 Sep 2010, hermann pitton wrote:
Am Mittwoch, den 22.09.2010, 01:23 +0200 schrieb Guennadi Liakhovetski:
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
This is a V4L2 driver for TI OMAP1 SoC camera interface.
[snip]
- } else {
dev_warn(dev, "%s: unhandled camera interrupt, status == "
"0x%0x\n", __func__, it_status);
Please, don't split strings
sorry for any OT interference.
But, are there any new rules out?
Maybe I missed them.
Either way, the above was forced during the last three years.
Not at all ?
No. Splitting print strings has always been discouraged, because it makes tracking back kernel logs difficult. The reason for this has been the 80 character line length limit, which has now been effectively lifted. I'm sure you can find enough links on the internet for any of these topics.
Thanks Guennadi
Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Guennadi,
thanks for the update!
If somebody ever would like to waste time on it, lots of patches and whole drivers have been forced into this limitations for strings too.
In fact, fixing only a few lines, including the offset, you almost always did hit it.
I'm pleased to hear now, that this problem never did exist ;))
Cheers, Hermann
Wednesday 22 September 2010 01:23:22 Guennadi Liakhovetski napisał(a):
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
This is a V4L2 driver for TI OMAP1 SoC camera interface.
Both videobuf-dma versions are supported, contig and sg, selectable with a module option. The former uses less processing power, but often fails to allocate contignuous buffer memory. The latter is free of this problem, but generates tens of DMA interrupts per frame. If contig memory allocation ever fails, the driver falls back to sg automatically on next open, but still can be switched back to contig manually. Both paths work stable for me, even under heavy load, on my OMAP1510 based Amstrad Delta videophone, that is the oldest, least powerfull OMAP1 implementation.
The interface generally works in pass-through mode. Since input data byte endianess can be swapped, it provides up to two v4l2 pixel formats per each of several soc_mbus formats that have their swapped endian counterparts.
Boards using this driver can provide it with the followning information:
- if and what freqency clock is expected by an on-board camera sensor,
- what is the maximum pixel clock that should be accepted from the
sensor, - what is the polarity of the sensor provided pixel clock,
- if the interface GPIO line is connected to a sensor reset/powerdown
input and what is the input polarity.
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl
Friday 30 July 2010 13:07:42 Guennadi Liakhovetski wrote:
So, I think, a very welcome improvement to the driver would be a cleaner separation between the two cases. Don't try that much to reuse the code as much as possible. Would be much better to have clean separation between the two implementations - whether dynamically switchable at runtime or at config time - would be best to separate the two implementations to the point, where each of them becomes understandable and maintainable.
Guennadi, I've tried to rearrange them spearated, as you requested, but finally decided to keep them together, as before, only better documented and cleaned up as much as possible. I'm rather satisfied with the result, but if you think it is still not enough understandable and maintainable, I'll take one more iteration and split both paths.
Well, I think, I'll move a bit towards the "if it breaks - someone gets to fix it, or it gets dropped" policy, i.e., I'll give you more freedom (don't know what's wrong with me today;))
Hi Guennadi, Thanks!
...
+#define DMA_FRAME_SHIFT(x) (x ? DMA_FRAME_SHIFT_SG : \
DMA_FRAME_SHIFT_CONTIG)
Don't you want to compare (x) against CONTIG and you want to put x in parenthesis in the DMA_FRAME_SHIFT macro.
Sure.
Besides, CONTIG and SG are not good enough names to be defined in a header under include/... Looks like you don't need them at all in the header? You only use them in this file, so, just move them inside.
Please see my very last comment below.
...
+static void videobuf_done(struct omap1_cam_dev *pcdev,
enum videobuf_state result)
+{
- struct omap1_cam_buf *buf = pcdev->active;
- struct videobuf_buffer *vb;
- struct device *dev = pcdev->icd->dev.parent;
- if (WARN_ON(!buf)) {
suspend_capture(pcdev);
disable_capture(pcdev);
return;
- }
- if (result == VIDEOBUF_ERROR)
suspend_capture(pcdev);
- vb = &buf->vb;
- if (waitqueue_active(&vb->done)) {
if (!pcdev->ready && result != VIDEOBUF_ERROR)
/*
* No next buffer has been entered into the DMA
* programming register set on time, so best we can do
* is stopping the capture before last DMA block,
* whether our CONTIG mode whole buffer or its last
* sgbuf in SG mode, gets overwritten by next frame.
*/
Hm, why do you think it's a good idea? This specific buffer completed successfully, but you want to fail it just because the next buffer is missing? Any specific reason for this?
Maybe my comment is not clear enough, but the below suspend_capture() doesn't indicate any failure on a frame just captured. It only prevents the frame from being overwritten by the already autoreinitialized DMA engine, pointing back to the same buffer once again.
Besides, you seem to also be considering the possibility of your ->ready == NULL, but the queue non-empty, in which case you just take the next buffer from the queue and continue with it. Why error out in this case?
pcdev->ready == NULL means no buffer was available when it was time to put it into the DMA programming register set. As a result, a next DMA transfer has just been autoreinitialized with the same buffer parameters as before. To protect the buffer from being overwriten unintentionally, we have to stop the DMA transfer as soon as possible, hopefully before the sensor starts sending out next frame data.
If a new buffer has been queued meanwhile, best we can do is stopping everything, programming the DMA with the new buffer, and setting up for a new transfer hardware auto startup on nearest frame start, be it the next one if we are lucky enough, or one after the next if we are too slow.
And even if also the queue is empty - still not sure, why.
I hope the above explanation clarifies why.
I'll try to rework the above comment to be more clear, OK? Any hints?
suspend_capture(pcdev);
vb->state = result;
do_gettimeofday(&vb->ts);
if (result != VIDEOBUF_ERROR)
vb->field_count++;
wake_up(&vb->done);
/* shift in next buffer */
buf = pcdev->ready;
pcdev->active = buf;
pcdev->ready = NULL;
if (!buf) {
/*
* No next buffer was ready on time (see above), so
* indicate error condition to force capture restart or
* stop, depending on next buffer already queued or not.
*/
result = VIDEOBUF_ERROR;
prepare_next_vb(pcdev);
buf = pcdev->ready;
pcdev->active = buf;
pcdev->ready = NULL;
}
- } else if (pcdev->ready) {
/*
* In both CONTIG and SG mode, the DMA engine has (might)
s/might/possibly/
OK
* already been autoreinitialized with the preprogrammed
* pcdev->ready buffer. We can either accept this fact
* and just swap the buffers, or provoke an error condition
* and restart capture. The former seems less intrusive.
*/
dev_dbg(dev, "%s: nobody waiting on videobuf, swap with next\n",
__func__);
pcdev->active = pcdev->ready;
if (pcdev->vb_mode == SG) {
/*
* In SG mode, we have to make sure that the buffer we
* are putting back into the pcdev->ready is marked
* fresh.
*/
buf->sgbuf = NULL;
}
pcdev->ready = buf;
buf = pcdev->active;
- } else {
/*
* No next buffer has been entered into
* the DMA programming register set on time.
*/
if (pcdev->vb_mode == CONTIG) {
/*
* In CONTIG mode, the DMA engine has already been
* reinitialized with the current buffer. Best we can do
* is not touching it.
*/
dev_dbg(dev,
"%s: nobody waiting on videobuf, reuse it\n",
__func__);
} else {
/*
* In SG mode, the DMA engine has just been
* autoreinitialized with the last sgbuf from the
* current list. Restart capture in order to transfer
* next frame start into the first sgbuf, not the last
* one.
*/
if (result != VIDEOBUF_ERROR) {
suspend_capture(pcdev);
result = VIDEOBUF_ERROR;
}
}
- }
- if (!buf) {
dev_dbg(dev, "%s: no more videobufs, stop capture\n", __func__);
disable_capture(pcdev);
return;
- }
- if (pcdev->vb_mode == CONTIG) {
/*
* In CONTIG mode, the current buffer parameters had already
* been entered into the DMA programming register set while the
* buffer was fetched with prepare_next_vb(), they may have also
* been transfered into the runtime set and already active if
* the DMA still running.
*/
- } else {
/* In SG mode, extra steps are required */
if (result == VIDEOBUF_ERROR)
/* make sure we (re)use sglist from start on error */
buf->sgbuf = NULL;
/*
* In any case, enter the next sgbuf parameters into the DMA
* programming register set. They will be used either during
* nearest DMA autoreinitialization or, in case of an error,
* on DMA startup below.
*/
try_next_sgbuf(pcdev->dma_ch, buf);
- }
- if (result == VIDEOBUF_ERROR) {
dev_dbg(dev, "%s: videobuf error; reset FIFO, restart DMA\n",
__func__);
start_capture(pcdev);
/*
* In SG mode, the above also resulted in the next sgbuf
* parameters being entered into the DMA programming register
* set, making them ready for next DMA autoreinitialization.
*/
- }
- /*
* Finally, try fetching next buffer. In CONTIG mode, it will also
* get entered it into the DMA programming register set,
s/get entered/enter/
OK.
* making it ready for next DMA autoreinitialization.
*/
- prepare_next_vb(pcdev);
+}
+static void dma_isr(int channel, unsigned short status, void *data) +{
- struct omap1_cam_dev *pcdev = data;
- struct omap1_cam_buf *buf = pcdev->active;
- unsigned long flags;
- spin_lock_irqsave(&pcdev->lock, flags);
- if (WARN_ON(!buf)) {
suspend_capture(pcdev);
disable_capture(pcdev);
goto out;
- }
- if (pcdev->vb_mode == CONTIG) {
/*
* In CONTIG mode, assume we have just managed to collect the
* whole frame, hopefully before our end of frame watchdog is
* triggered. Then, all we have to do is disabling the watchdog
* for this frame, and calling videobuf_done() with success
* indicated.
*/
CAM_WRITE(pcdev, MODE,
CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN);
videobuf_done(pcdev, VIDEOBUF_DONE);
- } else {
/*
* In SG mode, we have to process every sgbuf from the current
* sglist, one after another.
*/
if (buf->sgbuf) {
/*
* Current sglist not completed yet, try fetching next
* sgbuf, hopefully putting it into the DMA programming
* register set, making it ready for next DMA
* autoreinitialization.
*/
try_next_sgbuf(pcdev->dma_ch, buf);
if (buf->sgbuf)
goto out;
/* no more sgbufs in the current sglist */
if (buf->result != VIDEOBUF_ERROR) {
/*
* Video frame collected without errors, we can
* prepare for collecting a next one as soon as
* DMA gets autoreinitialized after the currennt
* (last) sgbuf is completed.
*/
buf = prepare_next_vb(pcdev);
if (!buf)
goto out;
try_next_sgbuf(pcdev->dma_ch, buf);
goto out;
Uh, sorry, maybe I cannot quite follow the logic here, but it doesn't seem quite correct to me. Looks like you do not complete the current (successfully completed) buffer, if you failed to prepare the next one?
Looks like there is also some space for improvement in my documentation here.
In short, until the DMA ISR is called with the buf->sgbuf reset back to NULL, which should happen while the previous DMA ISR's called try_next_sgbuf() has retuned NULL, the frame is still not ready, ie. still being filled with DMA. IOW, only the DMA interrupt with buf->sgbuf == NULL means that the last sgbuf DMA transfer has just been completed.
Meanwhile, a next sglist could already be put in advanvce into the DMA programming register set and just activated (copied to the DMA working register set) automatically.
}
}
/* end of videobuf */
videobuf_done(pcdev, buf->result);
- }
+out:
- spin_unlock_irqrestore(&pcdev->lock, flags);
+}
...
* If exactly 2 sgbufs from the next sglist has
s/has/have/
Thanks.
...
dev_warn(dev, "%s: unhandled camera interrupt, status == "
"0x%0x\n", __func__, it_status);
Please, don't split strings
OK.
...
+static int omap1_cam_add_device(struct soc_camera_device *icd) +{
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- u32 ctrlclock;
- if (pcdev->icd)
return -EBUSY;
- clk_enable(pcdev->clk);
- /* setup sensor clock */
- ctrlclock = CAM_READ(pcdev, CTRLCLOCK);
- ctrlclock &= ~(CAMEXCLK_EN | MCLK_EN | DPLL_EN);
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
- ctrlclock &= ~FOSCMOD_MASK;
- switch (pcdev->camexclk) {
- case 6000000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_6MHz;
break;
- case 8000000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_8MHz | DPLL_EN;
break;
- case 9600000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_9_6MHz | DPLL_EN;
break;
- case 12000000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_12MHz;
break;
- case 24000000:
ctrlclock |= CAMEXCLK_EN | FOSCMOD_24MHz | DPLL_EN;
- default:
This default doesn't make much sense here, maybe put it to one of these values, that you think is a reasonable fall back. Ah, right, you wanted to check, whether this can work...
Sorry, I forgot to answer your previously asked question about the MCLK_EN (master clock enable) requirement if not rising the CAMEXCLK_EN (sensor clock enable).
The documentation (http://focus.ti.com/lit/ug/spru684/spru684.pdf) says:
"the MCLK_EN bit must first be set before any camera-interface registers access"
so the answer is yes, the MCLK is required not only for providing a sensor with a clock, but also for the host interface itself to do its job.
The default case above (no CAMEXCLK_EN) is intended to be used on a board whith a sensor that is driven from a clock source other than the interface's CAM_EXCLK pin, which is keept idle in this case. Am I missing something?
My general assumption was to provide support for all interface hardware features which are supported by the existing soc_camera framework, not only those required by my board or sensor. Could it be that this approach breaks a general rule of not putting any code that is not (yet) used by any consumer?
break;
- }
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~DPLL_EN);
- /* enable internal clock */
- ctrlclock |= MCLK_EN;
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
- sensor_reset(pcdev, false);
- pcdev->icd = icd;
- dev_info(icd->dev.parent, "OMAP1 Camera driver attached to camera
%d\n", + icd->devnum);
- return 0;
+}
...
- ret = dma_align(&rect->width, &rect->height,
icd->current_fmt->host_fmt, + false);
- if (ret < 0) {
dev_err(dev, "%s: failed to align %ux%u %s with DMA\n",
__func__, rect->width, rect->height,
icd->current_fmt->host_fmt->name);
return ret;
- }
- subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, crop);
Missing "ret = "?
Yes, thanks!
- if (ret < 0) {
dev_warn(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__,
rect->width, rect->height, rect->left, rect->top);
return ret;
- }
...
- dev_notice(dev, "%s: sensor geometry not DMA aligned, trying to crop
to" + " %ux%u\n", __func__, pix->width, pix->height);
One line
OK.
- ret = get_crop(icd, rect);
- if (ret < 0) {
dev_err(dev, "%s: get_crop() failed\n", __func__);
return ret;
- }
- rect->width = pix->width;
- rect->height = pix->height;
- crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, &crop);
'ret = '?
Of course.
- if (ret < 0) {
dev_err(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__,
rect->width, rect->height, rect->left, rect->top);
return ret;
- }
...
sg_mode = 1;
"true"
Right.
...
+static int omap1_cam_set_bus_param(struct soc_camera_device *icd,
__u32 pixfmt)
+{
- struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
- struct omap1_cam_dev *pcdev = ici->priv;
- struct device *dev = icd->dev.parent;
- const struct soc_camera_format_xlate *xlate;
- const struct soc_mbus_pixelfmt *fmt;
- unsigned long camera_flags, common_flags;
- u32 ctrlclock, mode;
- int ret;
- camera_flags = icd->ops->query_bus_param(icd);
- common_flags = soc_camera_bus_param_compatible(camera_flags,
SOCAM_BUS_FLAGS);
- if (!common_flags)
return -EINVAL;
- /* Make choices, possibly based on platform configuration */
- if ((common_flags & SOCAM_PCLK_SAMPLE_RISING) &&
(common_flags & SOCAM_PCLK_SAMPLE_FALLING)) {
if (!pcdev->pdata ||
pcdev->pdata->flags & OMAP1_CAMERA_LCLK_RISING)
Out of curiosity - do you need to force a specific clock polarity on your platform
If you mean my board - no, I don't think so.
or do you know of any, where this is necessary (beyond the natural restriction, coming from the sensor - if any).
No, neither.
I.e., I'm not at all sure you need this flag, but if you do - I would apply it earlier - to mask the respective bit from SOCAM_BUS_FLAGS.
Maybe my understanding of what is actually going on here is not enough. TBH, I've just copy-pasted this piece of code from mx1_camera.c in order to provide support for a hardware feature that is available on my platform, regardles of any boards actually using it or not. Almost all existing soc_camera host drivers, ie. the pxa_camera.c and all of mx?_camera.c, follow the same or very similiar pattern, and the only exception, sh_mobile_ceu_camera.c, looks for me like just missing this hardware feature. Then, having no example implementation to follow and not ehough understanding of the matter, I'm not sure if I'm able to make you happy. Please give me more details if you think this is important.
common_flags &= ~SOCAM_PCLK_SAMPLE_FALLING;
else
common_flags &= ~SOCAM_PCLK_SAMPLE_RISING;
- }
- ret = icd->ops->set_bus_param(icd, common_flags);
- if (ret < 0)
return ret;
- ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK);
- if (ctrlclock & LCLK_EN)
CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
- if (common_flags & SOCAM_PCLK_SAMPLE_RISING) {
dev_dbg(dev, "CTRLCLOCK_REG |= POLCLK\n");
ctrlclock |= POLCLK;
- } else {
dev_dbg(dev, "CTRLCLOCK_REG &= ~POLCLK\n");
ctrlclock &= ~POLCLK;
- }
- CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
- if (ctrlclock & LCLK_EN)
CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
- /* select bus endianess */
- xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
- fmt = xlate->host_fmt;
- mode = CAM_READ(pcdev, MODE) & ~(RAZ_FIFO | IRQ_MASK | DMA);
- if (fmt->order == SOC_MBUS_ORDER_LE) {
dev_dbg(dev, "MODE_REG &= ~ORDERCAMD\n");
CAM_WRITE(pcdev, MODE, mode & ~ORDERCAMD);
- } else {
dev_dbg(dev, "MODE_REG |= ORDERCAMD\n");
CAM_WRITE(pcdev, MODE, mode | ORDERCAMD);
- }
- return 0;
+}
...
linux-2.6.36-rc3.orig/include/media/omap1_camera.h 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/include/media/omap1_camera.h 2010-09-08 23:41:12.000000000 +0200 @@ -0,0 +1,35 @@ +/*
- Header for V4L2 SoC Camera driver for OMAP1 Camera Interface
- Copyright (C) 2010, Janusz Krzysztofik jkrzyszt@tis.icnet.pl
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- */
+#ifndef __MEDIA_OMAP1_CAMERA_H_ +#define __MEDIA_OMAP1_CAMERA_H_
+#include <linux/bitops.h>
+#define OMAP1_CAMERA_IOSIZE 0x1c
+enum omap1_cam_vb_mode {
- CONTIG = 0,
- SG,
+};
See above - are these needed here?
+#define OMAP1_CAMERA_MIN_BUF_COUNT(x) ((x) == CONTIG ? 3 : 2)
ditto
I moved them both over to the header file because I was using the OMAP1_CAMERA_MIN_BUF_COUNT(CONTIG) macro once from the platform code in order to calculate the buffer size when calling the then NAKed dma_preallocate_coherent_memory(). Now I could put them back into the driver code, but if we ever get back to the concept of preallocating a contignuos piece of memory from the platform init code, we might need them back here, so maybe I should rather keep them, only rename the two enum values using a distinct name space. What do you think is better for now?
Thanks, Janusz
On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:
Wednesday 22 September 2010 01:23:22 Guennadi Liakhovetski napisał(a):
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
- vb = &buf->vb;
- if (waitqueue_active(&vb->done)) {
if (!pcdev->ready && result != VIDEOBUF_ERROR)
/*
* No next buffer has been entered into the DMA
* programming register set on time, so best we can do
* is stopping the capture before last DMA block,
* whether our CONTIG mode whole buffer or its last
* sgbuf in SG mode, gets overwritten by next frame.
*/
Hm, why do you think it's a good idea? This specific buffer completed successfully, but you want to fail it just because the next buffer is missing? Any specific reason for this?
Maybe my comment is not clear enough, but the below suspend_capture() doesn't indicate any failure on a frame just captured. It only prevents the frame from being overwritten by the already autoreinitialized DMA engine, pointing back to the same buffer once again.
Besides, you seem to also be considering the possibility of your ->ready == NULL, but the queue non-empty, in which case you just take the next buffer from the queue and continue with it. Why error out in this case?
pcdev->ready == NULL means no buffer was available when it was time to put it into the DMA programming register set.
But how? Buffers are added onto the list in omap1_videobuf_queue() under spin_lock_irqsave(); and there you also check ->ready and fill it in. In your completion you set ->ready = NULL, but then also call prepare_next_vb() to get the next buffer from the list - if there are any, so, how can it be NULL with a non-empty list?
As a result, a next DMA transfer has just been autoreinitialized with the same buffer parameters as before. To protect the buffer from being overwriten unintentionally, we have to stop the DMA transfer as soon as possible, hopefully before the sensor starts sending out next frame data.
If a new buffer has been queued meanwhile, best we can do is stopping everything, programming the DMA with the new buffer, and setting up for a new transfer hardware auto startup on nearest frame start, be it the next one if we are lucky enough, or one after the next if we are too slow.
And even if also the queue is empty - still not sure, why.
I hope the above explanation clarifies why.
I'll try to rework the above comment to be more clear, OK? Any hints?
linux-2.6.36-rc3.orig/include/media/omap1_camera.h 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/include/media/omap1_camera.h 2010-09-08 23:41:12.000000000 +0200 @@ -0,0 +1,35 @@ +/*
- Header for V4L2 SoC Camera driver for OMAP1 Camera Interface
- Copyright (C) 2010, Janusz Krzysztofik jkrzyszt@tis.icnet.pl
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- */
+#ifndef __MEDIA_OMAP1_CAMERA_H_ +#define __MEDIA_OMAP1_CAMERA_H_
+#include <linux/bitops.h>
+#define OMAP1_CAMERA_IOSIZE 0x1c
+enum omap1_cam_vb_mode {
- CONTIG = 0,
- SG,
+};
See above - are these needed here?
+#define OMAP1_CAMERA_MIN_BUF_COUNT(x) ((x) == CONTIG ? 3 : 2)
ditto
I moved them both over to the header file because I was using the OMAP1_CAMERA_MIN_BUF_COUNT(CONTIG) macro once from the platform code in order to calculate the buffer size when calling the then NAKed dma_preallocate_coherent_memory(). Now I could put them back into the driver code, but if we ever get back to the concept of preallocating a contignuos piece of memory from the platform init code, we might need them back here, so maybe I should rather keep them, only rename the two enum values using a distinct name space. What do you think is better for now?
Yeah, up to you, I'd say, but if you decide to keep them in the header, please, use a namespace.
I'm satisfied with your answers to the rest of my questions / comments:)
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Thursday 23 September 2010 15:33:54 Guennadi Liakhovetski napisał(a):
On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:
Wednesday 22 September 2010 01:23:22 Guennadi Liakhovetski napisał(a):
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
- vb = &buf->vb;
- if (waitqueue_active(&vb->done)) {
if (!pcdev->ready && result != VIDEOBUF_ERROR)
/*
* No next buffer has been entered into the DMA
* programming register set on time, so best we can do
* is stopping the capture before last DMA block,
* whether our CONTIG mode whole buffer or its last
* sgbuf in SG mode, gets overwritten by next frame.
*/
Hm, why do you think it's a good idea? This specific buffer completed successfully, but you want to fail it just because the next buffer is missing? Any specific reason for this?
Maybe my comment is not clear enough, but the below suspend_capture() doesn't indicate any failure on a frame just captured. It only prevents the frame from being overwritten by the already autoreinitialized DMA engine, pointing back to the same buffer once again.
Besides, you seem to also be considering the possibility of your ->ready == NULL, but the queue non-empty, in which case you just take the next buffer from the queue and continue with it. Why error out in this case?
pcdev->ready == NULL means no buffer was available when it was time to put it into the DMA programming register set.
But how? Buffers are added onto the list in omap1_videobuf_queue() under spin_lock_irqsave(); and there you also check ->ready and fill it in.
Guennadi, Yes, but only if pcdev->active is NULL, ie. both DMA and FIFO are idle, never if active:
+ list_add_tail(&vb->queue, &pcdev->capture); + vb->state = VIDEOBUF_QUEUED; + + if (pcdev->active) + return;
Since the transfer of the DMA programming register set content to the DMA working register set is done automatically by the DMA hardware, this can pretty well happen while I keep the lock here, so I can't be sure if it's not too late for entering new data into the programming register set. Then, I decided that this operation should be done only just after the DMA interrupt occured, ie. the current DMA programming register set content has just been used and can be overwriten.
I'll emphasize the above return; with a comment.
In your completion you set ->ready = NULL, but then also call prepare_next_vb() to get the next buffer from the list - if there are any, so, how can it be NULL with a non-empty list?
It happens after the above mentioned prepare_next_vb() gets nothing from an empty queue, so nothing is entered into the DMA programming register set, only the last, just activated, buffer is processed, then omap1_videobuf_queue() puts a new buffer into the queue while the active buffer is still filled in, and finally the DMA ISR is called on this last active buffer completion.
I hope this helps.
As a result, a next DMA transfer has just been autoreinitialized with the same buffer parameters as before. To protect the buffer from being overwriten unintentionally, we have to stop the DMA transfer as soon as possible, hopefully before the sensor starts sending out next frame data.
If a new buffer has been queued meanwhile, best we can do is stopping everything, programming the DMA with the new buffer, and setting up for a new transfer hardware auto startup on nearest frame start, be it the next one if we are lucky enough, or one after the next if we are too slow.
And even if also the queue is empty - still not sure, why.
I hope the above explanation clarifies why.
I'll try to rework the above comment to be more clear, OK? Any hints?
linux-2.6.36-rc3.orig/include/media/omap1_camera.h 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/include/media/omap1_camera.h 2010-09-08 23:41:12.000000000 +0200 @@ -0,0 +1,35 @@ +/*
- Header for V4L2 SoC Camera driver for OMAP1 Camera Interface
- Copyright (C) 2010, Janusz Krzysztofik jkrzyszt@tis.icnet.pl
- This program is free software; you can redistribute it and/or
modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation.
- */
+#ifndef __MEDIA_OMAP1_CAMERA_H_ +#define __MEDIA_OMAP1_CAMERA_H_
+#include <linux/bitops.h>
+#define OMAP1_CAMERA_IOSIZE 0x1c
+enum omap1_cam_vb_mode {
- CONTIG = 0,
- SG,
+};
See above - are these needed here?
+#define OMAP1_CAMERA_MIN_BUF_COUNT(x) ((x) == CONTIG ? 3 : 2)
ditto
I moved them both over to the header file because I was using the OMAP1_CAMERA_MIN_BUF_COUNT(CONTIG) macro once from the platform code in order to calculate the buffer size when calling the then NAKed dma_preallocate_coherent_memory(). Now I could put them back into the driver code, but if we ever get back to the concept of preallocating a contignuos piece of memory from the platform init code, we might need them back here, so maybe I should rather keep them, only rename the two enum values using a distinct name space. What do you think is better for now?
Yeah, up to you, I'd say, but if you decide to keep them in the header, please, use a namespace.
OK, I'll use a namespace then.
I'm satisfied with your answers to the rest of my questions / comments:)
Glad to hear :)
Thanks, Janusz
Thanks Guennadi
Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/ -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, 23 Sep 2010, Janusz Krzysztofik wrote:
Thursday 23 September 2010 15:33:54 Guennadi Liakhovetski napisał(a):
On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:
Wednesday 22 September 2010 01:23:22 Guennadi Liakhovetski napisał(a):
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
- vb = &buf->vb;
- if (waitqueue_active(&vb->done)) {
if (!pcdev->ready && result != VIDEOBUF_ERROR)
/*
* No next buffer has been entered into the DMA
* programming register set on time, so best we can do
* is stopping the capture before last DMA block,
* whether our CONTIG mode whole buffer or its last
* sgbuf in SG mode, gets overwritten by next frame.
*/
Hm, why do you think it's a good idea? This specific buffer completed successfully, but you want to fail it just because the next buffer is missing? Any specific reason for this?
Maybe my comment is not clear enough, but the below suspend_capture() doesn't indicate any failure on a frame just captured. It only prevents the frame from being overwritten by the already autoreinitialized DMA engine, pointing back to the same buffer once again.
Besides, you seem to also be considering the possibility of your ->ready == NULL, but the queue non-empty, in which case you just take the next buffer from the queue and continue with it. Why error out in this case?
pcdev->ready == NULL means no buffer was available when it was time to put it into the DMA programming register set.
But how? Buffers are added onto the list in omap1_videobuf_queue() under spin_lock_irqsave(); and there you also check ->ready and fill it in.
Guennadi, Yes, but only if pcdev->active is NULL, ie. both DMA and FIFO are idle, never if active:
- list_add_tail(&vb->queue, &pcdev->capture);
- vb->state = VIDEOBUF_QUEUED;
- if (pcdev->active)
return;
Since the transfer of the DMA programming register set content to the DMA working register set is done automatically by the DMA hardware, this can pretty well happen while I keep the lock here, so I can't be sure if it's not too late for entering new data into the programming register set. Then, I decided that this operation should be done only just after the DMA interrupt occured, ie. the current DMA programming register set content has just been used and can be overwriten.
I'll emphasize the above return; with a comment.
Ok
In your completion you set ->ready = NULL, but then also call prepare_next_vb() to get the next buffer from the list - if there are any, so, how can it be NULL with a non-empty list?
It happens after the above mentioned prepare_next_vb() gets nothing from an empty queue, so nothing is entered into the DMA programming register set, only the last, just activated, buffer is processed, then omap1_videobuf_queue() puts a new buffer into the queue while the active buffer is still filled in, and finally the DMA ISR is called on this last active buffer completion.
I hope this helps.
Let's assume it does:) You seem to really understand how this is working and even be willing to document the driver, thus making it possibly the best documented soc-camera related piece of software;)
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
This patch adds support for SoC camera interface to OMAP1 devices.
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
For successfull compilation, requires a header file provided by PATCH 1/6 from this series, "SoC Camera: add driver for OMAP1 camera interface".
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl --- v1->v2 changes: - no functional changes, - refreshed against linux-2.6.36-rc3
arch/arm/mach-omap1/devices.c | 43 ++++++++++++++++++++++++++++++ arch/arm/mach-omap1/include/mach/camera.h | 8 +++++ 2 files changed, 51 insertions(+)
diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c 2010-09-03 22:29:00.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c 2010-09-09 18:42:30.000000000 +0200 @@ -15,6 +15,7 @@ #include <linux/platform_device.h> #include <linux/io.h> #include <linux/spi/spi.h> +#include <linux/dma-mapping.h>
#include <mach/hardware.h> #include <asm/mach/map.h> @@ -25,6 +26,7 @@ #include <mach/gpio.h> #include <plat/mmc.h> #include <plat/omap7xx.h> +#include <mach/camera.h>
/*-------------------------------------------------------------------------*/
@@ -191,6 +193,47 @@ static inline void omap_init_spi100k(voi } #endif
+ +#define OMAP1_CAMERA_BASE 0xfffb6800 + +static struct resource omap1_camera_resources[] = { + [0] = { + .start = OMAP1_CAMERA_BASE, + .end = OMAP1_CAMERA_BASE + OMAP1_CAMERA_IOSIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = INT_CAMERA, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 omap1_camera_dma_mask = DMA_BIT_MASK(32); + +static struct platform_device omap1_camera_device = { + .name = "omap1-camera", + .id = 0, /* This is used to put cameras on this interface */ + .dev = { + .dma_mask = &omap1_camera_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, + .num_resources = ARRAY_SIZE(omap1_camera_resources), + .resource = omap1_camera_resources, +}; + +void __init omap1_set_camera_info(struct omap1_cam_platform_data *info) +{ + struct platform_device *dev = &omap1_camera_device; + int ret; + + dev->dev.platform_data = info; + + ret = platform_device_register(dev); + if (ret) + dev_err(&dev->dev, "unable to register device: %d\n", ret); +} + + /*-------------------------------------------------------------------------*/
static inline void omap_init_sti(void) {} diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h 2010-09-03 22:34:03.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h 2010-09-09 18:42:30.000000000 +0200 @@ -0,0 +1,8 @@ +#ifndef __ASM_ARCH_CAMERA_H_ +#define __ASM_ARCH_CAMERA_H_ + +#include <media/omap1_camera.h> + +extern void omap1_set_camera_info(struct omap1_cam_platform_data *); + +#endif /* __ASM_ARCH_CAMERA_H_ */
This patch adds support for SoC camera interface to OMAP1 devices.
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
For successfull compilation, requires a header file provided by PATCH 1/6 from this series, "SoC Camera: add driver for OMAP1 camera interface".
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl --- Resend because of wrapped lines, sorry. Janusz
v1->v2 changes: - no functional changes, - refreshed against linux-2.6.36-rc3
arch/arm/mach-omap1/devices.c | 43 ++++++++++++++++++++++++++++++ arch/arm/mach-omap1/include/mach/camera.h | 8 +++++ 2 files changed, 51 insertions(+)
diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c 2010-09-03 22:29:00.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c 2010-09-09 18:42:30.000000000 +0200 @@ -15,6 +15,7 @@ #include <linux/platform_device.h> #include <linux/io.h> #include <linux/spi/spi.h> +#include <linux/dma-mapping.h>
#include <mach/hardware.h> #include <asm/mach/map.h> @@ -25,6 +26,7 @@ #include <mach/gpio.h> #include <plat/mmc.h> #include <plat/omap7xx.h> +#include <mach/camera.h>
/*-------------------------------------------------------------------------*/
@@ -191,6 +193,47 @@ static inline void omap_init_spi100k(voi } #endif
+ +#define OMAP1_CAMERA_BASE 0xfffb6800 + +static struct resource omap1_camera_resources[] = { + [0] = { + .start = OMAP1_CAMERA_BASE, + .end = OMAP1_CAMERA_BASE + OMAP1_CAMERA_IOSIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = INT_CAMERA, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 omap1_camera_dma_mask = DMA_BIT_MASK(32); + +static struct platform_device omap1_camera_device = { + .name = "omap1-camera", + .id = 0, /* This is used to put cameras on this interface */ + .dev = { + .dma_mask = &omap1_camera_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, + .num_resources = ARRAY_SIZE(omap1_camera_resources), + .resource = omap1_camera_resources, +}; + +void __init omap1_set_camera_info(struct omap1_cam_platform_data *info) +{ + struct platform_device *dev = &omap1_camera_device; + int ret; + + dev->dev.platform_data = info; + + ret = platform_device_register(dev); + if (ret) + dev_err(&dev->dev, "unable to register device: %d\n", ret); +} + + /*-------------------------------------------------------------------------*/
static inline void omap_init_sti(void) {} diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h 2010-09-03 22:34:03.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h 2010-09-09 18:42:30.000000000 +0200 @@ -0,0 +1,8 @@ +#ifndef __ASM_ARCH_CAMERA_H_ +#define __ASM_ARCH_CAMERA_H_ + +#include <media/omap1_camera.h> + +extern void omap1_set_camera_info(struct omap1_cam_platform_data *); + +#endif /* __ASM_ARCH_CAMERA_H_ */
* Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100910 18:26]:
This patch adds support for SoC camera interface to OMAP1 devices.
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
For successfull compilation, requires a header file provided by PATCH 1/6 from this series, "SoC Camera: add driver for OMAP1 camera interface".
<snip>
diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h 2010-09-03 22:34:03.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h 2010-09-09 18:42:30.000000000 +0200 @@ -0,0 +1,8 @@ +#ifndef __ASM_ARCH_CAMERA_H_ +#define __ASM_ARCH_CAMERA_H_
+#include <media/omap1_camera.h>
+extern void omap1_set_camera_info(struct omap1_cam_platform_data *);
+#endif /* __ASM_ARCH_CAMERA_H_ */
Care to refresh this patch so it does not include media/omap1_camera.h?
That way things keep building if I merge this one along with the omap patches and the drivers/media patches can get merged their via media.
I think you can just move the OMAP1_CAMERA_IOSIZE to the devices.c or someplace like that?
Regards,
Tony
Friday 24 September 2010 01:23:10 Tony Lindgren napisał(a):
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100910 18:26]:
This patch adds support for SoC camera interface to OMAP1 devices.
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
For successfull compilation, requires a header file provided by PATCH 1/6 from this series, "SoC Camera: add driver for OMAP1 camera interface".
<snip>
diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h
linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h 2010-09-0 3 22:34:03.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h 2010-09-09 18:42:30.000000000 +0200 @@ -0,0 +1,8 @@ +#ifndef __ASM_ARCH_CAMERA_H_ +#define __ASM_ARCH_CAMERA_H_
+#include <media/omap1_camera.h>
+extern void omap1_set_camera_info(struct omap1_cam_platform_data *);
+#endif /* __ASM_ARCH_CAMERA_H_ */
Care to refresh this patch so it does not include media/omap1_camera.h?
That way things keep building if I merge this one along with the omap patches and the drivers/media patches can get merged their via media.
I think you can just move the OMAP1_CAMERA_IOSIZE to the devices.c or someplace like that?
Tony, Not exactly. I use the OMAP1_CAMERA_IOSIZE inside the driver when reserving space for register cache.
I think that I could just duplicate its definition in the devices.c for now, than clean things up with a folloup patch when both parts already get merged. Would this be acceptable?
Thanks, Janusz
Regards,
Tony
To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
* Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100923 16:37]:
Friday 24 September 2010 01:23:10 Tony Lindgren napisał(a):
I think you can just move the OMAP1_CAMERA_IOSIZE to the devices.c or someplace like that?
Tony, Not exactly. I use the OMAP1_CAMERA_IOSIZE inside the driver when reserving space for register cache.
I think that I could just duplicate its definition in the devices.c for now, than clean things up with a folloup patch when both parts already get merged. Would this be acceptable?
Yeah, that sounds good to me.
Tony
On Thu, 23 Sep 2010, Tony Lindgren wrote:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100923 16:37]:
Friday 24 September 2010 01:23:10 Tony Lindgren napisał(a):
I think you can just move the OMAP1_CAMERA_IOSIZE to the devices.c or someplace like that?
Tony, Not exactly. I use the OMAP1_CAMERA_IOSIZE inside the driver when reserving space for register cache.
I think that I could just duplicate its definition in the devices.c for now, than clean things up with a folloup patch when both parts already get merged. Would this be acceptable?
Yeah, that sounds good to me.
...better yet put a zero-length cache array at the end of your struct omap1_cam_dev and allocate it dynamically, calculated from the resource size.
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Friday 24 September 2010 08:54:20 Guennadi Liakhovetski napisał(a):
On Thu, 23 Sep 2010, Tony Lindgren wrote:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100923 16:37]:
Friday 24 September 2010 01:23:10 Tony Lindgren napisał(a):
I think you can just move the OMAP1_CAMERA_IOSIZE to the devices.c or someplace like that?
Tony, Not exactly. I use the OMAP1_CAMERA_IOSIZE inside the driver when reserving space for register cache.
I think that I could just duplicate its definition in the devices.c for now, than clean things up with a folloup patch when both parts already get merged. Would this be acceptable?
Yeah, that sounds good to me.
...better yet put a zero-length cache array at the end of your struct omap1_cam_dev and allocate it dynamically, calculated from the resource size.
Guennadi, Yes, this seems the best solution, thank you.
Tony, You'll soon get it as you ask: <media/camera.h> no longer included from <mach/camera.h>.
Thanks, Janusz
* Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100924 03:20]:
Friday 24 September 2010 08:54:20 Guennadi Liakhovetski napisał(a):
On Thu, 23 Sep 2010, Tony Lindgren wrote:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100923 16:37]:
Friday 24 September 2010 01:23:10 Tony Lindgren napisał(a):
I think you can just move the OMAP1_CAMERA_IOSIZE to the devices.c or someplace like that?
Tony, Not exactly. I use the OMAP1_CAMERA_IOSIZE inside the driver when reserving space for register cache.
I think that I could just duplicate its definition in the devices.c for now, than clean things up with a folloup patch when both parts already get merged. Would this be acceptable?
Yeah, that sounds good to me.
...better yet put a zero-length cache array at the end of your struct omap1_cam_dev and allocate it dynamically, calculated from the resource size.
Guennadi, Yes, this seems the best solution, thank you.
Tony, You'll soon get it as you ask: <media/camera.h> no longer included from <mach/camera.h>.
OK, sounds good to me.
Tony
That's up to the platform maintainer to review / apply this one, but if you like, a couple of nit-picks from me:
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
This patch adds support for SoC camera interface to OMAP1 devices.
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
For successfull compilation, requires a header file provided by PATCH 1/6 from this series, "SoC Camera: add driver for OMAP1 camera interface".
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl
v1->v2 changes:
- no functional changes,
- refreshed against linux-2.6.36-rc3
arch/arm/mach-omap1/devices.c | 43 ++++++++++++++++++++++++++++++ arch/arm/mach-omap1/include/mach/camera.h | 8 +++++ 2 files changed, 51 insertions(+)
diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c 2010-09-03 22:29:00.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c 2010-09-09 18:42:30.000000000 +0200 @@ -15,6 +15,7 @@ #include <linux/platform_device.h> #include <linux/io.h> #include <linux/spi/spi.h> +#include <linux/dma-mapping.h>
#include <mach/hardware.h> #include <asm/mach/map.h> @@ -25,6 +26,7 @@ #include <mach/gpio.h> #include <plat/mmc.h> #include <plat/omap7xx.h> +#include <mach/camera.h>
You might want to try to group headers according to their location, i.e., put <mach/...> higher - together with <mach/gpio.h>, also it helps to have headers alphabetically ordered.
/*-------------------------------------------------------------------------*/
@@ -191,6 +193,47 @@ static inline void omap_init_spi100k(voi } #endif
+#define OMAP1_CAMERA_BASE 0xfffb6800
+static struct resource omap1_camera_resources[] = {
- [0] = {
.start = OMAP1_CAMERA_BASE,
.end = OMAP1_CAMERA_BASE + OMAP1_CAMERA_IOSIZE - 1,
.flags = IORESOURCE_MEM,
- },
- [1] = {
.start = INT_CAMERA,
.flags = IORESOURCE_IRQ,
- },
+};
+static u64 omap1_camera_dma_mask = DMA_BIT_MASK(32);
+static struct platform_device omap1_camera_device = {
- .name = "omap1-camera",
- .id = 0, /* This is used to put cameras on this interface */
- .dev = {
.dma_mask = &omap1_camera_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
- },
- .num_resources = ARRAY_SIZE(omap1_camera_resources),
- .resource = omap1_camera_resources,
+};
+void __init omap1_set_camera_info(struct omap1_cam_platform_data *info) +{
- struct platform_device *dev = &omap1_camera_device;
- int ret;
- dev->dev.platform_data = info;
- ret = platform_device_register(dev);
- if (ret)
dev_err(&dev->dev, "unable to register device: %d\n", ret);
+}
/*-------------------------------------------------------------------------*/
static inline void omap_init_sti(void) {} diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h 2010-09-03 22:34:03.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h 2010-09-09 18:42:30.000000000 +0200 @@ -0,0 +1,8 @@ +#ifndef __ASM_ARCH_CAMERA_H_ +#define __ASM_ARCH_CAMERA_H_
+#include <media/omap1_camera.h>
+extern void omap1_set_camera_info(struct omap1_cam_platform_data *);
function declarations don't need an "extern" - something I've also been doing needlessly for a few years...
+#endif /* __ASM_ARCH_CAMERA_H_ */
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Wednesday 22 September 2010 08:53:19 Guennadi Liakhovetski napisał(a):
That's up to the platform maintainer to review / apply this one, but if you like, a couple of nit-picks from me:
Guennadi, Thanks for also looking at this!
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
This patch adds support for SoC camera interface to OMAP1 devices.
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
For successfull compilation, requires a header file provided by PATCH 1/6 from this series, "SoC Camera: add driver for OMAP1 camera interface".
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl
v1->v2 changes:
- no functional changes,
- refreshed against linux-2.6.36-rc3
arch/arm/mach-omap1/devices.c | 43 ++++++++++++++++++++++++++++++ arch/arm/mach-omap1/include/mach/camera.h | 8 +++++ 2 files changed, 51 insertions(+)
diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c 2010-09-03 22:29:00.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c 2010-09-09 18:42:30.000000000 +0200 @@ -15,6 +15,7 @@ #include <linux/platform_device.h> #include <linux/io.h> #include <linux/spi/spi.h> +#include <linux/dma-mapping.h>
#include <mach/hardware.h> #include <asm/mach/map.h> @@ -25,6 +26,7 @@ #include <mach/gpio.h> #include <plat/mmc.h> #include <plat/omap7xx.h> +#include <mach/camera.h>
You might want to try to group headers according to their location, i.e., put <mach/...> higher - together with <mach/gpio.h>, also it helps to have headers alphabetically ordered.
Yes, I will, thank you.
/*----------------------------------------------------------------------- --*/
@@ -191,6 +193,47 @@ static inline void omap_init_spi100k(voi } #endif
+#define OMAP1_CAMERA_BASE 0xfffb6800
+static struct resource omap1_camera_resources[] = {
- [0] = {
.start = OMAP1_CAMERA_BASE,
.end = OMAP1_CAMERA_BASE + OMAP1_CAMERA_IOSIZE - 1,
.flags = IORESOURCE_MEM,
- },
- [1] = {
.start = INT_CAMERA,
.flags = IORESOURCE_IRQ,
- },
+};
+static u64 omap1_camera_dma_mask = DMA_BIT_MASK(32);
+static struct platform_device omap1_camera_device = {
- .name = "omap1-camera",
- .id = 0, /* This is used to put cameras on this interface */
- .dev = {
.dma_mask = &omap1_camera_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
- },
- .num_resources = ARRAY_SIZE(omap1_camera_resources),
- .resource = omap1_camera_resources,
+};
+void __init omap1_set_camera_info(struct omap1_cam_platform_data *info) +{
- struct platform_device *dev = &omap1_camera_device;
- int ret;
- dev->dev.platform_data = info;
- ret = platform_device_register(dev);
- if (ret)
dev_err(&dev->dev, "unable to register device: %d\n", ret);
+}
/*----------------------------------------------------------------------- --*/
static inline void omap_init_sti(void) {} diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h
linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h 2010-09-0 3 22:34:03.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h 2010-09-09 18:42:30.000000000 +0200 @@ -0,0 +1,8 @@ +#ifndef __ASM_ARCH_CAMERA_H_ +#define __ASM_ARCH_CAMERA_H_
+#include <media/omap1_camera.h>
+extern void omap1_set_camera_info(struct omap1_cam_platform_data *);
function declarations don't need an "extern" - something I've also been doing needlessly for a few years...
Good to know. I'll drop it.
Tony, Any comments from you before I submit a hopefully final version?
Thanks, Janusz
+#endif /* __ASM_ARCH_CAMERA_H_ */
Thanks Guennadi
Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/ -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
This patch provides a V4L2 SoC Camera driver for OV6650 camera sensor, found on OMAP1 SoC based Amstrad Delta videophone.
Since I have no experience with camera sensors, and the sensor documentation I was able to find was not very comprehensive, I left most settings at their default (reset) values, except for: - those required for proper mediabus parameters and picture format setup, - those used by controls. Resulting picture quality is far from perfect, but better than nothing.
In order to be able to get / set the sensor frame rate from userspace, I decided to provide two not yet SoC camera supported operations, g_parm and s_parm. These can be used after applying patch 4/6 from this series, "SoC Camera: add support for g_parm / s_parm operations".
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl --- v1->v2 changes:
requested by Guennadi Liakhovetski (thanks!): - include <linux/bitops.h> if using BIT() macro, - sort headers alphabetically, - don't mix tabs with spaces (preferred) when separating symbols from #define keywords, - drop unused NUM_REGS definition, - optimize SET_SAT() and SAT_MASK macros, - reuse no longer needed function argument instead of declaring a new local variable, - don't touch auto controls when changing their correspondig manual settings, and vice versa, - drop probably unsupported auto-hue control, - initialize sensor by writing registers explicitly instead of using a "magic" initialization array, - avoid gotos, don't use them other than in failure cases, - make pclk computation more readable, - implement g_mbus_fmt() callback, - correct a few obvious mistakes, - remove a few extra whitespaces,
suggested by Ralph Corderoy (thanks!): - use one common format when hex printing register addresses and values, - optimize if(ret) vs. if(!ret) constructs usage, - replace a few if-else constructs with more compact, conditional expression based, when translating controls to register bits, - optimize ov6650_res_roundup(), - drop redundant cast of index from ov6650_enum_fmt(), - use variable identifiers rather than their types as sizeof() arguments,
other: - disable band filter, auto exposure control seems working more effectively without it, - refresh against linux-2.6.36-rc3.
drivers/media/video/Kconfig | 6 drivers/media/video/Makefile | 1 drivers/media/video/ov6650.c | 1242 ++++++++++++++++++++++++++++++++++++++++ include/media/v4l2-chip-ident.h | 1 4 files changed, 1250 insertions(+)
diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Kconfig linux-2.6.36-rc3/drivers/media/video/Kconfig --- linux-2.6.36-rc3.orig/drivers/media/video/Kconfig 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/Kconfig 2010-09-03 22:34:02.000000000 +0200 @@ -835,6 +835,12 @@ config SOC_CAMERA_PLATFORM help This is a generic SoC camera platform driver, useful for testing
+config SOC_CAMERA_OV6650 + tristate "ov6650 sensor support" + depends on SOC_CAMERA && I2C + ---help--- + This is a V4L2 SoC camera driver for the OmniVision OV6650 sensor + config SOC_CAMERA_OV772X tristate "ov772x camera support" depends on SOC_CAMERA && I2C diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Makefile linux-2.6.36-rc3/drivers/media/video/Makefile --- linux-2.6.36-rc3.orig/drivers/media/video/Makefile 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/Makefile 2010-09-03 22:34:02.000000000 +0200 @@ -79,6 +79,7 @@ obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o obj-$(CONFIG_SOC_CAMERA_MT9T112) += mt9t112.o obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o +obj-$(CONFIG_SOC_CAMERA_OV6650) += ov6650.o obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o obj-$(CONFIG_SOC_CAMERA_OV9640) += ov9640.o obj-$(CONFIG_SOC_CAMERA_RJ54N1) += rj54n1cb0c.o diff -upr linux-2.6.36-rc3.orig/drivers/media/video/ov6650.c linux-2.6.36-rc3/drivers/media/video/ov6650.c --- linux-2.6.36-rc3.orig/drivers/media/video/ov6650.c 2010-09-03 22:34:02.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/ov6650.c 2010-09-04 17:50:32.000000000 +0200 @@ -0,0 +1,1242 @@ +/* + * V4L2 SoC Camera driver for OmniVision OV6650 Camera Sensor + * + * Copyright (C) 2010 Janusz Krzysztofik jkrzyszt@tis.icnet.pl + * + * Based on OmniVision OV96xx Camera Driver + * Copyright (C) 2009 Marek Vasut marek.vasut@gmail.com + * + * Based on ov772x camera driver: + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto morimoto.kuninori@renesas.com + * + * Based on ov7670 and soc_camera_platform driver, + * Copyright 2006-7 Jonathan Corbet corbet@lwn.net + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski kernel@pengutronix.de + * + * Hardware specific bits initialy based on former work by Matt Callow + * drivers/media/video/omap/sensor_ov6650.c + * Copyright (C) 2006 Matt Callow + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <media/soc_camera.h> +#include <media/v4l2-chip-ident.h> + + +/* Register definitions */ +#define REG_GAIN 0x00 /* range 00 - 3F */ +#define REG_BLUE 0x01 +#define REG_RED 0x02 +#define REG_SAT 0x03 /* [7:4] saturation [0:3] reserved */ +#define REG_HUE 0x04 /* [7:6] rsrvd [5] hue en [4:0] hue */ + +#define REG_BRT 0x06 + +#define REG_PIDH 0x0a +#define REG_PIDL 0x0b + +#define REG_AECH 0x10 +#define REG_CLKRC 0x11 /* Data Format and Internal Clock */ + /* [7:6] Input system clock (MHz)*/ + /* 00=8, 01=12, 10=16, 11=24 */ + /* [5:0]: Internal Clock Pre-Scaler */ +#define REG_COMA 0x12 /* [7] Reset */ +#define REG_COMB 0x13 +#define REG_COMC 0x14 +#define REG_COMD 0x15 +#define REG_COML 0x16 +#define REG_HSTRT 0x17 +#define REG_HSTOP 0x18 +#define REG_VSTRT 0x19 +#define REG_VSTOP 0x1a +#define REG_PSHFT 0x1b +#define REG_MIDH 0x1c +#define REG_MIDL 0x1d +#define REG_HSYNS 0x1e +#define REG_HSYNE 0x1f +#define REG_COME 0x20 +#define REG_YOFF 0x21 +#define REG_UOFF 0x22 +#define REG_VOFF 0x23 +#define REG_AEW 0x24 +#define REG_AEB 0x25 +#define REG_COMF 0x26 +#define REG_COMG 0x27 +#define REG_COMH 0x28 +#define REG_COMI 0x29 + +#define REG_FRARL 0x2b +#define REG_COMJ 0x2c +#define REG_COMK 0x2d +#define REG_AVGY 0x2e +#define REG_REF0 0x2f +#define REG_REF1 0x30 +#define REG_REF2 0x31 +#define REG_FRAJH 0x32 +#define REG_FRAJL 0x33 +#define REG_FACT 0x34 +#define REG_L1AEC 0x35 +#define REG_AVGU 0x36 +#define REG_AVGV 0x37 + +#define REG_SPCB 0x60 +#define REG_SPCC 0x61 +#define REG_GAM1 0x62 +#define REG_GAM2 0x63 +#define REG_GAM3 0x64 +#define REG_SPCD 0x65 + +#define REG_SPCE 0x68 +#define REG_ADCL 0x69 + +#define REG_RMCO 0x6c +#define REG_GMCO 0x6d +#define REG_BMCO 0x6e + + +/* Register bits, values, etc. */ +#define OV6650_PIDH 0x66 /* high byte of product ID number */ +#define OV6650_PIDL 0x50 /* low byte of product ID number */ +#define OV6650_MIDH 0x7F /* high byte of mfg ID */ +#define OV6650_MIDL 0xA2 /* low byte of mfg ID */ + +#define DEF_GAIN 0x00 +#define DEF_BLUE 0x80 +#define DEF_RED 0x80 + +#define SAT_SHIFT 4 +#define SAT_MASK (0x0f << SAT_SHIFT) +#define SET_SAT(x) (((x) << SAT_SHIFT) & SAT_MASK) + +#define HUE_EN BIT(5) +#define HUE_MASK 0x1f +#define DEF_HUE 0x10 +#define SET_HUE(x) (HUE_EN | ((x) & HUE_MASK)) + +#define DEF_AECH 0x4D + +#define CLKRC_6MHz 0x00 +#define CLKRC_12MHz 0x40 +#define CLKRC_16MHz 0x80 +#define CLKRC_24MHz 0xc0 +#define CLKRC_DIV_MASK 0x3f +#define GET_CLKRC_DIV(x) (((x) & CLKRC_DIV_MASK) + 1) + +#define COMA_RESET BIT(7) +#define COMA_QCIF BIT(5) +#define COMA_RAW_RGB BIT(4) +#define COMA_RGB BIT(3) +#define COMA_BW BIT(2) +#define COMA_WORD_SWAP BIT(1) +#define COMA_BYTE_SWAP BIT(0) +#define DEF_COMA 0x00 + +#define COMB_FLIP_V BIT(7) +#define COMB_FLIP_H BIT(5) +#define COMB_BAND_FILTER BIT(4) +#define COMB_AWB BIT(2) +#define COMB_AGC BIT(1) +#define COMB_AEC BIT(0) +#define DEF_COMB 0x5f + +#define COML_ONE_CHANNEL BIT(7) + +#define DEF_HSTRT 0x24 +#define DEF_HSTOP 0xd4 +#define DEF_VSTRT 0x04 +#define DEF_VSTOP 0x94 + +#define COMF_HREF_LOW BIT(4) + +#define COMJ_PCLK_RISING BIT(4) +#define COMJ_VSYNC_HIGH BIT(0) + +/* supported resolutions */ +#define W_QCIF (DEF_HSTOP - DEF_HSTRT) +#define W_CIF (W_QCIF << 1) +#define H_QCIF (DEF_VSTOP - DEF_VSTRT) +#define H_CIF (H_QCIF << 1) + +#define FRAME_RATE_MAX 30 + + +struct ov6650_reg { + u8 reg; + u8 val; +}; + +struct ov6650 { + struct v4l2_subdev subdev; + + int gain; + int blue; + int red; + int saturation; + int hue; + int brightness; + int exposure; + int gamma; + bool vflip; + bool hflip; + bool awb; + bool agc; + int aec; + bool qcif; + unsigned long pclk_max; /* from resolution and format */ + unsigned long pclk_limit; /* from host */ + struct v4l2_fract timeperframe; /* as requested with s_parm */ + struct v4l2_rect rect; + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; +}; + + +static enum v4l2_mbus_pixelcode ov6650_codes[] = { + V4L2_MBUS_FMT_YUYV8_2X8, + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_YVYU8_2X8, + V4L2_MBUS_FMT_VYUY8_2X8, + V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_MBUS_FMT_GREY8_1X8, +}; + +static const struct v4l2_queryctrl ov6650_controls[] = { + { + .id = V4L2_CID_AUTOGAIN, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "AGC", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain", + .minimum = 0, + .maximum = 0x3f, + .step = 1, + .default_value = DEF_GAIN, + }, + { + .id = V4L2_CID_AUTO_WHITE_BALANCE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "AWB", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Blue", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = DEF_BLUE, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Red", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = DEF_RED, + }, + { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 0xf, + .step = 1, + .default_value = 0x8, + }, + { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Hue", + .minimum = 0, + .maximum = 0x1f, + .step = 1, + .default_value = DEF_HUE, + }, + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0x80, + }, + { + .id = V4L2_CID_EXPOSURE_AUTO, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC", + .minimum = 0, + .maximum = 3, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Exposure", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = DEF_AECH, + }, + { + .id = V4L2_CID_GAMMA, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gamma", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0x12, + }, + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Flip Vertically", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Flip Horizontally", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, +}; + +/* read a register */ +static int ov6650_reg_read(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret; + u8 data = reg; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + msg.flags = I2C_M_RD; + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + goto err; + + *val = data; + return 0; + +err: + dev_err(&client->dev, "Failed reading register 0x%02x!\n", reg); + return ret; +} + +/* write a register */ +static int ov6650_reg_write(struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + unsigned char data[2] = { reg, val }; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = data, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + msleep_interruptible(1); + + if (ret < 0) { + dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg); + return ret; + } + return 0; +} + + +/* Read a register, alter its bits, write it back */ +static int ov6650_reg_rmw(struct i2c_client *client, u8 reg, u8 set, u8 mask) +{ + u8 val; + int ret; + + ret = ov6650_reg_read(client, reg, &val); + if (ret) { + dev_err(&client->dev, + "[Read]-Modify-Write of register 0x%02x failed!\n", + reg); + return ret; + } + + val &= ~mask; + val |= set; + + ret = ov6650_reg_write(client, reg, val); + if (ret) + dev_err(&client->dev, + "Read-Modify-[Write] of register 0x%02x failed!\n", + reg); + + return ret; +} + +/* Soft reset the camera. This has nothing to do with the RESET pin! */ +static int ov6650_reset(struct i2c_client *client) +{ + int ret; + + dev_dbg(&client->dev, "reset\n"); + + ret = ov6650_reg_rmw(client, REG_COMA, COMA_RESET, 0); + if (ret) + dev_err(&client->dev, + "An error occured while entering soft reset!\n"); + + return ret; +} + +static struct ov6650 *to_ov6650(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ov6650, subdev); +} + +/* Start/Stop streaming from the device */ +static int ov6650_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +/* Alter bus settings on camera side */ +static int ov6650_set_bus_param(struct soc_camera_device *icd, + unsigned long flags) +{ + struct soc_camera_link *icl = to_soc_camera_link(icd); + struct i2c_client *client = to_i2c_client(to_soc_camera_control(icd)); + int ret; + + flags = soc_camera_apply_sensor_flags(icl, flags); + + if (flags & SOCAM_PCLK_SAMPLE_RISING) + ret = ov6650_reg_rmw(client, REG_COMJ, COMJ_PCLK_RISING, 0); + else + ret = ov6650_reg_rmw(client, REG_COMJ, 0, COMJ_PCLK_RISING); + if (ret) + return ret; + + if (flags & SOCAM_HSYNC_ACTIVE_LOW) + ret = ov6650_reg_rmw(client, REG_COMF, COMF_HREF_LOW, 0); + else + ret = ov6650_reg_rmw(client, REG_COMF, 0, COMF_HREF_LOW); + if (ret) + return ret; + + if (flags & SOCAM_VSYNC_ACTIVE_HIGH) + ret = ov6650_reg_rmw(client, REG_COMJ, COMJ_VSYNC_HIGH, 0); + else + ret = ov6650_reg_rmw(client, REG_COMJ, 0, COMJ_VSYNC_HIGH); + + return ret; +} + +/* Request bus settings on camera side */ +static unsigned long ov6650_query_bus_param(struct soc_camera_device *icd) +{ + struct soc_camera_link *icl = to_soc_camera_link(icd); + + unsigned long flags = SOCAM_MASTER | \ + SOCAM_PCLK_SAMPLE_RISING | SOCAM_PCLK_SAMPLE_FALLING | \ + SOCAM_HSYNC_ACTIVE_HIGH | SOCAM_HSYNC_ACTIVE_LOW | \ + SOCAM_VSYNC_ACTIVE_HIGH | SOCAM_VSYNC_ACTIVE_LOW | \ + SOCAM_DATA_ACTIVE_HIGH | SOCAM_DATAWIDTH_8; + + return soc_camera_apply_sensor_flags(icl, flags); +} + +/* Get status of additional camera capabilities */ +static int ov6650_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct i2c_client *client = sd->priv; + struct ov6650 *priv = to_ov6650(client); + uint8_t reg; + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + ctrl->value = priv->agc; + break; + case V4L2_CID_GAIN: + if (priv->agc) { + ret = ov6650_reg_read(client, REG_GAIN, ®); + ctrl->value = reg; + } else { + ctrl->value = priv->gain; + } + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + ctrl->value = priv->awb; + break; + case V4L2_CID_BLUE_BALANCE: + if (priv->awb) { + ret = ov6650_reg_read(client, REG_BLUE, ®); + ctrl->value = reg; + } else { + ctrl->value = priv->blue; + } + break; + case V4L2_CID_RED_BALANCE: + if (priv->awb) { + ret = ov6650_reg_read(client, REG_RED, ®); + ctrl->value = reg; + } else { + ctrl->value = priv->red; + } + break; + case V4L2_CID_SATURATION: + ctrl->value = priv->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = priv->hue; + break; + case V4L2_CID_BRIGHTNESS: + ctrl->value = priv->brightness; + break; + case V4L2_CID_EXPOSURE_AUTO: + ctrl->value = priv->aec; + break; + case V4L2_CID_EXPOSURE: + if (priv->aec) { + ret = ov6650_reg_read(client, REG_AECH, ®); + ctrl->value = reg; + } else { + ctrl->value = priv->exposure; + } + break; + case V4L2_CID_GAMMA: + ctrl->value = priv->gamma; + break; + case V4L2_CID_VFLIP: + ctrl->value = priv->vflip; + break; + case V4L2_CID_HFLIP: + ctrl->value = priv->hflip; + break; + } + return ret; +} + +/* Set status of additional camera capabilities */ +static int ov6650_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct i2c_client *client = sd->priv; + struct ov6650 *priv = to_ov6650(client); + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + ret = ov6650_reg_rmw(client, REG_COMB, + ctrl->value ? COMB_AGC : 0, COMB_AGC); + if (!ret) + priv->agc = ctrl->value; + break; + case V4L2_CID_GAIN: + ret = ov6650_reg_write(client, REG_GAIN, ctrl->value); + if (!ret) + priv->gain = ctrl->value; + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov6650_reg_rmw(client, REG_COMB, + ctrl->value ? COMB_AWB : 0, COMB_AWB); + if (!ret) + priv->awb = ctrl->value; + break; + case V4L2_CID_BLUE_BALANCE: + ret = ov6650_reg_write(client, REG_BLUE, ctrl->value); + if (!ret) + priv->blue = ctrl->value; + break; + case V4L2_CID_RED_BALANCE: + ret = ov6650_reg_write(client, REG_RED, ctrl->value); + if (!ret) + priv->red = ctrl->value; + break; + case V4L2_CID_SATURATION: + ret = ov6650_reg_rmw(client, REG_SAT, SET_SAT(ctrl->value), + SAT_MASK); + if (!ret) + priv->saturation = ctrl->value; + break; + case V4L2_CID_HUE: + ret = ov6650_reg_rmw(client, REG_HUE, SET_HUE(ctrl->value), + HUE_MASK); + if (!ret) + priv->hue = ctrl->value; + break; + case V4L2_CID_BRIGHTNESS: + ret = ov6650_reg_write(client, REG_BRT, ctrl->value); + if (!ret) + priv->brightness = ctrl->value; + break; + case V4L2_CID_EXPOSURE_AUTO: + switch (ctrl->value) { + case V4L2_EXPOSURE_AUTO: + ret = ov6650_reg_rmw(client, REG_COMB, COMB_AEC, 0); + break; + default: + ret = ov6650_reg_rmw(client, REG_COMB, 0, COMB_AEC); + break; + } + if (!ret) + priv->aec = ctrl->value; + break; + case V4L2_CID_EXPOSURE: + ret = ov6650_reg_write(client, REG_AECH, ctrl->value); + if (!ret) + priv->exposure = ctrl->value; + break; + case V4L2_CID_GAMMA: + ret = ov6650_reg_write(client, REG_GAM1, ctrl->value); + if (!ret) + priv->gamma = ctrl->value; + break; + case V4L2_CID_VFLIP: + ret = ov6650_reg_rmw(client, REG_COMB, + ctrl->value ? COMB_FLIP_V : 0, COMB_FLIP_V); + if (!ret) + priv->vflip = ctrl->value; + break; + case V4L2_CID_HFLIP: + ret = ov6650_reg_rmw(client, REG_COMB, + ctrl->value ? COMB_FLIP_H : 0, COMB_FLIP_H); + if (!ret) + priv->hflip = ctrl->value; + break; + } + + return ret; +} + +/* Get chip identification */ +static int ov6650_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + id->ident = V4L2_IDENT_OV6650; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov6650_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = sd->priv; + int ret; + u8 val; + + if (reg->reg & ~0xff) + return -EINVAL; + + reg->size = 1; + + ret = ov6650_reg_read(client, reg->reg, &val); + if (!ret) + reg->val = (__u64)val; + + return ret; +} + +static int ov6650_set_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = sd->priv; + + if (reg->reg & ~0xff || reg->val & ~0xff) + return -EINVAL; + + return ov6650_reg_write(client, reg->reg, reg->val); +} +#endif + +/* + * Select nearest higher resolution for capture. + * To be dropped once a common function for doing this is implemented. + */ +static void ov6650_res_roundup(u32 *width, u32 *height) +{ + int i, lasti; + int res_x[] = { W_QCIF, W_CIF }; + int res_y[] = { H_QCIF, H_CIF }; + + for (i = 0; i < ARRAY_SIZE(res_x); i++) { + lasti = i; + if (res_x[i] >= *width && res_y[i] >= *height) + break; + } + + *width = res_x[lasti]; + *height = res_y[lasti]; +} + +/* program default register values */ +static int ov6650_prog_dflt(struct i2c_client *client) +{ + int ret; + + dev_dbg(&client->dev, "reinitializing\n"); + + ret = ov6650_reg_write(client, REG_COMA, 0); /* ~COMA_RESET */ + if (!ret) + ret = ov6650_reg_rmw(client, REG_COMB, 0, COMB_BAND_FILTER); + + return ret; +} + +static int ov6650_g_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = sd->priv; + struct ov6650 *priv = to_ov6650(client); + + mf->width = priv->rect.width; + mf->height = priv->rect.height; + mf->code = priv->code; + mf->colorspace = priv->colorspace; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +/* set the format we will capture in */ +static int ov6650_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = sd->priv; + struct soc_camera_device *icd = client->dev.platform_data; + struct soc_camera_sense *sense = icd->sense; + struct ov6650 *priv = to_ov6650(client); + enum v4l2_mbus_pixelcode code = mf->code; + unsigned long pclk; + u8 coma_set = 0, coma_mask = 0, coml_set = 0, coml_mask = 0; + u8 clkrc, clkrc_div; + int ret; + + /* select color matrix configuration for given color encoding */ + switch (code) { + case V4L2_MBUS_FMT_GREY8_1X8: + dev_dbg(&client->dev, "pixel format GREY8_1X8\n"); + coma_set |= COMA_BW; + coma_mask |= COMA_RGB | COMA_WORD_SWAP | COMA_BYTE_SWAP; + break; + case V4L2_MBUS_FMT_YUYV8_2X8: + dev_dbg(&client->dev, "pixel format YUYV8_2X8_LE\n"); + coma_set |= COMA_WORD_SWAP; + coma_mask |= COMA_RGB | COMA_BW | COMA_BYTE_SWAP; + break; + case V4L2_MBUS_FMT_YVYU8_2X8: + dev_dbg(&client->dev, "pixel format YVYU8_2X8_LE (untested)\n"); + coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP | + COMA_BYTE_SWAP; + break; + case V4L2_MBUS_FMT_UYVY8_2X8: + dev_dbg(&client->dev, "pixel format YUYV8_2X8_BE\n"); + if (mf->width == W_CIF) { + coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP; + coma_mask |= COMA_RGB | COMA_BW; + } else { + coma_set |= COMA_BYTE_SWAP; + coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP; + } + break; + case V4L2_MBUS_FMT_VYUY8_2X8: + dev_dbg(&client->dev, "pixel format YVYU8_2X8_BE (untested)\n"); + if (mf->width == W_CIF) { + coma_set |= COMA_BYTE_SWAP; + coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP; + } else { + coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP; + coma_mask |= COMA_RGB | COMA_BW; + } + break; + case V4L2_MBUS_FMT_SBGGR8_1X8: + dev_dbg(&client->dev, "pixel format SBGGR8_1X8 (untested)\n"); + coma_set |= COMA_RAW_RGB | COMA_RGB; + coma_mask |= COMA_BW | COMA_BYTE_SWAP | COMA_WORD_SWAP; + break; + default: + dev_err(&client->dev, "Pixel format not handled: 0x%x\n", code); + return -EINVAL; + } + priv->code = code; + + if ((code == V4L2_MBUS_FMT_GREY8_1X8) || + (code == V4L2_MBUS_FMT_SBGGR8_1X8)) { + coml_mask |= COML_ONE_CHANNEL; + priv->pclk_max = 4000000; + } else { + coml_set |= COML_ONE_CHANNEL; + priv->pclk_max = 8000000; + } + + if (code == V4L2_MBUS_FMT_SBGGR8_1X8) + priv->colorspace = V4L2_COLORSPACE_SRGB; + else + priv->colorspace = V4L2_COLORSPACE_JPEG; + + /* + * Select register configuration for given resolution. + * To be replaced with a common function that does it, once available. + */ + ov6650_res_roundup(&mf->width, &mf->height); + + switch (mf->width) { + case W_QCIF: + dev_dbg(&client->dev, "resolution QCIF\n"); + priv->qcif = 1; + coma_set |= COMA_QCIF; + priv->pclk_max /= 2; + break; + case W_CIF: + dev_dbg(&client->dev, "resolution CIF\n"); + priv->qcif = 0; + coma_mask |= COMA_QCIF; + break; + default: + dev_err(&client->dev, "unspported resolution!\n"); + return -EINVAL; + } + + priv->rect.left = DEF_HSTRT << !priv->qcif; + priv->rect.top = DEF_VSTRT << !priv->qcif; + priv->rect.width = mf->width; + priv->rect.height = mf->height; + + if (priv->timeperframe.numerator && priv->timeperframe.denominator) + pclk = priv->pclk_max * priv->timeperframe.denominator / + (FRAME_RATE_MAX * priv->timeperframe.numerator); + else + pclk = priv->pclk_max; + + if (sense) { + if (sense->master_clock == 8000000) { + dev_dbg(&client->dev, "8MHz input clock\n"); + clkrc = CLKRC_6MHz; + } else if (sense->master_clock == 12000000) { + dev_dbg(&client->dev, "12MHz input clock\n"); + clkrc = CLKRC_12MHz; + } else if (sense->master_clock == 16000000) { + dev_dbg(&client->dev, "16MHz input clock\n"); + clkrc = CLKRC_16MHz; + } else if (sense->master_clock == 24000000) { + dev_dbg(&client->dev, "24MHz input clock\n"); + clkrc = CLKRC_24MHz; + } else { + dev_err(&client->dev, + "unspported input clock, check platform data\n"); + return -EINVAL; + } + priv->pclk_limit = sense->pixel_clock_max; + if (priv->pclk_limit && (priv->pclk_limit < pclk)) + pclk = priv->pclk_limit; + } else { + priv->pclk_limit = 0; + clkrc = 0xc0; + dev_dbg(&client->dev, "using default 24MHz input clock\n"); + } + + clkrc_div = (priv->pclk_max - 1) / pclk; + clkrc |= clkrc_div; + pclk = priv->pclk_max / clkrc_div; + dev_dbg(&client->dev, "pixel clock divider: %ld.%ld\n", + sense->master_clock / pclk, + 10 * sense->master_clock % pclk / pclk); + + ov6650_reset(client); + + ret = ov6650_prog_dflt(client); + if (!ret) + ret = ov6650_reg_rmw(client, REG_COMA, coma_set, coma_mask); + if (!ret) + ret = ov6650_reg_write(client, REG_CLKRC, clkrc); + if (!ret) + ret = ov6650_reg_rmw(client, REG_COML, coml_set, coml_mask); + + if (!ret) { + mf->code = code; + mf->colorspace = priv->colorspace; + } + + return ret; +} + +static int ov6650_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + /* + * Select register configuration for given resolution. + * To be replaced with a common function that does it, once available. + */ + ov6650_res_roundup(&mf->width, &mf->height); + + mf->field = V4L2_FIELD_NONE; + + switch (mf->code) { + case V4L2_MBUS_FMT_Y10_1X10: + mf->code = V4L2_MBUS_FMT_GREY8_1X8; + case V4L2_MBUS_FMT_GREY8_1X8: + case V4L2_MBUS_FMT_YVYU8_2X8: + case V4L2_MBUS_FMT_YUYV8_2X8: + case V4L2_MBUS_FMT_VYUY8_2X8: + case V4L2_MBUS_FMT_UYVY8_2X8: + mf->colorspace = V4L2_COLORSPACE_JPEG; + break; + default: + mf->code = V4L2_MBUS_FMT_SBGGR8_1X8; + case V4L2_MBUS_FMT_SBGGR8_1X8: + mf->colorspace = V4L2_COLORSPACE_SRGB; + break; + } + + return 0; +} + +static int ov6650_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(ov6650_codes)) + return -EINVAL; + + *code = ov6650_codes[index]; + return 0; +} + +static int ov6650_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + struct i2c_client *client = sd->priv; + struct ov6650 *priv = to_ov6650(client); + int shift = !priv->qcif; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* Crop limits depend on selected frame format (CIF/QCIF) */ + a->bounds.left = DEF_HSTRT << shift; + a->bounds.top = DEF_VSTRT << shift; + a->bounds.width = W_QCIF << shift; + a->bounds.height = H_QCIF << shift; + a->defrect = a->bounds; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int ov6650_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = sd->priv; + struct ov6650 *priv = to_ov6650(client); + struct v4l2_rect *rect = &a->c; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + rect->left = priv->rect.left; + rect->top = priv->rect.top; + rect->width = priv->rect.width; + rect->height = priv->rect.height; + + return 0; +} + +static int ov6650_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct i2c_client *client = sd->priv; + struct ov6650 *priv = to_ov6650(client); + struct v4l2_rect *rect = &a->c; + int shift = !priv->qcif; + u8 hstrt, vstrt, hstop, vstop; + int ret; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + hstrt = rect->left >> shift; + vstrt = rect->top >> shift; + hstop = hstrt + (rect->width >> shift); + vstop = vstrt + (rect->height >> shift); + + if ((hstop > DEF_HSTOP) || (vstop > DEF_VSTOP)) { + dev_err(&client->dev, "Invalid window geometry!\n"); + return -EINVAL; + } + + ret = ov6650_reg_write(client, REG_HSTRT, hstrt); + if (!ret) { + priv->rect.left = rect->left; + ret = ov6650_reg_write(client, REG_HSTOP, hstop); + } + if (!ret) { + priv->rect.width = rect->width; + ret = ov6650_reg_write(client, REG_VSTRT, vstrt); + } + if (!ret) { + priv->rect.top = rect->top; + ret = ov6650_reg_write(client, REG_VSTOP, vstop); + } + if (!ret) + priv->rect.height = rect->height; + + return ret; +} + +static int ov6650_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) +{ + struct i2c_client *client = sd->priv; + struct v4l2_captureparm *cp = &parms->parm.capture; + u8 clkrc; + int ret; + + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + ret = ov6650_reg_read(client, REG_CLKRC, &clkrc); + if (ret < 0) + return ret; + + memset(cp, 0, sizeof(*cp)); + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = GET_CLKRC_DIV(clkrc); + cp->timeperframe.denominator = FRAME_RATE_MAX; + + dev_dbg(&client->dev, "Frame interval: %u/%u\n", + cp->timeperframe.numerator, cp->timeperframe.denominator); + + return 0; +} + +static int ov6650_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) +{ + struct i2c_client *client = sd->priv; + struct ov6650 *priv = to_ov6650(client); + struct v4l2_captureparm *cp = &parms->parm.capture; + struct v4l2_fract *tpf = &cp->timeperframe; + int div, ret; + + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (cp->extendedmode != 0) + return -EINVAL; + + if (tpf->numerator == 0 || tpf->denominator == 0) + div = 1; /* Reset to full rate */ + else + div = (tpf->numerator * FRAME_RATE_MAX) / tpf->denominator; + + if (div == 0) + div = 1; + else if (div > CLKRC_DIV_MASK + 1) + div = CLKRC_DIV_MASK + 1; + + if (priv->pclk_max && priv->pclk_limit) { + ret = (priv->pclk_max - 1) / priv->pclk_limit; + if (div < ret) + div = ret; + } + + ret = ov6650_reg_rmw(client, REG_CLKRC, div - 1, CLKRC_DIV_MASK); + if (!ret) { + tpf->numerator = FRAME_RATE_MAX; + tpf->denominator = div; + priv->timeperframe = *tpf; + } + + return ret; +} + +static int ov6650_video_probe(struct soc_camera_device *icd, + struct i2c_client *client) +{ + u8 pidh, pidl, midh, midl; + int ret = 0; + + /* + * check and show product ID and manufacturer ID + */ + ret = ov6650_reg_read(client, REG_PIDH, &pidh); + if (!ret) + ret = ov6650_reg_read(client, REG_PIDL, &pidl); + if (!ret) + ret = ov6650_reg_read(client, REG_MIDH, &midh); + if (!ret) + ret = ov6650_reg_read(client, REG_MIDL, &midl); + + if (ret) + return ret; + + if ((pidh != OV6650_PIDH) || (pidl != OV6650_PIDL)) { + dev_err(&client->dev, "Product ID error 0x%02x:0x%02x\n", + pidh, pidl); + return -ENODEV; + } + + dev_info(&client->dev, + "ov6650 Product ID 0x%02x:0x%02x Manufacturer ID 0x%02x:0x%02x\n", + pidh, pidl, midh, midl); + + return 0; +} + +static struct soc_camera_ops ov6650_ops = { + .set_bus_param = ov6650_set_bus_param, + .query_bus_param = ov6650_query_bus_param, + .controls = ov6650_controls, + .num_controls = ARRAY_SIZE(ov6650_controls), +}; + +static struct v4l2_subdev_core_ops ov6650_core_ops = { + .g_ctrl = ov6650_g_ctrl, + .s_ctrl = ov6650_s_ctrl, + .g_chip_ident = ov6650_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov6650_get_register, + .s_register = ov6650_set_register, +#endif +}; + +static struct v4l2_subdev_video_ops ov6650_video_ops = { + .s_stream = ov6650_s_stream, + .g_mbus_fmt = ov6650_g_fmt, + .s_mbus_fmt = ov6650_s_fmt, + .try_mbus_fmt = ov6650_try_fmt, + .enum_mbus_fmt = ov6650_enum_fmt, + .cropcap = ov6650_cropcap, + .g_crop = ov6650_g_crop, + .s_crop = ov6650_s_crop, + .g_parm = ov6650_g_parm, + .s_parm = ov6650_s_parm, +}; + +static struct v4l2_subdev_ops ov6650_subdev_ops = { + .core = &ov6650_core_ops, + .video = &ov6650_video_ops, +}; + +/* + * i2c_driver function + */ +static int ov6650_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov6650 *priv; + struct soc_camera_device *icd = client->dev.platform_data; + struct soc_camera_link *icl; + int ret; + + if (!icd) { + dev_err(&client->dev, "Missing soc-camera data!\n"); + return -EINVAL; + } + + icl = to_soc_camera_link(icd); + if (!icl) { + dev_err(&client->dev, "Missing platform_data for driver\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&client->dev, + "Failed to allocate memory for private data!\n"); + return -ENOMEM; + } + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov6650_subdev_ops); + + icd->ops = &ov6650_ops; + + priv->qcif = false; + priv->rect.left = DEF_HSTRT << !false; + priv->rect.top = DEF_VSTRT << !false; + priv->rect.width = W_CIF; + priv->rect.height = H_CIF; + priv->code = V4L2_MBUS_FMT_YUYV8_2X8; + priv->colorspace = V4L2_COLORSPACE_JPEG; + + ret = ov6650_video_probe(icd, client); + + if (ret) { + icd->ops = NULL; + i2c_set_clientdata(client, NULL); + kfree(priv); + } + + return ret; +} + +static int ov6650_remove(struct i2c_client *client) +{ + struct ov6650 *priv = to_ov6650(client); + + i2c_set_clientdata(client, NULL); + kfree(priv); + return 0; +} + +static const struct i2c_device_id ov6650_id[] = { + { "ov6650", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov6650_id); + +static struct i2c_driver ov6650_i2c_driver = { + .driver = { + .name = "ov6650", + }, + .probe = ov6650_probe, + .remove = ov6650_remove, + .id_table = ov6650_id, +}; + +static int __init ov6650_module_init(void) +{ + return i2c_add_driver(&ov6650_i2c_driver); +} + +static void __exit ov6650_module_exit(void) +{ + i2c_del_driver(&ov6650_i2c_driver); +} + +module_init(ov6650_module_init); +module_exit(ov6650_module_exit); + +MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV6650"); +MODULE_AUTHOR("Janusz Krzysztofik jkrzyszt@tis.icnet.pl"); +MODULE_LICENSE("GPL v2"); diff -upr linux-2.6.36-rc3.orig/include/media/v4l2-chip-ident.h linux-2.6.36-rc3/include/media/v4l2-chip-ident.h --- linux-2.6.36-rc3.orig/include/media/v4l2-chip-ident.h 2010-09-03 22:30:25.000000000 +0200 +++ linux-2.6.36-rc3/include/media/v4l2-chip-ident.h 2010-09-03 22:34:02.000000000 +0200 @@ -70,6 +70,7 @@ enum { V4L2_IDENT_OV9655 = 255, V4L2_IDENT_SOI968 = 256, V4L2_IDENT_OV9640 = 257, + V4L2_IDENT_OV6650 = 258,
/* module saa7146: reserved range 300-309 */ V4L2_IDENT_SAA7146 = 300,
Ok, just a couple more comments, all looking quite good so far, if we get a new version soon enough, we still might manage it for 2.6.37
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
[snip]
+/* write a register */ +static int ov6650_reg_write(struct i2c_client *client, u8 reg, u8 val) +{
- int ret;
- unsigned char data[2] = { reg, val };
- struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = 2,
.buf = data,
- };
- ret = i2c_transfer(client->adapter, &msg, 1);
- msleep_interruptible(1);
Why do you want _interruptible here? Firstly it's just 1ms, secondly - why?...
- if (ret < 0) {
dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg);
return ret;
- }
- return 0;
+}
...
+/* set the format we will capture in */ +static int ov6650_s_fmt(struct v4l2_subdev *sd,
struct v4l2_mbus_framefmt *mf)
+{
- struct i2c_client *client = sd->priv;
- struct soc_camera_device *icd = client->dev.platform_data;
- struct soc_camera_sense *sense = icd->sense;
- struct ov6650 *priv = to_ov6650(client);
- enum v4l2_mbus_pixelcode code = mf->code;
- unsigned long pclk;
- u8 coma_set = 0, coma_mask = 0, coml_set = 0, coml_mask = 0;
- u8 clkrc, clkrc_div;
- int ret;
- /* select color matrix configuration for given color encoding */
- switch (code) {
- case V4L2_MBUS_FMT_GREY8_1X8:
dev_dbg(&client->dev, "pixel format GREY8_1X8\n");
coma_set |= COMA_BW;
coma_mask |= COMA_RGB | COMA_WORD_SWAP | COMA_BYTE_SWAP;
break;
- case V4L2_MBUS_FMT_YUYV8_2X8:
dev_dbg(&client->dev, "pixel format YUYV8_2X8_LE\n");
coma_set |= COMA_WORD_SWAP;
coma_mask |= COMA_RGB | COMA_BW | COMA_BYTE_SWAP;
break;
- case V4L2_MBUS_FMT_YVYU8_2X8:
dev_dbg(&client->dev, "pixel format YVYU8_2X8_LE (untested)\n");
coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP |
COMA_BYTE_SWAP;
break;
- case V4L2_MBUS_FMT_UYVY8_2X8:
dev_dbg(&client->dev, "pixel format YUYV8_2X8_BE\n");
if (mf->width == W_CIF) {
coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP;
coma_mask |= COMA_RGB | COMA_BW;
} else {
coma_set |= COMA_BYTE_SWAP;
coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP;
}
break;
- case V4L2_MBUS_FMT_VYUY8_2X8:
dev_dbg(&client->dev, "pixel format YVYU8_2X8_BE (untested)\n");
if (mf->width == W_CIF) {
coma_set |= COMA_BYTE_SWAP;
coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP;
} else {
coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP;
coma_mask |= COMA_RGB | COMA_BW;
}
break;
- case V4L2_MBUS_FMT_SBGGR8_1X8:
dev_dbg(&client->dev, "pixel format SBGGR8_1X8 (untested)\n");
coma_set |= COMA_RAW_RGB | COMA_RGB;
coma_mask |= COMA_BW | COMA_BYTE_SWAP | COMA_WORD_SWAP;
break;
- default:
dev_err(&client->dev, "Pixel format not handled: 0x%x\n", code);
return -EINVAL;
- }
- priv->code = code;
- if ((code == V4L2_MBUS_FMT_GREY8_1X8) ||
(code == V4L2_MBUS_FMT_SBGGR8_1X8)) {
Superfluous parenthesis
coml_mask |= COML_ONE_CHANNEL;
priv->pclk_max = 4000000;
- } else {
coml_set |= COML_ONE_CHANNEL;
priv->pclk_max = 8000000;
- }
coml_mask and coml_set are only set here and only used once below, so, dropping initialisation to 0 in variable definitions and just doing
+ if (code == V4L2_MBUS_FMT_GREY8_1X8 || + code == V4L2_MBUS_FMT_SBGGR8_1X8) { + coml_mask = COML_ONE_CHANNEL; + coml_set = 0; + priv->pclk_max = 4000000; + } else { + coml_mask = 0; + coml_set = COML_ONE_CHANNEL; + priv->pclk_max = 8000000; + }
would work too.
- if (code == V4L2_MBUS_FMT_SBGGR8_1X8)
priv->colorspace = V4L2_COLORSPACE_SRGB;
- else
priv->colorspace = V4L2_COLORSPACE_JPEG;
- /*
* Select register configuration for given resolution.
* To be replaced with a common function that does it, once available.
*/
- ov6650_res_roundup(&mf->width, &mf->height);
- switch (mf->width) {
- case W_QCIF:
dev_dbg(&client->dev, "resolution QCIF\n");
priv->qcif = 1;
coma_set |= COMA_QCIF;
priv->pclk_max /= 2;
break;
- case W_CIF:
dev_dbg(&client->dev, "resolution CIF\n");
priv->qcif = 0;
coma_mask |= COMA_QCIF;
break;
- default:
dev_err(&client->dev, "unspported resolution!\n");
return -EINVAL;
- }
- priv->rect.left = DEF_HSTRT << !priv->qcif;
- priv->rect.top = DEF_VSTRT << !priv->qcif;
- priv->rect.width = mf->width;
- priv->rect.height = mf->height;
Sorry, don't understand. The sensor can do both - cropping per HSTRT, HSTOP, VSTRT and VSTOP and scaling per COMA_CIF / COMA_QCIF, right? But which of them is stored in your priv->rect? Is this the input window (cropping) or the output one (scaling)? You overwrite it in .s_fmt and .s_crop...
- if (priv->timeperframe.numerator && priv->timeperframe.denominator)
pclk = priv->pclk_max * priv->timeperframe.denominator /
(FRAME_RATE_MAX * priv->timeperframe.numerator);
- else
pclk = priv->pclk_max;
- if (sense) {
if (sense->master_clock == 8000000) {
dev_dbg(&client->dev, "8MHz input clock\n");
clkrc = CLKRC_6MHz;
} else if (sense->master_clock == 12000000) {
dev_dbg(&client->dev, "12MHz input clock\n");
clkrc = CLKRC_12MHz;
} else if (sense->master_clock == 16000000) {
dev_dbg(&client->dev, "16MHz input clock\n");
clkrc = CLKRC_16MHz;
} else if (sense->master_clock == 24000000) {
dev_dbg(&client->dev, "24MHz input clock\n");
clkrc = CLKRC_24MHz;
} else {
dev_err(&client->dev,
"unspported input clock, check platform data\n");
return -EINVAL;
}
priv->pclk_limit = sense->pixel_clock_max;
if (priv->pclk_limit && (priv->pclk_limit < pclk))
Don't think the compiler would complain without the internal parenthesis here?
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Wednesday 22 September 2010 11:12:46 Guennadi Liakhovetski napisał(a):
Ok, just a couple more comments, all looking quite good so far, if we get a new version soon enough, we still might manage it for 2.6.37
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
[snip]
+/* write a register */ +static int ov6650_reg_write(struct i2c_client *client, u8 reg, u8 val) +{
- int ret;
- unsigned char data[2] = { reg, val };
- struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = 2,
.buf = data,
- };
- ret = i2c_transfer(client->adapter, &msg, 1);
- msleep_interruptible(1);
Why do you want _interruptible here? Firstly it's just 1ms, secondly - why?...
My bad. I didn't verified what a real difference between msleep() and msleep_interruptible() is, only found that msleep_interruptible(1) makes checkpatch.pl more happy than msleep(1), sorry.
What I can be sure is that a short delay is required here, otherwise the driver doesn't work correctly. To prevent the checkpatch.pl from complying against msleep(1), I think I could just replace it with msleep(20). What do you think?
- if (ret < 0) {
dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg);
return ret;
- }
- return 0;
+}
...
+/* set the format we will capture in */ +static int ov6650_s_fmt(struct v4l2_subdev *sd,
struct v4l2_mbus_framefmt *mf)
+{
- struct i2c_client *client = sd->priv;
- struct soc_camera_device *icd = client->dev.platform_data;
- struct soc_camera_sense *sense = icd->sense;
- struct ov6650 *priv = to_ov6650(client);
- enum v4l2_mbus_pixelcode code = mf->code;
- unsigned long pclk;
- u8 coma_set = 0, coma_mask = 0, coml_set = 0, coml_mask = 0;
- u8 clkrc, clkrc_div;
- int ret;
- /* select color matrix configuration for given color encoding */
- switch (code) {
- case V4L2_MBUS_FMT_GREY8_1X8:
dev_dbg(&client->dev, "pixel format GREY8_1X8\n");
coma_set |= COMA_BW;
coma_mask |= COMA_RGB | COMA_WORD_SWAP | COMA_BYTE_SWAP;
break;
- case V4L2_MBUS_FMT_YUYV8_2X8:
dev_dbg(&client->dev, "pixel format YUYV8_2X8_LE\n");
coma_set |= COMA_WORD_SWAP;
coma_mask |= COMA_RGB | COMA_BW | COMA_BYTE_SWAP;
break;
- case V4L2_MBUS_FMT_YVYU8_2X8:
dev_dbg(&client->dev, "pixel format YVYU8_2X8_LE (untested)\n");
coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP |
COMA_BYTE_SWAP;
break;
- case V4L2_MBUS_FMT_UYVY8_2X8:
dev_dbg(&client->dev, "pixel format YUYV8_2X8_BE\n");
if (mf->width == W_CIF) {
coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP;
coma_mask |= COMA_RGB | COMA_BW;
} else {
coma_set |= COMA_BYTE_SWAP;
coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP;
}
break;
- case V4L2_MBUS_FMT_VYUY8_2X8:
dev_dbg(&client->dev, "pixel format YVYU8_2X8_BE (untested)\n");
if (mf->width == W_CIF) {
coma_set |= COMA_BYTE_SWAP;
coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP;
} else {
coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP;
coma_mask |= COMA_RGB | COMA_BW;
}
break;
- case V4L2_MBUS_FMT_SBGGR8_1X8:
dev_dbg(&client->dev, "pixel format SBGGR8_1X8 (untested)\n");
coma_set |= COMA_RAW_RGB | COMA_RGB;
coma_mask |= COMA_BW | COMA_BYTE_SWAP | COMA_WORD_SWAP;
break;
- default:
dev_err(&client->dev, "Pixel format not handled: 0x%x\n", code);
return -EINVAL;
- }
- priv->code = code;
- if ((code == V4L2_MBUS_FMT_GREY8_1X8) ||
(code == V4L2_MBUS_FMT_SBGGR8_1X8)) {
Superfluous parenthesis
Will be dropped.
coml_mask |= COML_ONE_CHANNEL;
priv->pclk_max = 4000000;
- } else {
coml_set |= COML_ONE_CHANNEL;
priv->pclk_max = 8000000;
- }
coml_mask and coml_set are only set here and only used once below, so, dropping initialisation to 0 in variable definitions and just doing
- if (code == V4L2_MBUS_FMT_GREY8_1X8 ||
code == V4L2_MBUS_FMT_SBGGR8_1X8) {
coml_mask = COML_ONE_CHANNEL;
coml_set = 0;
priv->pclk_max = 4000000;
- } else {
coml_mask = 0;
coml_set = COML_ONE_CHANNEL;
priv->pclk_max = 8000000;
- }
would work too.
OK, I'll use your prefered pattern.
- if (code == V4L2_MBUS_FMT_SBGGR8_1X8)
priv->colorspace = V4L2_COLORSPACE_SRGB;
- else
priv->colorspace = V4L2_COLORSPACE_JPEG;
- /*
* Select register configuration for given resolution.
* To be replaced with a common function that does it, once available.
*/
- ov6650_res_roundup(&mf->width, &mf->height);
- switch (mf->width) {
- case W_QCIF:
dev_dbg(&client->dev, "resolution QCIF\n");
priv->qcif = 1;
coma_set |= COMA_QCIF;
priv->pclk_max /= 2;
break;
- case W_CIF:
dev_dbg(&client->dev, "resolution CIF\n");
priv->qcif = 0;
coma_mask |= COMA_QCIF;
break;
- default:
dev_err(&client->dev, "unspported resolution!\n");
return -EINVAL;
- }
- priv->rect.left = DEF_HSTRT << !priv->qcif;
- priv->rect.top = DEF_VSTRT << !priv->qcif;
- priv->rect.width = mf->width;
- priv->rect.height = mf->height;
Sorry, don't understand. The sensor can do both - cropping per HSTRT, HSTOP, VSTRT and VSTOP and scaling per COMA_CIF / COMA_QCIF, right?
Right.
But which of them is stored in your priv->rect? Is this the input window (cropping) or the output one (scaling)?
I'm not sure how I can follow your input/output concept here. Default (reset) values of HSTRT, HSTOP, VSTRT and VSTOP registers are the same for both CIF and QCIF, giving a 176x144 picture area in both cases. Than, when in CIF (reset default) mode, which actual size is double of that (352x288), I scale them by 2 when converting to priv->rect elements.
You overwrite it in .s_fmt and .s_crop...
I added the priv->rect to be returned by g_crop() instead of recalculating it from the register values. Then, I think I have to overwrite it on every geometry change, whether s_crop or s_fmt caused. Am I missing something?
- if (priv->timeperframe.numerator && priv->timeperframe.denominator)
pclk = priv->pclk_max * priv->timeperframe.denominator /
(FRAME_RATE_MAX * priv->timeperframe.numerator);
- else
pclk = priv->pclk_max;
- if (sense) {
if (sense->master_clock == 8000000) {
dev_dbg(&client->dev, "8MHz input clock\n");
clkrc = CLKRC_6MHz;
} else if (sense->master_clock == 12000000) {
dev_dbg(&client->dev, "12MHz input clock\n");
clkrc = CLKRC_12MHz;
} else if (sense->master_clock == 16000000) {
dev_dbg(&client->dev, "16MHz input clock\n");
clkrc = CLKRC_16MHz;
} else if (sense->master_clock == 24000000) {
dev_dbg(&client->dev, "24MHz input clock\n");
clkrc = CLKRC_24MHz;
} else {
dev_err(&client->dev,
"unspported input clock, check platform data\n");
return -EINVAL;
}
priv->pclk_limit = sense->pixel_clock_max;
if (priv->pclk_limit && (priv->pclk_limit < pclk))
Don't think the compiler would complain without the internal parenthesis here?
OK, I'll drop them.
Thanks, Janusz
Thanks Guennadi
Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/ -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:
Wednesday 22 September 2010 11:12:46 Guennadi Liakhovetski napisaÅ(a):
Ok, just a couple more comments, all looking quite good so far, if we get a new version soon enough, we still might manage it for 2.6.37
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
[snip]
+/* write a register */ +static int ov6650_reg_write(struct i2c_client *client, u8 reg, u8 val) +{
- int ret;
- unsigned char data[2] = { reg, val };
- struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = 2,
.buf = data,
- };
- ret = i2c_transfer(client->adapter, &msg, 1);
- msleep_interruptible(1);
Why do you want _interruptible here? Firstly it's just 1ms, secondly - why?...
My bad. I didn't verified what a real difference between msleep() and msleep_interruptible() is, only found that msleep_interruptible(1) makes checkpatch.pl more happy than msleep(1), sorry.
What I can be sure is that a short delay is required here, otherwise the driver doesn't work correctly. To prevent the checkpatch.pl from complying against msleep(1), I think I could just replace it with msleep(20). What do you think?
oh, no, don't think replacing msleep(1) with msleep(20) just to silence a compiler warning is a god idea...;) Well, use a udelay(1000). Or maybe try, whether a udelay(100) suffices too.
- priv->rect.left = DEF_HSTRT << !priv->qcif;
- priv->rect.top = DEF_VSTRT << !priv->qcif;
- priv->rect.width = mf->width;
- priv->rect.height = mf->height;
Sorry, don't understand. The sensor can do both - cropping per HSTRT, HSTOP, VSTRT and VSTOP and scaling per COMA_CIF / COMA_QCIF, right?
Right.
But which of them is stored in your priv->rect? Is this the input window (cropping) or the output one (scaling)?
I'm not sure how I can follow your input/output concept here. Default (reset) values of HSTRT, HSTOP, VSTRT and VSTOP registers are the same for both CIF and QCIF, giving a 176x144 picture area in both cases. Than, when in CIF (reset default) mode, which actual size is double of that (352x288), I scale them by 2 when converting to priv->rect elements.
You overwrite it in .s_fmt and .s_crop...
I added the priv->rect to be returned by g_crop() instead of recalculating it from the register values. Then, I think I have to overwrite it on every geometry change, whether s_crop or s_fmt caused. Am I missing something?
If I understand your sensor correctly, HSTRT etc. registers configure pretty much any (input) sensor window, whereas COMA and COML select whether to scale it to a CIF or to a QCIF output. So, these are two different things. Hence your ->rect can hold only one of the two - the sensor window or the output image. Since output image has only two options - CIF or QCIF, you don't need to store it in rect, you already have priv->qcif.
Oh, and one more thing - didn't notice before: in your cropcap you do
+ int shift = !priv->qcif; ... + a->bounds.left = DEF_HSTRT << shift; + a->bounds.top = DEF_VSTRT << shift; + a->bounds.width = W_QCIF << shift; + a->bounds.height = H_QCIF << shift;
Don't think this is right. cropcap shouldn't depend on any dynamic (configured by S_FMT) setting, it contains absolute limits of your hardware. I know I might have produced a couple of bad examples in the past - before I eventually settled with this definition... So, I think, it's best to put the full sensor resolution in cropcap.
...and, please, replace
+ priv->qcif = 1; with + priv->qcif = true; and + priv->qcif = 0; with + priv->qcif = false; in ov6650_s_fmt().
But I think your driver might have a problem with its cropping / scaling handling. Let's see if I understand it right:
1. as cropcap you currently return QCIF or CIF, depending on the last S_FMT, but let's say, you fix it to always return CIF.
2. in your s_fmt you accept only two output sizes: CIF or QCIF, that's ok, if that's all you can configure with your driver.
3. in s_crop you accept anything with left + width <= HSTOP and top + height <= VSTOP. This I don't understand. Your HSTOP and VSTOP are fixed values, based on QCIF plus some offsets. So, you would accept widths up to "a little larger than QCIF width" and similar heights. Then, without changing COMA and COML you assume, that the output size changed equally, because that's what you return in g_fmt.
Anyway, I think, there is some misunderstanding of the v4l2 cropping and scaling procedures. Please, have a look here: http://v4l2spec.bytesex.org/spec/x1904.htm. Do you agree, that what your driver is implementing doesn't reflect that correctly?;)
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Thursday 23 September 2010 18:06:15 Guennadi Liakhovetski napisał(a):
On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:
Wednesday 22 September 2010 11:12:46 Guennadi Liakhovetski napisaÅ(a):
Ok, just a couple more comments, all looking quite good so far, if we get a new version soon enough, we still might manage it for 2.6.37
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
[snip]
+/* write a register */ +static int ov6650_reg_write(struct i2c_client *client, u8 reg, u8 val) +{
- int ret;
- unsigned char data[2] = { reg, val };
- struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = 2,
.buf = data,
- };
- ret = i2c_transfer(client->adapter, &msg, 1);
- msleep_interruptible(1);
Why do you want _interruptible here? Firstly it's just 1ms, secondly - why?...
My bad. I didn't verified what a real difference between msleep() and msleep_interruptible() is, only found that msleep_interruptible(1) makes checkpatch.pl more happy than msleep(1), sorry.
What I can be sure is that a short delay is required here, otherwise the driver doesn't work correctly. To prevent the checkpatch.pl from complying against msleep(1), I think I could just replace it with msleep(20). What do you think?
oh, no, don't think replacing msleep(1) with msleep(20) just to silence a compiler warning is a god idea...;) Well, use a udelay(1000). Or maybe try, whether a udelay(100) suffices too.
OK. Thanks for the hints.
- priv->rect.left = DEF_HSTRT << !priv->qcif;
- priv->rect.top = DEF_VSTRT << !priv->qcif;
- priv->rect.width = mf->width;
- priv->rect.height = mf->height;
Sorry, don't understand. The sensor can do both - cropping per HSTRT, HSTOP, VSTRT and VSTOP and scaling per COMA_CIF / COMA_QCIF, right?
Right.
But which of them is stored in your priv->rect? Is this the input window (cropping) or the output one (scaling)?
I'm not sure how I can follow your input/output concept here. Default (reset) values of HSTRT, HSTOP, VSTRT and VSTOP registers are the same for both CIF and QCIF, giving a 176x144 picture area in both cases. Than, when in CIF (reset default) mode, which actual size is double of that (352x288), I scale them by 2 when converting to priv->rect elements.
You overwrite it in .s_fmt and .s_crop...
I added the priv->rect to be returned by g_crop() instead of recalculating it from the register values. Then, I think I have to overwrite it on every geometry change, whether s_crop or s_fmt caused. Am I missing something?
If I understand your sensor correctly, HSTRT etc. registers configure pretty much any (input) sensor window,
Let's say, not exceeding CIF geometry (352x288).
whereas COMA and COML select whether to scale it to a CIF or to a QCIF output.
I think the answer is: not exactly. AFAICT, the COMA_QCIF bit selects whether to scale it down by 2 (QCIF selection) or not (CIF selection).
So, these are two different things. Hence your ->rect can hold only one of the two - the sensor window or the output image. Since output image has only two options
- CIF or QCIF, you don't need to store it in rect, you already have
priv->qcif.
With the above reservation - yes, I could use priv->qcif to scale priv->rect down by 2 or not in g_fmt.
Oh, and one more thing - didn't notice before: in your cropcap you do
- int shift = !priv->qcif;
...
- a->bounds.left = DEF_HSTRT << shift;
- a->bounds.top = DEF_VSTRT << shift;
- a->bounds.width = W_QCIF << shift;
- a->bounds.height = H_QCIF << shift;
Don't think this is right. cropcap shouldn't depend on any dynamic (configured by S_FMT) setting, it contains absolute limits of your hardware. I know I might have produced a couple of bad examples in the past - before I eventually settled with this definition... So, I think, it's best to put the full sensor resolution in cropcap.
OK.
...and, please, replace
priv->qcif = 1;
with
priv->qcif = true;
and
priv->qcif = 0;
with
priv->qcif = false;
in ov6650_s_fmt().
Sure.
But I think your driver might have a problem with its cropping / scaling handling. Let's see if I understand it right:
- as cropcap you currently return QCIF or CIF, depending on the last
S_FMT,
Yes.
BTW, my S_FMT always calls ov6650_reset(), which resets the current crop to defaults. This behaviour doesn't follow the requirement of this operation being done only once, when the driver is first loaded, but not later. Should I restore the last crop after every reset? If yes, what is the purpose of defrect if applied only at driver first load?
but let's say, you fix it to always return CIF.
OK.
- in your s_fmt you accept only two output sizes: CIF or QCIF, that's ok,
if that's all you can configure with your driver.
Not any longer :). I'm able to configure using current crop geometry only, either unscaled or scaled down by 2. I'm not able to configure neither exact CIF nor QCIF if my current crop window doesn't match, unless I'm allowed to change the crop from here.
- in s_crop you accept anything with left + width <= HSTOP and top +
height <= VSTOP. This I don't understand. Your HSTOP and VSTOP are fixed values, based on QCIF plus some offsets. So, you would accept widths up to "a little larger than QCIF width" and similar heights.
Do you mean I should also verify if (rect->left >= DEF_HSTRT) and (rect->top
= DEF_VSTRT)? I should probably.
Then, without changing COMA and COML you assume, that the output size changed equally, because that's what you return in g_fmt.
I think I've verified, to the extent possible using v4l2-dbg, that it works like this. If I change the input height by overwriting VSTOP with a slightly higher value, the output seems to change proportionally and starts rolling down, since the host is not updated to handle the so changed frame size.
Anyway, I think, there is some misunderstanding of the v4l2 cropping and scaling procedures. Please, have a look here: http://v4l2spec.bytesex.org/spec/x1904.htm. Do you agree, that what your driver is implementing doesn't reflect that correctly?;)
Yes, I do. And I think that that the reason of this misunderstanding is my sensor just not matching the v4l2 model with its limited, probably uncommon scaling capability, or me still not being able to map the sensor to the v4l2 model correctly. What do you think?
Thanks, Janusz
On Fri, 24 Sep 2010, Janusz Krzysztofik wrote:
Thursday 23 September 2010 18:06:15 Guennadi Liakhovetski napisał(a):
On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:
Wednesday 22 September 2010 11:12:46 Guennadi Liakhovetski napisaÅ(a):
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
- priv->rect.left = DEF_HSTRT << !priv->qcif;
- priv->rect.top = DEF_VSTRT << !priv->qcif;
- priv->rect.width = mf->width;
- priv->rect.height = mf->height;
Sorry, don't understand. The sensor can do both - cropping per HSTRT, HSTOP, VSTRT and VSTOP and scaling per COMA_CIF / COMA_QCIF, right?
Right.
But which of them is stored in your priv->rect? Is this the input window (cropping) or the output one (scaling)?
I'm not sure how I can follow your input/output concept here. Default (reset) values of HSTRT, HSTOP, VSTRT and VSTOP registers are the same for both CIF and QCIF, giving a 176x144 picture area in both cases. Than, when in CIF (reset default) mode, which actual size is double of that (352x288), I scale them by 2 when converting to priv->rect elements.
You overwrite it in .s_fmt and .s_crop...
I added the priv->rect to be returned by g_crop() instead of recalculating it from the register values. Then, I think I have to overwrite it on every geometry change, whether s_crop or s_fmt caused. Am I missing something?
If I understand your sensor correctly, HSTRT etc. registers configure pretty much any (input) sensor window,
Let's say, not exceeding CIF geometry (352x288).
Yes, sorry, forgot to mention that.
whereas COMA and COML select whether to scale it to a CIF or to a QCIF output.
I think the answer is: not exactly. AFAICT, the COMA_QCIF bit selects whether to scale it down by 2 (QCIF selection) or not (CIF selection).
Ah! Ok, that it would be better to select different names for those bits.
So, these are two different things. Hence your ->rect can hold only one of the two - the sensor window or the output image. Since output image has only two options
- CIF or QCIF, you don't need to store it in rect, you already have
priv->qcif.
With the above reservation - yes, I could use priv->qcif to scale priv->rect down by 2 or not in g_fmt.
...and for the ->qcif member.
Oh, and one more thing - didn't notice before: in your cropcap you do
- int shift = !priv->qcif;
...
- a->bounds.left = DEF_HSTRT << shift;
- a->bounds.top = DEF_VSTRT << shift;
- a->bounds.width = W_QCIF << shift;
- a->bounds.height = H_QCIF << shift;
Don't think this is right. cropcap shouldn't depend on any dynamic (configured by S_FMT) setting, it contains absolute limits of your hardware. I know I might have produced a couple of bad examples in the past - before I eventually settled with this definition... So, I think, it's best to put the full sensor resolution in cropcap.
OK.
...and, please, replace
priv->qcif = 1;
with
priv->qcif = true;
and
priv->qcif = 0;
with
priv->qcif = false;
in ov6650_s_fmt().
Sure.
But I think your driver might have a problem with its cropping / scaling handling. Let's see if I understand it right:
- as cropcap you currently return QCIF or CIF, depending on the last
S_FMT,
Yes.
BTW, my S_FMT always calls ov6650_reset(), which resets the current crop to defaults.
Oh, does it mean all registers are reset to their defaults? That'd be not good - no v4l(2) ioctl, AFAIK, should affect parameters, not directly related to it. Even closing and reopening the video device node shouldn't reset values. So, maybe you should drop that reset completely.
This behaviour doesn't follow the requirement of this operation being done only once, when the driver is first loaded, but not later. Should I restore the last crop after every reset? If yes, what is the purpose of defrect if applied only at driver first load?
that's exactly the purpose, I think.
but let's say, you fix it to always return CIF.
OK.
- in your s_fmt you accept only two output sizes: CIF or QCIF, that's ok,
if that's all you can configure with your driver.
Not any longer :). I'm able to configure using current crop geometry only, either unscaled or scaled down by 2. I'm not able to configure neither exact CIF nor QCIF if my current crop window doesn't match, unless I'm allowed to change the crop from here.
Hm, but in your s_fmt you do:
+ switch (mf->width) { + case W_QCIF: + dev_dbg(&client->dev, "resolution QCIF\n"); + priv->qcif = 1; + coma_set |= COMA_QCIF; + priv->pclk_max /= 2; + break; + case W_CIF: + dev_dbg(&client->dev, "resolution CIF\n"); + priv->qcif = 0; + coma_mask |= COMA_QCIF; + break; + default: + dev_err(&client->dev, "unspported resolution!\n"); + return -EINVAL; + }
So, you accept only CIF or QCIF as your output window. Or do you mean a v3 by your "not any longer?" And yes, you are allowed to change your input sensor window, if that lets you configure your output format more precisely. And v.v. The rule is - the most recent command wins.
- in s_crop you accept anything with left + width <= HSTOP and top +
height <= VSTOP. This I don't understand. Your HSTOP and VSTOP are fixed values, based on QCIF plus some offsets. So, you would accept widths up to "a little larger than QCIF width" and similar heights.
Do you mean I should also verify if (rect->left >= DEF_HSTRT) and (rect->top
= DEF_VSTRT)? I should probably.
Yes, if you cannot handle smaller values.
Then, without changing COMA and COML you assume, that the output size changed equally, because that's what you return in g_fmt.
I think I've verified, to the extent possible using v4l2-dbg, that it works like this. If I change the input height by overwriting VSTOP with a slightly higher value, the output seems to change proportionally and starts rolling down, since the host is not updated to handle the so changed frame size.
Yes, now that I know, that that flag is actually a scale-down-by-2 switch.
Anyway, I think, there is some misunderstanding of the v4l2 cropping and scaling procedures. Please, have a look here: http://v4l2spec.bytesex.org/spec/x1904.htm. Do you agree, that what your driver is implementing doesn't reflect that correctly?;)
Yes, I do. And I think that that the reason of this misunderstanding is my sensor just not matching the v4l2 model with its limited, probably uncommon scaling capability, or me still not being able to map the sensor to the v4l2 model correctly. What do you think?
No, there's nothing wrong with your sensor:) So, what I would do is:
1. in your struct ov6650:
+ struct v4l2_rect rect; /* sensor cropping window */ + bool half_scale; /* scale down output by 2 */
2. in s_crop verify left, width, top, height, program them into the chip and store in ->rect
3. in g_crop just return values from ->rect
4. in s_fmt you have to select an input rectangle, that would allow you to possibly exactly configure the requested output format. Say, if you have a 320x240 cropping configured and you get an s_fmt request for 120x90. You can either set your input rectangle to 240x180 and scale it down by 2, or set the rectangle directly to 120x90. Obviously, it's better to use 240x180 and scale down, because that's closer to the current cropping of 320x240. So, in s_fmt you select a new input rectangle _closest_ to the currently configured one, that would allow you to configure the correct output format. Then you set your ->rect with the new values and your ->half_scale
5. in g_fmt you return ->rect scaled with ->half_scale
Makes sense?
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Friday 24 September 2010 08:52:32 Guennadi Liakhovetski napisał(a):
On Fri, 24 Sep 2010, Janusz Krzysztofik wrote:
Thursday 23 September 2010 18:06:15 Guennadi Liakhovetski napisał(a):
On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:
Wednesday 22 September 2010 11:12:46 Guennadi Liakhovetski napisaÅ
(a):
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
...
whereas COMA and COML select whether to scale it to a CIF or to a QCIF output.
I think the answer is: not exactly. AFAICT, the COMA_QCIF bit selects whether to scale it down by 2 (QCIF selection) or not (CIF selection).
Ah! Ok, that it would be better to select different names for those bits.
I was trying to keep all names more or less consistent with the wording used in the sensor documentation (which doesn't follow the v4l2 specification unfortunately :). In this case we have:
COMA Common Control A ... Bit[5]: Output format – resolution 0: CIF (352 x 288) 1: QCIF (176 x 144)
So I could rename it to something like COMA_OUTFMT or COMA_RESOLUTION. Which one sounds better for you?
...
But I think your driver might have a problem with its cropping / scaling handling. Let's see if I understand it right:
- as cropcap you currently return QCIF or CIF, depending on the last
S_FMT,
Yes.
BTW, my S_FMT always calls ov6650_reset(), which resets the current crop to defaults.
Oh, does it mean all registers are reset to their defaults? That'd be not good - no v4l(2) ioctl, AFAIK, should affect parameters, not directly related to it. Even closing and reopening the video device node shouldn't reset values. So, maybe you should drop that reset completely.
Shouldn't I rather move it over into the ov6650_video_probe()?
...
- in your s_fmt you accept only two output sizes: CIF or QCIF, that's
ok, if that's all you can configure with your driver.
Not any longer :). I'm able to configure using current crop geometry only, either unscaled or scaled down by 2. I'm not able to configure neither exact CIF nor QCIF if my current crop window doesn't match, unless I'm allowed to change the crop from here.
Hm, but in your s_fmt you do:
- switch (mf->width) {
- case W_QCIF:
dev_dbg(&client->dev, "resolution QCIF\n");
priv->qcif = 1;
coma_set |= COMA_QCIF;
priv->pclk_max /= 2;
break;
- case W_CIF:
dev_dbg(&client->dev, "resolution CIF\n");
priv->qcif = 0;
coma_mask |= COMA_QCIF;
break;
- default:
dev_err(&client->dev, "unspported resolution!\n");
return -EINVAL;
- }
So, you accept only CIF or QCIF as your output window. Or do you mean a v3 by your "not any longer?"
Exactly!
And yes, you are allowed to change your input sensor window, if that lets you configure your output format more precisely. And v.v. The rule is - the most recent command wins.
I see.
...
No, there's nothing wrong with your sensor:) So, what I would do is:
- in your struct ov6650:
- struct v4l2_rect rect; /* sensor cropping window */
- bool half_scale; /* scale down output by 2 */
- in s_crop verify left, width, top, height, program them into the chip
and store in ->rect
in g_crop just return values from ->rect
in s_fmt you have to select an input rectangle, that would allow you to
possibly exactly configure the requested output format. Say, if you have a 320x240 cropping configured and you get an s_fmt request for 120x90. You can either set your input rectangle to 240x180 and scale it down by 2, or set the rectangle directly to 120x90. Obviously, it's better to use 240x180 and scale down, because that's closer to the current cropping of 320x240. So, in s_fmt you select a new input rectangle _closest_ to the currently configured one, that would allow you to configure the correct output format. Then you set your ->rect with the new values and your ->half_scale
- in g_fmt you return ->rect scaled with ->half_scale
Makes sense?
Absolutely. Hope to submit v3 soon.
Thanks, Janusz
On Fri, 24 Sep 2010, Janusz Krzysztofik wrote:
Friday 24 September 2010 08:52:32 Guennadi Liakhovetski napisał(a):
On Fri, 24 Sep 2010, Janusz Krzysztofik wrote:
Thursday 23 September 2010 18:06:15 Guennadi Liakhovetski napisał(a):
On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:
Wednesday 22 September 2010 11:12:46 Guennadi Liakhovetski napisaÅ
(a):
On Sat, 11 Sep 2010, Janusz Krzysztofik wrote:
...
whereas COMA and COML select whether to scale it to a CIF or to a QCIF output.
I think the answer is: not exactly. AFAICT, the COMA_QCIF bit selects whether to scale it down by 2 (QCIF selection) or not (CIF selection).
Ah! Ok, that it would be better to select different names for those bits.
I was trying to keep all names more or less consistent with the wording used in the sensor documentation (which doesn't follow the v4l2 specification unfortunately :). In this case we have:
COMA Common Control A ... Bit[5]: Output format – resolution 0: CIF (352 x 288) 1: QCIF (176 x 144)
So I could rename it to something like COMA_OUTFMT or COMA_RESOLUTION. Which one sounds better for you?
ok, so, it means to which output window the _complete_ sensor area is mapped. And if you get a smaller sensor rectangle, you get a smaller output image, right? Ok, then you can just leave it as is.
...
But I think your driver might have a problem with its cropping / scaling handling. Let's see if I understand it right:
- as cropcap you currently return QCIF or CIF, depending on the last
S_FMT,
Yes.
BTW, my S_FMT always calls ov6650_reset(), which resets the current crop to defaults.
Oh, does it mean all registers are reset to their defaults? That'd be not good - no v4l(2) ioctl, AFAIK, should affect parameters, not directly related to it. Even closing and reopening the video device node shouldn't reset values. So, maybe you should drop that reset completely.
Shouldn't I rather move it over into the ov6650_video_probe()?
Good idea!:)
...
- in your s_fmt you accept only two output sizes: CIF or QCIF, that's
ok, if that's all you can configure with your driver.
Not any longer :). I'm able to configure using current crop geometry only, either unscaled or scaled down by 2. I'm not able to configure neither exact CIF nor QCIF if my current crop window doesn't match, unless I'm allowed to change the crop from here.
Hm, but in your s_fmt you do:
- switch (mf->width) {
- case W_QCIF:
dev_dbg(&client->dev, "resolution QCIF\n");
priv->qcif = 1;
coma_set |= COMA_QCIF;
priv->pclk_max /= 2;
break;
- case W_CIF:
dev_dbg(&client->dev, "resolution CIF\n");
priv->qcif = 0;
coma_mask |= COMA_QCIF;
break;
- default:
dev_err(&client->dev, "unspported resolution!\n");
return -EINVAL;
- }
So, you accept only CIF or QCIF as your output window. Or do you mean a v3 by your "not any longer?"
Exactly!
And yes, you are allowed to change your input sensor window, if that lets you configure your output format more precisely. And v.v. The rule is - the most recent command wins.
I see.
...
No, there's nothing wrong with your sensor:) So, what I would do is:
- in your struct ov6650:
- struct v4l2_rect rect; /* sensor cropping window */
- bool half_scale; /* scale down output by 2 */
- in s_crop verify left, width, top, height, program them into the chip
and store in ->rect
in g_crop just return values from ->rect
in s_fmt you have to select an input rectangle, that would allow you to
possibly exactly configure the requested output format. Say, if you have a 320x240 cropping configured and you get an s_fmt request for 120x90. You can either set your input rectangle to 240x180 and scale it down by 2, or set the rectangle directly to 120x90. Obviously, it's better to use 240x180 and scale down, because that's closer to the current cropping of 320x240. So, in s_fmt you select a new input rectangle _closest_ to the currently configured one, that would allow you to configure the correct output format. Then you set your ->rect with the new values and your ->half_scale
- in g_fmt you return ->rect scaled with ->half_scale
Makes sense?
Absolutely. Hope to submit v3 soon.
Good! Looking forward:)
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
This patch adds support for g_parm / s_parm operations to the SoC Camera framework. It is usefull for checking/setting camera frame rate.
Example usage can be found in the previous patch from this series, "SoC Camera: add driver for OV6650 sensor".
Created and tested against linux-2.6.36-rc3 on Amstrad Delta.
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl --- v1->v2 changes: - no functional changes, - refreshed against linux-2.6.36-rc3.
drivers/media/video/soc_camera.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff -upr linux-2.6.36-rc3.orig/drivers/media/video/soc_camera.c linux-2.6.36-rc3/drivers/media/video/soc_camera.c --- linux-2.6.36-rc3.orig/drivers/media/video/soc_camera.c 2010-09-03 22:29:44.000000000 +0200 +++ linux-2.6.36-rc3/drivers/media/video/soc_camera.c 2010-09-03 22:34:03.000000000 +0200 @@ -1148,6 +1148,20 @@ static int default_s_crop(struct soc_cam return v4l2_subdev_call(sd, video, s_crop, a); }
+static int default_g_parm(struct soc_camera_device *icd, + struct v4l2_streamparm *parm) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + return v4l2_subdev_call(sd, video, g_parm, parm); +} + +static int default_s_parm(struct soc_camera_device *icd, + struct v4l2_streamparm *parm) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + return v4l2_subdev_call(sd, video, s_parm, parm); +} + static void soc_camera_device_init(struct device *dev, void *pdata) { dev->platform_data = pdata; @@ -1179,6 +1193,10 @@ int soc_camera_host_register(struct soc_ ici->ops->get_crop = default_g_crop; if (!ici->ops->cropcap) ici->ops->cropcap = default_cropcap; + if (!ici->ops->set_parm) + ici->ops->set_parm = default_s_parm; + if (!ici->ops->get_parm) + ici->ops->get_parm = default_g_parm;
mutex_lock(&list_lock); list_for_each_entry(ix, &hosts, list) {
This patch adds configuration data and initialization code required for camera support to the Amstrad Delta board.
Three devices are declared: SoC camera, OMAP1 camera interface and OV6650 sensor.
Default 12MHz clock has been selected for driving the sensor. Pixel clock has been limited to get reasonable frame rates, not exceeding the board capabilities. Since both devices (interface and sensor) support both pixel clock polarities, decision on polarity selection has been left to drivers. Interface GPIO line has been found not functional, thus not configured.
Created and tested against linux-2.6.36-rc3.
Works on top of previous patches from the series, at least 1/6, 2/6 and 3/6.
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl --- v1->v2 changes: - no functional changes, - refreshed against linux-2.6.36-rc3
arch/arm/mach-omap1/board-ams-delta.c | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+)
diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/board-ams-delta.c linux-2.6.36-rc3/arch/arm/mach-omap1/board-ams-delta.c --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/board-ams-delta.c 2010-09-03 22:29:00.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/board-ams-delta.c 2010-09-10 22:01:24.000000000 +0200 @@ -19,6 +19,8 @@ #include <linux/platform_device.h> #include <linux/serial_8250.h>
+#include <media/soc_camera.h> + #include <asm/serial.h> #include <mach/hardware.h> #include <asm/mach-types.h> @@ -32,6 +34,7 @@ #include <plat/usb.h> #include <plat/board.h> #include <plat/common.h> +#include <mach/camera.h>
#include <mach/ams-delta-fiq.h>
@@ -213,10 +216,37 @@ static struct platform_device ams_delta_ .id = -1 };
+static struct i2c_board_info ams_delta_camera_board_info[] = { + { + I2C_BOARD_INFO("ov6650", 0x60), + }, +}; + +static struct soc_camera_link __initdata ams_delta_iclink = { + .bus_id = 0, /* OMAP1 SoC camera bus */ + .i2c_adapter_id = 1, + .board_info = &ams_delta_camera_board_info[0], + .module_name = "ov6650", +}; + +static struct platform_device ams_delta_camera_device = { + .name = "soc-camera-pdrv", + .id = 0, + .dev = { + .platform_data = &ams_delta_iclink, + }, +}; + +static struct omap1_cam_platform_data ams_delta_camera_platform_data = { + .camexclk_khz = 12000, /* default 12MHz clock, no extra DPLL */ + .lclk_khz_max = 1334, /* results in 5fps CIF, 10fps QCIF */ +}; + static struct platform_device *ams_delta_devices[] __initdata = { &ams_delta_kp_device, &ams_delta_lcd_device, &ams_delta_led_device, + &ams_delta_camera_device, };
static void __init ams_delta_init(void) @@ -225,6 +255,20 @@ static void __init ams_delta_init(void) omap_cfg_reg(UART1_TX); omap_cfg_reg(UART1_RTS);
+ /* parallel camera interface */ + omap_cfg_reg(H19_1610_CAM_EXCLK); + omap_cfg_reg(J15_1610_CAM_LCLK); + omap_cfg_reg(L18_1610_CAM_VS); + omap_cfg_reg(L15_1610_CAM_HS); + omap_cfg_reg(L19_1610_CAM_D0); + omap_cfg_reg(K14_1610_CAM_D1); + omap_cfg_reg(K15_1610_CAM_D2); + omap_cfg_reg(K19_1610_CAM_D3); + omap_cfg_reg(K18_1610_CAM_D4); + omap_cfg_reg(J14_1610_CAM_D5); + omap_cfg_reg(J19_1610_CAM_D6); + omap_cfg_reg(J18_1610_CAM_D7); + iotable_init(ams_delta_io_desc, ARRAY_SIZE(ams_delta_io_desc));
omap_board_config = ams_delta_config; @@ -236,6 +280,7 @@ static void __init ams_delta_init(void) ams_delta_latch2_write(~0, 0);
omap1_usb_init(&ams_delta_usb_config); + omap1_set_camera_info(&ams_delta_camera_platform_data); platform_add_devices(ams_delta_devices, ARRAY_SIZE(ams_delta_devices));
#ifdef CONFIG_AMS_DELTA_FIQ
* Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100910 18:20]:
This patch adds configuration data and initialization code required for camera support to the Amstrad Delta board.
Three devices are declared: SoC camera, OMAP1 camera interface and OV6650 sensor.
Default 12MHz clock has been selected for driving the sensor. Pixel clock has been limited to get reasonable frame rates, not exceeding the board capabilities. Since both devices (interface and sensor) support both pixel clock polarities, decision on polarity selection has been left to drivers. Interface GPIO line has been found not functional, thus not configured.
Created and tested against linux-2.6.36-rc3.
Works on top of previous patches from the series, at least 1/6, 2/6 and 3/6.
Queuing these last two patches of the series (5/6 and 6/6) for the upcoming merge window.
Tony
* Tony Lindgren tony@atomide.com [100923 16:06]:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100910 18:20]:
This patch adds configuration data and initialization code required for camera support to the Amstrad Delta board.
Three devices are declared: SoC camera, OMAP1 camera interface and OV6650 sensor.
Default 12MHz clock has been selected for driving the sensor. Pixel clock has been limited to get reasonable frame rates, not exceeding the board capabilities. Since both devices (interface and sensor) support both pixel clock polarities, decision on polarity selection has been left to drivers. Interface GPIO line has been found not functional, thus not configured.
Created and tested against linux-2.6.36-rc3.
Works on top of previous patches from the series, at least 1/6, 2/6 and 3/6.
Queuing these last two patches of the series (5/6 and 6/6) for the upcoming merge window.
BTW, these still depend on updated 2/6 to make compile happy.
Tony
Friday 24 September 2010 01:26:17 Tony Lindgren napisał(a):
- Tony Lindgren tony@atomide.com [100923 16:06]:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100910 18:20]:
This patch adds configuration data and initialization code required for camera support to the Amstrad Delta board.
Three devices are declared: SoC camera, OMAP1 camera interface and OV6650 sensor.
Default 12MHz clock has been selected for driving the sensor. Pixel clock has been limited to get reasonable frame rates, not exceeding the board capabilities. Since both devices (interface and sensor) support both pixel clock polarities, decision on polarity selection has been left to drivers. Interface GPIO line has been found not functional, thus not configured.
Created and tested against linux-2.6.36-rc3.
Works on top of previous patches from the series, at least 1/6, 2/6 and 3/6.
Queuing these last two patches of the series (5/6 and 6/6) for the upcoming merge window.
BTW, these still depend on updated 2/6 to make compile happy.
Not so simple: still depends on struct omap1_cam_platform_data definition from <media/omap1_camera.h>, included from <mach/camera.h>. Are you ready to accept another temporary workaround?
Thanks, Janusz
Tony
To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
* Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100923 16:52]:
Friday 24 September 2010 01:26:17 Tony Lindgren napisał(a):
- Tony Lindgren tony@atomide.com [100923 16:06]:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100910 18:20]:
This patch adds configuration data and initialization code required for camera support to the Amstrad Delta board.
Three devices are declared: SoC camera, OMAP1 camera interface and OV6650 sensor.
Default 12MHz clock has been selected for driving the sensor. Pixel clock has been limited to get reasonable frame rates, not exceeding the board capabilities. Since both devices (interface and sensor) support both pixel clock polarities, decision on polarity selection has been left to drivers. Interface GPIO line has been found not functional, thus not configured.
Created and tested against linux-2.6.36-rc3.
Works on top of previous patches from the series, at least 1/6, 2/6 and 3/6.
Queuing these last two patches of the series (5/6 and 6/6) for the upcoming merge window.
BTW, these still depend on updated 2/6 to make compile happy.
Not so simple: still depends on struct omap1_cam_platform_data definition from <media/omap1_camera.h>, included from <mach/camera.h>. Are you ready to accept another temporary workaround?
Heh I guess so. Or do you want to queue everything via linux-media?
Tony
On Thu, 23 Sep 2010, Tony Lindgren wrote:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100923 16:52]:
Friday 24 September 2010 01:26:17 Tony Lindgren napisał(a):
- Tony Lindgren tony@atomide.com [100923 16:06]:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100910 18:20]:
This patch adds configuration data and initialization code required for camera support to the Amstrad Delta board.
Three devices are declared: SoC camera, OMAP1 camera interface and OV6650 sensor.
Default 12MHz clock has been selected for driving the sensor. Pixel clock has been limited to get reasonable frame rates, not exceeding the board capabilities. Since both devices (interface and sensor) support both pixel clock polarities, decision on polarity selection has been left to drivers. Interface GPIO line has been found not functional, thus not configured.
Created and tested against linux-2.6.36-rc3.
Works on top of previous patches from the series, at least 1/6, 2/6 and 3/6.
Queuing these last two patches of the series (5/6 and 6/6) for the upcoming merge window.
BTW, these still depend on updated 2/6 to make compile happy.
Not so simple: still depends on struct omap1_cam_platform_data definition from <media/omap1_camera.h>, included from <mach/camera.h>. Are you ready to accept another temporary workaround?
Heh I guess so. Or do you want to queue everything via linux-media?
Yes, we often have to select via which tree to go, then the maintainer of the other tree just acks the patches. Sometimes we push them via different trees and try to enforce a specific merge order...
Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Friday 24 September 2010 08:57:06 Guennadi Liakhovetski napisał(a):
On Thu, 23 Sep 2010, Tony Lindgren wrote:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100923 16:52]:
Friday 24 September 2010 01:26:17 Tony Lindgren napisał(a):
- Tony Lindgren tony@atomide.com [100923 16:06]:
- Janusz Krzysztofik jkrzyszt@tis.icnet.pl [100910 18:20]:
This patch adds configuration data and initialization code required for camera support to the Amstrad Delta board.
Three devices are declared: SoC camera, OMAP1 camera interface and OV6650 sensor.
Default 12MHz clock has been selected for driving the sensor. Pixel clock has been limited to get reasonable frame rates, not exceeding the board capabilities. Since both devices (interface and sensor) support both pixel clock polarities, decision on polarity selection has been left to drivers. Interface GPIO line has been found not functional, thus not configured.
Created and tested against linux-2.6.36-rc3.
Works on top of previous patches from the series, at least 1/6, 2/6 and 3/6.
Queuing these last two patches of the series (5/6 and 6/6) for the upcoming merge window.
BTW, these still depend on updated 2/6 to make compile happy.
Not so simple: still depends on struct omap1_cam_platform_data definition from <media/omap1_camera.h>, included from <mach/camera.h>. Are you ready to accept another temporary workaround?
Heh I guess so. Or do you want to queue everything via linux-media?
AFAIK we can expect my arch/arm/mach-omap1/devices.c changes already resulting in a confilct with some ASoC OMAP related changes going via the sound tree, so the 2/6 should be better queued via the OMAP tree for Tony to keep control over it, with the rest of the seriers going either way.
Yes, we often have to select via which tree to go, then the maintainer of the other tree just acks the patches. Sometimes we push them via different trees and try to enforce a specific merge order...
What about
+ void omap1_set_camera_info(struct omap1_cam_platform_data *);
put temporarily into to the arch/arm/mach-omap1/board-ams-delta.c instead of including <mach/camera.h>, that could be replaced with <media/omap1_camera.h> then? May sound better than redefining struct omap1_cam_platform_data there, and should be safe to push everything except 2/6 via the media tree.
Then, replace the above hack back with #include <mach/camera.h> as a fix after both are merged.
Thanks, Janusz
This patch extends the Amstrad Delta camera support with LEDS trigger that can be used for automatic control of the on-board camera LED. The led turns on automatically on camera device open and turns off on camera device close.
Created and tested against linux-2.6.36-rc3.
Works on top of patch 5/6, "OMAP1: Amstrad Delta: add support for on-board camera"
Signed-off-by: Janusz Krzysztofik jkrzyszt@tis.icnet.pl --- Having no comments received on v1 of this patch, I assume nobody could see any benefit if I implemented this idea at the SoC camera or OMAP1 camera level, so I'm leaveing it as an Amstrad Delta only feature, as it originally was for v1.
Thanks, Janusz
v1->v2 changes: - no functional changes, - refreshed against linux-2.6.36-rc3.
arch/arm/mach-omap1/board-ams-delta.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/board-ams-delta.c linux-2.6.36-rc3/arch/arm/mach-omap1/board-ams-delta.c --- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/board-ams-delta.c 2010-09-10 22:01:24.000000000 +0200 +++ linux-2.6.36-rc3/arch/arm/mach-omap1/board-ams-delta.c 2010-09-10 22:08:29.000000000 +0200 @@ -16,6 +16,7 @@ #include <linux/init.h> #include <linux/input.h> #include <linux/interrupt.h> +#include <linux/leds.h> #include <linux/platform_device.h> #include <linux/serial_8250.h>
@@ -222,11 +223,30 @@ static struct i2c_board_info ams_delta_c }, };
+#ifdef CONFIG_LEDS_TRIGGERS +DEFINE_LED_TRIGGER(ams_delta_camera_led_trigger); + +static int ams_delta_camera_power(struct device *dev, int power) +{ + /* + * turn on camera LED + */ + if (power) + led_trigger_event(ams_delta_camera_led_trigger, LED_FULL); + else + led_trigger_event(ams_delta_camera_led_trigger, LED_OFF); + return 0; +} +#else +#define ams_delta_camera_power NULL +#endif + static struct soc_camera_link __initdata ams_delta_iclink = { .bus_id = 0, /* OMAP1 SoC camera bus */ .i2c_adapter_id = 1, .board_info = &ams_delta_camera_board_info[0], .module_name = "ov6650", + .power = ams_delta_camera_power, };
static struct platform_device ams_delta_camera_device = { @@ -281,6 +301,10 @@ static void __init ams_delta_init(void)
omap1_usb_init(&ams_delta_usb_config); omap1_set_camera_info(&ams_delta_camera_platform_data); +#ifdef CONFIG_LEDS_TRIGGERS + led_trigger_register_simple("ams_delta_camera", + &ams_delta_camera_led_trigger); +#endif platform_add_devices(ams_delta_devices, ARRAY_SIZE(ams_delta_devices));
#ifdef CONFIG_AMS_DELTA_FIQ