/* * linux/drivers/misc/tcsm.c * * Virtual device driver with tricky appoach to manage TCSM * * Copyright (C) 2006 Ingenic Semiconductor Inc. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "jz_cim.h" #include "jz_sensor.h" MODULE_AUTHOR("Lemon Liu"); MODULE_DESCRIPTION("Ingenic Camera driver"); MODULE_LICENSE("GPL"); //#define CIM_DEBUG #undef CIM_DEBUG #ifdef CIM_DEBUG #define dprintk(x...) printk(x) #else #define dprintk(x...) #endif #define CIM_NAME "cim" /* * CIM DMA descriptor */ struct cim_desc { u32 nextdesc; /* Physical address of next desc */ u32 framebuf; /* Physical address of frame buffer */ u32 frameid; /* Frame ID */ u32 dmacmd; /* DMA command */ }; /* * CIM device structure */ struct cim_device { cim_config_t cim_cfg; preview_param_t view_par; picture_param_t pic_par; unsigned char *mem_base; unsigned char *frm_buf; /*current trans buffer pointer*/ unsigned char *jpeg_buf; /* buf for jpeg data */ unsigned int mem_size; wait_queue_head_t wait_queue; }; static struct cim_device jz_cim_info = { #ifdef CONFIG_OV3640 .cim_cfg = { .cfg = CIM_CFG_PACK_4 | CIM_CFG_DSM_GCM | CIM_CFG_VSP | CIM_CFG_PCP | CIM_CFG_BYPASS | CIM_CFG_DMA_BURST_INCR8 | CIM_CTRL_FAST_MODE, .ctrl = CIM_CTRL_FRC_1 | CIM_CTRL_RXF_TRIG_4, .mclk = 24000000, }, #elif defined(CONFIG_OV2640) .cim_cfg = { .cfg = CIM_CFG_PACK_4 | CIM_CFG_DSM_GCM | CIM_CFG_VSP | CIM_CFG_BYPASS, .ctrl = CIM_CTRL_FRC_1 | CIM_CTRL_RXF_TRIG_4, .mclk = 24000000, }, #elif defined(CONFIG_OV9650) .cim_cfg = { .cfg = CIM_CFG_PACK_4 | CIM_CFG_DSM_GCM | CIM_CFG_VSP | CIM_CFG_BYPASS, .ctrl = CIM_CTRL_FRC_1 | CIM_CTRL_RXF_TRIG_4, .mclk = 24000000, }, #else /* CONFIG-SENSOR*/ #error "Define Sensor first..." #endif .view_par = {320, 240, 16, "yuv422"}, .pic_par = {640, 480, 16, "yuv422",}, }; static int cim_inited = 0; static int jpeg_reading_flag; static int cim_tran_buf_id; /*cim dma current transfer buffer ID*/ static int data_ready_buf_id; /*data ready for yuv convert buffer ID*/ static struct cim_desc cim_frame_desc[CIM_BUF_NUM] __attribute__ ((aligned (16))); static struct cim_desc cim_jpeg_desc __attribute__ ((aligned (16))); static struct cim_desc cim_test_jpeg_desc __attribute__ ((aligned (16))); static struct cim_device *jz_cim = &jz_cim_info; /*========================================================================== * CIM Module operations *========================================================================*/ /* * Init CIM module */ static void cim_print_regs(void) { printk("REG_CIM_CFG \t= \t0x%08x\n", REG_CIM_CFG); printk("REG_CIM_CTRL \t= \t0x%08x\n", REG_CIM_CTRL); printk("REG_CIM_STATE \t= \t0x%08x\n", REG_CIM_STATE); printk("REG_CIM_IID \t= \t0x%08x\n", REG_CIM_IID); printk("REG_CIM_DA \t= \t0x%08x\n", REG_CIM_DA); printk("REG_CIM_FA \t= \t0x%08x\n", REG_CIM_FA); printk("REG_CIM_FID \t= \t0x%08x\n", REG_CIM_FID); printk("REG_CIM_CMD \t= \t0x%08x\n", REG_CIM_CMD); printk("REG_CIM_SIZE \t= \t0x%08x\n", REG_CIM_SIZE); printk("REG_CIM_OFFSET \t= \t0x%08x\n", REG_CIM_OFFSET); } static void cim_config(cim_config_t *c) { REG_CIM_CFG = c->cfg; REG_CIM_CTRL = c->ctrl; REG_CIM_SIZE = c->size; REG_CIM_OFFSET = c->offs; #ifndef CIM_EXTCLK /* Set the master clock output, If use pll clock, enable it */ __cim_set_master_clk(__cpm_get_hclk(), c->mclk); #endif /* Enable sof, eof and stop interrupts*/ __cim_enable_eof_intr(); // __cim_enable_stop_intr(); #if defined(CONFIG_SOC_JZ4750) __cim_enable_rxfifo_overflow_intr(); #endif } /* * CIM start/stop operations */ #if 0 static int cim_start_dma(void) { if (start_inited == 0) { __cim_disable(); __cim_set_da(virt_to_phys(jz_cim->frame_desc)); __cim_clear_state(); // clear state register __cim_reset_rxfifo(); // resetting rxfifo __cim_unreset_rxfifo(); start_inited = 1; __cim_enable_dma(); // enable dma __cim_enable(); } interruptible_sleep_on(&jz_cim->wait_queue); frm_buf = (unsigned char *)cim_frame_desc[data_ready_buf_id].framebuf; return 0; } #endif inline static int get_ready_buf_id(void) { interruptible_sleep_on(&jz_cim->wait_queue); return data_ready_buf_id; } inline void cim_start(void) { dprintk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__); cim_tran_buf_id = 0; data_ready_buf_id = 0; __cim_disable(); __cim_set_da(virt_to_phys(&cim_frame_desc[cim_tran_buf_id])); __cim_clear_state(); // clear state register __cim_reset_rxfifo(); // resetting rxfifo __cim_unreset_rxfifo(); __cim_enable_dma(); // enable dma __cim_enable(); } inline static void cim_stop(void) { __cim_disable(); __cim_disable_dma(); __cim_clear_state(); } static int cim_device_init(void) { cim_config(&jz_cim->cim_cfg); __sensor_gpio_init(); return 0; } static int cim_snapshot(int mode) { int i; dprintk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__); jpeg_reading_flag = 1; for(i = 0; i < INVALID_PIC_BUF; i++) { __cim_disable(); __cim_set_da(virt_to_phys((&cim_test_jpeg_desc))); __cim_clear_state(); // clear state register __cim_reset_rxfifo(); // resetting rxfifo __cim_unreset_rxfifo(); __cim_enable_dma(); // enable dma __cim_enable(); interruptible_sleep_on(&jz_cim->wait_queue); } __cim_disable(); __cim_set_da(virt_to_phys(&cim_jpeg_desc)); __cim_clear_state(); // clear state register __cim_reset_rxfifo(); // resetting rxfifo __cim_unreset_rxfifo(); __cim_enable_dma(); // enable dma __cim_enable(); interruptible_sleep_on(&jz_cim->wait_queue); jpeg_reading_flag = 0; return 0; } /*========================================================================== * Framebuffer allocation and destroy *========================================================================*/ static struct cim_desc *init_cim_desc_list(void * base) { int i; unsigned char *p_buf; struct cim_desc *p_desc; struct cim_desc *desc_list_head __attribute__ ((aligned (16))); struct cim_desc *desc_list_tail __attribute__ ((aligned (16))); int frmsize = (((jz_cim->view_par.width * jz_cim->view_par.height * jz_cim->view_par.bpp + 7) >> 3) + 3) >> 2; /* word aligned */ desc_list_head = desc_list_tail = NULL; for (i = 0; i < CIM_BUF_NUM; i++) { p_desc = &cim_frame_desc[i]; p_buf = (void*)((((unsigned int)base + (MAX_PRE_SIZE * i)) >> 3) << 3); if (desc_list_head == NULL) { dprintk("Page_list_head\n"); desc_list_head = p_desc; } else desc_list_tail->nextdesc = virt_to_phys(p_desc); jz_cim->view_par.framebuf[i] = virt_to_phys(p_buf); desc_list_tail = p_desc; desc_list_tail->framebuf = virt_to_phys(p_buf); dprintk("framebuf addr is 0x%08x\n", (u32)desc_list_tail->framebuf); dprintk("frame_desc addr is 0x%08x\n",(u32)virt_to_phys(desc_list_tail)); desc_list_tail->frameid = i; desc_list_tail->dmacmd = frmsize; #if defined(CONFIG_SOC_JZ4750) desc_list_tail->dmacmd |= CIM_CMD_EOFINT; #else desc_list_tail->dmacmd |= (CIM_CMD_EOFINT | CIM_CMD_OFRCV); #endif dprintk("framedesc\t= 0x%08x\n",(unsigned int)virt_to_phys(desc_list_tail)); dprintk("framebuf \t= 0x%08x\n", (unsigned int)desc_list_tail->framebuf); dprintk("frameid \t= 0x%08x\n", (unsigned int)desc_list_tail->frameid); dprintk("dmacmd \t= 0x%08x\n", (unsigned int)desc_list_tail->dmacmd); dprintk("the desc_list_tail->dmacmd is 0x%08x\n", desc_list_tail->dmacmd); } desc_list_tail->nextdesc = virt_to_phys(desc_list_head); for (i = 0; i < CIM_BUF_NUM; i++) dma_cache_wback((unsigned long)(&cim_frame_desc[i]), sizeof(struct cim_desc)); /* prepare the jpeg descriptor */ p_buf = (void*)((((unsigned int)base + (MAX_PRE_SIZE * CIM_BUF_NUM)) >> 3) << 3); cim_test_jpeg_desc.framebuf = (unsigned int)virt_to_phys(p_buf); cim_test_jpeg_desc.nextdesc = (unsigned int)virt_to_phys(NULL); cim_test_jpeg_desc.frameid = 0xf0; cim_test_jpeg_desc.dmacmd = (4 >> 2) | CIM_CMD_EOFINT | CIM_CMD_STOP; dma_cache_wback_inv((unsigned long)&cim_test_jpeg_desc, sizeof(struct cim_desc)); jz_cim->pic_par.framebuf[0] = virt_to_phys(p_buf); cim_jpeg_desc.framebuf = (unsigned int)virt_to_phys(p_buf); cim_jpeg_desc.nextdesc = (unsigned int)virt_to_phys(NULL); cim_jpeg_desc.frameid = 0xff; frmsize = (((jz_cim->pic_par.width * jz_cim->pic_par.height * 16) >> 3) + 3) >> 2; /* word aligned */ if (strcmp(jz_cim->pic_par.format, "jpeg") == 0) { if (frmsize > (MAX_PICTURE_SIZE >> 2)) cim_jpeg_desc.dmacmd = (MAX_PICTURE_SIZE >> 2); else cim_jpeg_desc.dmacmd = frmsize; } else/* if ((strcmp(jz_cim->pic_par.format, "yuv422") == 0) || (strcmp(jz_cim->pic_par.format, "rgb565") == 0)) */ cim_jpeg_desc.dmacmd = frmsize; cim_jpeg_desc.frameid = 0xff; cim_jpeg_desc.dmacmd |= (CIM_CMD_EOFINT | CIM_CMD_STOP); dma_cache_wback_inv((unsigned long)&cim_jpeg_desc, sizeof(struct cim_desc)); return 0; } static int cim_fb_alloc(void) { #ifndef USE_DEFAULT_MEM int page_order; #endif /* Alloc max preview frame for chang preview size */ /* Total memsize = preview size + picture size */ jz_cim->mem_size = MAX_PRE_SIZE * CIM_BUF_NUM + MAX_PICTURE_SIZE; #ifndef USE_DEFAULT_MEM /* If no default memory, Alloc memory here */ page_order = get_order(jz_cim->mem_size); jz_cim->mem_base = (unsigned char *)__get_free_pages(GFP_KERNEL, page_order); if (jz_cim->mem_base == NULL) return -ENOMEM; #endif /* Descriptor list for cim DMA */ init_cim_desc_list(jz_cim->mem_base); return 0; } static void cim_fb_destroy(void) { #if 0 int pages; struct cim_desc *jz_frame_desc, *p_desc; __cim_disable_dma(); __cim_disable(); dprintk("jz_cim->frame_desc = %x\n", (u32)jz_cim->frame_desc); if (jz_cim->frame_desc == NULL) { printk("Original memory is NULL\n"); return; } jz_frame_desc = jz_cim->frame_desc; // dprintk("framebuf = %x,thisdesc = %x,frame_size= %d\n", (u32) jz_frame_desc->framebuf, (unsigned int)jz_frame_desc, (jz_frame_desc->dmacmd & 0xffffff) * 4); p_desc = (struct cim_desc *)phys_to_virt(jz_frame_desc->nextdesc); pages = jz_frame_desc->pagenum; dprintk("page_order = %d\n", pages); free_pages((unsigned long)phys_to_virt(jz_frame_desc->framebuf), pages); kfree(jz_frame_desc); jz_frame_desc = p_desc; jz_cim->frame_desc = NULL; start_init = 1; #endif } /*========================================================================== * Interrupt handler *========================================================================*/ static irqreturn_t cim_irq_handler(int irq, void *dev_id) { u32 state = REG_CIM_STATE; /* dprintk("REG_CIM_STATE = %x\n", REG_CIM_STATE); dprintk("IRQ:REG_CIM_CTRL = %x\n", REG_CIM_CTRL); dprintk("REG_CIM_IID \t= \t0x%08x\n", REG_CIM_IID); dprintk("REG_CIM_FID \t= \t0x%08x\n", REG_CIM_FID); */ if (state & CIM_STATE_DMA_EOF) { if (jpeg_reading_flag != 1) { data_ready_buf_id = REG_CIM_IID; cim_tran_buf_id = REG_CIM_FID; wake_up_interruptible(&jz_cim->wait_queue); // printk("preview sleep \n"); REG_CIM_STATE &= ~CIM_STATE_DMA_EOF; return IRQ_HANDLED; } else { cim_stop(); printk("wake_up_interruptible\n"); wake_up_interruptible(&jz_cim->wait_queue); REG_CIM_STATE = 0; } } #if defined(CONFIG_SOC_JZ4750) if (state & CIM_STATE_RXF_OF) { printk("OverFlow interrupt!\n"); __cim_disable(); REG_CIM_STATE = 0; __cim_reset_rxfifo(); // resetting rxfifo __cim_unreset_rxfifo(); __cim_enable_dma(); // enable dma __cim_enable(); return IRQ_HANDLED; } #endif /* clear status flags*/ REG_CIM_STATE = 0; return IRQ_HANDLED; } /*========================================================================== * File operations *========================================================================*/ static int cim_open(struct inode *inode, struct file *filp); static int cim_release(struct inode *inode, struct file *filp); static ssize_t cim_read(struct file *filp, char *buf, size_t size, loff_t *l); static ssize_t cim_write(struct file *filp, const char *buf, size_t size, loff_t *l); static int cim_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); static int cim_mmap(struct file *file, struct vm_area_struct *vma); static struct file_operations cim_fops = { open: cim_open, release: cim_release, read: cim_read, write: cim_write, ioctl: cim_ioctl, mmap: cim_mmap, }; static int cim_open(struct inode *inode, struct file *filp) { if (cim_inited == 0) { cim_device_init(); } /* allocate frame buffers */ cim_inited = 1; dprintk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__); try_module_get(THIS_MODULE); return 0; } static int cim_release(struct inode *inode, struct file *filp) { dprintk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__); cim_fb_destroy(); cim_stop(); module_put(THIS_MODULE); return 0; } static ssize_t cim_read(struct file *filp, char *buf, size_t size, loff_t *l) { unsigned long off = *l; if ((size + off) > jz_cim->mem_size) size = jz_cim->mem_size; memcpy(buf, jz_cim->mem_base + off, size); return size; } static ssize_t cim_write(struct file *filp, const char *buf, size_t size, loff_t *l) { printk("cim error: write is not implemented\n"); return -1; } /************************** * IOCTL Handlers * **************************/ /* * If use default mem, app need trans a mem_base to cim though "IOCTL_SET_MEM".(only once) * Else driver will alloc memory by itself. See cim_fb_alloc() for detail. * * Then "IOCTL_SET_CIM_CONFIG" and "IOCTL_SET_PREVIEW_PARAM" will be call to set preview parametes. * Now, call IOCTL_START_CIM to start data tranfer. * When Take a picture, * */ static int cim_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; switch (cmd) { case IOCTL_SET_I2C_ADDR: if (copy_from_user(&i2c_addr, (void *)arg, 4)) return -EFAULT; break; case IOCTL_SET_I2C_CLK: if (copy_from_user(&i2c_clk, (void *)arg, 4)) return -EFAULT; break; case IOCTL_WRITE_I2C_REG: { unsigned char regval[2]; if (copy_from_user(regval, (void *)arg, 2)) return -EFAULT; sensor_write_reg(regval[0], regval[1]); break; } case IOCTL_READ_I2C_REG: { unsigned char reg, val; if (copy_from_user(®, (void *)arg, 1)) return -EFAULT; val = sensor_read_reg(reg); if (copy_to_user((void *)(arg + 1), &val, 1)) return -EFAULT; break; } case IOCTL_WRITE_I2C_REG16: { unsigned short regval[2]; if (copy_from_user(regval, (void *)arg, 4)) return -EFAULT; sensor_write_reg16(regval[0], (unsigned char)regval[1]); break; } case IOCTL_READ_I2C_REG16: { unsigned short reg, val; if (copy_from_user(®, (void *)arg, 2)) return -EFAULT; val = sensor_read_reg16(reg); if (copy_to_user((void *)(arg + 1), &val, 2)) return -EFAULT; break; } #ifdef USE_DEFAULT_MEM case IOCTL_SET_MEM: jz_cim->mem_base = (unsigned char *)arg; cim_fb_alloc(); break; #endif case IOCTL_START_CIM: cim_start(); break; case IOCTL_STOP_CIM: cim_stop(); return 0; case IOCTL_GET_CIM_CONFIG: dprintk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__); return copy_to_user((void *)argp, (void *)&jz_cim->cim_cfg, sizeof(cim_config_t)) ? -EFAULT : 0; break; case IOCTL_SET_CIM_CONFIG: if (copy_from_user((void *)&jz_cim->cim_cfg, (void *)arg, sizeof(cim_config_t))) return -EFAULT; cim_config(&jz_cim->cim_cfg); break; case IOCTL_GET_PREVIEW_PARAM: dprintk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__); return copy_to_user(argp, &jz_cim->view_par, sizeof(preview_param_t)) ? -EFAULT : 0; break; case IOCTL_SET_PREVIEW_PARAM: { int i, framesize, wpf; /* words per frame */ preview_param_t p; if (copy_from_user((void *)&p, (void *)arg, sizeof(preview_param_t))) return -EFAULT; framesize = (p.width * p.height * p.bpp + 7) >> 3; if (framesize > MAX_PRE_SIZE){ printk("ERROR! Preview size is too large!\n"); return -EINVAL; } jz_cim->view_par.width = p.width; jz_cim->view_par.height = p.height; jz_cim->view_par.bpp = p.bpp; wpf = (framesize +3) >> 2 ; for (i = 0; i < CIM_BUF_NUM; i++) { cim_frame_desc[i].dmacmd &= ~CIM_CMD_LEN_MASK; cim_frame_desc[i].dmacmd |= wpf; dma_cache_wback((unsigned long)(&cim_frame_desc[i]), sizeof(struct cim_desc)); } break; } case IOCTL_GET_PICTURE_PARAM: return copy_to_user(argp, &jz_cim->pic_par, sizeof(picture_param_t)) ? -EFAULT : 0; break; case IOCTL_SET_PICTURE_PARAM: { int framesize, wpf; /* words per frame */ picture_param_t p; if (copy_from_user((void *)&p, (void *)arg, sizeof(picture_param_t))) return -EFAULT; framesize = (p.width * p.height * 16 + 7) >> 3; jz_cim->pic_par.width = p.width; jz_cim->pic_par.height = p.height; wpf = (framesize + 3) >> 2 ; cim_jpeg_desc.dmacmd &= ~CIM_CMD_LEN_MASK; cim_jpeg_desc.dmacmd |= wpf; dma_cache_wback((unsigned long)(&cim_jpeg_desc), sizeof(struct cim_desc)); break; } case IOCTL_PRINT_REGS: cim_print_regs(); break; case IOCTL_TAKE_PICTURE: cim_snapshot(0); break; case IOCTL_GET_CURRENT_BUF_ID: { int id; id = get_ready_buf_id(); return copy_to_user(argp, &id, 4) ? -EFAULT : 0; } break; default: printk("Not supported command: 0x%x\n", cmd); return -EINVAL; break; } return 0; } /* Use mmap /dev/fb can only get a non-cacheable Virtual Address. */ static int cim_mmap(struct file *file, struct vm_area_struct *vma) { unsigned long start; unsigned long off; u32 len; dprintk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__); off = vma->vm_pgoff << PAGE_SHIFT; /* frame buffer memory */ start = virt_to_phys(jz_cim->mem_base); len = PAGE_ALIGN((start & ~PAGE_MASK) + ((unsigned long)jz_cim->mem_size)); start &= PAGE_MASK; if ((vma->vm_end - vma->vm_start + off) > len) { printk("Error: vma is larger than memory length\n"); return -EINVAL; } off += start; vma->vm_pgoff = off >> PAGE_SHIFT; vma->vm_flags |= VM_IO; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); /* Uncacheable */ #if defined(CONFIG_MIPS32) pgprot_val(vma->vm_page_prot) &= ~_CACHE_MASK; pgprot_val(vma->vm_page_prot) |= _CACHE_UNCACHED; /* Uncacheable */ #endif if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) { return -EAGAIN; } return 0; } static struct miscdevice cim_dev = { CIM_MINOR, "cim", &cim_fops }; /* * Module init and exit */ static int __init cim_init(void) { int ret; /* GPIO init for cim pins and i2c SDA & SCL */ __gpio_as_cim(); __gpio_as_i2c(); /* waitqueue */ init_waitqueue_head(&jz_cim->wait_queue); #ifndef USE_DEFAULT_MEM /* Alloc memory for cim DMA*/ printk("Alloc memory for cim DMA\n"); ret = cim_fb_alloc(); if (ret) { printk("No mem: Alloc memory for cim DMA\n"); return ret; } #endif /* request interrupt for cim */ if ((ret = request_irq(IRQ_CIM, cim_irq_handler, IRQF_DISABLED, CIM_NAME, jz_cim))) { printk(KERN_ERR "request_irq return error, ret=%d\n", ret); cim_fb_destroy(); printk(KERN_ERR "CIM could not get IRQ\n"); return ret; } /* Register as a misc device */ ret = misc_register(&cim_dev); if (ret < 0) { return ret; } printk("Virtual Driver of JZ CIM registered\n"); return 0; } static void __exit cim_exit(void) { misc_deregister(&cim_dev); } module_init(cim_init); module_exit(cim_exit);