/*
* Copyright (c) 2011 Atmel Corporation
* Josh Wu, <josh.wu@atmel.com>
*
* Based on previous work by Lars Haring, <lars.haring@atmel.com>
* and Sedji Gaouaou
* Based on the bttv driver for Bt848 with respective copyright holders
*
* 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/completion.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <media/atmel-isi.h>
#include <media/soc_camera.h>
#include <media/soc_mediabus.h>
#include <media/videobuf2-dma-contig.h>
#define MAX_BUFFER_NUM 32
#define MAX_SUPPORT_WIDTH 2048
#define MAX_SUPPORT_HEIGHT 2048
#define VID_LIMIT_BYTES (16 * 1024 * 1024)
#define MIN_FRAME_RATE 15
#define FRAME_INTERVAL_MILLI_SEC (1000 / MIN_FRAME_RATE)
/* Frame buffer descriptor */
struct fbd {
/* Physical address of the frame buffer */
u32 fb_address;
/* DMA Control Register(only in HISI2) */
u32 dma_ctrl;
/* Physical address of the next fbd */
u32 next_fbd_address;
};
static void set_dma_ctrl(struct fbd *fb_desc, u32 ctrl)
{
fb_desc->dma_ctrl = ctrl;
}
struct isi_dma_desc {
struct list_head list;
struct fbd *p_fbd;
u32 fbd_phys;
};
/* Frame buffer data */
struct frame_buffer {
struct vb2_buffer vb;
struct isi_dma_desc *p_dma_desc;
struct list_head list;
};
struct atmel_isi {
/* Protects the access of variables shared with the ISR */
spinlock_t lock;
void __iomem *regs;
int sequence;
struct vb2_alloc_ctx *alloc_ctx;
/* Allocate descriptors for dma buffer use */
struct fbd *p_fb_descriptors;
u32 fb_descriptors_phys;
struct list_head dma_desc_head;
struct isi_dma_desc dma_desc[MAX_BUFFER_NUM];
struct completion complete;
/* ISI peripherial clock */
struct clk *pclk;
/* ISI_MCK, feed to camera sensor to generate pixel clock */
struct clk *mck;
unsigned int irq;
struct isi_platform_data *pdata;
u16 width_flags; /* max 12 bits */
struct list_head video_buffer_list;
struct frame_buffer *active;
struct soc_camera_host soc_host;
};
static void isi_writel(struct atmel_isi *isi, u32 reg, u32 val)
{
writel(val, isi->regs + reg);
}
static u32 isi_readl(struct atmel_isi *isi, u32 reg)
{
return readl(isi->regs + reg);
}
static int configure_geometry(struct atmel_isi *isi, u32 width,
u32 height, enum v4l2_mbus_pixelcode code)
{
u32 cfg2, cr;
switch (code) {
/* YUV, including grey */
case V4L2_MBUS_FMT_Y8_1X8:
cr = ISI_CFG2_GRAYSCALE;
break;
case V4L2_MBUS_FMT_VYUY8_2X8:
cr = ISI_CFG2_YCC_SWAP_MODE_3;
break;
case V4L2_MBUS_FMT_UYVY8_2X8:
cr = ISI_CFG2_YCC_SWAP_MODE_2;
break;
case V4L2_MBUS_FMT_YVYU8_2X8:
cr = ISI_CFG2_YCC_SWAP_MODE_1;
break;
case V4L2_MBUS_FMT_YUYV8_2X8:
cr = ISI_CFG2_YCC_SWAP_DEFAULT;
break;
/* RGB, TODO */
default:
return -EINVAL;
}
isi_writel(isi, ISI_CTRL, ISI_CTRL_DIS);
cfg2 = isi_readl(isi, ISI_CFG2);
/* Set YCC swap mode */
cfg2 &= ~ISI_CFG2_YCC_SWAP_MODE_MASK;
cfg2 |= cr;
/* Set width */
cfg2 &= ~(ISI_CFG2_IM_HSIZE_MASK);
cfg2 |= ((width - 1) << ISI_CFG2_IM_HSIZE_OFFSET) &
ISI_CFG2_IM_HSIZE_MASK;
/* Set height */
cfg2 &= ~(ISI_CFG2_IM_VSIZE_MASK);
cfg2 |= ((height - 1) << ISI_CFG2_IM_VSIZE_OFFSET)
& ISI_CFG2_IM_VSIZE_MASK;
isi_writel(isi, ISI_CFG2, cfg2);
return 0;
}
static irqreturn_t atmel_isi_handle_streaming(struct atmel_isi *isi)
{
if (isi->active) {
struct vb2_buffer *vb = &isi->active->vb;
struct frame_buffer *buf = isi->active;
list_del_init(&buf->list);
v4l2_get_timestamp(&vb->v4l2_buf.timestamp);
vb->v4l2_buf.sequence = isi->sequence++;
vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
}
if (list_empty(&isi->video_buffer_list)) {
isi->active = NULL;
} else {
/* start next dma frame. */
isi->active