📄 cafe_ccic.c
字号:
/* * A driver for the CMOS camera controller in the Marvell 88ALP01 "cafe" * multifunction chip. Currently works with the Omnivision OV7670 * sensor. * * The data sheet for this device can be found at: * http://www.marvell.com/products/pcconn/88ALP01.jsp * * Copyright 2006 One Laptop Per Child Association, Inc. * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> * * Written by Jonathan Corbet, corbet@lwn.net. * * This file may be distributed under the terms of the GNU General * Public License, version 2. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/mm.h>#include <linux/pci.h>#include <linux/i2c.h>#include <linux/interrupt.h>#include <linux/spinlock.h>#include <linux/videodev2.h>#include "compat.h"#include <media/v4l2-common.h>#include <media/v4l2-ioctl.h>#include <media/v4l2-chip-ident.h>#include <linux/device.h>#include <linux/wait.h>#include <linux/list.h>#include <linux/dma-mapping.h>#include <linux/delay.h>#include <linux/debugfs.h>#include <linux/jiffies.h>#include <linux/vmalloc.h>#include <asm/uaccess.h>#include <asm/io.h>#include "cafe_ccic-regs.h"#define CAFE_VERSION 0x000002/* * Parameters. */MODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>");MODULE_DESCRIPTION("Marvell 88ALP01 CMOS Camera Controller driver");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("Video");/* * Internal DMA buffer management. Since the controller cannot do S/G I/O, * we must have physically contiguous buffers to bring frames into. * These parameters control how many buffers we use, whether we * allocate them at load time (better chance of success, but nails down * memory) or when somebody tries to use the camera (riskier), and, * for load-time allocation, how big they should be. * * The controller can cycle through three buffers. We could use * more by flipping pointers around, but it probably makes little * sense. */#define MAX_DMA_BUFS 3static int alloc_bufs_at_read;module_param(alloc_bufs_at_read, bool, 0444);MODULE_PARM_DESC(alloc_bufs_at_read, "Non-zero value causes DMA buffers to be allocated when the " "video capture device is read, rather than at module load " "time. This saves memory, but decreases the chances of " "successfully getting those buffers.");static int n_dma_bufs = 3;module_param(n_dma_bufs, uint, 0644);MODULE_PARM_DESC(n_dma_bufs, "The number of DMA buffers to allocate. Can be either two " "(saves memory, makes timing tighter) or three.");static int dma_buf_size = VGA_WIDTH * VGA_HEIGHT * 2; /* Worst case */module_param(dma_buf_size, uint, 0444);MODULE_PARM_DESC(dma_buf_size, "The size of the allocated DMA buffers. If actual operating " "parameters require larger buffers, an attempt to reallocate " "will be made.");static int min_buffers = 1;module_param(min_buffers, uint, 0644);MODULE_PARM_DESC(min_buffers, "The minimum number of streaming I/O buffers we are willing " "to work with.");static int max_buffers = 10;module_param(max_buffers, uint, 0644);MODULE_PARM_DESC(max_buffers, "The maximum number of streaming I/O buffers an application " "will be allowed to allocate. These buffers are big and live " "in vmalloc space.");static int flip;module_param(flip, bool, 0444);MODULE_PARM_DESC(flip, "If set, the sensor will be instructed to flip the image " "vertically.");enum cafe_state { S_NOTREADY, /* Not yet initialized */ S_IDLE, /* Just hanging around */ S_FLAKED, /* Some sort of problem */ S_SINGLEREAD, /* In read() */ S_SPECREAD, /* Speculative read (for future read()) */ S_STREAMING /* Streaming data */};/* * Tracking of streaming I/O buffers. */struct cafe_sio_buffer { struct list_head list; struct v4l2_buffer v4lbuf; char *buffer; /* Where it lives in kernel space */ int mapcount; struct cafe_camera *cam;};/* * A description of one of our devices. * Locking: controlled by s_mutex. Certain fields, however, require * the dev_lock spinlock; they are marked as such by comments. * dev_lock is also required for access to device registers. */struct cafe_camera{ enum cafe_state state; unsigned long flags; /* Buffer status, mainly (dev_lock) */ int users; /* How many open FDs */ struct file *owner; /* Who has data access (v4l2) */ /* * Subsystem structures. */ struct pci_dev *pdev; struct video_device v4ldev; struct i2c_adapter i2c_adapter; struct i2c_client *sensor; unsigned char __iomem *regs; struct list_head dev_list; /* link to other devices */ /* DMA buffers */ unsigned int nbufs; /* How many are alloc'd */ int next_buf; /* Next to consume (dev_lock) */ unsigned int dma_buf_size; /* allocated size */ void *dma_bufs[MAX_DMA_BUFS]; /* Internal buffer addresses */ dma_addr_t dma_handles[MAX_DMA_BUFS]; /* Buffer bus addresses */ unsigned int specframes; /* Unconsumed spec frames (dev_lock) */ unsigned int sequence; /* Frame sequence number */ unsigned int buf_seq[MAX_DMA_BUFS]; /* Sequence for individual buffers */ /* Streaming buffers */ unsigned int n_sbufs; /* How many we have */ struct cafe_sio_buffer *sb_bufs; /* The array of housekeeping structs */ struct list_head sb_avail; /* Available for data (we own) (dev_lock) */ struct list_head sb_full; /* With data (user space owns) (dev_lock) */ struct tasklet_struct s_tasklet; /* Current operating parameters */ u32 sensor_type; /* Currently ov7670 only */ struct v4l2_pix_format pix_format; /* Locks */ struct mutex s_mutex; /* Access to this structure */ spinlock_t dev_lock; /* Access to device */ /* Misc */ wait_queue_head_t smbus_wait; /* Waiting on i2c events */ wait_queue_head_t iowait; /* Waiting on frame data */#ifdef CONFIG_VIDEO_ADV_DEBUG struct dentry *dfs_regs; struct dentry *dfs_cam_regs;#endif};/* * Status flags. Always manipulated with bit operations. */#define CF_BUF0_VALID 0 /* Buffers valid - first three */#define CF_BUF1_VALID 1#define CF_BUF2_VALID 2#define CF_DMA_ACTIVE 3 /* A frame is incoming */#define CF_CONFIG_NEEDED 4 /* Must configure hardware *//* * Start over with DMA buffers - dev_lock needed. */static void cafe_reset_buffers(struct cafe_camera *cam){ int i; cam->next_buf = -1; for (i = 0; i < cam->nbufs; i++) clear_bit(i, &cam->flags); cam->specframes = 0;}static inline int cafe_needs_config(struct cafe_camera *cam){ return test_bit(CF_CONFIG_NEEDED, &cam->flags);}static void cafe_set_config_needed(struct cafe_camera *cam, int needed){ if (needed) set_bit(CF_CONFIG_NEEDED, &cam->flags); else clear_bit(CF_CONFIG_NEEDED, &cam->flags);}/* * Debugging and related. */#define cam_err(cam, fmt, arg...) \ dev_err(&(cam)->pdev->dev, fmt, ##arg);#define cam_warn(cam, fmt, arg...) \ dev_warn(&(cam)->pdev->dev, fmt, ##arg);#define cam_dbg(cam, fmt, arg...) \ dev_dbg(&(cam)->pdev->dev, fmt, ##arg);/* ---------------------------------------------------------------------*//* * We keep a simple list of known devices to search at open time. */static LIST_HEAD(cafe_dev_list);static DEFINE_MUTEX(cafe_dev_list_lock);static void cafe_add_dev(struct cafe_camera *cam){ mutex_lock(&cafe_dev_list_lock); list_add_tail(&cam->dev_list, &cafe_dev_list); mutex_unlock(&cafe_dev_list_lock);}static void cafe_remove_dev(struct cafe_camera *cam){ mutex_lock(&cafe_dev_list_lock); list_del(&cam->dev_list); mutex_unlock(&cafe_dev_list_lock);}static struct cafe_camera *cafe_find_dev(int minor){ struct cafe_camera *cam; mutex_lock(&cafe_dev_list_lock); list_for_each_entry(cam, &cafe_dev_list, dev_list) { if (cam->v4ldev.minor == minor) goto done; } cam = NULL; done: mutex_unlock(&cafe_dev_list_lock); return cam;}static struct cafe_camera *cafe_find_by_pdev(struct pci_dev *pdev){ struct cafe_camera *cam; mutex_lock(&cafe_dev_list_lock); list_for_each_entry(cam, &cafe_dev_list, dev_list) { if (cam->pdev == pdev) goto done; } cam = NULL; done: mutex_unlock(&cafe_dev_list_lock); return cam;}/* ------------------------------------------------------------------------ *//* * Device register I/O */static inline void cafe_reg_write(struct cafe_camera *cam, unsigned int reg, unsigned int val){ iowrite32(val, cam->regs + reg);}static inline unsigned int cafe_reg_read(struct cafe_camera *cam, unsigned int reg){ return ioread32(cam->regs + reg);}static inline void cafe_reg_write_mask(struct cafe_camera *cam, unsigned int reg, unsigned int val, unsigned int mask){ unsigned int v = cafe_reg_read(cam, reg); v = (v & ~mask) | (val & mask); cafe_reg_write(cam, reg, v);}static inline void cafe_reg_clear_bit(struct cafe_camera *cam, unsigned int reg, unsigned int val){ cafe_reg_write_mask(cam, reg, 0, val);}static inline void cafe_reg_set_bit(struct cafe_camera *cam, unsigned int reg, unsigned int val){ cafe_reg_write_mask(cam, reg, val, val);}/* -------------------------------------------------------------------- *//* * The I2C/SMBUS interface to the camera itself starts here. The * controller handles SMBUS itself, presenting a relatively simple register * interface; all we have to do is to tell it where to route the data. */#define CAFE_SMBUS_TIMEOUT (HZ) /* generous */static int cafe_smbus_write_done(struct cafe_camera *cam){ unsigned long flags; int c1; /* * We must delay after the interrupt, or the controller gets confused * and never does give us good status. Fortunately, we don't do this * often. */ udelay(20); spin_lock_irqsave(&cam->dev_lock, flags); c1 = cafe_reg_read(cam, REG_TWSIC1); spin_unlock_irqrestore(&cam->dev_lock, flags); return (c1 & (TWSIC1_WSTAT|TWSIC1_ERROR)) != TWSIC1_WSTAT;}static int cafe_smbus_write_data(struct cafe_camera *cam, u16 addr, u8 command, u8 value){ unsigned int rval; unsigned long flags; DEFINE_WAIT(the_wait); spin_lock_irqsave(&cam->dev_lock, flags); rval = TWSIC0_EN | ((addr << TWSIC0_SID_SHIFT) & TWSIC0_SID); rval |= TWSIC0_OVMAGIC; /* Make OV sensors work */ /* * Marvell sez set clkdiv to all 1's for now. */ rval |= TWSIC0_CLKDIV; cafe_reg_write(cam, REG_TWSIC0, rval); (void) cafe_reg_read(cam, REG_TWSIC1); /* force write */ rval = value | ((command << TWSIC1_ADDR_SHIFT) & TWSIC1_ADDR); cafe_reg_write(cam, REG_TWSIC1, rval); spin_unlock_irqrestore(&cam->dev_lock, flags); /* * Time to wait for the write to complete. THIS IS A RACY * WAY TO DO IT, but the sad fact is that reading the TWSIC1 * register too quickly after starting the operation sends * the device into a place that may be kinder and better, but * which is absolutely useless for controlling the sensor. In * practice we have plenty of time to get into our sleep state * before the interrupt hits, and the worst case is that we * time out and then see that things completed, so this seems * the best way for now. */ do { prepare_to_wait(&cam->smbus_wait, &the_wait, TASK_UNINTERRUPTIBLE); schedule_timeout(1); /* even 1 jiffy is too long */ finish_wait(&cam->smbus_wait, &the_wait); } while (!cafe_smbus_write_done(cam));#ifdef IF_THE_CAFE_HARDWARE_WORKED_RIGHT wait_event_timeout(cam->smbus_wait, cafe_smbus_write_done(cam), CAFE_SMBUS_TIMEOUT);#endif spin_lock_irqsave(&cam->dev_lock, flags); rval = cafe_reg_read(cam, REG_TWSIC1); spin_unlock_irqrestore(&cam->dev_lock, flags); if (rval & TWSIC1_WSTAT) { cam_err(cam, "SMBUS write (%02x/%02x/%02x) timed out\n", addr, command, value); return -EIO; } if (rval & TWSIC1_ERROR) { cam_err(cam, "SMBUS write (%02x/%02x/%02x) error\n", addr, command, value); return -EIO; } return 0;}static int cafe_smbus_read_done(struct cafe_camera *cam){ unsigned long flags; int c1; /* * We must delay after the interrupt, or the controller gets confused * and never does give us good status. Fortunately, we don't do this * often. */ udelay(20); spin_lock_irqsave(&cam->dev_lock, flags); c1 = cafe_reg_read(cam, REG_TWSIC1); spin_unlock_irqrestore(&cam->dev_lock, flags); return c1 & (TWSIC1_RVALID|TWSIC1_ERROR);}static int cafe_smbus_read_data(struct cafe_camera *cam, u16 addr, u8 command, u8 *value){ unsigned int rval; unsigned long flags; spin_lock_irqsave(&cam->dev_lock, flags); rval = TWSIC0_EN | ((addr << TWSIC0_SID_SHIFT) & TWSIC0_SID); rval |= TWSIC0_OVMAGIC; /* Make OV sensors work */ /* * Marvel sez set clkdiv to all 1's for now. */ rval |= TWSIC0_CLKDIV; cafe_reg_write(cam, REG_TWSIC0, rval); (void) cafe_reg_read(cam, REG_TWSIC1); /* force write */ rval = TWSIC1_READ | ((command << TWSIC1_ADDR_SHIFT) & TWSIC1_ADDR); cafe_reg_write(cam, REG_TWSIC1, rval); spin_unlock_irqrestore(&cam->dev_lock, flags); wait_event_timeout(cam->smbus_wait, cafe_smbus_read_done(cam), CAFE_SMBUS_TIMEOUT); spin_lock_irqsave(&cam->dev_lock, flags); rval = cafe_reg_read(cam, REG_TWSIC1); spin_unlock_irqrestore(&cam->dev_lock, flags); if (rval & TWSIC1_ERROR) { cam_err(cam, "SMBUS read (%02x/%02x) error\n", addr, command); return -EIO; } if (! (rval & TWSIC1_RVALID)) { cam_err(cam, "SMBUS read (%02x/%02x) timed out\n", addr, command); return -EIO; } *value = rval & 0xff; return 0;}/* * Perform a transfer over SMBUS. This thing is called under * the i2c bus lock, so we shouldn't race with ourselves... */static int cafe_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, char rw, u8 command, int size, union i2c_smbus_data *data){ struct cafe_camera *cam = i2c_get_adapdata(adapter); int ret = -EINVAL; /* * Refuse to talk to anything but OV cam chips. We should * never even see an attempt to do so, but one never knows. */#if 0 /* client needs to talk during attach process */ if (! cam->sensor) { cam_err(cam, "SMBUS xfer with no client\n"); return -EINVAL; }#endif if (cam->sensor && addr != cam->sensor->addr) { cam_err(cam, "funky smbus addr %d\n", addr); return -EINVAL; } /* * This interface would appear to only do byte data ops. OK * it can do word too, but the cam chip has no use for that. */ if (size != I2C_SMBUS_BYTE_DATA) { cam_err(cam, "funky xfer size %d\n", size); return -EINVAL; } if (rw == I2C_SMBUS_WRITE) ret = cafe_smbus_write_data(cam, addr, command, data->byte); else if (rw == I2C_SMBUS_READ) ret = cafe_smbus_read_data(cam, addr, command, &data->byte); return ret;}static void cafe_smbus_enable_irq(struct cafe_camera *cam){ unsigned long flags; spin_lock_irqsave(&cam->dev_lock, flags); cafe_reg_set_bit(cam, REG_IRQMASK, TWSIIRQS); spin_unlock_irqrestore(&cam->dev_lock, flags);}static u32 cafe_smbus_func(struct i2c_adapter *adapter){ return I2C_FUNC_SMBUS_READ_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE_DATA;}static struct i2c_algorithm cafe_smbus_algo = { .smbus_xfer = cafe_smbus_xfer, .functionality = cafe_smbus_func};/* Somebody is on the bus */static int cafe_cam_init(struct cafe_camera *cam);static void cafe_ctlr_stop_dma(struct cafe_camera *cam);static void cafe_ctlr_power_down(struct cafe_camera *cam);static int cafe_smbus_attach(struct i2c_client *client){ struct cafe_camera *cam = i2c_get_adapdata(client->adapter); /* * Don't talk to chips we don't recognize. */ if (client->driver->id == I2C_DRIVERID_OV7670) { cam->sensor = client; return cafe_cam_init(cam); } return -EINVAL;}static int cafe_smbus_detach(struct i2c_client *client){ struct cafe_camera *cam = i2c_get_adapdata(client->adapter); if (cam->sensor == client) { cafe_ctlr_stop_dma(cam); cafe_ctlr_power_down(cam); cam_err(cam, "lost the sensor!\n"); cam->sensor = NULL; /* Bummer, no camera */ cam->state = S_NOTREADY; } return 0;}static int cafe_smbus_setup(struct cafe_camera *cam){ struct i2c_adapter *adap = &cam->i2c_adapter; int ret; cafe_smbus_enable_irq(cam); adap->id = I2C_HW_SMBUS_CAFE; adap->class = I2C_CLASS_CAM_DIGITAL; adap->owner = THIS_MODULE; adap->client_register = cafe_smbus_attach; adap->client_unregister = cafe_smbus_detach; adap->algo = &cafe_smbus_algo; strcpy(adap->name, "cafe_ccic"); adap->dev.parent = &cam->pdev->dev;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -