2008-07-17 09:59:28 +08:00
|
|
|
/*
|
|
|
|
* V4L2 Driver for SuperH Mobile CEU interface
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 Magnus Damm
|
|
|
|
*
|
|
|
|
* Based on V4L2 Driver for PXA camera host - "pxa_camera.c",
|
|
|
|
*
|
|
|
|
* Copyright (C) 2006, Sascha Hauer, Pengutronix
|
|
|
|
* Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/time.h>
|
|
|
|
#include <linux/version.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/videodev2.h>
|
2009-08-14 18:49:17 +08:00
|
|
|
#include <linux/pm_runtime.h>
|
2008-07-17 09:59:28 +08:00
|
|
|
|
|
|
|
#include <media/v4l2-common.h>
|
|
|
|
#include <media/v4l2-dev.h>
|
|
|
|
#include <media/soc_camera.h>
|
|
|
|
#include <media/sh_mobile_ceu.h>
|
|
|
|
#include <media/videobuf-dma-contig.h>
|
|
|
|
|
|
|
|
/* register offsets for sh7722 / sh7723 */
|
|
|
|
|
2008-10-17 06:50:22 +08:00
|
|
|
#define CAPSR 0x00 /* Capture start register */
|
|
|
|
#define CAPCR 0x04 /* Capture control register */
|
|
|
|
#define CAMCR 0x08 /* Capture interface control register */
|
|
|
|
#define CMCYR 0x0c /* Capture interface cycle register */
|
|
|
|
#define CAMOR 0x10 /* Capture interface offset register */
|
|
|
|
#define CAPWR 0x14 /* Capture interface width register */
|
|
|
|
#define CAIFR 0x18 /* Capture interface input format register */
|
|
|
|
#define CSTCR 0x20 /* Camera strobe control register (<= sh7722) */
|
|
|
|
#define CSECR 0x24 /* Camera strobe emission count register (<= sh7722) */
|
|
|
|
#define CRCNTR 0x28 /* CEU register control register */
|
|
|
|
#define CRCMPR 0x2c /* CEU register forcible control register */
|
|
|
|
#define CFLCR 0x30 /* Capture filter control register */
|
|
|
|
#define CFSZR 0x34 /* Capture filter size clip register */
|
|
|
|
#define CDWDR 0x38 /* Capture destination width register */
|
|
|
|
#define CDAYR 0x3c /* Capture data address Y register */
|
|
|
|
#define CDACR 0x40 /* Capture data address C register */
|
|
|
|
#define CDBYR 0x44 /* Capture data bottom-field address Y register */
|
|
|
|
#define CDBCR 0x48 /* Capture data bottom-field address C register */
|
|
|
|
#define CBDSR 0x4c /* Capture bundle destination size register */
|
|
|
|
#define CFWCR 0x5c /* Firewall operation control register */
|
|
|
|
#define CLFCR 0x60 /* Capture low-pass filter control register */
|
|
|
|
#define CDOCR 0x64 /* Capture data output control register */
|
|
|
|
#define CDDCR 0x68 /* Capture data complexity level register */
|
|
|
|
#define CDDAR 0x6c /* Capture data complexity level address register */
|
|
|
|
#define CEIER 0x70 /* Capture event interrupt enable register */
|
|
|
|
#define CETCR 0x74 /* Capture event flag clear register */
|
|
|
|
#define CSTSR 0x7c /* Capture status register */
|
|
|
|
#define CSRTR 0x80 /* Capture software reset register */
|
|
|
|
#define CDSSR 0x84 /* Capture data size register */
|
|
|
|
#define CDAYR2 0x90 /* Capture data address Y register 2 */
|
|
|
|
#define CDACR2 0x94 /* Capture data address C register 2 */
|
|
|
|
#define CDBYR2 0x98 /* Capture data bottom-field address Y register 2 */
|
|
|
|
#define CDBCR2 0x9c /* Capture data bottom-field address C register 2 */
|
2008-07-17 09:59:28 +08:00
|
|
|
|
|
|
|
/* per video frame buffer */
|
|
|
|
struct sh_mobile_ceu_buffer {
|
|
|
|
struct videobuf_buffer vb; /* v4l buffer must be first */
|
|
|
|
const struct soc_camera_data_format *fmt;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct sh_mobile_ceu_dev {
|
|
|
|
struct soc_camera_host ici;
|
|
|
|
struct soc_camera_device *icd;
|
|
|
|
|
|
|
|
unsigned int irq;
|
|
|
|
void __iomem *base;
|
|
|
|
unsigned long video_limit;
|
|
|
|
|
2008-07-18 04:30:47 +08:00
|
|
|
/* lock used to protect videobuf */
|
2008-07-17 09:59:28 +08:00
|
|
|
spinlock_t lock;
|
|
|
|
struct list_head capture;
|
|
|
|
struct videobuf_buffer *active;
|
|
|
|
|
|
|
|
struct sh_mobile_ceu_info *pdata;
|
2008-12-18 23:38:06 +08:00
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
u32 cflcr;
|
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
unsigned int is_interlaced:1;
|
|
|
|
unsigned int image_mode:1;
|
|
|
|
unsigned int is_16bit:1;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct sh_mobile_ceu_cam {
|
|
|
|
struct v4l2_rect camera_rect;
|
2009-08-25 22:46:54 +08:00
|
|
|
struct v4l2_rect camera_max;
|
2009-08-25 22:46:52 +08:00
|
|
|
const struct soc_camera_data_format *extra_fmt;
|
2008-12-18 23:38:06 +08:00
|
|
|
const struct soc_camera_data_format *camera_fmt;
|
2008-07-17 09:59:28 +08:00
|
|
|
};
|
|
|
|
|
2009-02-23 23:12:58 +08:00
|
|
|
static unsigned long make_bus_param(struct sh_mobile_ceu_dev *pcdev)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
flags = SOCAM_MASTER |
|
|
|
|
SOCAM_PCLK_SAMPLE_RISING |
|
|
|
|
SOCAM_HSYNC_ACTIVE_HIGH |
|
|
|
|
SOCAM_HSYNC_ACTIVE_LOW |
|
|
|
|
SOCAM_VSYNC_ACTIVE_HIGH |
|
|
|
|
SOCAM_VSYNC_ACTIVE_LOW |
|
|
|
|
SOCAM_DATA_ACTIVE_HIGH;
|
|
|
|
|
|
|
|
if (pcdev->pdata->flags & SH_CEU_FLAG_USE_8BIT_BUS)
|
|
|
|
flags |= SOCAM_DATAWIDTH_8;
|
|
|
|
|
|
|
|
if (pcdev->pdata->flags & SH_CEU_FLAG_USE_16BIT_BUS)
|
|
|
|
flags |= SOCAM_DATAWIDTH_16;
|
|
|
|
|
|
|
|
if (flags & SOCAM_DATAWIDTH_MASK)
|
|
|
|
return flags;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
static void ceu_write(struct sh_mobile_ceu_dev *priv,
|
2008-12-18 23:50:30 +08:00
|
|
|
unsigned long reg_offs, u32 data)
|
2008-07-17 09:59:28 +08:00
|
|
|
{
|
|
|
|
iowrite32(data, priv->base + reg_offs);
|
|
|
|
}
|
|
|
|
|
2008-12-18 23:50:30 +08:00
|
|
|
static u32 ceu_read(struct sh_mobile_ceu_dev *priv, unsigned long reg_offs)
|
2008-07-17 09:59:28 +08:00
|
|
|
{
|
|
|
|
return ioread32(priv->base + reg_offs);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Videobuf operations
|
|
|
|
*/
|
|
|
|
static int sh_mobile_ceu_videobuf_setup(struct videobuf_queue *vq,
|
|
|
|
unsigned int *count,
|
|
|
|
unsigned int *size)
|
|
|
|
{
|
|
|
|
struct soc_camera_device *icd = vq->priv_data;
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
|
|
|
int bytes_per_pixel = (icd->current_fmt->depth + 7) >> 3;
|
|
|
|
|
2009-08-25 22:46:17 +08:00
|
|
|
*size = PAGE_ALIGN(icd->rect_current.width * icd->rect_current.height *
|
|
|
|
bytes_per_pixel);
|
2008-07-17 09:59:28 +08:00
|
|
|
|
|
|
|
if (0 == *count)
|
|
|
|
*count = 2;
|
|
|
|
|
|
|
|
if (pcdev->video_limit) {
|
|
|
|
while (*size * *count > pcdev->video_limit)
|
|
|
|
(*count)--;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(&icd->dev, "count=%d, size=%d\n", *count, *size);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void free_buffer(struct videobuf_queue *vq,
|
|
|
|
struct sh_mobile_ceu_buffer *buf)
|
|
|
|
{
|
|
|
|
struct soc_camera_device *icd = vq->priv_data;
|
|
|
|
|
2008-07-18 12:35:14 +08:00
|
|
|
dev_dbg(&icd->dev, "%s (vb=0x%p) 0x%08lx %zd\n", __func__,
|
2008-07-17 09:59:28 +08:00
|
|
|
&buf->vb, buf->vb.baddr, buf->vb.bsize);
|
|
|
|
|
|
|
|
if (in_interrupt())
|
|
|
|
BUG();
|
|
|
|
|
2009-03-13 17:08:20 +08:00
|
|
|
videobuf_waiton(&buf->vb, 0, 0);
|
2008-07-17 09:59:28 +08:00
|
|
|
videobuf_dma_contig_free(vq, &buf->vb);
|
|
|
|
dev_dbg(&icd->dev, "%s freed\n", __func__);
|
|
|
|
buf->vb.state = VIDEOBUF_NEEDS_INIT;
|
|
|
|
}
|
|
|
|
|
2008-12-18 23:50:30 +08:00
|
|
|
#define CEU_CETCR_MAGIC 0x0317f313 /* acknowledge magical interrupt sources */
|
|
|
|
#define CEU_CETCR_IGRW (1 << 4) /* prohibited register access interrupt bit */
|
|
|
|
#define CEU_CEIER_CPEIE (1 << 0) /* one-frame capture end interrupt */
|
|
|
|
#define CEU_CAPCR_CTNCP (1 << 16) /* continuous capture mode (if set) */
|
|
|
|
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
static void sh_mobile_ceu_capture(struct sh_mobile_ceu_dev *pcdev)
|
|
|
|
{
|
2008-12-18 23:38:06 +08:00
|
|
|
struct soc_camera_device *icd = pcdev->icd;
|
2008-12-18 23:51:21 +08:00
|
|
|
dma_addr_t phys_addr_top, phys_addr_bottom;
|
2008-12-18 23:50:30 +08:00
|
|
|
|
|
|
|
/* The hardware is _very_ picky about this sequence. Especially
|
|
|
|
* the CEU_CETCR_MAGIC value. It seems like we need to acknowledge
|
|
|
|
* several not-so-well documented interrupt sources in CETCR.
|
|
|
|
*/
|
|
|
|
ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) & ~CEU_CEIER_CPEIE);
|
|
|
|
ceu_write(pcdev, CETCR, ~ceu_read(pcdev, CETCR) & CEU_CETCR_MAGIC);
|
|
|
|
ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) | CEU_CEIER_CPEIE);
|
|
|
|
ceu_write(pcdev, CAPCR, ceu_read(pcdev, CAPCR) & ~CEU_CAPCR_CTNCP);
|
|
|
|
ceu_write(pcdev, CETCR, CEU_CETCR_MAGIC ^ CEU_CETCR_IGRW);
|
2008-07-17 09:59:28 +08:00
|
|
|
|
2008-12-18 23:38:06 +08:00
|
|
|
if (!pcdev->active)
|
|
|
|
return;
|
|
|
|
|
2008-12-18 23:51:21 +08:00
|
|
|
phys_addr_top = videobuf_to_dma_contig(pcdev->active);
|
|
|
|
ceu_write(pcdev, CDAYR, phys_addr_top);
|
2009-03-13 17:08:20 +08:00
|
|
|
if (pcdev->is_interlaced) {
|
2009-08-25 22:46:17 +08:00
|
|
|
phys_addr_bottom = phys_addr_top + icd->rect_current.width;
|
2008-12-18 23:51:21 +08:00
|
|
|
ceu_write(pcdev, CDBYR, phys_addr_bottom);
|
|
|
|
}
|
2008-12-18 23:38:06 +08:00
|
|
|
|
|
|
|
switch (icd->current_fmt->fourcc) {
|
|
|
|
case V4L2_PIX_FMT_NV12:
|
|
|
|
case V4L2_PIX_FMT_NV21:
|
2008-12-18 23:45:33 +08:00
|
|
|
case V4L2_PIX_FMT_NV16:
|
|
|
|
case V4L2_PIX_FMT_NV61:
|
2009-08-25 22:46:17 +08:00
|
|
|
phys_addr_top += icd->rect_current.width *
|
|
|
|
icd->rect_current.height;
|
2008-12-18 23:51:21 +08:00
|
|
|
ceu_write(pcdev, CDACR, phys_addr_top);
|
2009-03-13 17:08:20 +08:00
|
|
|
if (pcdev->is_interlaced) {
|
2009-08-25 22:46:17 +08:00
|
|
|
phys_addr_bottom = phys_addr_top +
|
|
|
|
icd->rect_current.width;
|
2008-12-18 23:51:21 +08:00
|
|
|
ceu_write(pcdev, CDBCR, phys_addr_bottom);
|
|
|
|
}
|
2008-07-17 09:59:28 +08:00
|
|
|
}
|
2008-12-18 23:38:06 +08:00
|
|
|
|
|
|
|
pcdev->active->state = VIDEOBUF_ACTIVE;
|
|
|
|
ceu_write(pcdev, CAPSR, 0x1); /* start capture */
|
2008-07-17 09:59:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int sh_mobile_ceu_videobuf_prepare(struct videobuf_queue *vq,
|
|
|
|
struct videobuf_buffer *vb,
|
|
|
|
enum v4l2_field field)
|
|
|
|
{
|
|
|
|
struct soc_camera_device *icd = vq->priv_data;
|
|
|
|
struct sh_mobile_ceu_buffer *buf;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
buf = container_of(vb, struct sh_mobile_ceu_buffer, vb);
|
|
|
|
|
2008-07-18 12:35:14 +08:00
|
|
|
dev_dbg(&icd->dev, "%s (vb=0x%p) 0x%08lx %zd\n", __func__,
|
2008-07-17 09:59:28 +08:00
|
|
|
vb, vb->baddr, vb->bsize);
|
|
|
|
|
|
|
|
/* Added list head initialization on alloc */
|
|
|
|
WARN_ON(!list_empty(&vb->queue));
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
/* This can be useful if you want to see if we actually fill
|
|
|
|
* the buffer with something */
|
|
|
|
memset((void *)vb->baddr, 0xaa, vb->bsize);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
BUG_ON(NULL == icd->current_fmt);
|
|
|
|
|
|
|
|
if (buf->fmt != icd->current_fmt ||
|
2009-08-25 22:46:17 +08:00
|
|
|
vb->width != icd->rect_current.width ||
|
|
|
|
vb->height != icd->rect_current.height ||
|
2008-07-17 09:59:28 +08:00
|
|
|
vb->field != field) {
|
|
|
|
buf->fmt = icd->current_fmt;
|
2009-08-25 22:46:17 +08:00
|
|
|
vb->width = icd->rect_current.width;
|
|
|
|
vb->height = icd->rect_current.height;
|
2008-07-17 09:59:28 +08:00
|
|
|
vb->field = field;
|
|
|
|
vb->state = VIDEOBUF_NEEDS_INIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
vb->size = vb->width * vb->height * ((buf->fmt->depth + 7) >> 3);
|
|
|
|
if (0 != 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
|
|
free_buffer(vq, buf);
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-08-06 07:06:31 +08:00
|
|
|
/* Called under spinlock_irqsave(&pcdev->lock, ...) */
|
2008-07-17 09:59:28 +08:00
|
|
|
static void sh_mobile_ceu_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 sh_mobile_ceu_dev *pcdev = ici->priv;
|
|
|
|
|
2008-07-18 12:35:14 +08:00
|
|
|
dev_dbg(&icd->dev, "%s (vb=0x%p) 0x%08lx %zd\n", __func__,
|
2008-07-17 09:59:28 +08:00
|
|
|
vb, vb->baddr, vb->bsize);
|
|
|
|
|
2008-10-17 06:51:20 +08:00
|
|
|
vb->state = VIDEOBUF_QUEUED;
|
2008-07-17 09:59:28 +08:00
|
|
|
list_add_tail(&vb->queue, &pcdev->capture);
|
|
|
|
|
|
|
|
if (!pcdev->active) {
|
|
|
|
pcdev->active = vb;
|
|
|
|
sh_mobile_ceu_capture(pcdev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sh_mobile_ceu_videobuf_release(struct videobuf_queue *vq,
|
|
|
|
struct videobuf_buffer *vb)
|
|
|
|
{
|
2009-08-25 22:46:51 +08:00
|
|
|
struct soc_camera_device *icd = vq->priv_data;
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&pcdev->lock, flags);
|
|
|
|
|
|
|
|
if (pcdev->active == vb) {
|
|
|
|
/* disable capture (release DMA buffer), reset */
|
|
|
|
ceu_write(pcdev, CAPSR, 1 << 16);
|
|
|
|
pcdev->active = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((vb->state == VIDEOBUF_ACTIVE || vb->state == VIDEOBUF_QUEUED) &&
|
|
|
|
!list_empty(&vb->queue)) {
|
|
|
|
vb->state = VIDEOBUF_ERROR;
|
|
|
|
list_del_init(&vb->queue);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&pcdev->lock, flags);
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
free_buffer(vq, container_of(vb, struct sh_mobile_ceu_buffer, vb));
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct videobuf_queue_ops sh_mobile_ceu_videobuf_ops = {
|
|
|
|
.buf_setup = sh_mobile_ceu_videobuf_setup,
|
|
|
|
.buf_prepare = sh_mobile_ceu_videobuf_prepare,
|
|
|
|
.buf_queue = sh_mobile_ceu_videobuf_queue,
|
|
|
|
.buf_release = sh_mobile_ceu_videobuf_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
static irqreturn_t sh_mobile_ceu_irq(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = data;
|
|
|
|
struct videobuf_buffer *vb;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&pcdev->lock, flags);
|
|
|
|
|
|
|
|
vb = pcdev->active;
|
2009-08-25 22:46:51 +08:00
|
|
|
if (!vb)
|
|
|
|
/* Stale interrupt from a released buffer */
|
|
|
|
goto out;
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
list_del_init(&vb->queue);
|
|
|
|
|
|
|
|
if (!list_empty(&pcdev->capture))
|
|
|
|
pcdev->active = list_entry(pcdev->capture.next,
|
|
|
|
struct videobuf_buffer, queue);
|
|
|
|
else
|
|
|
|
pcdev->active = NULL;
|
|
|
|
|
|
|
|
sh_mobile_ceu_capture(pcdev);
|
|
|
|
|
|
|
|
vb->state = VIDEOBUF_DONE;
|
|
|
|
do_gettimeofday(&vb->ts);
|
|
|
|
vb->field_count++;
|
|
|
|
wake_up(&vb->done);
|
2009-08-25 22:46:51 +08:00
|
|
|
|
|
|
|
out:
|
2008-07-17 09:59:28 +08:00
|
|
|
spin_unlock_irqrestore(&pcdev->lock, flags);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2008-12-18 23:28:54 +08:00
|
|
|
/* Called with .video_lock held */
|
2008-07-17 09:59:28 +08:00
|
|
|
static int sh_mobile_ceu_add_device(struct soc_camera_device *icd)
|
|
|
|
{
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
|
|
|
|
|
|
|
if (pcdev->icd)
|
2009-08-25 22:43:33 +08:00
|
|
|
return -EBUSY;
|
2008-07-17 09:59:28 +08:00
|
|
|
|
|
|
|
dev_info(&icd->dev,
|
|
|
|
"SuperH Mobile CEU driver attached to camera %d\n",
|
|
|
|
icd->devnum);
|
|
|
|
|
2009-08-25 22:28:22 +08:00
|
|
|
clk_enable(pcdev->clk);
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
ceu_write(pcdev, CAPSR, 1 << 16); /* reset */
|
|
|
|
while (ceu_read(pcdev, CSTSR) & 1)
|
|
|
|
msleep(1);
|
|
|
|
|
|
|
|
pcdev->icd = icd;
|
2009-08-25 22:43:33 +08:00
|
|
|
|
|
|
|
return 0;
|
2008-07-17 09:59:28 +08:00
|
|
|
}
|
|
|
|
|
2008-12-18 23:28:54 +08:00
|
|
|
/* Called with .video_lock held */
|
2008-07-17 09:59:28 +08:00
|
|
|
static void sh_mobile_ceu_remove_device(struct soc_camera_device *icd)
|
|
|
|
{
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
2008-10-17 06:51:20 +08:00
|
|
|
unsigned long flags;
|
2008-07-17 09:59:28 +08:00
|
|
|
|
|
|
|
BUG_ON(icd != pcdev->icd);
|
|
|
|
|
|
|
|
/* disable capture, disable interrupts */
|
|
|
|
ceu_write(pcdev, CEIER, 0);
|
|
|
|
ceu_write(pcdev, CAPSR, 1 << 16); /* reset */
|
2008-10-17 06:51:20 +08:00
|
|
|
|
|
|
|
/* make sure active buffer is canceled */
|
|
|
|
spin_lock_irqsave(&pcdev->lock, flags);
|
|
|
|
if (pcdev->active) {
|
|
|
|
list_del(&pcdev->active->queue);
|
|
|
|
pcdev->active->state = VIDEOBUF_ERROR;
|
|
|
|
wake_up_all(&pcdev->active->done);
|
|
|
|
pcdev->active = NULL;
|
|
|
|
}
|
|
|
|
spin_unlock_irqrestore(&pcdev->lock, flags);
|
|
|
|
|
2009-08-25 22:28:22 +08:00
|
|
|
clk_disable(pcdev->clk);
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
dev_info(&icd->dev,
|
|
|
|
"SuperH Mobile CEU driver detached from camera %d\n",
|
|
|
|
icd->devnum);
|
|
|
|
|
|
|
|
pcdev->icd = NULL;
|
|
|
|
}
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
/*
|
|
|
|
* See chapter 29.4.12 "Capture Filter Control Register (CFLCR)"
|
|
|
|
* in SH7722 Hardware Manual
|
|
|
|
*/
|
|
|
|
static unsigned int size_dst(unsigned int src, unsigned int scale)
|
|
|
|
{
|
|
|
|
unsigned int mant_pre = scale >> 12;
|
|
|
|
if (!src || !scale)
|
|
|
|
return src;
|
|
|
|
return ((mant_pre + 2 * (src - 1)) / (2 * mant_pre) - 1) *
|
|
|
|
mant_pre * 4096 / scale + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int size_src(unsigned int dst, unsigned int scale)
|
|
|
|
{
|
|
|
|
unsigned int mant_pre = scale >> 12, tmp;
|
|
|
|
if (!dst || !scale)
|
|
|
|
return dst;
|
|
|
|
for (tmp = ((dst - 1) * scale + 2048 * mant_pre) / 4096 + 1;
|
|
|
|
size_dst(tmp, scale) < dst;
|
|
|
|
tmp++)
|
|
|
|
;
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u16 calc_scale(unsigned int src, unsigned int *dst)
|
|
|
|
{
|
|
|
|
u16 scale;
|
|
|
|
|
|
|
|
if (src == *dst)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
scale = (src * 4096 / *dst) & ~7;
|
|
|
|
|
|
|
|
while (scale > 4096 && size_dst(src, scale) < *dst)
|
|
|
|
scale -= 8;
|
|
|
|
|
|
|
|
*dst = size_dst(src, scale);
|
|
|
|
|
|
|
|
return scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* rect is guaranteed to not exceed the scaled camera rectangle */
|
2009-08-25 22:46:52 +08:00
|
|
|
static void sh_mobile_ceu_set_rect(struct soc_camera_device *icd,
|
|
|
|
struct v4l2_rect *rect)
|
|
|
|
{
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_cam *cam = icd->host_priv;
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
2009-08-25 22:46:54 +08:00
|
|
|
int width, height, cfszr_width, cdwdr_width, in_width, in_height;
|
|
|
|
unsigned int left_offset, top_offset, left, top;
|
|
|
|
unsigned int hscale = pcdev->cflcr & 0xffff;
|
|
|
|
unsigned int vscale = (pcdev->cflcr >> 16) & 0xffff;
|
2009-08-25 22:46:52 +08:00
|
|
|
u32 camor;
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
/* Switch to the camera scale */
|
|
|
|
left = size_src(rect->left, hscale);
|
|
|
|
top = size_src(rect->top, vscale);
|
|
|
|
|
|
|
|
dev_dbg(&icd->dev, "Left %u * 0x%x = %u, top %u * 0x%x = %u\n",
|
|
|
|
rect->left, hscale, left, rect->top, vscale, top);
|
|
|
|
|
|
|
|
if (left > cam->camera_rect.left) {
|
|
|
|
left_offset = left - cam->camera_rect.left;
|
2009-08-25 22:46:52 +08:00
|
|
|
} else {
|
|
|
|
left_offset = 0;
|
2009-08-25 22:46:54 +08:00
|
|
|
left = cam->camera_rect.left;
|
2009-08-25 22:46:52 +08:00
|
|
|
}
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
if (top > cam->camera_rect.top) {
|
|
|
|
top_offset = top - cam->camera_rect.top;
|
2009-08-25 22:46:52 +08:00
|
|
|
} else {
|
|
|
|
top_offset = 0;
|
2009-08-25 22:46:54 +08:00
|
|
|
top = cam->camera_rect.top;
|
2009-08-25 22:46:52 +08:00
|
|
|
}
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
dev_dbg(&icd->dev, "New left %u, top %u, offsets %u:%u\n",
|
|
|
|
rect->left, rect->top, left_offset, top_offset);
|
2009-08-25 22:46:52 +08:00
|
|
|
|
|
|
|
if (pcdev->image_mode) {
|
|
|
|
width = rect->width;
|
2009-08-25 22:46:54 +08:00
|
|
|
in_width = cam->camera_rect.width;
|
|
|
|
if (!pcdev->is_16bit) {
|
2009-08-25 22:46:52 +08:00
|
|
|
width *= 2;
|
2009-08-25 22:46:54 +08:00
|
|
|
in_width *= 2;
|
|
|
|
left_offset *= 2;
|
|
|
|
}
|
2009-08-25 22:46:52 +08:00
|
|
|
cfszr_width = cdwdr_width = rect->width;
|
|
|
|
} else {
|
2009-08-25 22:46:54 +08:00
|
|
|
unsigned int w_factor = (icd->current_fmt->depth + 7) >> 3;
|
|
|
|
if (!pcdev->is_16bit)
|
|
|
|
w_factor *= 2;
|
|
|
|
|
|
|
|
width = rect->width * w_factor / 2;
|
|
|
|
in_width = cam->camera_rect.width * w_factor / 2;
|
|
|
|
left_offset = left_offset * w_factor / 2;
|
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
cfszr_width = pcdev->is_16bit ? width : width / 2;
|
|
|
|
cdwdr_width = pcdev->is_16bit ? width * 2 : width;
|
|
|
|
}
|
|
|
|
|
|
|
|
height = rect->height;
|
2009-08-25 22:46:54 +08:00
|
|
|
in_height = cam->camera_rect.height;
|
2009-08-25 22:46:52 +08:00
|
|
|
if (pcdev->is_interlaced) {
|
|
|
|
height /= 2;
|
2009-08-25 22:46:54 +08:00
|
|
|
in_height /= 2;
|
|
|
|
top_offset /= 2;
|
2009-08-25 22:46:52 +08:00
|
|
|
cdwdr_width *= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
camor = left_offset | (top_offset << 16);
|
|
|
|
ceu_write(pcdev, CAMOR, camor);
|
2009-08-25 22:46:54 +08:00
|
|
|
ceu_write(pcdev, CAPWR, (in_height << 16) | in_width);
|
2009-08-25 22:46:52 +08:00
|
|
|
ceu_write(pcdev, CFSZR, (height << 16) | cfszr_width);
|
|
|
|
ceu_write(pcdev, CDWDR, cdwdr_width);
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 capture_save_reset(struct sh_mobile_ceu_dev *pcdev)
|
|
|
|
{
|
|
|
|
u32 capsr = ceu_read(pcdev, CAPSR);
|
|
|
|
ceu_write(pcdev, CAPSR, 1 << 16); /* reset, stop capture */
|
|
|
|
return capsr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void capture_restore(struct sh_mobile_ceu_dev *pcdev, u32 capsr)
|
|
|
|
{
|
|
|
|
unsigned long timeout = jiffies + 10 * HZ;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Wait until the end of the current frame. It can take a long time,
|
|
|
|
* but if it has been aborted by a CAPSR reset, it shoule exit sooner.
|
|
|
|
*/
|
|
|
|
while ((ceu_read(pcdev, CSTSR) & 1) && time_before(jiffies, timeout))
|
|
|
|
msleep(1);
|
|
|
|
|
|
|
|
if (time_after(jiffies, timeout)) {
|
|
|
|
dev_err(pcdev->ici.v4l2_dev.dev,
|
|
|
|
"Timeout waiting for frame end! Interface problem?\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Wait until reset clears, this shall not hang... */
|
|
|
|
while (ceu_read(pcdev, CAPSR) & (1 << 16))
|
|
|
|
udelay(10);
|
|
|
|
|
|
|
|
/* Anything to restore? */
|
|
|
|
if (capsr & ~(1 << 16))
|
|
|
|
ceu_write(pcdev, CAPSR, capsr);
|
|
|
|
}
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
static int sh_mobile_ceu_set_bus_param(struct soc_camera_device *icd,
|
|
|
|
__u32 pixfmt)
|
|
|
|
{
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
2009-08-25 22:46:52 +08:00
|
|
|
int ret;
|
2008-07-17 09:59:28 +08:00
|
|
|
unsigned long camera_flags, common_flags, value;
|
2009-08-25 22:46:52 +08:00
|
|
|
int yuv_lineskip;
|
|
|
|
struct sh_mobile_ceu_cam *cam = icd->host_priv;
|
|
|
|
u32 capsr = capture_save_reset(pcdev);
|
2008-07-17 09:59:28 +08:00
|
|
|
|
|
|
|
camera_flags = icd->ops->query_bus_param(icd);
|
|
|
|
common_flags = soc_camera_bus_param_compatible(camera_flags,
|
2009-02-23 23:12:58 +08:00
|
|
|
make_bus_param(pcdev));
|
2008-07-17 09:59:28 +08:00
|
|
|
if (!common_flags)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = icd->ops->set_bus_param(icd, common_flags);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
switch (common_flags & SOCAM_DATAWIDTH_MASK) {
|
|
|
|
case SOCAM_DATAWIDTH_8:
|
2009-08-25 22:46:52 +08:00
|
|
|
pcdev->is_16bit = 0;
|
2008-07-17 09:59:28 +08:00
|
|
|
break;
|
|
|
|
case SOCAM_DATAWIDTH_16:
|
2009-08-25 22:46:52 +08:00
|
|
|
pcdev->is_16bit = 1;
|
2008-07-17 09:59:28 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ceu_write(pcdev, CRCNTR, 0);
|
|
|
|
ceu_write(pcdev, CRCMPR, 0);
|
|
|
|
|
2008-12-18 23:38:06 +08:00
|
|
|
value = 0x00000010; /* data fetch by default */
|
2009-08-25 22:46:54 +08:00
|
|
|
yuv_lineskip = 0;
|
2008-12-18 23:38:06 +08:00
|
|
|
|
|
|
|
switch (icd->current_fmt->fourcc) {
|
|
|
|
case V4L2_PIX_FMT_NV12:
|
|
|
|
case V4L2_PIX_FMT_NV21:
|
|
|
|
yuv_lineskip = 1; /* skip for NV12/21, no skip for NV16/61 */
|
2008-12-18 23:45:33 +08:00
|
|
|
/* fall-through */
|
|
|
|
case V4L2_PIX_FMT_NV16:
|
|
|
|
case V4L2_PIX_FMT_NV61:
|
2009-08-25 22:46:52 +08:00
|
|
|
switch (cam->camera_fmt->fourcc) {
|
2008-12-18 23:38:06 +08:00
|
|
|
case V4L2_PIX_FMT_UYVY:
|
|
|
|
value = 0x00000000; /* Cb0, Y0, Cr0, Y1 */
|
|
|
|
break;
|
|
|
|
case V4L2_PIX_FMT_VYUY:
|
|
|
|
value = 0x00000100; /* Cr0, Y0, Cb0, Y1 */
|
|
|
|
break;
|
|
|
|
case V4L2_PIX_FMT_YUYV:
|
|
|
|
value = 0x00000200; /* Y0, Cb0, Y1, Cr0 */
|
|
|
|
break;
|
|
|
|
case V4L2_PIX_FMT_YVYU:
|
|
|
|
value = 0x00000300; /* Y0, Cr0, Y1, Cb0 */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BUG();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-12-18 23:50:30 +08:00
|
|
|
if (icd->current_fmt->fourcc == V4L2_PIX_FMT_NV21 ||
|
|
|
|
icd->current_fmt->fourcc == V4L2_PIX_FMT_NV61)
|
2008-12-18 23:45:33 +08:00
|
|
|
value ^= 0x00000100; /* swap U, V to change from NV1x->NVx1 */
|
2008-12-18 23:38:06 +08:00
|
|
|
|
2008-12-18 23:50:30 +08:00
|
|
|
value |= common_flags & SOCAM_VSYNC_ACTIVE_LOW ? 1 << 1 : 0;
|
|
|
|
value |= common_flags & SOCAM_HSYNC_ACTIVE_LOW ? 1 << 0 : 0;
|
2009-08-25 22:46:52 +08:00
|
|
|
value |= pcdev->is_16bit ? 1 << 12 : 0;
|
2008-07-17 09:59:28 +08:00
|
|
|
ceu_write(pcdev, CAMCR, value);
|
|
|
|
|
|
|
|
ceu_write(pcdev, CAPCR, 0x00300000);
|
2009-03-13 17:08:20 +08:00
|
|
|
ceu_write(pcdev, CAIFR, pcdev->is_interlaced ? 0x101 : 0);
|
2008-07-17 09:59:28 +08:00
|
|
|
|
|
|
|
mdelay(1);
|
2009-08-25 22:46:52 +08:00
|
|
|
sh_mobile_ceu_set_rect(icd, &icd->rect_current);
|
2008-07-17 09:59:28 +08:00
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
ceu_write(pcdev, CFLCR, pcdev->cflcr);
|
2008-10-17 06:50:22 +08:00
|
|
|
|
|
|
|
/* A few words about byte order (observed in Big Endian mode)
|
|
|
|
*
|
|
|
|
* In data fetch mode bytes are received in chunks of 8 bytes.
|
|
|
|
* D0, D1, D2, D3, D4, D5, D6, D7 (D0 received first)
|
|
|
|
*
|
|
|
|
* The data is however by default written to memory in reverse order:
|
|
|
|
* D7, D6, D5, D4, D3, D2, D1, D0 (D7 written to lowest byte)
|
|
|
|
*
|
|
|
|
* The lowest three bits of CDOCR allows us to do swapping,
|
2008-10-17 06:50:56 +08:00
|
|
|
* using 7 we swap the data bytes to match the incoming order:
|
|
|
|
* D0, D1, D2, D3, D4, D5, D6, D7
|
2008-10-17 06:50:22 +08:00
|
|
|
*/
|
2008-12-18 23:38:06 +08:00
|
|
|
value = 0x00000017;
|
|
|
|
if (yuv_lineskip)
|
|
|
|
value &= ~0x00000010; /* convert 4:2:2 -> 4:2:0 */
|
|
|
|
|
|
|
|
ceu_write(pcdev, CDOCR, value);
|
2008-07-17 09:59:28 +08:00
|
|
|
ceu_write(pcdev, CFWCR, 0); /* keep "datafetch firewall" disabled */
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
dev_dbg(&icd->dev, "S_FMT successful for %c%c%c%c %ux%u@%u:%u\n",
|
2009-08-25 22:46:52 +08:00
|
|
|
pixfmt & 0xff, (pixfmt >> 8) & 0xff,
|
|
|
|
(pixfmt >> 16) & 0xff, (pixfmt >> 24) & 0xff,
|
|
|
|
icd->rect_current.width, icd->rect_current.height,
|
|
|
|
icd->rect_current.left, icd->rect_current.top);
|
|
|
|
|
|
|
|
capture_restore(pcdev, capsr);
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
/* not in bundle mode: skip CBDSR, CDAYR2, CDACR2, CDBYR2, CDBCR2 */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-18 22:49:06 +08:00
|
|
|
static int sh_mobile_ceu_try_bus_param(struct soc_camera_device *icd)
|
2008-07-17 09:59:28 +08:00
|
|
|
{
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
|
|
|
unsigned long camera_flags, common_flags;
|
|
|
|
|
|
|
|
camera_flags = icd->ops->query_bus_param(icd);
|
|
|
|
common_flags = soc_camera_bus_param_compatible(camera_flags,
|
2009-02-23 23:12:58 +08:00
|
|
|
make_bus_param(pcdev));
|
2008-07-17 09:59:28 +08:00
|
|
|
if (!common_flags)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-18 23:38:06 +08:00
|
|
|
static const struct soc_camera_data_format sh_mobile_ceu_formats[] = {
|
|
|
|
{
|
|
|
|
.name = "NV12",
|
|
|
|
.depth = 12,
|
|
|
|
.fourcc = V4L2_PIX_FMT_NV12,
|
|
|
|
.colorspace = V4L2_COLORSPACE_JPEG,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "NV21",
|
|
|
|
.depth = 12,
|
|
|
|
.fourcc = V4L2_PIX_FMT_NV21,
|
|
|
|
.colorspace = V4L2_COLORSPACE_JPEG,
|
|
|
|
},
|
2008-12-18 23:45:33 +08:00
|
|
|
{
|
|
|
|
.name = "NV16",
|
|
|
|
.depth = 16,
|
|
|
|
.fourcc = V4L2_PIX_FMT_NV16,
|
|
|
|
.colorspace = V4L2_COLORSPACE_JPEG,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "NV61",
|
|
|
|
.depth = 16,
|
|
|
|
.fourcc = V4L2_PIX_FMT_NV61,
|
|
|
|
.colorspace = V4L2_COLORSPACE_JPEG,
|
|
|
|
},
|
2008-12-18 23:38:06 +08:00
|
|
|
};
|
|
|
|
|
2008-12-18 22:49:06 +08:00
|
|
|
static int sh_mobile_ceu_get_formats(struct soc_camera_device *icd, int idx,
|
|
|
|
struct soc_camera_format_xlate *xlate)
|
|
|
|
{
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
2008-12-18 23:38:06 +08:00
|
|
|
int ret, k, n;
|
2008-12-18 22:49:06 +08:00
|
|
|
int formats = 0;
|
2009-08-25 22:46:52 +08:00
|
|
|
struct sh_mobile_ceu_cam *cam;
|
2008-12-18 22:49:06 +08:00
|
|
|
|
|
|
|
ret = sh_mobile_ceu_try_bus_param(icd);
|
|
|
|
if (ret < 0)
|
|
|
|
return 0;
|
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
if (!icd->host_priv) {
|
|
|
|
cam = kzalloc(sizeof(*cam), GFP_KERNEL);
|
|
|
|
if (!cam)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
icd->host_priv = cam;
|
2009-08-25 22:46:54 +08:00
|
|
|
cam->camera_max = icd->rect_max;
|
2009-08-25 22:46:52 +08:00
|
|
|
} else {
|
|
|
|
cam = icd->host_priv;
|
|
|
|
}
|
|
|
|
|
2009-02-23 23:12:58 +08:00
|
|
|
/* Beginning of a pass */
|
|
|
|
if (!idx)
|
2009-08-25 22:46:52 +08:00
|
|
|
cam->extra_fmt = NULL;
|
2009-02-23 23:12:58 +08:00
|
|
|
|
2008-12-18 22:49:06 +08:00
|
|
|
switch (icd->formats[idx].fourcc) {
|
2008-12-18 23:38:06 +08:00
|
|
|
case V4L2_PIX_FMT_UYVY:
|
|
|
|
case V4L2_PIX_FMT_VYUY:
|
|
|
|
case V4L2_PIX_FMT_YUYV:
|
|
|
|
case V4L2_PIX_FMT_YVYU:
|
2009-08-25 22:46:52 +08:00
|
|
|
if (cam->extra_fmt)
|
2009-02-23 23:12:58 +08:00
|
|
|
goto add_single_format;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Our case is simple so far: for any of the above four camera
|
|
|
|
* formats we add all our four synthesized NV* formats, so,
|
|
|
|
* just marking the device with a single flag suffices. If
|
|
|
|
* the format generation rules are more complex, you would have
|
|
|
|
* to actually hang your already added / counted formats onto
|
|
|
|
* the host_priv pointer and check whether the format you're
|
|
|
|
* going to add now is already there.
|
|
|
|
*/
|
2009-08-25 22:46:52 +08:00
|
|
|
cam->extra_fmt = (void *)sh_mobile_ceu_formats;
|
2009-02-23 23:12:58 +08:00
|
|
|
|
2008-12-18 23:38:06 +08:00
|
|
|
n = ARRAY_SIZE(sh_mobile_ceu_formats);
|
|
|
|
formats += n;
|
|
|
|
for (k = 0; xlate && k < n; k++) {
|
|
|
|
xlate->host_fmt = &sh_mobile_ceu_formats[k];
|
|
|
|
xlate->cam_fmt = icd->formats + idx;
|
|
|
|
xlate->buswidth = icd->formats[idx].depth;
|
|
|
|
xlate++;
|
2009-08-25 22:43:33 +08:00
|
|
|
dev_dbg(ici->v4l2_dev.dev, "Providing format %s using %s\n",
|
2008-12-18 23:38:06 +08:00
|
|
|
sh_mobile_ceu_formats[k].name,
|
|
|
|
icd->formats[idx].name);
|
|
|
|
}
|
2008-12-18 22:49:06 +08:00
|
|
|
default:
|
2009-02-23 23:12:58 +08:00
|
|
|
add_single_format:
|
2008-12-18 22:49:06 +08:00
|
|
|
/* Generic pass-through */
|
|
|
|
formats++;
|
|
|
|
if (xlate) {
|
|
|
|
xlate->host_fmt = icd->formats + idx;
|
|
|
|
xlate->cam_fmt = icd->formats + idx;
|
|
|
|
xlate->buswidth = icd->formats[idx].depth;
|
|
|
|
xlate++;
|
2009-08-25 22:43:33 +08:00
|
|
|
dev_dbg(ici->v4l2_dev.dev,
|
2008-12-18 22:49:06 +08:00
|
|
|
"Providing format %s in pass-through mode\n",
|
|
|
|
icd->formats[idx].name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return formats;
|
|
|
|
}
|
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
static void sh_mobile_ceu_put_formats(struct soc_camera_device *icd)
|
|
|
|
{
|
|
|
|
kfree(icd->host_priv);
|
|
|
|
icd->host_priv = NULL;
|
|
|
|
}
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
/* Check if any dimension of r1 is smaller than respective one of r2 */
|
2009-08-25 22:46:52 +08:00
|
|
|
static bool is_smaller(struct v4l2_rect *r1, struct v4l2_rect *r2)
|
|
|
|
{
|
|
|
|
return r1->width < r2->width || r1->height < r2->height;
|
|
|
|
}
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
/* Check if r1 fails to cover r2 */
|
|
|
|
static bool is_inside(struct v4l2_rect *r1, struct v4l2_rect *r2)
|
|
|
|
{
|
|
|
|
return r1->left > r2->left || r1->top > r2->top ||
|
|
|
|
r1->left + r1->width < r2->left + r2->width ||
|
|
|
|
r1->top + r1->height < r2->top + r2->height;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* CEU can scale and crop, but we don't want to waste bandwidth and kill the
|
|
|
|
* framerate by always requesting the maximum image from the client. For
|
|
|
|
* cropping we also have to take care of the current scale. The common for both
|
|
|
|
* scaling and cropping approach is:
|
|
|
|
* 1. try if the client can produce exactly what requested by the user
|
|
|
|
* 2. if (1) failed, try to double the client image until we get one big enough
|
|
|
|
* 3. if (2) failed, try to request the maximum image
|
|
|
|
*/
|
2009-03-13 17:08:20 +08:00
|
|
|
static int sh_mobile_ceu_set_crop(struct soc_camera_device *icd,
|
|
|
|
struct v4l2_rect *rect)
|
|
|
|
{
|
2009-08-25 22:46:52 +08:00
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
2009-08-25 22:46:54 +08:00
|
|
|
struct v4l2_rect cam_rect, target, cam_max;
|
2009-08-25 22:46:52 +08:00
|
|
|
struct sh_mobile_ceu_cam *cam = icd->host_priv;
|
2009-08-25 22:46:54 +08:00
|
|
|
unsigned int hscale = pcdev->cflcr & 0xffff;
|
|
|
|
unsigned int vscale = (pcdev->cflcr >> 16) & 0xffff;
|
2009-08-25 22:46:52 +08:00
|
|
|
unsigned short width, height;
|
|
|
|
u32 capsr;
|
|
|
|
int ret;
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
/* Scale back up into client units */
|
|
|
|
cam_rect.left = size_src(rect->left, hscale);
|
|
|
|
cam_rect.width = size_src(rect->width, hscale);
|
|
|
|
cam_rect.top = size_src(rect->top, vscale);
|
|
|
|
cam_rect.height = size_src(rect->height, vscale);
|
|
|
|
|
|
|
|
target = cam_rect;
|
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
capsr = capture_save_reset(pcdev);
|
2009-08-25 22:46:54 +08:00
|
|
|
dev_dbg(&icd->dev, "CAPSR 0x%x, CFLCR 0x%x\n", capsr, pcdev->cflcr);
|
2009-08-25 22:46:52 +08:00
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
/* First attempt - see if the client can deliver a perfect result */
|
2009-08-25 22:46:52 +08:00
|
|
|
ret = icd->ops->set_crop(icd, &cam_rect);
|
2009-08-25 22:46:54 +08:00
|
|
|
if (!ret && !memcmp(&target, &cam_rect, sizeof(target))) {
|
|
|
|
dev_dbg(&icd->dev, "Camera S_CROP successful for %ux%u@%u:%u\n",
|
2009-08-25 22:46:52 +08:00
|
|
|
cam_rect.width, cam_rect.height,
|
|
|
|
cam_rect.left, cam_rect.top);
|
|
|
|
goto ceu_set_rect;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Try to fix cropping, that camera hasn't managed to do */
|
2009-08-25 22:46:54 +08:00
|
|
|
dev_dbg(&icd->dev, "Fix camera S_CROP %d for %ux%u@%u:%u"
|
|
|
|
" to %ux%u@%u:%u\n",
|
2009-08-25 22:46:52 +08:00
|
|
|
ret, cam_rect.width, cam_rect.height,
|
2009-08-25 22:46:54 +08:00
|
|
|
cam_rect.left, cam_rect.top,
|
|
|
|
target.width, target.height, target.left, target.top);
|
2009-08-25 22:46:52 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Popular special case - some cameras can only handle fixed sizes like
|
|
|
|
* QVGA, VGA,... Take care to avoid infinite loop.
|
|
|
|
*/
|
2009-08-25 22:46:54 +08:00
|
|
|
width = max(cam_rect.width, 1);
|
|
|
|
height = max(cam_rect.height, 1);
|
|
|
|
cam_max.width = size_src(icd->rect_max.width, hscale);
|
|
|
|
cam_max.left = size_src(icd->rect_max.left, hscale);
|
|
|
|
cam_max.height = size_src(icd->rect_max.height, vscale);
|
|
|
|
cam_max.top = size_src(icd->rect_max.top, vscale);
|
|
|
|
while (!ret && (is_smaller(&cam_rect, &target) ||
|
|
|
|
is_inside(&cam_rect, &target)) &&
|
|
|
|
cam_max.width >= width && cam_max.height >= height) {
|
|
|
|
|
|
|
|
width *= 2;
|
|
|
|
height *= 2;
|
2009-08-25 22:46:52 +08:00
|
|
|
cam_rect.width = width;
|
|
|
|
cam_rect.height = height;
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
/* We do not know what the camera is capable of, play safe */
|
|
|
|
if (cam_rect.left > target.left)
|
|
|
|
cam_rect.left = cam_max.left;
|
|
|
|
|
|
|
|
if (cam_rect.left + cam_rect.width < target.left + target.width)
|
|
|
|
cam_rect.width = target.left + target.width -
|
|
|
|
cam_rect.left;
|
|
|
|
|
|
|
|
if (cam_rect.top > target.top)
|
|
|
|
cam_rect.top = cam_max.top;
|
|
|
|
|
|
|
|
if (cam_rect.top + cam_rect.height < target.top + target.height)
|
|
|
|
cam_rect.height = target.top + target.height -
|
|
|
|
cam_rect.top;
|
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
if (cam_rect.width + cam_rect.left >
|
2009-08-25 22:46:54 +08:00
|
|
|
cam_max.width + cam_max.left)
|
|
|
|
cam_rect.left = max(cam_max.width + cam_max.left -
|
|
|
|
cam_rect.width, cam_max.left);
|
2009-08-25 22:46:52 +08:00
|
|
|
|
|
|
|
if (cam_rect.height + cam_rect.top >
|
2009-08-25 22:46:54 +08:00
|
|
|
cam_max.height + cam_max.top)
|
|
|
|
cam_rect.top = max(cam_max.height + cam_max.top -
|
|
|
|
cam_rect.height, cam_max.top);
|
2009-08-25 22:46:52 +08:00
|
|
|
|
|
|
|
ret = icd->ops->set_crop(icd, &cam_rect);
|
2009-08-25 22:46:54 +08:00
|
|
|
dev_dbg(&icd->dev, "Camera S_CROP %d for %ux%u@%u:%u\n",
|
2009-08-25 22:46:52 +08:00
|
|
|
ret, cam_rect.width, cam_rect.height,
|
|
|
|
cam_rect.left, cam_rect.top);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the camera failed to configure cropping, it should not modify the
|
|
|
|
* rectangle
|
|
|
|
*/
|
2009-08-25 22:46:54 +08:00
|
|
|
if ((ret < 0 && (is_smaller(&icd->rect_current, rect) ||
|
|
|
|
is_inside(&icd->rect_current, rect))) ||
|
|
|
|
is_smaller(&cam_rect, &target) || is_inside(&cam_rect, &target)) {
|
2009-08-25 22:46:52 +08:00
|
|
|
/*
|
|
|
|
* The camera failed to configure a suitable cropping,
|
|
|
|
* we cannot use the current rectangle, set to max
|
|
|
|
*/
|
2009-08-25 22:46:54 +08:00
|
|
|
cam_rect = cam_max;
|
2009-08-25 22:46:52 +08:00
|
|
|
ret = icd->ops->set_crop(icd, &cam_rect);
|
2009-08-25 22:46:54 +08:00
|
|
|
dev_dbg(&icd->dev, "Camera S_CROP %d for max %ux%u@%u:%u\n",
|
2009-08-25 22:46:52 +08:00
|
|
|
ret, cam_rect.width, cam_rect.height,
|
|
|
|
cam_rect.left, cam_rect.top);
|
|
|
|
if (ret < 0)
|
|
|
|
/* All failed, hopefully resume current capture */
|
|
|
|
goto resume_capture;
|
2009-08-25 22:46:54 +08:00
|
|
|
|
|
|
|
/* Finally, adjust the target rectangle */
|
|
|
|
if (target.width > cam_rect.width)
|
|
|
|
target.width = cam_rect.width;
|
|
|
|
if (target.height > cam_rect.height)
|
|
|
|
target.height = cam_rect.height;
|
|
|
|
if (target.left + target.width > cam_rect.left + cam_rect.width)
|
|
|
|
target.left = cam_rect.left + cam_rect.width -
|
|
|
|
target.width;
|
|
|
|
if (target.top + target.height > cam_rect.top + cam_rect.height)
|
|
|
|
target.top = cam_rect.top + cam_rect.height -
|
|
|
|
target.height;
|
2009-08-25 22:46:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* We now have a rectangle, larger than requested, let's crop */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We have to preserve camera rectangle between close() / open(),
|
|
|
|
* because soc-camera core calls .set_fmt() on each first open() with
|
|
|
|
* last before last close() _user_ rectangle, which can be different
|
|
|
|
* from camera rectangle.
|
|
|
|
*/
|
2009-08-25 22:46:54 +08:00
|
|
|
dev_dbg(&icd->dev,
|
|
|
|
"SH S_CROP from %ux%u@%u:%u to %ux%u@%u:%u, scale to %ux%u@%u:%u\n",
|
2009-08-25 22:46:52 +08:00
|
|
|
cam_rect.width, cam_rect.height, cam_rect.left, cam_rect.top,
|
2009-08-25 22:46:54 +08:00
|
|
|
target.width, target.height, target.left, target.top,
|
2009-08-25 22:46:52 +08:00
|
|
|
rect->width, rect->height, rect->left, rect->top);
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
ceu_set_rect:
|
|
|
|
cam->camera_rect = cam_rect;
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
rect->width = size_dst(target.width, hscale);
|
|
|
|
rect->left = size_dst(target.left, hscale);
|
|
|
|
rect->height = size_dst(target.height, vscale);
|
|
|
|
rect->top = size_dst(target.top, vscale);
|
|
|
|
|
|
|
|
sh_mobile_ceu_set_rect(icd, rect);
|
|
|
|
|
|
|
|
resume_capture:
|
2009-08-25 22:46:52 +08:00
|
|
|
/* Set CAMOR, CAPWR, CFSZR, take care of CDWDR */
|
|
|
|
if (pcdev->active)
|
|
|
|
capsr |= 1;
|
|
|
|
capture_restore(pcdev, capsr);
|
|
|
|
|
|
|
|
/* Even if only camera cropping succeeded */
|
|
|
|
return ret;
|
2009-03-13 17:08:20 +08:00
|
|
|
}
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
/* Similar to set_crop multistage iterative algorithm */
|
2008-12-01 20:45:21 +08:00
|
|
|
static int sh_mobile_ceu_set_fmt(struct soc_camera_device *icd,
|
2009-03-13 17:08:20 +08:00
|
|
|
struct v4l2_format *f)
|
2008-07-17 09:59:28 +08:00
|
|
|
{
|
2008-12-18 22:49:06 +08:00
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
2009-08-25 22:46:54 +08:00
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
2009-08-25 22:46:52 +08:00
|
|
|
struct sh_mobile_ceu_cam *cam = icd->host_priv;
|
|
|
|
struct v4l2_pix_format *pix = &f->fmt.pix;
|
|
|
|
__u32 pixfmt = pix->pixelformat;
|
2008-12-18 22:49:06 +08:00
|
|
|
const struct soc_camera_format_xlate *xlate;
|
2009-08-25 22:46:54 +08:00
|
|
|
unsigned int width = pix->width, height = pix->height, tmp_w, tmp_h;
|
|
|
|
u16 vscale, hscale;
|
|
|
|
int ret, is_interlaced;
|
|
|
|
|
|
|
|
switch (pix->field) {
|
|
|
|
case V4L2_FIELD_INTERLACED:
|
|
|
|
is_interlaced = 1;
|
|
|
|
break;
|
|
|
|
case V4L2_FIELD_ANY:
|
|
|
|
default:
|
|
|
|
pix->field = V4L2_FIELD_NONE;
|
|
|
|
/* fall-through */
|
|
|
|
case V4L2_FIELD_NONE:
|
|
|
|
is_interlaced = 0;
|
|
|
|
break;
|
|
|
|
}
|
2008-12-01 20:44:59 +08:00
|
|
|
|
2008-12-18 22:49:06 +08:00
|
|
|
xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
|
|
|
|
if (!xlate) {
|
2009-08-25 22:43:33 +08:00
|
|
|
dev_warn(ici->v4l2_dev.dev, "Format %x not found\n", pixfmt);
|
2008-12-18 22:49:06 +08:00
|
|
|
return -EINVAL;
|
2008-12-01 20:44:59 +08:00
|
|
|
}
|
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
pix->pixelformat = xlate->cam_fmt->fourcc;
|
2009-08-25 22:43:33 +08:00
|
|
|
ret = v4l2_device_call_until_err(&ici->v4l2_dev, (__u32)icd, video, s_fmt, f);
|
2009-08-25 22:46:52 +08:00
|
|
|
pix->pixelformat = pixfmt;
|
2009-08-25 22:46:54 +08:00
|
|
|
dev_dbg(&icd->dev, "Camera %d fmt %ux%u, requested %ux%u, max %ux%u\n",
|
|
|
|
ret, pix->width, pix->height, width, height,
|
|
|
|
icd->rect_max.width, icd->rect_max.height);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
switch (pixfmt) {
|
|
|
|
case V4L2_PIX_FMT_NV12:
|
|
|
|
case V4L2_PIX_FMT_NV21:
|
|
|
|
case V4L2_PIX_FMT_NV16:
|
|
|
|
case V4L2_PIX_FMT_NV61:
|
|
|
|
pcdev->image_mode = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pcdev->image_mode = 0;
|
2008-12-18 22:49:06 +08:00
|
|
|
}
|
2008-12-01 20:44:59 +08:00
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
if ((abs(width - pix->width) < 4 && abs(height - pix->height) < 4) ||
|
|
|
|
!pcdev->image_mode || is_interlaced) {
|
|
|
|
hscale = 0;
|
|
|
|
vscale = 0;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Camera set a format, but geometry is not precise, try to improve */
|
|
|
|
/*
|
|
|
|
* FIXME: when soc-camera is converted to implement traditional S_FMT
|
|
|
|
* and S_CROP semantics, replace CEU limits with camera maxima
|
|
|
|
*/
|
|
|
|
tmp_w = pix->width;
|
|
|
|
tmp_h = pix->height;
|
|
|
|
while ((width > tmp_w || height > tmp_h) &&
|
|
|
|
tmp_w < 2560 && tmp_h < 1920) {
|
|
|
|
tmp_w = min(2 * tmp_w, (__u32)2560);
|
|
|
|
tmp_h = min(2 * tmp_h, (__u32)1920);
|
|
|
|
pix->width = tmp_w;
|
|
|
|
pix->height = tmp_h;
|
|
|
|
pix->pixelformat = xlate->cam_fmt->fourcc;
|
|
|
|
ret = v4l2_device_call_until_err(&ici->v4l2_dev, (__u32)icd,
|
|
|
|
video, s_fmt, f);
|
|
|
|
pix->pixelformat = pixfmt;
|
|
|
|
dev_dbg(&icd->dev, "Camera scaled to %ux%u\n",
|
|
|
|
pix->width, pix->height);
|
|
|
|
if (ret < 0) {
|
|
|
|
/* This shouldn't happen */
|
|
|
|
dev_err(&icd->dev, "Client failed to set format: %d\n",
|
|
|
|
ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We cannot scale up */
|
|
|
|
if (width > pix->width)
|
|
|
|
width = pix->width;
|
|
|
|
|
|
|
|
if (height > pix->height)
|
|
|
|
height = pix->height;
|
|
|
|
|
|
|
|
/* Let's rock: scale pix->{width x height} down to width x height */
|
|
|
|
hscale = calc_scale(pix->width, &width);
|
|
|
|
vscale = calc_scale(pix->height, &height);
|
|
|
|
|
|
|
|
dev_dbg(&icd->dev, "W: %u : 0x%x = %u, H: %u : 0x%x = %u\n",
|
|
|
|
pix->width, hscale, width, pix->height, vscale, height);
|
|
|
|
|
|
|
|
out:
|
|
|
|
pcdev->cflcr = hscale | (vscale << 16);
|
|
|
|
|
|
|
|
icd->buswidth = xlate->buswidth;
|
|
|
|
icd->current_fmt = xlate->host_fmt;
|
|
|
|
cam->camera_fmt = xlate->cam_fmt;
|
|
|
|
cam->camera_rect.width = pix->width;
|
|
|
|
cam->camera_rect.height = pix->height;
|
|
|
|
|
|
|
|
icd->rect_max.left = size_dst(cam->camera_max.left, hscale);
|
|
|
|
icd->rect_max.width = size_dst(cam->camera_max.width, hscale);
|
|
|
|
icd->rect_max.top = size_dst(cam->camera_max.top, vscale);
|
|
|
|
icd->rect_max.height = size_dst(cam->camera_max.height, vscale);
|
|
|
|
|
|
|
|
icd->rect_current.left = icd->rect_max.left;
|
|
|
|
icd->rect_current.top = icd->rect_max.top;
|
|
|
|
|
|
|
|
pcdev->is_interlaced = is_interlaced;
|
|
|
|
|
|
|
|
pix->width = width;
|
|
|
|
pix->height = height;
|
|
|
|
|
|
|
|
return 0;
|
2008-07-17 09:59:28 +08:00
|
|
|
}
|
|
|
|
|
2008-12-01 20:45:21 +08:00
|
|
|
static int sh_mobile_ceu_try_fmt(struct soc_camera_device *icd,
|
|
|
|
struct v4l2_format *f)
|
2008-07-17 09:59:28 +08:00
|
|
|
{
|
2008-12-18 22:49:06 +08:00
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
const struct soc_camera_format_xlate *xlate;
|
2009-08-25 22:46:52 +08:00
|
|
|
struct v4l2_pix_format *pix = &f->fmt.pix;
|
|
|
|
__u32 pixfmt = pix->pixelformat;
|
2009-08-25 22:46:54 +08:00
|
|
|
int width, height;
|
2008-12-18 23:51:21 +08:00
|
|
|
int ret;
|
2008-12-01 20:44:53 +08:00
|
|
|
|
2008-12-18 22:49:06 +08:00
|
|
|
xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
|
|
|
|
if (!xlate) {
|
2009-08-25 22:43:33 +08:00
|
|
|
dev_warn(ici->v4l2_dev.dev, "Format %x not found\n", pixfmt);
|
2008-12-01 20:44:59 +08:00
|
|
|
return -EINVAL;
|
2008-12-18 22:49:06 +08:00
|
|
|
}
|
2008-12-01 20:44:59 +08:00
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
/* FIXME: calculate using depth and bus width */
|
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
v4l_bound_align_image(&pix->width, 2, 2560, 1,
|
|
|
|
&pix->height, 4, 1920, 2, 0);
|
2008-07-17 09:59:28 +08:00
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
width = pix->width;
|
|
|
|
height = pix->height;
|
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
pix->bytesperline = pix->width *
|
2008-12-18 22:49:06 +08:00
|
|
|
DIV_ROUND_UP(xlate->host_fmt->depth, 8);
|
2009-08-25 22:46:52 +08:00
|
|
|
pix->sizeimage = pix->height * pix->bytesperline;
|
2008-12-01 20:44:59 +08:00
|
|
|
|
2009-08-25 22:46:52 +08:00
|
|
|
pix->pixelformat = xlate->cam_fmt->fourcc;
|
2009-08-25 22:43:33 +08:00
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
/* limit to sensor capabilities */
|
2009-08-25 22:46:54 +08:00
|
|
|
ret = v4l2_device_call_until_err(&ici->v4l2_dev, (__u32)icd, video,
|
|
|
|
try_fmt, f);
|
2009-08-25 22:46:52 +08:00
|
|
|
pix->pixelformat = pixfmt;
|
2008-12-18 23:51:21 +08:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2009-08-25 22:46:54 +08:00
|
|
|
switch (pixfmt) {
|
|
|
|
case V4L2_PIX_FMT_NV12:
|
|
|
|
case V4L2_PIX_FMT_NV21:
|
|
|
|
case V4L2_PIX_FMT_NV16:
|
|
|
|
case V4L2_PIX_FMT_NV61:
|
|
|
|
/* FIXME: check against rect_max after converting soc-camera */
|
|
|
|
/* We can scale precisely, need a bigger image from camera */
|
|
|
|
if (pix->width < width || pix->height < height) {
|
|
|
|
int tmp_w = pix->width, tmp_h = pix->height;
|
|
|
|
pix->width = 2560;
|
|
|
|
pix->height = 1920;
|
|
|
|
ret = v4l2_device_call_until_err(&ici->v4l2_dev,
|
|
|
|
(__u32)icd, video,
|
|
|
|
try_fmt, f);
|
|
|
|
if (ret < 0) {
|
|
|
|
/* Shouldn't actually happen... */
|
|
|
|
dev_err(&icd->dev,
|
|
|
|
"FIXME: try_fmt() returned %d\n", ret);
|
|
|
|
pix->width = tmp_w;
|
|
|
|
pix->height = tmp_h;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pix->width > width)
|
|
|
|
pix->width = width;
|
|
|
|
if (pix->height > height)
|
|
|
|
pix->height = height;
|
2008-12-18 23:51:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2008-07-17 09:59:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int sh_mobile_ceu_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 sh_mobile_ceu_buffer *buf;
|
|
|
|
|
|
|
|
buf = container_of(icf->vb_vidq.bufs[i],
|
|
|
|
struct sh_mobile_ceu_buffer, vb);
|
|
|
|
INIT_LIST_HEAD(&buf->vb.queue);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int sh_mobile_ceu_poll(struct file *file, poll_table *pt)
|
|
|
|
{
|
|
|
|
struct soc_camera_file *icf = file->private_data;
|
|
|
|
struct sh_mobile_ceu_buffer *buf;
|
|
|
|
|
|
|
|
buf = list_entry(icf->vb_vidq.stream.next,
|
|
|
|
struct sh_mobile_ceu_buffer, 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 int sh_mobile_ceu_querycap(struct soc_camera_host *ici,
|
|
|
|
struct v4l2_capability *cap)
|
|
|
|
{
|
|
|
|
strlcpy(cap->card, "SuperH_Mobile_CEU", sizeof(cap->card));
|
|
|
|
cap->version = KERNEL_VERSION(0, 0, 5);
|
|
|
|
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sh_mobile_ceu_init_videobuf(struct videobuf_queue *q,
|
|
|
|
struct soc_camera_device *icd)
|
|
|
|
{
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
|
|
|
|
|
|
|
videobuf_queue_dma_contig_init(q,
|
|
|
|
&sh_mobile_ceu_videobuf_ops,
|
2009-08-25 22:43:33 +08:00
|
|
|
ici->v4l2_dev.dev, &pcdev->lock,
|
2008-07-17 09:59:28 +08:00
|
|
|
V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
2009-03-13 17:08:20 +08:00
|
|
|
pcdev->is_interlaced ?
|
|
|
|
V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE,
|
2008-07-17 09:59:28 +08:00
|
|
|
sizeof(struct sh_mobile_ceu_buffer),
|
|
|
|
icd);
|
|
|
|
}
|
|
|
|
|
2009-08-25 22:44:15 +08:00
|
|
|
static int sh_mobile_ceu_get_ctrl(struct soc_camera_device *icd,
|
|
|
|
struct v4l2_control *ctrl)
|
|
|
|
{
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
switch (ctrl->id) {
|
|
|
|
case V4L2_CID_SHARPNESS:
|
|
|
|
val = ceu_read(pcdev, CLFCR);
|
|
|
|
ctrl->value = val ^ 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return -ENOIOCTLCMD;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sh_mobile_ceu_set_ctrl(struct soc_camera_device *icd,
|
|
|
|
struct v4l2_control *ctrl)
|
|
|
|
{
|
|
|
|
struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = ici->priv;
|
|
|
|
|
|
|
|
switch (ctrl->id) {
|
|
|
|
case V4L2_CID_SHARPNESS:
|
|
|
|
switch (icd->current_fmt->fourcc) {
|
|
|
|
case V4L2_PIX_FMT_NV12:
|
|
|
|
case V4L2_PIX_FMT_NV21:
|
|
|
|
case V4L2_PIX_FMT_NV16:
|
|
|
|
case V4L2_PIX_FMT_NV61:
|
|
|
|
ceu_write(pcdev, CLFCR, !ctrl->value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
return -ENOIOCTLCMD;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct v4l2_queryctrl sh_mobile_ceu_controls[] = {
|
|
|
|
{
|
|
|
|
.id = V4L2_CID_SHARPNESS,
|
|
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
|
|
.name = "Low-pass filter",
|
|
|
|
.minimum = 0,
|
|
|
|
.maximum = 1,
|
|
|
|
.step = 1,
|
|
|
|
.default_value = 0,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
static struct soc_camera_host_ops sh_mobile_ceu_host_ops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.add = sh_mobile_ceu_add_device,
|
|
|
|
.remove = sh_mobile_ceu_remove_device,
|
2008-12-18 22:49:06 +08:00
|
|
|
.get_formats = sh_mobile_ceu_get_formats,
|
2009-08-25 22:46:52 +08:00
|
|
|
.put_formats = sh_mobile_ceu_put_formats,
|
2009-03-13 17:08:20 +08:00
|
|
|
.set_crop = sh_mobile_ceu_set_crop,
|
2008-12-01 20:45:21 +08:00
|
|
|
.set_fmt = sh_mobile_ceu_set_fmt,
|
|
|
|
.try_fmt = sh_mobile_ceu_try_fmt,
|
2009-08-25 22:44:15 +08:00
|
|
|
.set_ctrl = sh_mobile_ceu_set_ctrl,
|
|
|
|
.get_ctrl = sh_mobile_ceu_get_ctrl,
|
2008-07-17 09:59:28 +08:00
|
|
|
.reqbufs = sh_mobile_ceu_reqbufs,
|
|
|
|
.poll = sh_mobile_ceu_poll,
|
|
|
|
.querycap = sh_mobile_ceu_querycap,
|
|
|
|
.set_bus_param = sh_mobile_ceu_set_bus_param,
|
|
|
|
.init_videobuf = sh_mobile_ceu_init_videobuf,
|
2009-08-25 22:44:15 +08:00
|
|
|
.controls = sh_mobile_ceu_controls,
|
|
|
|
.num_controls = ARRAY_SIZE(sh_mobile_ceu_controls),
|
2008-07-17 09:59:28 +08:00
|
|
|
};
|
|
|
|
|
2009-08-25 22:43:33 +08:00
|
|
|
static int __devinit sh_mobile_ceu_probe(struct platform_device *pdev)
|
2008-07-17 09:59:28 +08:00
|
|
|
{
|
|
|
|
struct sh_mobile_ceu_dev *pcdev;
|
|
|
|
struct resource *res;
|
|
|
|
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 || !irq) {
|
|
|
|
dev_err(&pdev->dev, "Not enough CEU platform resources.\n");
|
|
|
|
err = -ENODEV;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL);
|
|
|
|
if (!pcdev) {
|
|
|
|
dev_err(&pdev->dev, "Could not allocate pcdev\n");
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&pcdev->capture);
|
|
|
|
spin_lock_init(&pcdev->lock);
|
|
|
|
|
|
|
|
pcdev->pdata = pdev->dev.platform_data;
|
|
|
|
if (!pcdev->pdata) {
|
|
|
|
err = -EINVAL;
|
|
|
|
dev_err(&pdev->dev, "CEU platform data not set.\n");
|
|
|
|
goto exit_kfree;
|
|
|
|
}
|
|
|
|
|
2009-04-24 23:55:18 +08:00
|
|
|
base = ioremap_nocache(res->start, resource_size(res));
|
2008-07-17 09:59:28 +08:00
|
|
|
if (!base) {
|
|
|
|
err = -ENXIO;
|
|
|
|
dev_err(&pdev->dev, "Unable to ioremap CEU registers.\n");
|
|
|
|
goto exit_kfree;
|
|
|
|
}
|
|
|
|
|
|
|
|
pcdev->irq = irq;
|
|
|
|
pcdev->base = base;
|
|
|
|
pcdev->video_limit = 0; /* only enabled if second resource exists */
|
|
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
|
|
if (res) {
|
|
|
|
err = dma_declare_coherent_memory(&pdev->dev, res->start,
|
|
|
|
res->start,
|
2009-04-24 23:55:18 +08:00
|
|
|
resource_size(res),
|
2008-07-17 09:59:28 +08:00
|
|
|
DMA_MEMORY_MAP |
|
|
|
|
DMA_MEMORY_EXCLUSIVE);
|
|
|
|
if (!err) {
|
|
|
|
dev_err(&pdev->dev, "Unable to declare CEU memory.\n");
|
|
|
|
err = -ENXIO;
|
|
|
|
goto exit_iounmap;
|
|
|
|
}
|
|
|
|
|
2009-04-24 23:55:18 +08:00
|
|
|
pcdev->video_limit = resource_size(res);
|
2008-07-17 09:59:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* request irq */
|
|
|
|
err = request_irq(pcdev->irq, sh_mobile_ceu_irq, IRQF_DISABLED,
|
V4L/DVB (9521): V4L: struct device - replace bus_id with dev_name(), dev_set_name()
This patch is part of a larger patch series which will remove
the "char bus_id[20]" name string from struct device. The device
name is managed in the kobject anyway, and without any size
limitation, and just needlessly copied into "struct device".
To set and read the device name dev_name(dev) and dev_set_name(dev)
must be used. If your code uses static kobjects, which it shouldn't
do, "const char *init_name" can be used to statically provide the
name the registered device should have. At registration time, the
init_name field is cleared, to enforce the use of dev_name(dev) to
access the device name at a later time.
We need to get rid of all occurrences of bus_id in the entire tree
to be able to enable the new interface. Please apply this patch,
and possibly convert any remaining remaining occurrences of bus_id.
We want to submit a patch to -next, which will remove bus_id from
"struct device", to find the remaining pieces to convert, and finally
switch over to the new api, which will remove the 20 bytes array
and does no longer have a size limitation.
Thanks,
Kay
Signed-off-by: Kay Sievers <kay.sievers@vrfy.org>
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
2008-10-30 11:51:46 +08:00
|
|
|
dev_name(&pdev->dev), pcdev);
|
2008-07-17 09:59:28 +08:00
|
|
|
if (err) {
|
|
|
|
dev_err(&pdev->dev, "Unable to register CEU interrupt.\n");
|
|
|
|
goto exit_release_mem;
|
|
|
|
}
|
|
|
|
|
2009-08-14 18:49:17 +08:00
|
|
|
pm_suspend_ignore_children(&pdev->dev, true);
|
|
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
pm_runtime_resume(&pdev->dev);
|
2008-10-31 19:21:44 +08:00
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
pcdev->ici.priv = pcdev;
|
2009-08-25 22:43:33 +08:00
|
|
|
pcdev->ici.v4l2_dev.dev = &pdev->dev;
|
2008-07-17 09:59:28 +08:00
|
|
|
pcdev->ici.nr = pdev->id;
|
V4L/DVB (9521): V4L: struct device - replace bus_id with dev_name(), dev_set_name()
This patch is part of a larger patch series which will remove
the "char bus_id[20]" name string from struct device. The device
name is managed in the kobject anyway, and without any size
limitation, and just needlessly copied into "struct device".
To set and read the device name dev_name(dev) and dev_set_name(dev)
must be used. If your code uses static kobjects, which it shouldn't
do, "const char *init_name" can be used to statically provide the
name the registered device should have. At registration time, the
init_name field is cleared, to enforce the use of dev_name(dev) to
access the device name at a later time.
We need to get rid of all occurrences of bus_id in the entire tree
to be able to enable the new interface. Please apply this patch,
and possibly convert any remaining remaining occurrences of bus_id.
We want to submit a patch to -next, which will remove bus_id from
"struct device", to find the remaining pieces to convert, and finally
switch over to the new api, which will remove the 20 bytes array
and does no longer have a size limitation.
Thanks,
Kay
Signed-off-by: Kay Sievers <kay.sievers@vrfy.org>
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
2008-10-30 11:51:46 +08:00
|
|
|
pcdev->ici.drv_name = dev_name(&pdev->dev);
|
|
|
|
pcdev->ici.ops = &sh_mobile_ceu_host_ops;
|
2008-07-17 09:59:28 +08:00
|
|
|
|
|
|
|
err = soc_camera_host_register(&pcdev->ici);
|
|
|
|
if (err)
|
2009-08-14 18:49:17 +08:00
|
|
|
goto exit_free_irq;
|
2008-07-17 09:59:28 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
exit_free_irq:
|
|
|
|
free_irq(pcdev->irq, pcdev);
|
|
|
|
exit_release_mem:
|
|
|
|
if (platform_get_resource(pdev, IORESOURCE_MEM, 1))
|
|
|
|
dma_release_declared_memory(&pdev->dev);
|
|
|
|
exit_iounmap:
|
|
|
|
iounmap(base);
|
|
|
|
exit_kfree:
|
|
|
|
kfree(pcdev);
|
|
|
|
exit:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2009-08-25 22:43:33 +08:00
|
|
|
static int __devexit sh_mobile_ceu_remove(struct platform_device *pdev)
|
2008-07-17 09:59:28 +08:00
|
|
|
{
|
2009-04-24 23:55:48 +08:00
|
|
|
struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev);
|
|
|
|
struct sh_mobile_ceu_dev *pcdev = container_of(soc_host,
|
|
|
|
struct sh_mobile_ceu_dev, ici);
|
2008-07-17 09:59:28 +08:00
|
|
|
|
2009-04-24 23:55:48 +08:00
|
|
|
soc_camera_host_unregister(soc_host);
|
2008-07-17 09:59:28 +08:00
|
|
|
free_irq(pcdev->irq, pcdev);
|
|
|
|
if (platform_get_resource(pdev, IORESOURCE_MEM, 1))
|
|
|
|
dma_release_declared_memory(&pdev->dev);
|
|
|
|
iounmap(pcdev->base);
|
|
|
|
kfree(pcdev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-08-14 18:49:17 +08:00
|
|
|
static int sh_mobile_ceu_runtime_nop(struct device *dev)
|
|
|
|
{
|
|
|
|
/* Runtime PM callback shared between ->runtime_suspend()
|
|
|
|
* and ->runtime_resume(). Simply returns success.
|
|
|
|
*
|
|
|
|
* This driver re-initializes all registers after
|
|
|
|
* pm_runtime_get_sync() anyway so there is no need
|
|
|
|
* to save and restore registers here.
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct dev_pm_ops sh_mobile_ceu_dev_pm_ops = {
|
|
|
|
.runtime_suspend = sh_mobile_ceu_runtime_nop,
|
|
|
|
.runtime_resume = sh_mobile_ceu_runtime_nop,
|
|
|
|
};
|
|
|
|
|
2008-07-17 09:59:28 +08:00
|
|
|
static struct platform_driver sh_mobile_ceu_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "sh_mobile_ceu",
|
2009-08-14 18:49:17 +08:00
|
|
|
.pm = &sh_mobile_ceu_dev_pm_ops,
|
2008-07-17 09:59:28 +08:00
|
|
|
},
|
|
|
|
.probe = sh_mobile_ceu_probe,
|
2009-08-25 22:43:33 +08:00
|
|
|
.remove = __exit_p(sh_mobile_ceu_remove),
|
2008-07-17 09:59:28 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int __init sh_mobile_ceu_init(void)
|
|
|
|
{
|
|
|
|
return platform_driver_register(&sh_mobile_ceu_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit sh_mobile_ceu_exit(void)
|
|
|
|
{
|
2008-08-02 06:48:51 +08:00
|
|
|
platform_driver_unregister(&sh_mobile_ceu_driver);
|
2008-07-17 09:59:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(sh_mobile_ceu_init);
|
|
|
|
module_exit(sh_mobile_ceu_exit);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("SuperH Mobile CEU driver");
|
|
|
|
MODULE_AUTHOR("Magnus Damm");
|
|
|
|
MODULE_LICENSE("GPL");
|
2009-08-25 22:28:22 +08:00
|
|
|
MODULE_ALIAS("platform:sh_mobile_ceu");
|