/*
* 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/v4l2-of.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)
#define ISI_DEFAULT_MCLK_FREQ 25000000
/* 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;
dma_addr_t 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;
dma_addr_t 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, u32 code)
{
u32 cfg2, cr;
switch (code) {
/* YUV, including grey */
case MEDIA_BUS_FMT_Y8_1X8:
cr = ISI_CFG2_GRAYSCALE;
break;
case MEDIA_BUS_FMT_VYUY8_2X8:
cr = ISI_CFG2_YCC_SWAP_MODE_3;
break;
case MEDIA_BUS_FMT_UYVY8_2X8:
cr = ISI_CFG2_YCC_SWAP_MODE_2;
break;
case MEDIA_BUS_FMT_YVYU8_2X8:
cr = ISI_CFG2_YCC_SWAP_MODE_1;
break;
case MEDIA_BUS_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)
{