From 0d55b08388f12c7c22cae9c6c745995d051624ba Mon Sep 17 00:00:00 2001 From: Zheng Ba Date: Thu, 1 Apr 2010 16:29:43 +0800 Subject: [PATCH 3/3] Moorestown Camera Imaging driver Beta 10.0 Patch-mainline: 2.6.35? Changes from Beta 9.0: 1. Fixed hsd sighting: 3469638 3469639 3469710 3469822 (high) 3469697 (medium) Changes from Beta 8.0: 1. Fixed hsd sighting 3469056 3469058 (critical) 3469705 3469696 3469709 3469510 (medium) Changes from Beta 7.0: 1. Fixed hsd sighting 3469681,3469682,3469683 (high) Changes from Beta 6.0: 1. Fixed hsd sighting 3469668 (high) 2. Fixed ov5630 v4l2 view-finding dark issue 3. Enabled support for popular v4l2 applications (cheese, skype, ffmpeg) Changes from Beta 5.1: 1. Fixed CRITICAL sighting 3469558 -- ciapp fails to launch with segment fault 2. Fixed HIGH sighting 3479513 -- ov5630 AWB unstable 3. Improved KMOT sensor 720p fps from 30 to 40 Changes from Beta 5.0: Fixed a critical issue of camera driver not loading -- hsd 3469557 Main changes from Beta 4.0: Fixed 4 HSD sightings: 3469392,3469099,3469470,3469500 Main changes from Beta 3.0: Fixed 7 HSD sightings: 3469264,3469112,3469395,3469103,3469105,3469471,3469484 Main changes from Beta 2.0: Fixed 6 HSD sightings: 3469047,3469315,3469317,3469101,3468409,3469391 Main changes from Beta 1.1: 1. Added interrupt mode for jpeg capture and KMOT viewfinding 2. Fixed HSD sighting 3469228 and 3469147 Main changes from Alpha2: Enabled MIPI interface in ISP driver and KMOT sensor s5k4e1. Enabled FIFO in ISP driver, which doubled the fps in view-finding mode. Enabled Subdev Framework in CI kernel driver. Enabled AF Continuous Mode. Enabled AE scene evaluation. Enabled the camera drivers in kernel: Device Drivers --> Multimedia support --> Video For Linux Device Drivers --> Mulitmedia support --> Video capture adapters --> --> Moorestown Langwell Camera Imaging Subsystem support. Kernel configs: 1. camera driver depends on GPIO library and I2C driver. CONFIG_GENERIC_GPIO=y CONFIG_I2C=y CONFIG_GPIOLIB=y 2. camera driver depends on videobuf-core and videobuf-dma-contig. VIDEOBUF_GEN=y VIDEOBUF_DMA_CONTIG=y 3. enable multimedia support and video capture. CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y CONFIG_VIDEO_V4L2_COMMON=y CONFIG_VIDEO_MEDIA=y CONFIG_VIDEO_V4L2=y 4. camera drivers incluing ISP, 5630, 5630-motor, s5k4e1, s5k4e1-motor, 2650, 9665, flash. CONFIG_VIDEO_MRSTCI=y CONFIG_VIDEO_MRST_ISP=y CONFIG_VIDEO_MRST_OV5630=y CONFIG_VIDEO_MRST_OV5630_MOTOR=y CONFIG_VIDEO_MRST_S5K4E1=y CONFIG_VIDEO_MRST_S5K4E1_MOTOR=y CONFIG_VIDEO_MRST_FLASH=y CONFIG_VIDEO_MRST_OV2650=y CONFIG_VIDEO_MRST_OV9665=y Signed-off-by: Zheng Ba --- drivers/media/video/mrstci/mrstflash/Kconfig | 9 + drivers/media/video/mrstci/mrstflash/Makefile | 3 + drivers/media/video/mrstci/mrstflash/mrstflash.c | 150 +++ drivers/media/video/mrstci/mrstov2650/Kconfig | 9 + drivers/media/video/mrstci/mrstov2650/Makefile | 3 + drivers/media/video/mrstci/mrstov2650/mrstov2650.c | 1190 ++++++++++++++++++++ drivers/media/video/mrstci/mrstov2650/ov2650.h | 766 +++++++++++++ drivers/media/video/mrstci/mrstov5630/Kconfig | 9 + drivers/media/video/mrstci/mrstov5630/Makefile | 4 + drivers/media/video/mrstci/mrstov5630/ov5630.c | 1153 +++++++++++++++++++ drivers/media/video/mrstci/mrstov5630/ov5630.h | 672 +++++++++++ .../media/video/mrstci/mrstov5630_motor/Kconfig | 9 + .../media/video/mrstci/mrstov5630_motor/Makefile | 3 + .../mrstci/mrstov5630_motor/mrstov5630_motor.c | 428 +++++++ .../video/mrstci/mrstov5630_motor/ov5630_motor.h | 86 ++ drivers/media/video/mrstci/mrstov9665/Kconfig | 9 + drivers/media/video/mrstci/mrstov9665/Makefile | 3 + drivers/media/video/mrstci/mrstov9665/mrstov9665.c | 972 ++++++++++++++++ drivers/media/video/mrstci/mrstov9665/ov9665.h | 263 +++++ drivers/media/video/mrstci/mrsts5k4e1/Kconfig | 9 + drivers/media/video/mrstci/mrsts5k4e1/Makefile | 3 + drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.c | 1024 +++++++++++++++++ drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.h | 662 +++++++++++ .../media/video/mrstci/mrsts5k4e1_motor/Kconfig | 9 + .../media/video/mrstci/mrsts5k4e1_motor/Makefile | 3 + .../mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.c | 430 +++++++ .../mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.h | 102 ++ 27 files changed, 7983 insertions(+), 0 deletions(-) create mode 100644 drivers/media/video/mrstci/mrstflash/Kconfig create mode 100644 drivers/media/video/mrstci/mrstflash/Makefile create mode 100644 drivers/media/video/mrstci/mrstflash/mrstflash.c create mode 100644 drivers/media/video/mrstci/mrstov2650/Kconfig create mode 100644 drivers/media/video/mrstci/mrstov2650/Makefile create mode 100644 drivers/media/video/mrstci/mrstov2650/mrstov2650.c create mode 100644 drivers/media/video/mrstci/mrstov2650/ov2650.h create mode 100644 drivers/media/video/mrstci/mrstov5630/Kconfig create mode 100644 drivers/media/video/mrstci/mrstov5630/Makefile create mode 100644 drivers/media/video/mrstci/mrstov5630/ov5630.c create mode 100644 drivers/media/video/mrstci/mrstov5630/ov5630.h create mode 100644 drivers/media/video/mrstci/mrstov5630_motor/Kconfig create mode 100644 drivers/media/video/mrstci/mrstov5630_motor/Makefile create mode 100644 drivers/media/video/mrstci/mrstov5630_motor/mrstov5630_motor.c create mode 100644 drivers/media/video/mrstci/mrstov5630_motor/ov5630_motor.h create mode 100644 drivers/media/video/mrstci/mrstov9665/Kconfig create mode 100644 drivers/media/video/mrstci/mrstov9665/Makefile create mode 100644 drivers/media/video/mrstci/mrstov9665/mrstov9665.c create mode 100644 drivers/media/video/mrstci/mrstov9665/ov9665.h create mode 100755 drivers/media/video/mrstci/mrsts5k4e1/Kconfig create mode 100644 drivers/media/video/mrstci/mrsts5k4e1/Makefile create mode 100755 drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.c create mode 100755 drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.h create mode 100755 drivers/media/video/mrstci/mrsts5k4e1_motor/Kconfig create mode 100644 drivers/media/video/mrstci/mrsts5k4e1_motor/Makefile create mode 100644 drivers/media/video/mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.c create mode 100644 drivers/media/video/mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.h diff --git a/drivers/media/video/mrstci/mrstflash/Kconfig b/drivers/media/video/mrstci/mrstflash/Kconfig new file mode 100644 index 0000000..72099c5 --- /dev/null +++ b/drivers/media/video/mrstci/mrstflash/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_MRST_FLASH + tristate "Moorestown flash" + depends on I2C && VIDEO_MRST_ISP + + ---help--- + Say Y here if your platform support moorestown flash. + + To compile this driver as a module, choose M here: the + module will be called mrstov2650.ko. diff --git a/drivers/media/video/mrstci/mrstflash/Makefile b/drivers/media/video/mrstci/mrstflash/Makefile new file mode 100644 index 0000000..068f638 --- /dev/null +++ b/drivers/media/video/mrstci/mrstflash/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_VIDEO_MRST_FLASH) += mrstflash.o + +EXTRA_CFLAGS += -I$(src)/../include diff --git a/drivers/media/video/mrstci/mrstflash/mrstflash.c b/drivers/media/video/mrstci/mrstflash/mrstflash.c new file mode 100644 index 0000000..5611e6b --- /dev/null +++ b/drivers/media/video/mrstci/mrstflash/mrstflash.c @@ -0,0 +1,150 @@ +/* + * Support for Moorestown Langwell Camera Imaging camera flash. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +MODULE_AUTHOR("Xiaolin Zhang "); +MODULE_DESCRIPTION("A low-level driver for mrst flash"); +MODULE_LICENSE("GPL"); + +static int flash_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + +#define V4L2_IDENT_MRST_FLASH 8248 + return v4l2_chip_ident_i2c_client(client, chip, + V4L2_IDENT_MRST_FLASH, 0); +} + +static const struct v4l2_subdev_core_ops flash_core_ops = { + .g_chip_ident = flash_g_chip_ident, +}; +static const struct v4l2_subdev_ops flash_ops = { + .core = &flash_core_ops, +}; + +static int flash_detect(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + u8 pid; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (adapter->nr != 0) + return -ENODEV; + + pid = i2c_smbus_read_byte_data(client, 0x10); + if (pid == 0x18) { + printk(KERN_ERR "camera flash device found\n"); + v4l_dbg(1, debug, client, "found camera flash device"); + } else { + printk(KERN_ERR "no camera flash device found\n"); + return -ENODEV; + } + + return 0; +} + +static int flash_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 pid, ver; + int ret = -1; + struct v4l2_subdev *sd; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + sd = kzalloc(sizeof(struct v4l2_subdev), GFP_KERNEL); + ret = flash_detect(client); + if (ret) + return -ENODEV; + + v4l2_i2c_subdev_init(sd, client, &flash_ops); + + ver = i2c_smbus_read_byte_data(client, 0x50); + v4l_dbg(1, debug, client, "detect:CST from device is 0x%x", ver); + pid = i2c_smbus_read_byte_data(client, 0x20); + v4l_dbg(1, debug, client, "detect:MFPC from device is 0x%x", pid); + pid = i2c_smbus_read_byte_data(client, 0xA0); + v4l_dbg(1, debug, client, "detect:TCC from device is 0x%x", pid); + pid = i2c_smbus_read_byte_data(client, 0xB0); + v4l_dbg(1, debug, client, "detect:FCC from device is 0x%x", pid); + pid = i2c_smbus_read_byte_data(client, 0xC0); + v4l_dbg(1, debug, client, "detect:FDC from device is 0x%x", pid); + i2c_smbus_write_byte_data(client, 0xc0, 0xff); /*set FST to 1000us*/ + pid = i2c_smbus_read_byte_data(client, 0xc0); + v4l_dbg(1, debug, client, "FDC from device is 0x%x", pid); + + v4l_dbg(1, debug, client, + "successfully load camera flash device driver"); + return 0; +} + +static int flash_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + + return 0; +} + +static const struct i2c_device_id flash_id[] = { + {"mrst_camera_flash", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, flash_id); + +static struct v4l2_i2c_driver_data v4l2_i2c_data = { + .name = "mrst_camera_flash", + .probe = flash_probe, + .remove = flash_remove, + .id_table = flash_id, +}; diff --git a/drivers/media/video/mrstci/mrstov2650/Kconfig b/drivers/media/video/mrstci/mrstov2650/Kconfig new file mode 100644 index 0000000..d39d894 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov2650/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_MRST_OV2650 + tristate "Moorestown OV2650 SoC Sensor" + depends on I2C && VIDEO_MRST_ISP + + ---help--- + Say Y here if your platform support OV2650 SoC Sensor. + + To compile this driver as a module, choose M here: the + module will be called mrstov2650.ko. diff --git a/drivers/media/video/mrstci/mrstov2650/Makefile b/drivers/media/video/mrstci/mrstov2650/Makefile new file mode 100644 index 0000000..fb16d57 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov2650/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_VIDEO_MRST_OV2650) += mrstov2650.o + +EXTRA_CFLAGS += -I$(src)/../include \ No newline at end of file diff --git a/drivers/media/video/mrstci/mrstov2650/mrstov2650.c b/drivers/media/video/mrstci/mrstov2650/mrstov2650.c new file mode 100644 index 0000000..7f0d478 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov2650/mrstov2650.c @@ -0,0 +1,1190 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci_sensor_common.h" +#include "ov2650.h" + +static int mrstov2650_debug; +module_param(mrstov2650_debug, int, 0644); +MODULE_PARM_DESC(mrstov2650_debug, "Debug level (0-1)"); + +#define dprintk(level, fmt, arg...) do { \ + if (mrstov2650_debug >= level) \ + printk(KERN_DEBUG "mrstisp@%s: " fmt "\n", \ + __func__, ## arg); } \ + while (0) + +#define eprintk(fmt, arg...) \ + printk(KERN_ERR "mrstisp@%s: line %d: " fmt "\n", \ + __func__, __LINE__, ## arg); + +#define DBG_entering dprintk(2, "entering"); +#define DBG_leaving dprintk(2, "leaving"); +#define DBG_line dprintk(2, " line: %d", __LINE__); + +static inline struct ci_sensor_config *to_sensor_config(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ci_sensor_config, sd); +} + +static struct ov2650_format_struct { + __u8 *desc; + __u32 pixelformat; + struct regval_list *regs; +} ov2650_formats[] = { + { + .desc = "YUYV 4:2:2", + .pixelformat = SENSOR_MODE_BT601, + .regs = NULL, + }, +}; +#define N_OV2650_FMTS ARRAY_SIZE(ov2650_formats) + +static struct ov2650_res_struct { + __u8 *desc; + int res; + int width; + int height; + /* FIXME: correct the fps values.. */ + int fps; + bool used; + struct regval_list *regs; +} ov2650_res[] = { + { + .desc = "UXGA", + .res = SENSOR_RES_UXGA, + .width = 1600, + .height = 1200, + .fps = 15, + .used = 0, + .regs = ov2650_res_uxga, + }, + { + .desc = "SXGA", + .res = SENSOR_RES_SXGA, + .width = 1280, + .height = 1024, + .fps = 15, + .used = 0, + .regs = ov2650_res_sxga, + }, + { + .desc = "SVGA", + .res = SENSOR_RES_SVGA, + .width = 800, + .height = 600, + .fps = 15, + .used = 0, + .regs = ov2650_res_svga, + }, + { + .desc = "VGA", + .res = SENSOR_RES_VGA, + .width = 640, + .height = 480, + .fps = 15, + .used = 0, + .regs = ov2650_res_vga_vario, + }, + { + .desc = "QVGA", + .res = SENSOR_RES_QVGA, + .width = 320, + .height = 240, + .fps = 15, + .used = 0, + .regs = ov2650_res_qvga, + }, +}; + +#define N_RES (ARRAY_SIZE(ov2650_res)) + +/* + * I2C Read & Write stuff + */ +static int ov2650_read(struct i2c_client *c, u16 reg, u8 *value) +{ + int ret; + int i; + struct i2c_msg msg[2]; + u8 msgbuf[2]; + u8 ret_val = 0; + *value = 0; + /* Read needs two message to go */ + memset(&msg, 0, sizeof(msg)); + msgbuf[0] = 0; + msgbuf[1] = 0; + i = 0; + msgbuf[i++] = reg >> 8; + msgbuf[i++] = reg; + msg[0].addr = c->addr; + msg[0].buf = msgbuf; + msg[0].len = i; + + msg[1].addr = c->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = &ret_val; + msg[1].len = 1; + + ret = i2c_transfer(c->adapter, &msg[0], 2); + *value = ret_val; + + ret = (ret == 2) ? 0 : -1; + return ret; +} + +static int ov2650_write(struct i2c_client *c, u16 reg, u8 value) +{ + int ret, i; + struct i2c_msg msg; + u8 msgbuf[3]; + + /* Writing only needs one message */ + memset(&msg, 0, sizeof(msg)); + i = 0; + msgbuf[i++] = reg >> 8; + msgbuf[i++] = reg; + msgbuf[i++] = value; + + msg.addr = c->addr; + msg.flags = 0; + msg.buf = msgbuf; + msg.len = i; + + ret = i2c_transfer(c->adapter, &msg, 1); + + /* If this is a reset register, wait for 1ms */ + if (reg == OV2650_SYS && (value & 0x80)) + msleep(3); + + ret = (ret == 1) ? 0 : -1; + return ret; +} + +static int ov2650_write_array(struct i2c_client *c, struct regval_list *vals) +{ + struct regval_list *p; + u8 read_val = 0; + int err_num = 0; + int i = 0; + p = vals; + while (p->reg_num != 0xffff) { + ov2650_write(c, p->reg_num, p->value); + ov2650_read(c, p->reg_num, &read_val); + if (read_val != p->value) + err_num++; + p++; + i++; + } + return 0; +} + +static int ov2650_set_data_pin_in(struct i2c_client *client) +{ + int ret = 0; + u8 reg; + + ret += ov2650_write(client, 0x30b0, 0x00); + + ret += ov2650_read(client, 0x30b1, ®); + reg &= 0xfc; + ret += ov2650_write(client, 0x30b1, reg); + + return ret; +} + +static int ov2650_set_data_pin_out(struct i2c_client *client) +{ + int ret = 0; + u8 reg; + + ret += ov2650_write(client, 0x30b0, 0xff); + + ret += ov2650_read(client, 0x30b1, ®); + reg &= 0xfc; + reg |= 0x03; + ret += ov2650_write(client, 0x30b1, reg); + + return ret; +} +/* + * Sensor specific helper function + */ +static int ov2650_standby(void) +{ + gpio_set_value(GPIO_STDBY_PIN, 1); + dprintk(1, "PM: standby called\n"); + return 0; +} + +static int ov2650_wakeup(void) +{ + gpio_set_value(GPIO_STDBY_PIN, 0); + dprintk(1, "PM: wakeup called\n"); + return 0; +} + +static int ov2650_s_power(struct v4l2_subdev *sd, u32 val) +{ + if (val == 1) + ov2650_standby(); + if (val == 0) + ov2650_wakeup(); + return 0; +} + +static int ov2650_init(struct i2c_client *c) +{ + int ret; + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct ci_sensor_config *info = to_sensor_config(sd); + + /* Fill the configuration structure */ + /* Note this default configuration value */ + info->mode = ov2650_formats[0].pixelformat; + info->res = ov2650_res[0].res; + info->type = SENSOR_TYPE_SOC; + info->bls = SENSOR_BLS_OFF; + info->gamma = SENSOR_GAMMA_ON; + info->cconv = SENSOR_CCONV_ON; + info->blc = SENSOR_BLC_AUTO; + info->agc = SENSOR_AGC_AUTO; + info->awb = SENSOR_AWB_AUTO; + info->aec = SENSOR_AEC_AUTO; + info->bus_width = SENSOR_BUSWIDTH_8BIT_ZZ; + info->ycseq = SENSOR_YCSEQ_YCBYCR; + info->conv422 = SENSOR_CONV422_COSITED; + info->bpat = SENSOR_BPAT_BGBGGRGR;/* GRGRBGBG; */ + info->field_inv = SENSOR_FIELDINV_NOSWAP; + info->field_sel = SENSOR_FIELDSEL_BOTH; + info->hpol = SENSOR_HPOL_REFPOS; + info->vpol = SENSOR_VPOL_POS; + info->edge = SENSOR_EDGE_RISING; + info->flicker_freq = SENSOR_FLICKER_100; + info->cie_profile = 0; + memcpy(info->name, "ov2650", 7); + + ret = ov2650_write(c, OV2650_SYS, 0x80); + /* Set registers into default config value */ + ret += ov2650_write_array(c, ov2650_def_reg); + + /* added by wen to stop sensor from streaming */ + ov2650_write(c, 0x3086, 0x0f); + ov2650_set_data_pin_in(c); + ssleep(1); + + return ret; +} + +static int distance(struct ov2650_res_struct *res, u32 w, u32 h) +{ + int ret; + if (res->width < w || res->height < h) + return -1; + + ret = ((res->width - w) + (res->height - h)); + return ret; +} + +static int ov2650_try_res(u32 *w, u32 *h) +{ + struct ov2650_res_struct *res_index, *p = NULL; + int dis, last_dis = ov2650_res->width + ov2650_res->height; + + dprintk(1, "&&&&& before %dx%d", *w, *h); + for (res_index = ov2650_res; + res_index < ov2650_res + N_RES; + res_index++) { + if ((res_index->width <= *w) && (res_index->height <= *h)) + break; + dis = distance(res_index, *w, *h); + if (dis < last_dis) { + last_dis = dis; + p = res_index; + } + } + if ((res_index->width < *w) || (res_index->height < *h)) { + if (res_index != ov2650_res) + res_index--; + } + + /* + if (p == NULL) { + p = ov2650_res; + } + + if ((w != NULL) && (h != NULL)) { + *w = p->width; + *h = p->height; + } + */ + if (res_index == ov2650_res + N_RES) + res_index = ov2650_res + N_RES - 1; + + *w = res_index->width; + *h = res_index->height; + + dprintk(1, "&&&&& after %dx%d", *w, *h); + return 0; +} + +static struct ov2650_res_struct *ov2650_to_res(u32 w, u32 h) +{ + struct ov2650_res_struct *res_index; + + for (res_index = ov2650_res; + res_index < ov2650_res + N_RES; + res_index++) + if ((res_index->width == w) && (res_index->height == h)) + break; + + if (res_index >= ov2650_res + N_RES) + res_index--; /* Take the bigger one */ + + return res_index; +} + +static int ov2650_try_fmt(struct v4l2_subdev *sd, + struct v4l2_format *fmt) +{ + DBG_entering; + dprintk(1, "&&&&& before %dx%d", fmt->fmt.pix.width, + fmt->fmt.pix.height); + return ov2650_try_res(&fmt->fmt.pix.width, &fmt->fmt.pix.height); + dprintk(1, "&&&&& after %dx%d", fmt->fmt.pix.width, + fmt->fmt.pix.height); + DBG_leaving; +} + +static int ov2650_get_fmt(struct v4l2_subdev *sd, + struct v4l2_format *fmt) +{ + struct ci_sensor_config *info = to_sensor_config(sd); + unsigned short width, height; + int index; + + ci_sensor_res2size(info->res, &width, &height); + + /* Marked the current sensor res as being "used" */ + for (index = 0; index < N_RES; index++) { + if ((width == ov2650_res[index].width) && + (height == ov2650_res[index].height)) { + ov2650_res[index].used = 1; + continue; + } + ov2650_res[index].used = 0; + } + + fmt->fmt.pix.width = width; + fmt->fmt.pix.height = height; + return 0; +} + +static int ov2650_set_fmt(struct v4l2_subdev *sd, struct v4l2_format *fmt) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ci_sensor_config *info = to_sensor_config(sd); + int ret = 0; + struct ov2650_res_struct *res_index; + u32 width, height; + int index; + + DBG_entering; + + width = fmt->fmt.pix.width; + height = fmt->fmt.pix.height; + + ret = ov2650_try_res(&width, &height); + res_index = ov2650_to_res(width, height); + + ov2650_wakeup(); + + /* if ((info->res != res_index->res) && (res_index->regs)) { */ + if (res_index->regs) { + + dprintk(2, "changing res from to %dx%d", width, height); + ret = ov2650_write(client, OV2650_SYS, 0x80); + ret += ov2650_write_array(client, ov2650_def_reg); + ret += ov2650_write_array(client, res_index->regs); + + /* add to debug + if(res_index->res == SENSOR_RES_VGA) { + ret += ov2650_write_array(c, ov2650_def_reg); + ret += ov2650_write_array(c, res_index->regs); + } else { + ret += ov2650_write_array(c, ov2650_res_vga_reverse); + ret += ov2650_write_array(c, res_index->regs); + } + */ + + /* Add delay here to get better image */ + /* + if (res_index->res == SENSOR_RES_SXGA || + res_index->res == SENSOR_RES_UXGA) + msleep(2000); + else + msleep(900); + */ + + /* Marked current sensor res as being "used" */ + for (index = 0; index < N_RES; index++) { + if ((width == ov2650_res[index].width) && + (height == ov2650_res[index].height)) { + ov2650_res[index].used = 1; + continue; + } + ov2650_res[index].used = 0; + } + + for (index = 0; index < N_RES; index++) + dprintk(2, "index = %d, used = %d\n", index, + ov2650_res[index].used); + + } + + info->res = res_index->res; + + /* + int i; + unsigned char value; + printk(KERN_WARNING "2650 reg dumping start:\n"); + for(i = 0x3000; i <= 0x360B; i ++) { + ov2650_read(c, i, &value); + printk(KERN_WARNING "reg at offset %4x = %x\n", i, value); + } + printk(KERN_WARNING "2650 reg dumping finished:\n"); + */ + + DBG_leaving; + + return ret; +} + +static int ov2650_q_hflip(struct v4l2_subdev *sd, __s32 *value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err; + unsigned char v; + + err = ov2650_read(client, OV2650_TMC_6, &v); + *value = (v & 0x02) == 0x02; + return err; +} + +static int ov2650_t_hflip(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ci_sensor_config *info = to_sensor_config(sd); + unsigned char v, v1 = 0; + int err; + + value = value >= 1 ? 1 : 0; + err = ov2650_read(client, OV2650_TMC_6, &v); + if (value) { + v |= 0x02; + v1 |= 0x08; + info->bpat = SENSOR_BPAT_GRGRBGBG;/*BGBGGRGR;*/ + } else { + v &= ~0x02; + v1 &= ~0x08; + info->bpat = SENSOR_BPAT_BGBGGRGR; + } + err += ov2650_write(client, OV2650_TMC_6, v); + err += ov2650_write(client, 0x3090, v1); + msleep(10); /* FIXME */ + + return err; +} + +static int ov2650_q_vflip(struct v4l2_subdev *sd, __s32 *value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err; + unsigned char v; + + err = ov2650_read(client, OV2650_TMC_6, &v); + *value = (v & 0x01) == 0x01; + return err; +} + + +static int ov2650_t_vflip(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err = 0; + unsigned char v; + + value = value >= 1 ? 1 : 0; + err = ov2650_read(client, OV2650_TMC_6, &v); + if (value) + v |= 0x01; + else + v &= ~0x01; + err += ov2650_write(client, OV2650_TMC_6, v); + msleep(10); /* FIXME */ + + return err; +} + +#if 0 +static int ov2650_t_awb(struct i2c_client *c, int value) +{ + unsigned char v; + int ret; + struct ci_sensor_config *info = i2c_get_clientdata(c); + + value = value >= 1 ? 1 : 0; + ret = ov2650_read(c, OV2650_ISP_CTL_0, &v); + if (value & 0x01) { + v |= 0x30; + info->awb = SENSOR_AWB_AUTO; + } else { + v &= ~0x30; + info->awb = SENSOR_AWB_OFF; + } + ret += ov2650_write(c, OV2650_ISP_CTL_0, v); + msleep(10); /* FIXME */ + + return ret; +} + +static int ov2650_q_awb(struct i2c_client *c, int *value) +{ + int ret; + unsigned char v; + + ret = ov2650_read(c, OV2650_ISP_CTL_0, &v); + *value = (v & 0x30) == 0x30; + return ret; +} + +static int ov2650_t_agc(struct i2c_client *c, int value) +{ + unsigned char v; + int ret; + struct ci_sensor_config *info = i2c_get_clientdata(c); + + value = value >= 1 ? 1 : 0; + ret = ov2650_read(c, OV2650_ISP_CTL_0, &v); + if (value & 0x01) { + v |= 0x10; + info->agc = SENSOR_AGC_AUTO; + } else { + v &= ~0x10; + info->agc = SENSOR_AGC_OFF; + } + ret += ov2650_write(c, OV2650_ISP_CTL_0, v); + msleep(10); /* FIXME */ + + return ret; +} + +static int ov2650_q_agc(struct i2c_client *c, int *value) +{ + int ret; + unsigned char v; + + ret = ov2650_read(c, OV2650_ISP_CTL_0, &v); + *value = (v & 0x10) == 0x10; + return ret; +} + +static int ov2650_t_blc(struct i2c_client *c, int value) +{ + unsigned char v; + int ret; + + value = value >= 1 ? 1 : 0; + + ret = ov2650_read(c, OV2650_BLCC, &v); + if (value & 0x01) + v |= 0x10; + else + v &= ~0x10; + ret += ov2650_write(c, OV2650_BLCC, v); + msleep(10); /* FIXME */ + + return ret; +} + +static int ov2650_q_blc(struct i2c_client *c, int *value) +{ + int ret; + unsigned char v; + + ret = ov2650_read(c, OV2650_BLCC, &v); + *value = (v & 0x10) == 0x10; + return ret; +} +#endif + +static struct ov2650_control { + struct v4l2_queryctrl qc; + int (*query)(struct v4l2_subdev *sd, __s32 *value); + int (*tweak)(struct v4l2_subdev *sd, int value); +} ov2650_controls[] = { + { + .qc = { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + .tweak = ov2650_t_vflip, + .query = ov2650_q_vflip, + }, + { + .qc = { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Horizontal mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + .tweak = ov2650_t_hflip, + .query = ov2650_q_hflip, + }, +#if 0 + { + .parm = { + .index = V4L2_CID_AUTO_WHITE_BALANCE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto White Balance", + .min = 0, + .max = 1, + .step = 1, + .def_value = 1, + }, + .tweak = ov2650_t_awb, + .query = ov2650_q_awb, + }, + { + .parm = { + .index = V4L2_CID_AUTOGAIN, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto Gain Control", + .min = 0, + .max = 1, + .step = 1, + .def_value = 1, + }, + .tweak = ov2650_t_agc, + .query = ov2650_q_agc, + + }, + { + .parm = { + .index = V4L2_CID_BLACK_LEVEL, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Black Level Control", + .min = 0, + .max = 1, + .step = 1, + .def_value = 1, + }, + .tweak = ov2650_t_blc, + .query = ov2650_q_blc, + + }, +#endif +}; +#define N_CONTROLS (ARRAY_SIZE(ov2650_controls)) + +static struct ov2650_control *ov2650_find_control(__u32 id) +{ + int i; + + for (i = 0; i < N_CONTROLS; i++) + if (ov2650_controls[i].qc.id == id) + return ov2650_controls + i; + return NULL; +} + +static int ov2650_queryctrl(struct v4l2_subdev *sd, + struct v4l2_queryctrl *qc) +{ + struct ov2650_control *octrl; + octrl = ov2650_find_control(qc->id); + if (NULL == octrl) + return -EINVAL; + *qc = octrl->qc; + return 0; +} + +static int ov2650_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct ov2650_control *octrl = ov2650_find_control(ctrl->id); + int ret; + + if (octrl == NULL) + return -EINVAL; + ret = octrl->query(sd, &ctrl->value); + if (ret >= 0) + return 0; + return ret; +} + +static int ov2650_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct ov2650_control *octrl = ov2650_find_control(ctrl->id); + int ret; + + if (octrl == NULL) + return -EINVAL; + ret = octrl->tweak(sd, ctrl->value); + if (ret >= 0) + return 0; + return ret; +} +#if 0 +static int ov2650_get_caps(struct i2c_client *c, struct ci_sensor_caps *caps) +{ + if (caps == NULL) + return -EIO; + + caps->bus_width = SENSOR_BUSWIDTH_8BIT_ZZ; + caps->mode = SENSOR_MODE_BT601; + caps->field_inv = SENSOR_FIELDINV_NOSWAP; + caps->field_sel = SENSOR_FIELDSEL_BOTH; + caps->ycseq = SENSOR_YCSEQ_YCBYCR; + caps->conv422 = SENSOR_CONV422_COSITED; + caps->bpat = SENSOR_BPAT_BGBGGRGR; + caps->hpol = SENSOR_HPOL_REFPOS; + caps->vpol = SENSOR_VPOL_POS; + caps->edge = SENSOR_EDGE_RISING; + caps->bls = SENSOR_BLS_OFF; + caps->gamma = SENSOR_GAMMA_ON; + caps->cconv = SENSOR_CCONV_ON; + caps->res = SENSOR_RES_UXGA | SENSOR_RES_SXGA | SENSOR_RES_SVGA + | SENSOR_RES_VGA | SENSOR_RES_QVGA; + caps->blc = SENSOR_BLC_AUTO; + caps->agc = SENSOR_AGC_AUTO; + caps->awb = SENSOR_AWB_AUTO; + caps->aec = SENSOR_AEC_AUTO; + caps->cie_profile = 0; + caps->flicker_freq = SENSOR_FLICKER_100 | SENSOR_FLICKER_120; + caps->type = SENSOR_TYPE_SOC; + /* caps->name = "ov2650"; */ + strcpy(caps->name, "ov2650"); + + return 0; +} + +static int ov2650_get_config(struct i2c_client *c, + struct ci_sensor_config *config) +{ + struct ci_sensor_config *info = i2c_get_clientdata(c); + + if (config == NULL) { + printk(KERN_WARNING "sensor_get_config: NULL pointer\n"); + return -EIO; + } + + memcpy(config, info, sizeof(struct ci_sensor_config)); + + return 0; +} + +static int ov2650_setup(struct i2c_client *c, + const struct ci_sensor_config *config) +{ + int ret; + struct ov2650_res_struct *res_index; + struct ci_sensor_config *info = i2c_get_clientdata(c); + u16 width, high; + + /* Soft reset camera first*/ + ret = ov2650_write(c, OV2650_SYS, 0x80); + + /* Set registers into default config value */ + ret += ov2650_write_array(c, ov2650_def_reg); + + /* set image resolution */ + ci_sensor_res2size(config->res, &width, &high); + ret += ov2650_try_res(c, &width, &high); + res_index = ov2650_find_res(width, high); + if (res_index->regs) + ret += ov2650_write_array(c, res_index->regs); + if (!ret) + info->res = res_index->res; + + + if (config->blc != info->blc) { + ret += ov2650_t_blc(c, config->blc); + info->blc = config->blc; + } + + if (config->agc != info->agc) { + ret += ov2650_t_agc(c, config->agc); + info->agc = config->agc; + } + + if (config->awb != info->awb) { + ret += ov2650_t_awb(c, config->awb); + info->awb = config->awb; + } + /* Add some delay here to get a better image*/ + if (res_index->res == SENSOR_RES_SXGA || + res_index->res == SENSOR_RES_UXGA) + msleep(2000); + else + msleep(900); + + return ret; +} + +/* + * File operation functions + */ + + + +static int ov2650_open(struct i2c_setting *c, void *priv) +{ + struct i2c_client *client = c->sensor_client; + /* Just wake up sensor */ + if (ov2650_wakeup()) + return -EIO; + ov2650_init(client); + /*Sleep sensor now*/ + ov2650_write(client, 0x3086, 0x0f); + + /* set data pin to input */ + if (ov2650_set_data_pin_in(client)) + return -EIO; + + return 0; +} + +static int ov2650_release(struct i2c_setting *c, void *priv) +{ + /* Just suspend the sensor */ + ov2650_standby(); + return 0; +} + +static int ov2650_on(struct i2c_setting *c) +{ + int ret; + + /* Software wake up sensor */ + ret = ov2650_write(c->sensor_client, 0x3086, 0x00); + + /* set data pin to output */ + return ret + ov2650_set_data_pin_out(c->sensor_client); +} + +static int ov2650_off(struct i2c_setting *c) +{ + int ret; + + /* Software standby sensor */ + ret = ov2650_write(c->sensor_client, 0x3086, 0x0f); + + /* set data pin to input */ + return ret + ov2650_set_data_pin_in(c->sensor_client); +} + +static struct sensor_device ov2650 = { + .name = "OV2650", + .type = SENSOR_TYPE_SOC, + .minor = -1, + .open = ov2650_open, + .release = ov2650_release, + .on = ov2650_on, + .off = ov2650_off, + .querycap = ov2650_get_caps, + .get_config = ov2650_get_config, + .set_config = ov2650_setup, + .enum_parm = ov2650_queryctrl, + .get_parm = ov2650_g_ctrl, + .set_parm = ov2650_s_ctrl, + .try_res = ov2650_try_res, + .set_res = ov2650_set_res, + .suspend = ov2650_standby, + .resume = ov2650_wakeup, + .get_ls_corr_config = NULL, + .set_awb = NULL, + .set_aec = NULL, + .set_blc = NULL, + /* TBC */ +}; +#endif + +static int ov2650_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + DBG_entering; + + + if (enable) { + ov2650_write(client, 0x3086, 0x00); + ov2650_set_data_pin_out(client); + msleep(2000); + } else { + ov2650_write(client, 0x3086, 0x0f); + ov2650_set_data_pin_in(client); + } + + DBG_leaving; + return 0; +} + +static int ov2650_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_frmsizeenum *fsize) +{ + unsigned int index = fsize->index; + + DBG_entering; + + if (index >= N_RES) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = ov2650_res[index].width; + fsize->discrete.height = ov2650_res[index].height; + fsize->reserved[0] = ov2650_res[index].used; + + DBG_leaving; + + return 0; +} + +static int ov2650_enum_frameintervals(struct v4l2_subdev *sd, + struct v4l2_frmivalenum *fival) +{ + unsigned int index = fival->index; + + DBG_entering; + + if (index >= N_RES) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1; + fival->discrete.denominator = ov2650_res[index].fps; + + DBG_leaving; + + return 0; +} +static int ov2650_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + +#define V4L2_IDENT_OV2650 8244 + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_OV2650, 0); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov2650_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char val = 0; + int ret; + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ret = ov2650_read(client, reg->reg & 0xffff, &val); + reg->val = val; + reg->size = 1; + return ret; +} + +static int ov2650_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ov2650_write(client, reg->reg & 0xffff, reg->val & 0xff); + return 0; +} +#endif + +static const struct v4l2_subdev_video_ops ov2650_video_ops = { + .try_fmt = ov2650_try_fmt, + .s_fmt = ov2650_set_fmt, + .g_fmt = ov2650_get_fmt, + .s_stream = ov2650_s_stream, + .enum_framesizes = ov2650_enum_framesizes, + .enum_frameintervals = ov2650_enum_frameintervals, +}; + +static const struct v4l2_subdev_core_ops ov2650_core_ops = { + .g_chip_ident = ov2650_g_chip_ident, + .queryctrl = ov2650_queryctrl, + .g_ctrl = ov2650_g_ctrl, + .s_ctrl = ov2650_s_ctrl, + .s_gpio = ov2650_s_power, + /*.g_ext_ctrls = ov2650_g_ext_ctrls,*/ + /*.s_ext_ctrls = ov2650_s_ext_ctrls,*/ +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov2650_g_register, + .s_register = ov2650_s_register, +#endif +}; + +static const struct v4l2_subdev_ops ov2650_ops = { + .core = &ov2650_core_ops, + .video = &ov2650_video_ops, +}; + +/* + * Basic i2c stuff + */ +#if 0 +static unsigned short normal_i2c[] = {I2C_OV2650 >> 1, I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +static struct i2c_driver ov2650_driver; +#endif +static int ov2650_detect(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int adap_id = i2c_adapter_id(adapter); + u8 value; + + printk(KERN_WARNING "Now start ov2650 detect\n"); + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return -ENODEV; + + if (adap_id != 1) + return -ENODEV; + + /* if (ov2650_wakeup()) */ + /* return -ENODEV; */ + ov2650_wakeup(); + + ov2650_read(client, OV2650_PID_L, &value); + if (value != 0x52) + return -ENODEV; + + return 0; +} + +static int ov2650_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ci_sensor_config *info; + struct v4l2_subdev *sd; + int ret = -1; + + DBG_entering; + + printk(KERN_INFO "Init ov2650 sensor \n"); + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + /* + * Setup sensor configuration structure + */ + info = kzalloc(sizeof(struct ci_sensor_config), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = ov2650_detect(client); + if (ret) { + kfree(info); + return -ENODEV; + } + + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &ov2650_ops); + + /* + * TODO: Need to check if this can be here. + * Turn into standby mode + */ + /* ov2650_standby(); */ + ret += ov2650_init(client); + ov2650_standby(); + + printk(KERN_INFO "Init ov2650 sensor success, ret = %d\n", ret); + + DBG_leaving; + return 0; +} + +static int ov2650_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + kfree(to_sensor_config(sd)); + return 0; +} + +static const struct i2c_device_id ov2650_id[] = { + {"ov2650", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ov2650_id); + +static struct v4l2_i2c_driver_data v4l2_i2c_data = { + .name = "ov2650", + .probe = ov2650_probe, + .remove = ov2650_remove, + /* .suspend = ov2650_suspend, + * .resume = ov2650_resume, */ + .id_table = ov2650_id, +}; + +MODULE_AUTHOR("Xiaolin Zhang "); +MODULE_DESCRIPTION("A low-level driver for OmniVision 2650 sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mrstci/mrstov2650/ov2650.h b/drivers/media/video/mrstci/mrstov2650/ov2650.h new file mode 100644 index 0000000..f5c0418 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov2650/ov2650.h @@ -0,0 +1,766 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#define I2C_OV2650 0x60 +/* Should add to kernel source */ +#define I2C_DRIVERID_OV2650 1047 +/* GPIO pin on Moorestown */ +#define GPIO_SCLK_25 44 +#define GPIO_STB_PIN 47 +#define GPIO_STDBY_PIN 48 +#define GPIO_RESET_PIN 50 + +/* System control register */ +#define OV2650_AGC 0x3000 +#define OV2650_AGCS 0x3001 +#define OV2650_AEC_H 0x3002 +#define OV2650_AEC_L 0x3003 +#define OV2650_AECL 0x3004 +#define OV2650_AECS_H 0x3008 +#define OV2650_AECS_L 0x3009 +#define OV2650_PID_H 0x300A +#define OV2650_PID_L 0x300B +#define OV2650_SCCB 0x300C +#define OV2650_PCLK 0x300D +#define OV2650_PLL_1 0x300E +#define OV2650_PLL_2 0x300F +#define OV2650_PLL_3 0x3010 +#define OV2650_CLK 0x3011 +#define OV2650_SYS 0x3012 +#define OV2650_AUTO_1 0x3013 +#define OV2650_AUTO_2 0x3014 +#define OV2650_AUTO_3 0x3015 +#define OV2650_AUTO_4 0x3016 +#define OV2650_AUTO_5 0x3017 +#define OV2650_WPT 0x3018 +#define OV2650_BPT 0x3019 +#define OV2650_VPT 0x301A +#define OV2650_YAVG 0x301B +#define OV2650_AECG_50 0x301C +#define OV2650_AECG_60 0x301D +#define OV2650_RZM_H 0x301E +#define OV2650_RZM_L 0x301F +#define OV2650_HS_H 0x3020 +#define OV2650_HS_L 0x3021 +#define OV2650_VS_H 0x3022 +#define OV2650_VS_L 0x3023 +#define OV2650_HW_H 0x3024 +#define OV2650_HW_L 0x3025 +#define OV2650_VH_H 0x3026 +#define OV2650_VH_L 0x3027 +#define OV2650_HTS_H 0x3028 +#define OV2650_HTS_L 0x3029 +#define OV2650_VTS_H 0x302A +#define OV2650_VTS_L 0x302B +#define OV2650_EXHTS 0x302C +#define OV2650_EXVTS_H 0x302D +#define OV2650_EXVTS_L 0x302E +#define OV2650_WET_0 0x3030 +#define OV2650_WET_1 0x3031 +#define OV2650_WET_2 0x3032 +#define OV2650_WET_3 0x3033 +#define OV2650_AHS_H 0x3038 +#define OV2650_AHS_L 0x3039 +#define OV2650_AVS_H 0x303A +#define OV2650_AVS_L 0x303B +#define OV2650_AHW_H 0x303C +#define OV2650_AHW_L 0x303D +#define OV2650_AVH_H 0x303E +#define OV2650_AVH_L 0x303F +#define OV2650_HISTO_0 0x3040 +#define OV2650_HISTO_1 0x3041 +#define OV2650_HISTO_2 0x3042 +#define OV2650_HISTO_3 0x3043 +#define OV2650_HISTO_4 0x3044 +#define OV2650_BLC9A 0x3069 +#define OV2650_BLCC 0x306C +#define OV2650_BLCD 0x306D +#define OV2650_BLCF 0x306F +#define OV2650_BD50_L 0x3070 +#define OV2650_BD50_H 0x3071 +#define OV2650_BD60_L 0x3072 +#define OV2650_BD60_H 0x3073 +#define OV2650_TMC_0 0x3076 +#define OV2650_TMC_1 0x3077 +#define OV2650_TMC_2 0x3078 +#define OV2650_TMC_4 0x307A +#define OV2650_TMC_6 0x307C +#define OV2650_TMC_8 0x307E +#define OV2650_TMC_I2C 0x3084 +#define OV2650_TMC_10 0x3086 +#define OV2650_TMC_11 0x3087 +#define OV2650_ISP_XO_H 0x3088 +#define OV2650_ISP_XO_L 0x3089 +#define OV2650_ISP_YO_H 0x308A +#define OV2650_ISP_YO_L 0x308B +#define OV2650_TMC_12 0x308C +#define OV2650_TMC_13 0x308D +#define OV2650_EFUSE 0x308F +#define OV2650_IO_CTL_0 0x30B0 +#define OV2650_IO_CRL_1 0x30B1 +#define OV2650_IO_CTL_2 0x30B2 +#define OV2650_LAEC 0x30F0 +#define OV2650_GRP_EOP 0x30FF + +/* SC registers */ +#define OV2650_SC_CTL_0 0x3100 +#define OV2650_SC_SYN_CTL_0 0x3104 +#define OV2650_SC_SYN_CTL_1 0x3105 +#define OV2650_SC_SYN_CTL_3 0x3107 +#define OV2650_SC_SYN_CTL_4 0x3108 + +/* DSP control register */ +#define OV2650_ISP_CTL_0 0x3300 +#define OV2650_ISP_CTL_1 0x3301 +#define OV2650_ISP_CTL_2 0x3302 +#define OV2650_ISP_CTL_3 0x3303 +#define OV2650_ISP_CTL_4 0x3304 +#define OV2650_ISP_CTL_5 0x3305 +#define OV2650_ISP_CTL_6 0x3306 +#define OV2650_ISP_CTL_7 0x3307 +#define OV2650_ISP_CTL_8 0x3308 +#define OV2650_ISP_CTL_9 0x3309 +#define OV2650_ISP_CTL_A 0x330A +#define OV2650_ISP_CTL_B 0x330B +#define OV2650_ISP_CTL_10 0x3310 +#define OV2650_ISP_CTL_11 0x3311 +#define OV2650_ISP_CTL_12 0x3312 +#define OV2650_ISP_CTL_13 0x3313 +#define OV2650_ISP_CTL_14 0x3314 +#define OV2650_ISP_CTL_15 0x3315 +#define OV2650_ISP_CTL_16 0x3316 +#define OV2650_ISP_CTL_17 0x3317 +#define OV2650_ISP_CTL_18 0x3318 +#define OV2650_ISP_CTL_19 0x3319 +#define OV2650_ISP_CTL_1A 0x331A +#define OV2650_ISP_CTL_1B 0x331B +#define OV2650_ISP_CTL_1C 0x331C +#define OV2650_ISP_CTL_1D 0x331D +#define OV2650_ISP_CTL_1E 0x331E +#define OV2650_ISP_CTL_20 0x3320 +#define OV2650_ISP_CTL_21 0x3321 +#define OV2650_ISP_CTL_22 0x3322 +#define OV2650_ISP_CTL_23 0x3323 +#define OV2650_ISP_CTL_24 0x3324 +#define OV2650_ISP_CTL_27 0x3327 +#define OV2650_ISP_CTL_28 0x3328 +#define OV2650_ISP_CTL_29 0x3329 +#define OV2650_ISP_CTL_2A 0x332A +#define OV2650_ISP_CTL_2B 0x332B +#define OV2650_ISP_CTL_2C 0x332C +#define OV2650_ISP_CTL_2D 0x332D +#define OV2650_ISP_CTL_2E 0x332E +#define OV2650_ISP_CTL_2F 0x332F +#define OV2650_ISP_CTL_30 0x3330 +#define OV2650_ISP_CTL_31 0x3331 +#define OV2650_ISP_CTL_32 0x3332 +#define OV2650_ISP_CTL_33 0x3333 +#define OV2650_ISP_CTL_34 0x3334 +#define OV2650_ISP_CTL_35 0x3335 +#define OV2650_ISP_CTL_36 0x3336 +#define OV2650_ISP_CTL_40 0x3340 +#define OV2650_ISP_CTL_41 0x3341 +#define OV2650_ISP_CTL_42 0x3342 +#define OV2650_ISP_CTL_43 0x3343 +#define OV2650_ISP_CTL_44 0x3344 +#define OV2650_ISP_CTL_45 0x3345 +#define OV2650_ISP_CTL_46 0x3346 +#define OV2650_ISP_CTL_47 0x3347 +#define OV2650_ISP_CTL_48 0x3348 +#define OV2650_ISP_CTL_49 0x3349 +#define OV2650_ISP_CTL_4A 0x334A +#define OV2650_ISP_CTL_4B 0x334B +#define OV2650_ISP_CTL_4C 0x334C +#define OV2650_ISP_CTL_4D 0x334D +#define OV2650_ISP_CTL_4E 0x334E +#define OV2650_ISP_CTL_4F 0x334F +#define OV2650_ISP_CTL_50 0x3350 +#define OV2650_ISP_CTL_51 0x3351 +#define OV2650_ISP_CTL_52 0x3352 +#define OV2650_ISP_CTL_53 0x3353 +#define OV2650_ISP_CTL_54 0x3354 +#define OV2650_ISP_CTL_55 0x3355 +#define OV2650_ISP_CTL_56 0x3356 +#define OV2650_ISP_CTL_57 0x3357 +#define OV2650_ISP_CTL_58 0x3358 +#define OV2650_ISP_CTL_59 0x3359 +#define OV2650_ISP_CTL_5A 0x335A +#define OV2650_ISP_CTL_5B 0x335B +#define OV2650_ISP_CTL_5C 0x335C +#define OV2650_ISP_CTL_5D 0x335D +#define OV2650_ISP_CTL_5E 0x335E +#define OV2650_ISP_CTL_5F 0x335F +#define OV2650_ISP_CTL_60 0x3360 +#define OV2650_ISP_CTL_61 0x3361 +#define OV2650_ISP_CTL_62 0x3362 +#define OV2650_ISP_CTL_63 0x3363 +#define OV2650_ISP_CTL_64 0x3364 +#define OV2650_ISP_CTL_65 0x3365 +#define OV2650_ISP_CTL_6A 0x336A +#define OV2650_ISP_CTL_6B 0x336B +#define OV2650_ISP_CTL_6C 0x336C +#define OV2650_ISP_CTL_6E 0x336E +#define OV2650_ISP_CTL_71 0x3371 +#define OV2650_ISP_CTL_72 0x3372 +#define OV2650_ISP_CTL_73 0x3373 +#define OV2650_ISP_CTL_74 0x3374 +#define OV2650_ISP_CTL_75 0x3375 +#define OV2650_ISP_CTL_76 0x3376 +#define OV2650_ISP_CTL_77 0x3377 +#define OV2650_ISP_CTL_78 0x3378 +#define OV2650_ISP_CTL_79 0x3379 +#define OV2650_ISP_CTL_7A 0x337A +#define OV2650_ISP_CTL_7B 0x337B +#define OV2650_ISP_CTL_7C 0x337C +#define OV2650_ISP_CTL_80 0x3380 +#define OV2650_ISP_CTL_81 0x3381 +#define OV2650_ISP_CTL_82 0x3382 +#define OV2650_ISP_CTL_83 0x3383 +#define OV2650_ISP_CTL_84 0x3384 +#define OV2650_ISP_CTL_85 0x3385 +#define OV2650_ISP_CTL_86 0x3386 +#define OV2650_ISP_CTL_87 0x3387 +#define OV2650_ISP_CTL_88 0x3388 +#define OV2650_ISP_CTL_89 0x3389 +#define OV2650_ISP_CTL_8A 0x338A +#define OV2650_ISP_CTL_8B 0x338B +#define OV2650_ISP_CTL_8C 0x338C +#define OV2650_ISP_CTL_8D 0x338D +#define OV2650_ISP_CTL_8E 0x338E +#define OV2650_ISP_CTL_90 0x3390 +#define OV2650_ISP_CTL_91 0x3391 +#define OV2650_ISP_CTL_92 0x3392 +#define OV2650_ISP_CTL_93 0x3393 +#define OV2650_ISP_CTL_94 0x3394 +#define OV2650_ISP_CTL_95 0x3395 +#define OV2650_ISP_CTL_96 0x3396 +#define OV2650_ISP_CTL_97 0x3397 +#define OV2650_ISP_CTL_98 0x3398 +#define OV2650_ISP_CTL_99 0x3399 +#define OV2650_ISP_CTL_9A 0x339A +#define OV2650_ISP_CTL_A0 0x33A0 +#define OV2650_ISP_CTL_A1 0x33A1 +#define OV2650_ISP_CTL_A2 0x33A2 +#define OV2650_ISP_CTL_A3 0x33A3 +#define OV2650_ISP_CTL_A4 0x33A4 +#define OV2650_ISP_CTL_A5 0x33A5 +#define OV2650_ISP_CTL_A6 0x33A6 +#define OV2650_ISP_CTL_A7 0x33A7 +#define OV2650_ISP_CTL_A8 0x33A8 +#define OV2650_ISP_CTL_AA 0x33AA +#define OV2650_ISP_CTL_AB 0x33AB +#define OV2650_ISP_CTL_AC 0x33AC +#define OV2650_ISP_CTL_AD 0x33AD +#define OV2650_ISP_CTL_AE 0x33AE +#define OV2650_ISP_CTL_AF 0x33AF +#define OV2650_ISP_CTL_B0 0x33B0 +#define OV2650_ISP_CTL_B1 0x33B1 +#define OV2650_ISP_CTL_B2 0x33B2 +#define OV2650_ISP_CTL_B3 0x33B3 +#define OV2650_ISP_CTL_B4 0x33B4 +#define OV2650_ISP_CTL_B5 0x33B5 +#define OV2650_ISP_CTL_B6 0x33B6 +#define OV2650_ISP_CTL_B7 0x33B7 +#define OV2650_ISP_CTL_B8 0x33B8 +#define OV2650_ISP_CTL_B9 0x33B9 + +/* Format register */ +#define OV2650_FMT_CTL_0 0x3400 +#define OV2650_FMT_CTL_1 0x3401 +#define OV2650_FMT_CTL_2 0x3402 +#define OV2650_FMT_CTL_3 0x3403 +#define OV2650_FMT_CTL_4 0x3404 +#define OV2650_FMT_CTL_5 0x3405 +#define OV2650_FMT_CTL_6 0x3406 +#define OV2650_FMT_CTL_7 0x3407 +#define OV2650_FMT_CTL_8 0x3408 +#define OV2650_DITHER_CTL 0x3409 +#define OV2650_DVP_CTL_0 0x3600 +#define OV2650_DVP_CTL_1 0x3601 +#define OV2650_DVP_CTL_6 0x3606 +#define OV2650_DVP_CTL_7 0x3607 +#define OV2650_DVP_CTL_9 0x3609 +#define OV2650_DVP_CTL_B 0x360B + +/* General definition for ov2650 */ +#define OV2650_OUTWND_MAX_H UXGA_SIZE_H +#define OV2650_OUTWND_MAX_V UXGA_SIZE_V + +struct regval_list { + u16 reg_num; + u8 value; +}; + +/* + * Default register value + * 1600x1200 YUV + */ +static struct regval_list ov2650_def_reg[] = { + {0x3012, 0x80}, + {0x308c, 0x80}, + {0x308d, 0x0e}, + {0x360b, 0x00}, + {0x30b0, 0xff}, + {0x30b1, 0xff}, + {0x30b2, 0x27}, + + {0x300e, 0x34}, + {0x300f, 0xa6}, + {0x3010, 0x81}, + {0x3082, 0x01}, + {0x30f4, 0x01}, + {0x3090, 0x33}, + {0x3091, 0xc0}, + {0x30ac, 0x42}, + + {0x30d1, 0x08}, + {0x30a8, 0x56}, + {0x3015, 0x03}, + {0x3093, 0x00}, + {0x307e, 0xe5}, + {0x3079, 0x00}, + {0x30aa, 0x42}, + {0x3017, 0x40}, + {0x30f3, 0x82}, + {0x306a, 0x0c}, + {0x306d, 0x00}, + {0x336a, 0x3c}, + {0x3076, 0x6a}, + {0x30d9, 0x8c}, + {0x3016, 0x82}, + {0x3601, 0x30}, + {0x304e, 0x88}, + {0x30f1, 0x82}, + {0x3011, 0x02}, + + {0x3013, 0xf7}, + {0x301c, 0x13}, + {0x301d, 0x17}, + {0x3070, 0x3e}, + {0x3072, 0x34}, + + {0x30af, 0x00}, + {0x3048, 0x1f}, + {0x3049, 0x4e}, + {0x304a, 0x20}, + {0x304f, 0x20}, + {0x304b, 0x02}, + {0x304c, 0x00}, + {0x304d, 0x02}, + {0x304f, 0x20}, + {0x30a3, 0x10}, + {0x3013, 0xf7}, + {0x3014, 0x44}, + {0x3071, 0x00}, + {0x3070, 0x3e}, + {0x3073, 0x00}, + {0x3072, 0x34}, + {0x301c, 0x12}, + {0x301d, 0x16}, + {0x304d, 0x42}, + {0x304a, 0x40}, + {0x304f, 0x40}, + {0x3095, 0x07}, + {0x3096, 0x16}, + {0x3097, 0x1d}, + + {0x3020, 0x01}, + {0x3021, 0x18}, + {0x3022, 0x00}, + {0x3023, 0x0a}, + {0x3024, 0x06}, + {0x3025, 0x58}, + {0x3026, 0x04}, + {0x3027, 0xbc}, + {0x3088, 0x06}, + {0x3089, 0x40}, + {0x308a, 0x04}, + {0x308b, 0xb0}, + {0x3316, 0x64}, + {0x3317, 0x4b}, + {0x3318, 0x00}, + {0x331a, 0x64}, + {0x331b, 0x4b}, + {0x331c, 0x00}, + {0x3100, 0x00}, + + {0x3320, 0xfa}, + {0x3321, 0x11}, + {0x3322, 0x92}, + {0x3323, 0x01}, + {0x3324, 0x97}, + {0x3325, 0x02}, + {0x3326, 0xff}, + {0x3327, 0x0c}, + {0x3328, 0x10}, + {0x3329, 0x10}, + {0x332a, 0x58}, + {0x332b, 0x50}, + {0x332c, 0xbe}, + {0x332d, 0xe1}, + {0x332e, 0x43}, + {0x332f, 0x36}, + {0x3330, 0x4d}, + {0x3331, 0x44}, + {0x3332, 0xf8}, + {0x3333, 0x0a}, + {0x3334, 0xf0}, + {0x3335, 0xf0}, + {0x3336, 0xf0}, + {0x3337, 0x40}, + {0x3338, 0x40}, + {0x3339, 0x40}, + {0x333a, 0x00}, + {0x333b, 0x00}, + + {0x3380, 0x28}, + {0x3381, 0x48}, + {0x3382, 0x10}, + {0x3383, 0x23}, + {0x3384, 0xc0}, + {0x3385, 0xe5}, + {0x3386, 0xc2}, + {0x3387, 0xb3}, + {0x3388, 0x0e}, + {0x3389, 0x98}, + {0x338a, 0x01}, + + {0x3340, 0x0e}, + {0x3341, 0x1a}, + {0x3342, 0x31}, + {0x3343, 0x45}, + {0x3344, 0x5a}, + {0x3345, 0x69}, + {0x3346, 0x75}, + {0x3347, 0x7e}, + {0x3348, 0x88}, + {0x3349, 0x96}, + {0x334a, 0xa3}, + {0x334b, 0xaf}, + {0x334c, 0xc4}, + {0x334d, 0xd7}, + {0x334e, 0xe8}, + {0x334f, 0x20}, + + {0x3350, 0x32}, + {0x3351, 0x25}, + {0x3352, 0x80}, + {0x3353, 0x1e}, + {0x3354, 0x00}, + {0x3355, 0x85}, + {0x3356, 0x32}, + {0x3357, 0x25}, + {0x3358, 0x80}, + {0x3359, 0x1b}, + {0x335a, 0x00}, + {0x335b, 0x85}, + {0x335c, 0x32}, + {0x335d, 0x25}, + {0x335e, 0x80}, + {0x335f, 0x1b}, + {0x3360, 0x00}, + {0x3361, 0x85}, + {0x3363, 0x70}, + {0x3364, 0x7f}, + {0x3365, 0x00}, + {0x3366, 0x00}, + + {0x3301, 0xff}, + {0x338B, 0x11}, + {0x338c, 0x10}, + {0x338d, 0x40}, + + {0x3370, 0xd0}, + {0x3371, 0x00}, + {0x3372, 0x00}, + {0x3373, 0x40}, + {0x3374, 0x10}, + {0x3375, 0x10}, + {0x3376, 0x04}, + {0x3377, 0x00}, + {0x3378, 0x04}, + {0x3379, 0x80}, + + {0x3069, 0x84}, + {0x307c, 0x10}, + {0x3087, 0x02}, + + {0x3300, 0xfc}, + {0x3302, 0x01}, + {0x3400, 0x00}, + {0x3606, 0x20}, + {0x3601, 0x30}, + {0x30f3, 0x83}, + {0x304e, 0x88}, + + {0x3086, 0x0f}, + {0x3086, 0x00}, + + {0xffff, 0xff}, +}; + +/* 800x600 */ +static struct regval_list ov2650_res_svga[] = { + + {0x306f, 0x14}, + {0x302a, 0x02}, + {0x302b, 0x84}, + {0x3012, 0x10}, + {0x3011, 0x01}, + + {0x3070, 0x5d}, + {0x3072, 0x4d}, + + {0x3014, 0x84}, + {0x301c, 0x07}, + {0x301d, 0x09}, + {0x3070, 0x50}, + {0x3071, 0x00}, + {0x3072, 0x42}, + {0x3073, 0x00}, + + {0x3020, 0x01}, + {0x3021, 0x18}, + {0x3022, 0x00}, + {0x3023, 0x06}, + {0x3024, 0x06}, + {0x3025, 0x58}, + {0x3026, 0x02}, + {0x3027, 0x5e}, + {0x3088, 0x03}, + {0x3089, 0x20}, + {0x308a, 0x02}, + {0x308b, 0x58}, + {0x3316, 0x64}, + {0x3317, 0x25}, + {0x3318, 0x80}, + {0x3319, 0x08}, + {0x331a, 0x64}, + {0x331b, 0x4b}, + {0x331c, 0x00}, + {0x331d, 0x38}, + {0x3100, 0x00}, + + {0x3302, 0x11}, + + {0x3011, 0x01}, + {0x300f, 0xa6}, + {0x300e, 0x36}, + {0x3010, 0x81}, + {0x302e, 0x00}, + {0x302d, 0x00}, + {0x302c, 0x00}, + {0x302b, 0x84}, + {0x3014, 0x84}, + {0x301c, 0x07}, + {0x301d, 0x09}, + {0x3070, 0x50}, + {0x3071, 0x00}, + {0x3072, 0x42}, + {0x3073, 0x00}, + + {0x3086, 0x0f}, + {0x3086, 0x00}, + {0xffff, 0xff}, +}; + +/* 640x480 */ +static struct regval_list ov2650_res_vga_vario[] = { + {0x306f, 0x14}, + {0x302a, 0x02}, + {0x302b, 0x6a}, + {0x3012, 0x10}, + {0x3011, 0x01}, + + {0x3070, 0x5d}, + {0x3072, 0x4d}, + + {0x301c, 0x05}, + {0x301d, 0x06}, + + {0x3020, 0x01}, + {0x3021, 0x18}, + {0x3022, 0x00}, + {0x3023, 0x06}, + {0x3024, 0x06}, + {0x3025, 0x58}, + {0x3026, 0x02}, + {0x3027, 0x61}, + {0x3088, 0x02}, + {0x3089, 0x80}, + {0x308a, 0x01}, + {0x308b, 0xe0}, + {0x3316, 0x64}, + {0x3317, 0x25}, + {0x3318, 0x80}, + {0x3319, 0x08}, + {0x331a, 0x28}, + {0x331b, 0x1e}, + {0x331c, 0x00}, + {0x331d, 0x38}, + {0x3100, 0x00}, + + {0x3302, 0x11}, + {0x3011, 0x00}, + + {0x3014, 0x84}, /* note this */ + {0x3086, 0x0f}, + {0x3086, 0x00}, + {0xffff, 0xff}, +}; + +/* 640x480 reverse */ +/* +static struct regval_list ov2650_res_vga_reverse[] = { + {0x306f, 0x10}, + {0x302a, 0x04}, + {0x302b, 0xd4}, + {0x3012, 0x00}, + {0x3011, 0x02}, + + {0x3070, 0x3e}, + {0x3072, 0x34}, + + {0x301c, 0x12}, + {0x301d, 0x16}, + + {0x3020, 0x01}, + {0x3021, 0x18}, + {0x3022, 0x00}, + {0x3023, 0x0a}, + {0x3024, 0x06}, + {0x3025, 0x58}, + {0x3026, 0x04}, + {0x3027, 0xbc}, + {0x3088, 0x06}, + {0x3089, 0x40}, + {0x308a, 0x04}, + {0x308b, 0xb0}, + {0x3316, 0x64}, + {0x3317, 0xb4}, + {0x3318, 0x00}, + {0x3319, 0x6c}, + {0x331a, 0x64}, + {0x331b, 0x4b}, + {0x331c, 0x00}, + {0x331d, 0x6c}, + {0x3100, 0x00}, + + {0x3302, 0x01}, + {0x3011, 0x02}, + + {0x3014, 0x44}, + {0x3086, 0x0f}, + {0x3086, 0x00}, + {0xffff, 0xff}, +}; + +*/ +/* 320x240 */ +static struct regval_list ov2650_res_qvga[] = { + {0x306f, 0x14}, + {0x302a, 0x02}, + {0x302b, 0x6a}, + + {0x3012, 0x10}, + {0x3011, 0x01}, + + {0x3070, 0x5d}, + {0x3072, 0x4d}, + {0x301c, 0x05}, + {0x301d, 0x06}, + + {0x3023, 0x06}, + {0x3026, 0x02}, + {0x3027, 0x61}, + {0x3088, 0x01}, + {0x3089, 0x40}, + {0x308a, 0x00}, + {0x308b, 0xf0}, + {0x3316, 0x64}, + {0x3317, 0x25}, + {0x3318, 0x80}, + {0x3319, 0x08}, + {0x331a, 0x14}, + {0x331b, 0x0f}, + {0x331c, 0x00}, + {0x331d, 0x38}, + {0x3100, 0x00}, + + {0x3015, 0x02}, /* note this */ + {0x3014, 0x84}, + {0x3302, 0x11}, + {0x3086, 0x0f}, + {0x3086, 0x00}, + {0xffff, 0xff}, +}; + +static struct regval_list ov2650_res_uxga[] = { + /* Note this added by debug */ + {0x3014, 0x84}, + {0x301c, 0x13}, + {0x301d, 0x17}, + {0x3070, 0x40}, + {0x3071, 0x00}, + {0x3072, 0x36}, + {0x3073, 0x00}, + + {0xffff, 0xff}, +}; + +static struct regval_list ov2650_res_sxga[] = { + {0x3011, 0x02}, + + {0x3020, 0x01}, + {0x3021, 0x18}, + {0x3022, 0x00}, + {0x3023, 0x0a}, + {0x3024, 0x06}, + {0x3025, 0x58}, + {0x3026, 0x04}, + {0x3027, 0xbc}, + {0x3088, 0x05}, + {0x3089, 0x00}, + {0x308a, 0x04}, + {0x308b, 0x00}, + {0x3316, 0x64}, + {0x3317, 0x4b}, + {0x3318, 0x00}, + {0x331a, 0x50}, + {0x331b, 0x40}, + {0x331c, 0x00}, + + {0x3302, 0x11}, + + {0x3014, 0x84}, + {0x301c, 0x13}, + {0x301d, 0x17}, + {0x3070, 0x40}, + {0x3071, 0x00}, + {0x3072, 0x36}, + {0x3073, 0x00}, + + {0x3086, 0x0f}, + {0x3086, 0x00}, + {0xffff, 0xff}, +}; diff --git a/drivers/media/video/mrstci/mrstov5630/Kconfig b/drivers/media/video/mrstci/mrstov5630/Kconfig new file mode 100644 index 0000000..a28ddc2 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov5630/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_MRST_OV5630 + tristate "Moorestown OV5630 RAW Sensor" + depends on I2C && VIDEO_MRST_ISP + + ---help--- + Say Y here if your platform support OV5630 RAW Sensor. + + To compile this driver as a module, choose M here: the + module will be called mrstov2650.ko. diff --git a/drivers/media/video/mrstci/mrstov5630/Makefile b/drivers/media/video/mrstci/mrstov5630/Makefile new file mode 100644 index 0000000..c67abff --- /dev/null +++ b/drivers/media/video/mrstci/mrstov5630/Makefile @@ -0,0 +1,4 @@ +mrstov5630-objs = ov5630.o +obj-$(CONFIG_VIDEO_MRST_OV5630) += mrstov5630.o + +EXTRA_CFLAGS += -I$(src)/../include diff --git a/drivers/media/video/mrstci/mrstov5630/ov5630.c b/drivers/media/video/mrstci/mrstov5630/ov5630.c new file mode 100644 index 0000000..6498153 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov5630/ov5630.c @@ -0,0 +1,1153 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ci_sensor_common.h" +#include "ov5630.h" + +static int mrstov5630_debug; +module_param(mrstov5630_debug, int, 0644); +MODULE_PARM_DESC(mrstov5630_debug, "Debug level (0-1)"); + +#define dprintk(level, fmt, arg...) do { \ + if (mrstov5630_debug >= level) \ + printk(KERN_DEBUG "mrstisp@%s: " fmt "\n", \ + __func__, ## arg); } \ + while (0) + +#define eprintk(fmt, arg...) \ + printk(KERN_ERR "mrstisp@%s: line %d: " fmt "\n", \ + __func__, __LINE__, ## arg); + +#define DBG_entering dprintk(2, "entering"); +#define DBG_leaving dprintk(2, "leaving"); +#define DBG_line dprintk(2, " line: %d", __LINE__); + +static inline struct ci_sensor_config *to_sensor_config(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ci_sensor_config, sd); +} + +/* static int ov5630_set_res(struct i2c_client *c, const int w, const int h); + */ +static struct ov5630_format_struct { + __u8 *desc; + __u32 pixelformat; + struct regval_list *regs; +} ov5630_formats[] = { + { + .desc = "Raw RGB Bayer", + .pixelformat = SENSOR_MODE_BAYER, + .regs = NULL, + }, +}; +#define N_OV5630_FMTS ARRAY_SIZE(ov5630_formats) + +static struct ov5630_res_struct { + __u8 *desc; + int res; + int width; + int height; + /* FIXME: correct the fps values.. */ + int fps; + bool used; + struct regval_list *regs; +} ov5630_res[] = { + { + .desc = "QSXGA_PLUS4", + .res = SENSOR_RES_QXGA_PLUS, + .width = 2592, + .height = 1944, + .fps = 15, + .used = 0, + .regs = ov5630_res_qsxga_plus4, + }, + { + .desc = "1080P", + .res = SENSOR_RES_1080P, + .width = 1920, + .height = 1080, + .fps = 25, + .used = 0, + .regs = ov5630_res_1080p, + }, + { + .desc = "XGA_PLUS", + .res = SENSOR_RES_XGA_PLUS, + .width = 1280, + .height = 960, + .fps = 30, + .used = 0, + .regs = ov5630_res_xga_plus, + }, + { + .desc = "720p", + .res = SENSOR_RES_720P, + .width = 1280, + .height = 720, + .fps = 34, + .used = 0, + .regs = ov5630_res_720p, + }, + { + .desc = "VGA", + .res = SENSOR_RES_VGA, + .width = 640, + .height = 480, + .fps = 39, + .used = 0, + .regs = ov5630_res_vga_ac04_bill, + }, +}; + +#define N_RES (ARRAY_SIZE(ov5630_res)) + +/* + * I2C Read & Write stuff + */ +static int ov5630_read(struct i2c_client *c, u32 reg, u32 *value) +{ + int ret; + int i; + struct i2c_msg msg[2]; + u8 msgbuf[2]; + u8 ret_val = 0; + *value = 0; + /* Read needs two message to go */ + memset(&msg, 0, sizeof(msg)); + msgbuf[0] = 0; + msgbuf[1] = 0; + i = 0; + + msgbuf[i++] = ((u16)reg) >> 8; + msgbuf[i++] = ((u16)reg) & 0xff; + msg[0].addr = c->addr; + msg[0].buf = msgbuf; + msg[0].len = i; + + msg[1].addr = c->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = &ret_val; + msg[1].len = 1; + + ret = i2c_transfer(c->adapter, &msg[0], 2); + *value = ret_val; + + ret = (ret == 2) ? 0 : -1; + return ret; +} + +static int ov5630_write(struct i2c_client *c, u32 reg, u32 value) +{ + int ret, i; + struct i2c_msg msg; + u8 msgbuf[3]; + + /* Writing only needs one message */ + memset(&msg, 0, sizeof(msg)); + i = 0; + msgbuf[i++] = ((u16)reg) >> 8; + msgbuf[i++] = (u16)reg & 0xff; + msgbuf[i++] = (u8)value; + + msg.addr = c->addr; + msg.flags = 0; + msg.buf = msgbuf; + msg.len = i; + + ret = i2c_transfer(c->adapter, &msg, 1); + + /* If this is a reset register, wait for 1ms */ + if (reg == OV5630_SYS && (value & 0x80)) + msleep(3); + + ret = (ret == 1) ? 0 : -1; + return ret; +} + +static int ov5630_write_array(struct i2c_client *c, struct regval_list *vals) +{ + struct regval_list *p; + u32 read_val = 0; + int err_num = 0; + int i = 0; + p = vals; + while (p->reg_num != 0xffff) { + ov5630_write(c, (u32)p->reg_num, (u32)p->value); + ov5630_read(c, (u32)p->reg_num, &read_val); + if (read_val != p->value) + err_num++; + p++; + i++; + } + return 0; +} + +/* + * Sensor specific helper function + */ +static int ov5630_standby(void) +{ + gpio_set_value(GPIO_STDBY_PIN, 1); + /* ov5630_motor_standby(); */ + dprintk(1, "PM: standby called\n"); + return 0; +} + +static int ov5630_wakeup(void) +{ + gpio_set_value(GPIO_STDBY_PIN, 0); + /* ov5630_motor_wakeup(); */ + dprintk(1, "PM: wakeup called\n"); + return 0; +} + +static int ov5630_s_power(struct v4l2_subdev *sd, u32 val) +{ + if (val == 1) + ov5630_standby(); + if (val == 0) + ov5630_wakeup(); + return 0; +} + +static int ov5630_set_img_ctrl(struct i2c_client *c, + const struct ci_sensor_config *config) +{ + int err = 0; + u32 reg_val = 0; + /* struct ci_sensor_config *info = i2c_get_clientdata(c); */ + + switch (config->blc) { + case SENSOR_BLC_OFF: + err |= ov5630_read(c, OV5630_ISP_CTL00, ®_val); + err |= ov5630_write(c, OV5630_ISP_CTL00, reg_val & 0xFE); + break; + case SENSOR_BLC_AUTO: + err |= ov5630_read(c, OV5630_ISP_CTL00, ®_val); + err |= ov5630_write(c, OV5630_ISP_CTL00, reg_val | 0x01); + break; + } + + switch (config->agc) { + case SENSOR_AGC_AUTO: + err |= ov5630_read(c, OV5630_AUTO_1, ®_val); + err |= ov5630_write(c, OV5630_AUTO_1, reg_val | 0x04); + break; + case SENSOR_AGC_OFF: + err |= ov5630_read(c, OV5630_AUTO_1, ®_val); + err |= ov5630_write(c, OV5630_AUTO_1, reg_val & ~0x04); + break; + } + + switch (config->awb) { + case SENSOR_AWB_AUTO: + err |= ov5630_read(c, OV5630_ISP_CTL00, ®_val); + err |= ov5630_write(c, OV5630_ISP_CTL00, reg_val | 0x30); + break; + case SENSOR_AWB_OFF: + err |= ov5630_read(c, OV5630_ISP_CTL00, ®_val); + err |= ov5630_write(c, OV5630_ISP_CTL00, reg_val & ~0x30); + break; + } + + switch (config->aec) { + case SENSOR_AEC_AUTO: + err |= ov5630_read(c, OV5630_AUTO_1, ®_val); + err |= ov5630_write(c, OV5630_AUTO_1, reg_val | 0xFB); + break; + case SENSOR_AEC_OFF: + err |= ov5630_read(c, OV5630_AUTO_1, ®_val); + err |= ov5630_write(c, OV5630_AUTO_1, reg_val & 0xF6); + break; + } + + return err; +} + +static int ov5630_init(struct i2c_client *c) +{ + int ret; + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct ci_sensor_config *info = to_sensor_config(sd); + char *name = ""; + + /* Fill the configuration structure */ + /* Note this default configuration value */ + info->mode = ov5630_formats[0].pixelformat; + info->res = ov5630_res[0].res; + info->type = SENSOR_TYPE_RAW; + info->bls = SENSOR_BLS_OFF; + info->gamma = SENSOR_GAMMA_OFF; + info->cconv = SENSOR_CCONV_OFF; + info->blc = SENSOR_BLC_AUTO; + info->agc = SENSOR_AGC_AUTO; + info->awb = SENSOR_AWB_AUTO; + info->aec = SENSOR_AEC_AUTO; + /* info->bus_width = SENSOR_BUSWIDTH_10BIT; */ + info->bus_width = SENSOR_BUSWIDTH_10BIT_ZZ; + info->ycseq = SENSOR_YCSEQ_YCBYCR; + /* info->conv422 = SENSOR_CONV422_NOCOSITED; */ + info->conv422 = SENSOR_CONV422_COSITED; + info->bpat = SENSOR_BPAT_BGBGGRGR; + info->field_inv = SENSOR_FIELDINV_NOSWAP; + info->field_sel = SENSOR_FIELDSEL_BOTH; + info->hpol = SENSOR_HPOL_REFPOS; + info->vpol = SENSOR_VPOL_NEG; + info->edge = SENSOR_EDGE_RISING; + info->flicker_freq = SENSOR_FLICKER_100; + info->cie_profile = SENSOR_CIEPROF_F11; + name = "ov5630"; + memcpy(info->name, name, 7); + + /* Reset sensor hardware, and implement the setting*/ + ret = ov5630_write(c, (u32)OV5630_SYS, (u32)0x80); + ret += ov5630_write(c, (u32)OV5630_IMAGE_SYSTEM, (u32)0x00); + + /* Set registers into default config value */ + ret += ov5630_write_array(c, ov5630_def_reg); + + /* Set MIPI interface */ +#ifdef OV5630_MIPI + ret += ov5630_write_array(c, ov5630_mipi); +#endif + + /* turn off AE AEB AGC */ + ret += ov5630_set_img_ctrl(c, info); + + /* streaming */ + /* ret += ov5630_write(c, (u32)OV5630_IMAGE_SYSTEM, (u32)0x01); */ + /* ret += ov5630_write(c, (u32)0x3096, (u32)0x50); */ + /* /ssleep(1); */ + + /* Added by wen to stop sensor from streaming */ + ov5630_write(c, (u32)OV5630_IMAGE_SYSTEM, (u32)0x00); + ov5630_write(c, 0x30b0, 0x00); + ov5630_write(c, 0x30b1, 0x00); + return ret; +} + +static int distance(struct ov5630_res_struct *res, u32 w, u32 h) +{ + int ret; + if (res->width < w || res->height < h) + return -1; + + ret = ((res->width - w) + (res->height - h)); + return ret; +} +static int ov5630_try_res(u32 *w, u32 *h) +{ + struct ov5630_res_struct *res_index, *p = NULL; + int dis, last_dis = ov5630_res->width + ov5630_res->height; + + DBG_entering; + + for (res_index = ov5630_res; + res_index < ov5630_res + N_RES; + res_index++) { + if ((res_index->width < *w) || (res_index->height < *h)) + break; + dis = distance(res_index, *w, *h); + if (dis < last_dis) { + last_dis = dis; + p = res_index; + } + } + + if (p == NULL) + p = ov5630_res; + else if ((p->width < *w) || (p->height < *h)) { + if (p != ov5630_res) + p--; + } + + if ((w != NULL) && (h != NULL)) { + *w = p->width; + *h = p->height; + } + + DBG_leaving; + return 0; +} + +static struct ov5630_res_struct *ov5630_to_res(u32 w, u32 h) +{ + struct ov5630_res_struct *res_index; + + for (res_index = ov5630_res; + res_index < ov5630_res + N_RES; + res_index++) + if ((res_index->width == w) && (res_index->height == h)) + break; + + if (res_index >= ov5630_res + N_RES) + res_index--; /* Take the bigger one */ + + return res_index; +} + +static int ov5630_try_fmt(struct v4l2_subdev *sd, + struct v4l2_format *fmt) +{ + DBG_entering; + return ov5630_try_res(&fmt->fmt.pix.width, &fmt->fmt.pix.height); + DBG_leaving; +} + +static int ov5630_get_fmt(struct v4l2_subdev *sd, + struct v4l2_format *fmt) +{ + struct ci_sensor_config *info = to_sensor_config(sd); + unsigned short width, height; + int index; + + ci_sensor_res2size(info->res, &width, &height); + + /* Marked the current sensor res as being "used" */ + for (index = 0; index < N_RES; index++) { + if ((width == ov5630_res[index].width) && + (height == ov5630_res[index].height)) { + ov5630_res[index].used = 1; + continue; + } + ov5630_res[index].used = 0; + } + + fmt->fmt.pix.width = width; + fmt->fmt.pix.height = height; + return 0; +} + +static int ov5630_set_fmt(struct v4l2_subdev *sd, struct v4l2_format *fmt) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + struct ci_sensor_config *info = to_sensor_config(sd); + int ret = 0; + struct ov5630_res_struct *res_index; + u32 width, height; + int index; + + DBG_entering; + + width = fmt->fmt.pix.width; + height = fmt->fmt.pix.height; + + dprintk(1, "was told to set fmt (%d x %d) ", width, height); + + ret = ov5630_try_res(&width, &height); + + dprintk(1, "setting fmt (%d x %d) ", width, height); + + res_index = ov5630_to_res(width, height); + + ov5630_wakeup(); + + if (res_index->regs) { + /* Soft reset camera first*/ + ret = ov5630_write(c, (u32)OV5630_SYS, (u32)0x80); + + /* software sleep/standby */ + ret += ov5630_write(c, (u32)OV5630_IMAGE_SYSTEM, (u32)0x00); + + /* Set registers into default config value */ + ret += ov5630_write_array(c, ov5630_def_reg); + + /* set image resolution */ + ret += ov5630_write_array(c, res_index->regs); + + /* turn off AE AEB AGC */ + ret += ov5630_set_img_ctrl(c, info); + + /* Set MIPI interface */ +#ifdef OV5630_MIPI + ret += ov5630_write_array(c, ov5630_mipi); +#endif + + if (res_index->res == SENSOR_RES_VGA) + ret += ov5630_write(c, (u32)0x3015, (u32)0x03); + + /* streaming */ + ret = ov5630_write(c, (u32)OV5630_IMAGE_SYSTEM, (u32)0x01); + ret = ov5630_write(c, (u32)0x3096, (u32)0x50); + + info->res = res_index->res; + + /* Marked current sensor res as being "used" */ + for (index = 0; index < N_RES; index++) { + if ((width == ov5630_res[index].width) && + (height == ov5630_res[index].height)) { + ov5630_res[index].used = 1; + continue; + } + ov5630_res[index].used = 0; + } + + for (index = 0; index < N_RES; index++) + dprintk(2, "index = %d, used = %d\n", index, + ov5630_res[index].used); + } else { + eprintk("no res for (%d x %d)", width, height); + } + + DBG_leaving; + return ret; +} + +static int ov5630_t_gain(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u32 v; + + DBG_entering; + + dprintk(2, "writing gain %x to 0x3000", value); + + ov5630_read(client, 0x3000, &v); + v = (v & 0x80) + value; + ov5630_write(client, 0x3000, v); + + dprintk(2, "gain %x was writen to 0x3000", v); + + DBG_leaving; + return 0; +} + +static int ov5630_t_exposure(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u32 v; + u32 reg_val; + + DBG_entering; + + ov5630_read(client, 0x3013, &v); + dprintk(2, "0x3013 = %x", v); + if (v & 0x05) { + /* turn off agc/aec */ + v = v & 0xfa; + ov5630_write(client, 0x3013, v); + /* turn off awb */ + ov5630_read(client, OV5630_ISP_CTL00, ®_val); + ov5630_write(client, OV5630_ISP_CTL00, reg_val & ~0x30); + } + ov5630_read(client, 0x3014, &v); + dprintk(2, "0x3014 = %x", v); + ov5630_read(client, 0x3002, &v); + dprintk(2, "0x3002 = %x", v); + ov5630_read(client, 0x3003, &v); + dprintk(2, "0x3003 = %x", v); + + dprintk(2, "writing exposure %x to 0x3002/3", value); + + v = value >> 8; + ov5630_write(client, 0x3002, v); + dprintk(2, "exposure %x was writen to 0x3002", v); + + v = value & 0xff; + ov5630_write(client, 0x3003, v); + dprintk(2, "exposure %x was writen to 0x3003", v); + + DBG_leaving; + return 0; +} + +static struct ov5630_control { + struct v4l2_queryctrl qc; + int (*query)(struct v4l2_subdev *sd, __s32 *value); + int (*tweak)(struct v4l2_subdev *sd, int value); +} ov5630_controls[] = { + { + .qc = { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x0, + .maximum = 0xFF, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + .tweak = ov5630_t_gain, +/* .query = ov5630_q_gain, */ + }, + { + .qc = { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x0, + .maximum = 0xFFFF, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + .tweak = ov5630_t_exposure, +/* .query = ov5630_q_exposure; */ + }, +}; +#define N_CONTROLS (ARRAY_SIZE(ov5630_controls)) + +/* +static int ov5630_g_gain(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char v; + + DBG_entering; + + ov5630_write(client, 0x3000, &v); + dprintk(2, "writing gain %x to 0x3000", value); + + value + DBG_leaving; + return 0 +} +*/ + +static struct ov5630_control *ov5630_find_control(__u32 id) +{ + int i; + + for (i = 0; i < N_CONTROLS; i++) + if (ov5630_controls[i].qc.id == id) + return ov5630_controls + i; + return NULL; +} + +static int ov5630_queryctrl(struct v4l2_subdev *sd, + struct v4l2_queryctrl *qc) +{ + struct ov5630_control *ctrl = ov5630_find_control(qc->id); + + if (ctrl == NULL) + return -EINVAL; + *qc = ctrl->qc; + return 0; +} + +static int ov5630_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + /* + struct ov5630_control *octrl = ov5630_find_control(ctrl->id); + + int ret; + + if (octrl == NULL) + return -EINVAL; + ret = octrl->query(sd, &ctrl->value); + if (ret >= 0) + return 0; + return ret; + */ + return 0; +} + +static int ov5630_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct ov5630_control *octrl = ov5630_find_control(ctrl->id); + int ret; + + if (octrl == NULL) + return -EINVAL; + ret = octrl->tweak(sd, ctrl->value); + if (ret >= 0) + return 0; + return ret; +} + +#if 0 +static int ov5630_get_caps(struct i2c_client *c, struct ci_sensor_caps *caps) +{ + if (caps == NULL) + return -EIO; + + caps->bus_width = SENSOR_BUSWIDTH_10BIT; + caps->mode = SENSOR_MODE_BAYER; + caps->field_inv = SENSOR_FIELDINV_NOSWAP; + caps->field_sel = SENSOR_FIELDSEL_BOTH; + caps->ycseq = SENSOR_YCSEQ_YCBYCR; + caps->conv422 = SENSOR_CONV422_NOCOSITED; + caps->bpat = SENSOR_BPAT_BGBGGRGR; + caps->hpol = SENSOR_HPOL_REFPOS; + caps->vpol = SENSOR_VPOL_NEG; + caps->edge = SENSOR_EDGE_RISING; + caps->bls = SENSOR_BLS_OFF; + caps->gamma = SENSOR_GAMMA_OFF; + caps->cconv = SENSOR_CCONV_OFF; + caps->res = SENSOR_RES_QXGA_PLUS | SENSOR_RES_1080P | + SENSOR_RES_XGA_PLUS | SENSOR_RES_720P | SENSOR_RES_VGA; + caps->blc = SENSOR_BLC_OFF; + caps->agc = SENSOR_AGC_OFF; + caps->awb = SENSOR_AWB_OFF; + caps->aec = SENSOR_AEC_OFF; + caps->cie_profile = SENSOR_CIEPROF_D65 | SENSOR_CIEPROF_D75 | + SENSOR_CIEPROF_F11 | SENSOR_CIEPROF_F12 | SENSOR_CIEPROF_A | + SENSOR_CIEPROF_F2; + caps->flicker_freq = SENSOR_FLICKER_100 | SENSOR_FLICKER_120; + caps->type = SENSOR_TYPE_RAW; + /* caps->name = "ov5630"; */ + strcpy(caps->name, "ov5630"); + + return 0; +} + +static int ov5630_get_config(struct i2c_client *c, + struct ci_sensor_config *config) +{ + struct ci_sensor_config *info = i2c_get_clientdata(c); + + if (config == NULL) { + printk(KERN_WARNING "sensor_get_config: NULL pointer\n"); + return -EIO; + } + + memcpy(config, info, sizeof(struct ci_sensor_config)); + + return 0; +} + +static int ov5630_setup(struct i2c_client *c, + const struct ci_sensor_config *config) +{ + int ret; + u16 width, high; + struct ov5630_res_struct *res_index; + struct ci_sensor_config *info = i2c_get_clientdata(c); + + /* Soft reset camera first*/ + ret = ov5630_write(c, (u32)OV5630_SYS, (u32)0x80); + + /* software sleep/standby */ + ret = ov5630_write(c, (u32)OV5630_IMAGE_SYSTEM, (u32)0x00); + + /* Set registers into default config value */ + ret = ov5630_write_array(c, ov5630_def_reg); + + /* set image resolution */ + ci_sensor_res2size(config->res, &width, &high); + ret += ov5630_try_res(&width, &high); + res_index = ov5630_find_res(width, high); + if (res_index->regs) + ret += ov5630_write_array(c, res_index->regs); + if (!ret) + info->res = res_index->res; + + ret += ov5630_set_img_ctrl(c, config); + + /* Set MIPI interface */ +#ifdef OV5630_MIPI + ret += ov5630_write_array(c, ov5630_mipi); +#endif + + /* streaming */ + ret += ov5630_write(c, (u32)OV5630_IMAGE_SYSTEM, (u32)0x01); + ret += ov5630_write(c, (u32)0x3096, (u32)0x50); + + /*Note here for the time delay */ + /* ssleep(1); */ + msleep(500); + return ret; +} + +/* + * File operation functions + */ +static int ov5630_dvp_enable(struct i2c_client *client) +{ + int ret; + + u8 reg; + + ret = ov5630_read(client, 0x3506, ®); + reg &= 0xdf; + reg |= 0x20; + ret += ov5630_write(client, 0x3506, reg); + + return ret; +} + +static int ov5630_dvp_disable(struct i2c_client *client) +{ + int ret; + + u8 reg; + + ret = ov5630_read(client, 0x3506, ®); + reg &= 0xdf; + ret += ov5630_write(client, 0x3506, reg); + + return ret; +} + +static int ov5630_open(struct i2c_setting *c, void *priv) +{ + /* Just wake up sensor */ + if (ov5630_wakeup()) + return -EIO; + ov5630_init(c->sensor_client); + /* ov5630_motor_init(c->motor_client); */ + ov5630_write(c->sensor_client, (u32)OV5630_IMAGE_SYSTEM, (u32)0x00); + + /* disable dvp_en */ + ov5630_dvp_disable(c->sensor_client); + + return 0; +} + +static int ov5630_release(struct i2c_setting *c, void *priv) +{ + /* Just suspend the sensor */ + if (ov5630_standby()) + return -EIO; + return 0; +} + +static int ov5630_on(struct i2c_setting *c) +{ + int ret; + + /* Software wake up sensor */ + ret = ov5630_write(c->sensor_client, + (u32)OV5630_IMAGE_SYSTEM, (u32)0x01); + + /* enable dvp_en */ + return ret + ov5630_dvp_enable(c->sensor_client); +} + +static int ov5630_off(struct i2c_setting *c) +{ + int ret; + + /* Software standby sensor */ + ret = ov5630_write(c->sensor_client, + (u32)OV5630_IMAGE_SYSTEM, (u32)0x00); + /* disable dvp_en */ + return ret + ov5630_dvp_disable(c->sensor_client); +} + +static struct sensor_device ov5630 = { + .name = "ov5630", + .type = SENSOR_TYPE_RAW, + .minor = -1, + .open = ov5630_open, + .release = ov5630_release, + .on = ov5630_on, + .off = ov5630_off, + .querycap = ov5630_get_caps, + .get_config = ov5630_get_config, + .set_config = ov5630_setup, + .enum_parm = ov5630_queryctrl, + .get_parm = ov5630_g_ctrl, + .set_parm = ov5630_s_ctrl, + .try_res = ov5630_try_res, + .set_res = ov5630_set_res, + .get_ls_corr_config = NULL, + .mdi_get_focus = ov5630_motor_get_focus, + .mdi_set_focus = ov5630_motor_set_focus, + .mdi_max_step = ov5630_motor_max_step, + .mdi_calibrate = NULL, + .read = ov5630_read, + .write = ov5630_write, + .suspend = ov5630_standby, + .resume = ov5630_wakeup, + /* TBC */ +}; +#endif + +static int ov5630_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + DBG_entering; + + if (enable) { + ov5630_write(client, (u32)OV5630_IMAGE_SYSTEM, (u32)0x01); + ov5630_write(client, 0x30b0, 0xff); + ov5630_write(client, 0x30b1, 0xff); + msleep(500); + } else { + ov5630_write(client, (u32)OV5630_IMAGE_SYSTEM, (u32)0x00); + ov5630_write(client, 0x30b0, 0x00); + ov5630_write(client, 0x30b1, 0x00); + } + + DBG_leaving; + return 0; +} + +static int ov5630_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_frmsizeenum *fsize) +{ + unsigned int index = fsize->index; + + DBG_entering; + + if (index >= N_RES) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = ov5630_res[index].width; + fsize->discrete.height = ov5630_res[index].height; + fsize->reserved[0] = ov5630_res[index].used; + + DBG_leaving; + + return 0; +} + +static int ov5630_enum_frameintervals(struct v4l2_subdev *sd, + struct v4l2_frmivalenum *fival) +{ + unsigned int index = fival->index; + + DBG_entering; + + if (index >= N_RES) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1; + fival->discrete.denominator = ov5630_res[index].fps; + + DBG_leaving; + + return 0; +} + +static int ov5630_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + +#define V4L2_IDENT_OV5630 8245 + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_OV5630, 0); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov5630_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char val = 0; + int ret; + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ret = ov5630_read(client, reg->reg & 0xffff, &val); + reg->val = val; + reg->size = 1; + return ret; +} + +static int ov5630_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ov5630_write(client, reg->reg & 0xffff, reg->val & 0xff); + return 0; +} +#endif + +static const struct v4l2_subdev_video_ops ov5630_video_ops = { + .try_fmt = ov5630_try_fmt, + .s_fmt = ov5630_set_fmt, + .g_fmt = ov5630_get_fmt, + .s_stream = ov5630_s_stream, + .enum_framesizes = ov5630_enum_framesizes, + .enum_frameintervals = ov5630_enum_frameintervals, +}; + +static const struct v4l2_subdev_core_ops ov5630_core_ops = { + .g_chip_ident = ov5630_g_chip_ident, + .queryctrl = ov5630_queryctrl, + .g_ctrl = ov5630_g_ctrl, + .s_ctrl = ov5630_s_ctrl, + .s_gpio = ov5630_s_power, + /*.g_ext_ctrls = ov5630_g_ext_ctrls,*/ + /*.s_ext_ctrls = ov5630_s_ext_ctrls,*/ +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov5630_g_register, + .s_register = ov5630_s_register, +#endif +}; + +static const struct v4l2_subdev_ops ov5630_ops = { + .core = &ov5630_core_ops, + .video = &ov5630_video_ops, +}; + +/* + * Basic i2c stuff + */ +/* +static unsigned short normal_i2c[] = {I2C_OV5630 >> 1, + I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +static struct i2c_driver ov5630_driver; +*/ +static int ov5630_detect(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int adap_id = i2c_adapter_id(adapter); + u32 value; + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { + eprintk("error i2c check func"); + return -ENODEV; + } + + if (adap_id != 1) { + eprintk("adap_id != 1"); + return -ENODEV; + } + + /* if (ov5630_wakeup()) */ + /* return -ENODEV; */ + ov5630_wakeup(); + + ov5630_read(client, (u32)OV5630_PID_H, &value); + if ((u8)value != 0x56) { + dprintk(1, "PID != 0x56, but %x", value); + dprintk(2, "client->addr = %x", client->addr); + return -ENODEV; + } + + printk(KERN_INFO "Init ov5630 sensor success\n"); + return 0; +} + +static int ov5630_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ci_sensor_config *info; + struct v4l2_subdev *sd; + int ret = -1; +/* struct i2c_client *motor; */ + + DBG_entering; + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + /* + * Setup sensor configuration structure + */ + info = kzalloc(sizeof(struct ci_sensor_config), GFP_KERNEL); + if (!info) { + eprintk("fail to malloc for ci_sensor_config"); + ret = -ENOMEM; + goto out; + } + + ret = ov5630_detect(client); + if (ret) { + dprintk(1, "error ov5630_detect"); + goto out_free; + } + + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &ov5630_ops); + + /* + * Initialization OV5630 + * then turn into standby mode + */ + /* ret = ov5630_standby(); */ + ret = ov5630_init(client); + if (ret) { + eprintk("error calling ov5630_init"); + goto out_free; + } + ov5630_standby(); + + ret = 0; + goto out; + +out_free: + kfree(info); + DBG_leaving; +out: + return ret; +} + +/* + * XXX: Need to be checked + */ +static int ov5630_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + DBG_entering; + + v4l2_device_unregister_subdev(sd); + kfree(to_sensor_config(sd)); + + DBG_leaving; + return 0; +} + +static const struct i2c_device_id ov5630_id[] = { + {"ov5630", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ov5630_id); + +static struct v4l2_i2c_driver_data v4l2_i2c_data = { + .name = "ov5630", + .probe = ov5630_probe, + .remove = ov5630_remove, + /* .suspend = ov5630_suspend, + * .resume = ov5630_resume, */ + .id_table = ov5630_id, +}; + +MODULE_AUTHOR("Xiaolin Zhang "); +MODULE_DESCRIPTION("A low-level driver for OmniVision 5630 sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mrstci/mrstov5630/ov5630.h b/drivers/media/video/mrstci/mrstov5630/ov5630.h new file mode 100644 index 0000000..3da0ecd --- /dev/null +++ b/drivers/media/video/mrstci/mrstov5630/ov5630.h @@ -0,0 +1,672 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#define I2C_OV5630 0x6C +/* Should add to kernel source */ +#define I2C_DRIVERID_OV5630 1046 +/* GPIO pin on Moorestown */ +#define GPIO_SCLK_25 44 +#define GPIO_STB_PIN 47 +#define GPIO_STDBY_PIN 49 +#define GPIO_RESET_PIN 50 + +/* System control register */ +#define OV5630_AGC 0x3000 +#define OV5630_AGCS 0x3001 +#define OV5630_AEC_H 0x3002 +#define OV5630_AEC_L 0x3003 +#define OV5630_LAEC_H 0x3004 +#define OV5630_LAEC_L 0x3005 +#define OV5630_AECS_H 0x3008 +#define OV5630_AECS_L 0x3009 +#define OV5630_PID_H 0x300A +#define OV5630_PID_L 0x300B +#define OV5630_SCCB_ID 0x300C +#define OV5630_PLL_1 0x300E +#define OV5630_PLL_2 0x300F +#define OV5630_PLL_3 0x3010 +#define OV5630_PLL_4 0x3011 +#define OV5630_SYS 0x3012 +#define OV5630_AUTO_1 0x3013 +#define OV5630_AUTO_2 0x3014 +#define OV5630_AUTO_3 0x3015 +#define OV5630_AUTO_4 0x3016 +#define OV5630_AUTO_5 0x3017 +#define OV5630_WPT 0x3018 +#define OV5630_BPT 0x3019 +#define OV5630_VPT 0x301A +#define OV5630_YAVG 0x301B +#define OV5630_AECG_50 0x301C +#define OV5630_AECG_60 0x301D +#define OV5630_ADDVS_H 0x301E +#define OV5630_ADDVS_L 0x301F +#define OV5630_FRAME_LENGTH_LINES_H 0x3020 +#define OV5630_FRAME_LENGTH_LINES_L 0x3021 +#define OV5630_LINE_LENGTH_PCK_H 0x3022 +#define OV5630_LINE_LENGTH_PCK_L 0x3023 +#define OV5630_X_ADDR_START_H 0x3024 +#define OV5630_X_ADDR_START_L 0x3025 +#define OV5630_Y_ADDR_START_H 0x3026 +#define OV5630_Y_ADDR_START_L 0x3027 +#define OV5630_X_ADDR_END_H 0x3028 +#define OV5630_X_ADDR_END_L 0x3029 +#define OV5630_Y_ADDR_END_H 0x302A +#define OV5630_Y_ADDR_END_L 0x302B +#define OV5630_X_OUTPUT_SIZE_H 0x302C +#define OV5630_X_OUTPUT_SIZE_L 0x302D +#define OV5630_Y_OUTPUT_SIZE_H 0x302E +#define OV5630_Y_OUTPUT_SIZE_L 0x302F +#define OV5630_FRAME_CNT 0x3030 +#define OV5630_DATR_LMO_0 0x3038 +#define OV5630_DATR_LMO_1 0x3039 +#define OV5630_DATR_LMO_2 0x303A +#define OV5630_DATR_D56 0x303D +#define OV5630_DATR_EF 0x303E +#define OV5630_R_SIGMA_0 0x3048 +#define OV5630_R_SIGMA_1 0x3049 +#define OV5630_R_SIGMA_2 0x304A +#define OV5630_R_SIGMA_3 0x304B +#define OV5630_R_SIGMA_4 0x304C +#define OV5630_R_SIGMA_5 0x304D +#define OV5630_D56COM 0x304E +#define OV5630_5060TH 0x3050 +#define OV5630_LMO_TH1 0x3058 +#define OV5630_LMO_TH2 0x3059 +#define OV5630_LMO_K 0x305A +#define OV5630_BD50ST_H 0x305C +#define OV5630_BD50ST_L 0x305D +#define OV5630_BD60ST_H 0x305E +#define OV5630_BD60ST_L 0x305F +#define OV5630_HSYNST 0x306D +#define OV5630_HSYNED 0x306E +#define OV5630_HSYNED_HSYNST 0x306F +#define OV5630_TMC_RWIN0 0x3070 +#define OV5630_IO_CTRL0 0x30B0 +#define OV5630_IO_CTRL1 0x30B1 +#define OV5630_IO_CTRL2 0x30B2 +#define OV5630_DSIO_0 0x30B3 +#define OV5630_DSIO_1 0x30B4 +#define OV5630_DSIO_2 0x30B5 +#define OV5630_TMC_10 0x30B6 +#define OV5630_TMC_12 0x30B7 +#define OV5630_TMC_14 0x30B9 +#define OV5630_TMC_COM4 0x30BA +#define OV5630_TMC_REG6C 0x30BB +#define OV5630_TMC_REG6E 0x30BC +#define OV5630_R_CLK_S 0x30BD +#define OV5630_R_CLK_A 0x30BE +#define OV5630_R_CLK_A1 0x30BF +#define OV5630_FRS_0 0x30E0 +#define OV5630_FRS_1 0x30E1 +#define OV5630_FRS_2 0x30E2 +#define OV5630_FRS_3 0x30E3 +#define OV5630_FRS_FECNT 0x30E4 +#define OV5630_FRS_FECNT_0 0x30E5 +#define OV5630_FRS_FECNT_1 0x30E6 +#define OV5630_FRS_RFRM 0x30E7 +#define OV5630_FRS_RSTRB 0x30E8 +#define OV5630_SA1TMC 0x30E9 +#define OV5630_TMC_MISC0 0x30EA +#define OV5630_TMC_MISC1 0x30EB +#define OV5630_FLEX_TXP 0x30F0 +#define OV5630_FLEX_FLT 0x30F1 +#define OV5630_FLEX_TXT 0x30F2 +#define OV5630_FLEX_HBK 0x30F3 +#define OV5630_FLEX_HSG 0x30F4 +#define OV5630_FLEX_SA1SFT 0x30F5 +#define OV5630_RVSOPT 0x30F6 +#define OV5630_AUTO 0x30F7 +#define OV5630_IMAGE_TRANSFORM 0x30F8 +#define OV5630_IMAGE_LUM 0x30F9 +#define OV5630_IMAGE_SYSTEM 0x30FA +#define OV5630_GROUP_WR 0x30FF + +/* CIF control register */ +#define OV5630_CIF_CTRL2 0x3202 + +/* ISP control register */ +#define OV5630_ISP_CTL00 0x3300 +#define OV5630_ISP_CTL01 0x3301 +#define OV5630_ISP_CTL02 0x3302 +#define OV5630_ISP_03 0x3303 +#define OV5630_ISP_DIG_GAIN_MAN 0x3304 +#define OV5630_ISP_BIAS_MAN 0x3305 +#define OV5630_ISP_06 0x3306 +#define OV5630_ISP_STABLE_RANGE 0x3307 +#define OV5630_ISP_R_GAIN_MAN_1 0x3308 +#define OV5630_ISP_R_GAIN_MAN_0 0x3309 +#define OV5630_ISP_G_GAIN_MAN_1 0x330A +#define OV5630_ISP_G_GAIN_MAN_0 0x330B +#define OV5630_ISP_B_GAIN_MAN_1 0x330C +#define OV5630_ISP_B_GAIN_MAN_0 0x330D +#define OV5630_ISP_STABLE_RANGEW 0x330E +#define OV5630_ISP_AWB_FRAME_CNT 0x330F +#define OV5630_ISP_11 0x3311 +#define OV5630_ISP_12 0x3312 +#define OV5630_ISP_13 0x3313 +#define OV5630_ISP_HSIZE_IN_1 0x3314 +#define OV5630_ISP_HSIZE_IN_0 0x3315 +#define OV5630_ISP_VSIZE_IN_1 0x3316 +#define OV5630_ISP_VSIZE_IN_0 0x3317 +#define OV5630_ISP_18 0x3318 +#define OV5630_ISP_19 0x3319 +#define OV5630_ISP_EVEN_MAN0 0x331A +#define OV5630_ISP_EVEN_MAN1 0x331B +#define OV5630_ISP_EVEN_MAN2 0x331C +#define OV5630_ISP_EVEN_MAN3 0x331D +#define OV5630_ISP_1E 0x331E +#define OV5630_ISP_1F 0x331F +#define OV5630_ISP_BLC_LMT_OPTION 0x3320 +#define OV5630_ISP_BLC_THRE 0x3321 +#define OV5630_ISP_22 0x3322 +#define OV5630_ISP_23 0x3323 +#define OV5630_ISP_BLC_MAN0_1 0x3324 +#define OV5630_ISP_BLC_MAN0_0 0x3325 +#define OV5630_ISP_BLC_MAN1_1 0x3326 +#define OV5630_ISP_BLC_MAN1_0 0x3327 +#define OV5630_ISP_BLC_MAN2_1 0x3328 +#define OV5630_ISP_BLC_MAN2_0 0x3329 +#define OV5630_ISP_BLC_MAN3_1 0x332A +#define OV5630_ISP_BLC_MAN3_0 0x332B +#define OV5630_ISP_BLC_MAN4_1 0x332C +#define OV5630_ISP_BLC_MAN4_0 0x332D +#define OV5630_ISP_BLC_MAN5_1 0x332E +#define OV5630_ISP_BLC_MAN5_0 0x332F +#define OV5630_ISP_BLC_MAN6_1 0x3330 +#define OV5630_ISP_BLC_MAN6_0 0x3331 +#define OV5630_ISP_BLC_MAN7_1 0x3332 +#define OV5630_ISP_BLC_MAN7_0 0x3333 +#define OV5630_ISP_CD 0x33CD +#define OV5630_ISP_FF 0x33FF + +/* clipping control register */ +#define OV5630_CLIP_CTRL0 0x3400 +#define OV5630_CLIP_CTRL1 0x3401 +#define OV5630_CLIP_CTRL2 0x3402 +#define OV5630_CLIP_CTRL3 0x3403 +#define OV5630_CLIP_CTRL4 0x3404 +#define OV5630_CLIP_CTRL5 0x3405 +#define OV5630_CLIP_CTRL6 0x3406 +#define OV5630_CLIP_CTRL7 0x3407 + +/* DVP control register */ +#define OV5630_DVP_CTRL00 0x3500 +#define OV5630_DVP_CTRL01 0x3501 +#define OV5630_DVP_CTRL02 0x3502 +#define OV5630_DVP_CTRL03 0x3503 +#define OV5630_DVP_CTRL04 0x3504 +#define OV5630_DVP_CTRL05 0x3505 +#define OV5630_DVP_CTRL06 0x3506 +#define OV5630_DVP_CTRL07 0x3507 +#define OV5630_DVP_CTRL08 0x3508 +#define OV5630_DVP_CTRL09 0x3509 +#define OV5630_DVP_CTRL0A 0x350A +#define OV5630_DVP_CTRL0B 0x350B +#define OV5630_DVP_CTRL0C 0x350C +#define OV5630_DVP_CTRL0D 0x350D +#define OV5630_DVP_CTRL0E 0x350E +#define OV5630_DVP_CTRL0F 0x350F +#define OV5630_DVP_CTRL10 0x3510 +#define OV5630_DVP_CTRL11 0x3511 +#define OV5630_DVP_CTRL12 0x3512 +#define OV5630_DVP_CTRL13 0x3513 +#define OV5630_DVP_CTRL14 0x3514 +#define OV5630_DVP_CTRL15 0x3515 +#define OV5630_DVP_CTRL16 0x3516 +#define OV5630_DVP_CTRL17 0x3517 +#define OV5630_DVP_CTRL18 0x3518 +#define OV5630_DVP_CTRL19 0x3519 +#define OV5630_DVP_CTRL1A 0x351A +#define OV5630_DVP_CTRL1B 0x351B +#define OV5630_DVP_CTRL1C 0x351C +#define OV5630_DVP_CTRL1D 0x351D +#define OV5630_DVP_CTRL1E 0x351E +#define OV5630_DVP_CTRL1F 0x351F + +/* MIPI control register */ +#define OV5630_MIPI_CTRL00 0x3600 +#define OV5630_MIPI_CTRL01 0x3601 +#define OV5630_MIPI_CTRL02 0x3602 +#define OV5630_MIPI_CTRL03 0x3603 +#define OV5630_MIPI_CTRL04 0x3604 +#define OV5630_MIPI_CTRL05 0x3605 +#define OV5630_MIPI_CTRL06 0x3606 +#define OV5630_MIPI_CTRL07 0x3607 +#define OV5630_MIPI_CTRL08 0x3608 +#define OV5630_MIPI_CTRL09 0x3609 +#define OV5630_MIPI_CTRL0A 0x360A +#define OV5630_MIPI_CTRL0B 0x360B +#define OV5630_MIPI_CTRL0C 0x360C +#define OV5630_MIPI_CTRL0D 0x360D +#define OV5630_MIPI_CTRL0E 0x360E +#define OV5630_MIPI_CTRL0F 0x360F +#define OV5630_MIPI_CTRL10 0x3610 +#define OV5630_MIPI_CTRL11 0x3611 +#define OV5630_MIPI_CTRL12 0x3612 +#define OV5630_MIPI_CTRL13 0x3613 +#define OV5630_MIPI_CTRL14 0x3614 +#define OV5630_MIPI_CTRL15 0x3615 +#define OV5630_MIPI_CTRL16 0x3616 +#define OV5630_MIPI_CTRL17 0x3617 +#define OV5630_MIPI_CTRL18 0x3618 +#define OV5630_MIPI_CTRL19 0x3619 +#define OV5630_MIPI_CTRL1A 0x361A +#define OV5630_MIPI_CTRL1B 0x361B +#define OV5630_MIPI_CTRL1C 0x361C +#define OV5630_MIPI_CTRL1D 0x361D +#define OV5630_MIPI_CTRL1E 0x361E +#define OV5630_MIPI_CTRL1F 0x361F +#define OV5630_MIPI_CTRL20 0x3620 +#define OV5630_MIPI_CTRL21 0x3621 +#define OV5630_MIPI_CTRL22 0x3622 +#define OV5630_MIPI_CTRL23 0x3623 +#define OV5630_MIPI_CTRL24 0x3624 +#define OV5630_MIPI_CTRL25 0x3625 +#define OV5630_MIPI_CTRL26 0x3626 +#define OV5630_MIPI_CTRL27 0x3627 +#define OV5630_MIPI_CTRL28 0x3628 +#define OV5630_MIPI_CTRL29 0x3629 +#define OV5630_MIPI_CTRL2A 0x362A +#define OV5630_MIPI_CTRL2B 0x362B +#define OV5630_MIPI_CTRL2C 0x362C +#define OV5630_MIPI_CTRL2D 0x362D +#define OV5630_MIPI_CTRL2E 0x362E +#define OV5630_MIPI_CTRL2F 0x362F +#define OV5630_MIPI_CTRL30 0x3630 +#define OV5630_MIPI_CTRL31 0x3631 +#define OV5630_MIPI_CTRL32 0x3632 +#define OV5630_MIPI_CTRL33 0x3633 +#define OV5630_MIPI_CTRL34 0x3634 +#define OV5630_MIPI_CTRL35 0x3635 +#define OV5630_MIPI_CTRL36 0x3636 +#define OV5630_MIPI_CTRL37 0x3637 +#define OV5630_MIPI_CTRL38 0x3638 +#define OV5630_MIPI_CTRL39 0x3639 +#define OV5630_MIPI_CTRL3A 0x363A +#define OV5630_MIPI_CTRL3B 0x363B +#define OV5630_MIPI_CTRL3C 0x363C +#define OV5630_MIPI_CTRL3D 0x363D +#define OV5630_MIPI_CTRL3E 0x363E +#define OV5630_MIPI_CTRL3F 0x363F +#define OV5630_MIPI_RO61 0x3661 +#define OV5630_MIPI_RO62 0x3662 +#define OV5630_MIPI_RO63 0x3663 +#define OV5630_MIPI_RO64 0x3664 +#define OV5630_MIPI_RO65 0x3665 +#define OV5630_MIPI_RO66 0x3666 + +/* General definition for ov5630 */ +#define OV5630_OUTWND_MAX_H QSXXGA_PLUS4_SIZE_H +#define OV5630_OUTWND_MAX_V QSXGA_PLUS4_SIZE_V + +struct regval_list { + u16 reg_num; + u8 value; +}; + +/* + * Default register value + * 5Mega Pixel, 2592x1944 + */ +static struct regval_list ov5630_def_reg[] = { + {0x300f, 0x00}, /*00*/ + {0x30b2, 0x32}, + {0x3084, 0x44}, + {0x3016, 0x01}, + {0x308a, 0x25}, + + {0x3013, 0xff}, + {0x3015, 0x03}, + {0x30bf, 0x02}, + + {0x3065, 0x50}, + {0x3068, 0x08}, + {0x30ac, 0x05}, + {0x309e, 0x24}, + {0x3091, 0x04}, + + {0x3075, 0x22}, + {0x3076, 0x23}, + {0x3077, 0x24}, + {0x3078, 0x25}, + + {0x30b5, 0x0c}, + {0x3090, 0x67}, + + {0x30f9, 0x11}, + {0x3311, 0x80}, + {0x3312, 0x1f}, + + {0x3103, 0x10}, + {0x305c, 0x01}, + {0x305d, 0x29}, + {0x305e, 0x00}, + {0x305f, 0xf7}, + {0x308d, 0x0b}, + {0x30ad, 0x20}, + {0x3072, 0x0d}, + {0x308b, 0x82}, + {0x3317, 0x9c}, + {0x3318, 0x22}, + {0x3025, 0x20}, + {0x3027, 0x08}, + {0x3029, 0x3f}, + {0x302b, 0xa3}, + {0x3319, 0x22}, + {0x30a1, 0xc4}, + {0x306a, 0x05}, + {0x3315, 0x22}, + {0x30ae, 0x25}, + {0x3304, 0x40}, + {0x3099, 0x49}, + + {0x300e, 0xb1/*b0*/}, /* Note this PLL setting*/ + {0x300f, 0x10}, /*00*/ + {0x3010, 0x07}, /*change from 0f according to SV */ + {0x3011, 0x40}, + {0x30af, 0x10}, + {0x304a, 0x00}, + {0x304d, 0x00}, + + {0x304e, 0x22}, + {0x304d, 0xa0}, + {0x3058, 0x00}, + {0x3059, 0xff}, + {0x305a, 0x00}, + + {0x30e9, 0x04}, + {0x3084, 0x44}, + {0x3090, 0x67}, + {0x30e9, 0x04}, + + {0x30b5, 0x1c}, + {0x331f, 0x22}, + {0x30ae, 0x15}, + {0x3304, 0x4c}, + + {0x3300, 0xfb}, + {0x3071, 0x34}, + {0x30e7, 0x01}, + {0x3302, 0x60}, + {0x331e, 0x05}, + {0x3321, 0x04}, + + /* Mark end */ + {0xffff, 0xff}, + +}; + +/* MIPI register are removed by Wen */ + +/* 2592x1944 */ +static struct regval_list ov5630_res_qsxga_plus4[] = { + {0x3020, 0x07}, + {0x3021, 0xbc}, + {0x3022, 0x0c/*0a*/}, + {0x3023, 0xa0/*00*/}, + {0x305c, 0x01}, + {0x305d, 0x29}, + {0x305e, 0x00}, + {0x305f, 0xf7}, + + /* 30fps , 96 MHZ*/ + /* {0x300f, 0x10}, */ + {0x300f, 0x10}, + {0x300e, 0xb1}, + /* mipi */ +#ifdef MIPI + {0x30b0, 0x00}, + {0x30b1, 0xfc}, + {0x3603, 0x50}, + {0x3601, 0x0F}, + /* lan2 bit 10*/ + {0x3010, 0x07}, + {0x30fa, 0x01}, + /* {0x 30f8 09 */ + {0x3096, 0x50}, + /* end mipi*/ +#else + /* parrral */ + {0x30fa, 0x01}, +#endif + /* end post*/ + {0xffff, 0xff}, +}; + +/* 1920x1080 */ +static struct regval_list ov5630_res_1080p[] = { + /*res start*/ + {0x3020, 0x04}, + {0x3021, 0x5c}, + {0x3022, 0x0b/*0a*/}, + {0x3023, 0x32/*00*/}, + {0x305c, 0x01}, + {0x305d, 0x2c}, + {0x3024, 0x01}, + {0x3025, 0x6e/*70*/}, + {0x3026, 0x01}, + {0x3027, 0xb8}, + {0x3028, 0x08}, + {0x3029, 0xef}, + {0x302a, 0x05}, + {0x302b, 0xf3}, + {0x302c, 0x07}, + {0x302d, 0x80}, + {0x302e, 0x04}, + {0x302f, 0x38}, + {0x3314, 0x07}, + {0x3315, 0x82/*80*/}, + {0x3316, 0x04}, + {0x3317, 0x3c}, + + /* 30fps , 96 MHZ*/ + {0x300f, 0x10}, /* 00 */ + {0x300e, 0xb1}, + + /* mipi */ +#ifdef MIPI + {0x30b0, 0x00}, + {0x30b1, 0xfc}, + {0x3603, 0x50}, + {0x3601, 0x0F}, + /* lan2 bit 10*/ + {0x3010, 0x07}, + {0x30fa, 0x01}, + /* {0x 30f8 09 */ + {0x3096, 0x50}, + /* end mipi*/ +#else + /* parrral */ + {0x30fa, 0x01}, +#endif + /* end post*/ + {0xffff, 0xff}, +}; + +/* 1280x960 V1F2_H1F2 */ +static struct regval_list ov5630_res_xga_plus[] = { + {0x3020, 0x03}, + {0x3021, 0xe4}, + {0x3022, 0x0c/*07*/}, + {0x3023, 0x8c/*76*/}, + {0x305c, 0x00}, + {0x305d, 0xb1}, + {0x3024, 0x00}, + {0x3025, 0x30}, + {0x3026, 0x00}, + {0x3027, 0x10/*14*/}, + {0x3028, 0x0a}, + {0x3029, 0x2f}, + {0x302a, 0x07}, + {0x302b, 0xa7/*a7*/}, + {0x302c, 0x05}, + {0x302d, 0x00}, + {0x302e, 0x03}, + {0x302f, 0xc0}, + + {0x30f8, 0x05}, + {0x30f9, 0x13}, + {0x3314, 0x05}, + {0x3315, 0x02/*00*/}, + {0x3316, 0x03}, + {0x3317, 0xc4}, + + {0x300f, 0x10}, /* 00 */ + {0x300e, 0xb1}, + +#ifdef MIPI + {0x30b0, 0x00}, + {0x30b1, 0xfc}, + {0x3603, 0x50}, + {0x3601, 0x0F}, + /* lan2 bit 10*/ + {0x3010, 0x07}, + {0x30fa, 0x01}, + /* {0x 30f8 09 */ + {0x3096, 0x50}, + /* end mipi*/ +#else + /* parrral */ + {0x30fa, 0x01}, +#endif + + {0xffff, 0xff}, +}; + +/* 1280x720, V1F2 & H1F2 */ +static struct regval_list ov5630_res_720p[] = { + {0x3020, 0x02}, + {0x3021, 0xf4}, + {0x3022, 0x07}, + {0x3023, 0x80}, + {0x305c, 0x00}, + {0x305d, 0xff}, + {0x305e, 0x00}, + {0x305f, 0xd4}, + + /* Crop then downscale */ + {0x3024, 0x00}, + {0x3025, 0x2c}, + {0x3026, 0x00}, + {0x3027, 0xf0}, + {0x3028, 0x0a}, + {0x3029, 0x2f}, + {0x302a, 0x08}, + {0x302b, 0x97}, + + {0x30f8, 0x05}, + + {0x302c, 0x05}, + {0x302d, 0x00}, + {0x302e, 0x02}, + {0x302f, 0xd0}, + + {0x30f9, 0x13}, + {0x3314, 0x05}, + {0x3315, 0x04}, + {0x3316, 0x02}, + {0x3317, 0xd4}, + + /* Add this to test setting from OVT */ + {0x300f, 0x10}, /*00*/ + {0x300e, 0xb0}, + +#ifdef MIPI + {0x30b0, 0x00}, + {0x30b1, 0xfc}, + {0x3603, 0x50}, + {0x3601, 0x0F}, + /* lan2 bit 10*/ + {0x3010, 0x07}, + {0x30fa, 0x01}, + /* {0x 30f8 09 */ + {0x3096, 0x50}, + /* end mipi*/ +#else + /* parrral */ + {0x30fa, 0x01}, +#endif + + {0xffff, 0xff}, +}; + +/*VGA 40fps now*/ +static struct regval_list ov5630_res_vga_ac04_bill[] = { + /* res setting*/ + {0x3020, 0x02}, + {0x3021, 0x04}, + {0x3022, 0x08}, + {0x3023, 0x48}, + {0x305c, 0x00}, + {0x305d, 0x5e}, + {0x3024, 0x00}, + {0x3025, 0x2c},/*2c*/ + {0x3026, 0x00}, + {0x3027, 0x14}, + {0x3028, 0x0a}, + {0x3029, 0x2f}, + {0x302a, 0x07}, + {0x302b, 0xa3}, + {0x302c, 0x02}, + {0x302d, 0x80}, + {0x302e, 0x01}, + {0x302f, 0xe0}, + + {0x30b3, 0x09}, + {0x3301, 0xc1}, + {0x3313, 0xf1}, + {0x3314, 0x05}, + {0x3315, 0x04},/*04*/ + {0x3316, 0x01}, + {0x3317, 0xe4}, + {0x3318, 0x20}, + + {0x300f, 0x10/*00*/}, + {0x30f8, 0x09}, + + {0x300f, 0x11}, + {0x300e, 0xb2}, + + {0x3015, 0x02}, + /* mipi */ +#ifdef MIPI + {0x30b0, 0x00}, + {0x30b1, 0xfc}, + {0x3603, 0x50}, + {0x3601, 0x0F}, + /* lan2 bit 10*/ + {0x3010, 0x07}, + {0x30fa, 0x01}, + /* {0x 30f8 09 */ + {0x3096, 0x50}, + /* end mipi*/ +#else + + /* parrral */ + {0x30fa, 0x01}, + {0x30f8, 0x09}, + {0x3096, 0x50}, +#endif + + {0xffff, 0xff}, +}; diff --git a/drivers/media/video/mrstci/mrstov5630_motor/Kconfig b/drivers/media/video/mrstci/mrstov5630_motor/Kconfig new file mode 100644 index 0000000..b6dcf62 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov5630_motor/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_MRST_OV5630_MOTOR + tristate "Moorestown OV5630 motor" + depends on I2C && VIDEO_MRST_ISP && VIDEO_MRST_OV5630 + + ---help--- + Say Y here if your platform support OV5630 motor + + To compile this driver as a module, choose M here: the + module will be called mrstov2650.ko. diff --git a/drivers/media/video/mrstci/mrstov5630_motor/Makefile b/drivers/media/video/mrstci/mrstov5630_motor/Makefile new file mode 100644 index 0000000..056b2a6 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov5630_motor/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_VIDEO_MRST_OV2650) += mrstov5630_motor.o + +EXTRA_CFLAGS += -I$(src)/../include diff --git a/drivers/media/video/mrstci/mrstov5630_motor/mrstov5630_motor.c b/drivers/media/video/mrstci/mrstov5630_motor/mrstov5630_motor.c new file mode 100644 index 0000000..1bb7274 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov5630_motor/mrstov5630_motor.c @@ -0,0 +1,428 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ov5630_motor.h" + +/* #define OSPM */ +#include +#define PMIC_WRITE1(ipcbuf, reg1, val1) \ + do { \ + memset(&ipcbuf, 0, sizeof(struct ipc_pmic_reg_data)); \ + ipcbuf.ioc = 0; \ + ipcbuf.num_entries = 1; \ + ipcbuf.pmic_reg_data[0].register_address = reg1; \ + ipcbuf.pmic_reg_data[0].value = val1; \ + if (ipc_pmic_register_write(&ipcbuf, 1) != 0) { \ + return -1; \ + } \ + } while (0); + +static int mrstov5630_motor_debug; +module_param(mrstov5630_motor_debug, int, 0644); +MODULE_PARM_DESC(mrstov5630_motor_debug, "Debug level (0-1)"); + +#define dprintk(level, fmt, arg...) do { \ + if (mrstov5630_motor_debug >= level) \ + printk(KERN_DEBUG "mrstisp@%s: " fmt "\n", \ + __func__, ## arg); } \ + while (0) + +#define eprintk(fmt, arg...) \ + printk(KERN_ERR "mrstisp@%s: line %d: " fmt "\n", \ + __func__, __LINE__, ## arg); + +#define DBG_entering dprintk(2, "entering"); +#define DBG_leaving dprintk(2, "leaving"); +#define DBG_line dprintk(2, " line: %d", __LINE__); + +static inline struct ov5630_motor *to_motor_config(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov5630_motor, sd); +} + +static int motor_read(struct i2c_client *c, u16 *reg) +{ + int ret; + struct i2c_msg msg; + u8 msgbuf[2]; + + /* Read needs two message to go */ + msgbuf[0] = 0; + msgbuf[1] = 0; + + memset(&msg, 0, sizeof(msg)); + msg.addr = c->addr; + msg.buf = msgbuf; + msg.len = 2; + msg.flags = I2C_M_RD; + + ret = i2c_transfer(c->adapter, &msg, 1); + + *reg = (msgbuf[0] << 8 | msgbuf[1]); + + ret = (ret == 1) ? 0 : -1; + return ret; +} + +static int motor_write(struct i2c_client *c, u16 reg) +{ + int ret; + struct i2c_msg msg; + u8 msgbuf[2]; + + /* Writing only needs one message */ + memset(&msg, 0, sizeof(msg)); + msgbuf[0] = reg >> 8; + msgbuf[1] = reg; + + msg.addr = c->addr; + msg.flags = 0; + msg.buf = msgbuf; + msg.len = 2; + + ret = i2c_transfer(c->adapter, &msg, 1); + + ret = (ret == 1) ? 0 : -1; + return ret; +} + +static int ov5630_motor_goto_position(struct i2c_client *c, + unsigned short code, + struct ov5630_motor *config) +{ + int max_code, min_code; + u8 cmdh, cmdl; + u16 cmd, val = 0; + + max_code = config->macro_code; + min_code = config->infin_code; + + if (code > max_code) + code = max_code; + if (code < min_code) + code = min_code; + + cmdh = (MOTOR_DAC_CODE_H(code)); + cmdl = (MOTOR_DAC_CODE_L(code) | MOTOR_DAC_CTRL_MODE_2(SUB_MODE_4)); + cmd = cmdh << 8 | cmdl; + + motor_write(c, cmd); + /*Delay more than full-scale transition time 8.8ms*/ + msleep(8); + motor_read(c, &val); + + return (cmd == val ? 0 : -1); +} + +int ov5630_motor_wakeup(void) +{ + return gpio_direction_output(GPIO_AF_PD, 1); +} + +int ov5630_motor_standby(void) +{ + return gpio_direction_output(GPIO_AF_PD, 0); +} + +int ov5630_motor_init(struct i2c_client *client, struct ov5630_motor *config) +{ + int ret; + int infin_cur, macro_cur; +#ifdef OSPM + /* Power on motor */ + struct ipc_pmic_reg_data ipcbuf; + + PMIC_WRITE1(ipcbuf, 0x50, 0x27); + printk(KERN_WARNING "Power on Vcc33 for motor\n"); +#endif + + infin_cur = MAX(MOTOR_INFIN_CUR, MOTOR_DAC_MIN_CUR); + macro_cur = MIN(MOTOR_MACRO_CUR, MOTOR_DAC_MAX_CUR); + + config->infin_cur = infin_cur; + config->macro_cur = macro_cur; + + config->infin_code = (int)((infin_cur * MOTOR_DAC_MAX_CODE) + / MOTOR_DAC_MAX_CUR); + config->macro_code = (int)((macro_cur * MOTOR_DAC_MAX_CODE) + / MOTOR_DAC_MAX_CUR); + + config->max_step = ((config->macro_code - config->infin_code) + >> MOTOR_STEP_SHIFT) + 1; + /* Note here, maybe macro_code */ + ret = ov5630_motor_goto_position(client, config->infin_code, config); + if (!ret) + config->cur_code = config->infin_code; + else + printk(KERN_ERR "Error while initializing motor\n"); + + return ret; +} + +int ov5630_motor_set_focus(struct i2c_client *c, int step, + struct ov5630_motor *config) +{ + int s_code, ret; + int max_step = config->max_step; + unsigned int val = step; + + DBG_entering; + dprintk(1, "setting setp %d", step); + if (val > max_step) + val = max_step; + + s_code = (val << MOTOR_STEP_SHIFT); + s_code += config->infin_code; + + ret = ov5630_motor_goto_position(c, s_code, config); + if (!ret) + config->cur_code = s_code; + + DBG_leaving; + return ret; +} + +static int ov5630_motor_s_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + struct ov5630_motor *config = to_motor_config(sd); + int ret; + + DBG_entering; + ret = ov5630_motor_set_focus(c, ctrl->value, config); + if (ret) { + eprintk("error call ov5630_motor_set_focue"); + return ret; + } + DBG_leaving; + return 0; +} +int ov5630_motor_get_focus(struct i2c_client *c, unsigned int *step, + struct ov5630_motor *config) +{ + int ret_step; + + ret_step = ((config->cur_code - config->infin_code) + >> MOTOR_STEP_SHIFT); + + if (ret_step <= config->max_step) + *step = ret_step; + else + *step = config->max_step; + + return 0; +} + +static int ov5630_motor_g_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + struct ov5630_motor *config = to_motor_config(sd); + int ret; + + DBG_entering; + dprintk(2, "c = %p, config = %p, ctrl = %p", c, config, ctrl); + ret = ov5630_motor_get_focus(c, &ctrl->value, config); + if (ret) { + eprintk("error call ov5630_motor_get_focue"); + return ret; + } + DBG_leaving; + return 0; +} +int ov5630_motor_max_step(struct i2c_client *c, unsigned int *max_code, + struct ov5630_motor *config) +{ + if (config->max_step != 0) + *max_code = config->max_step; + return 0; +} + +static int ov5630_motor_queryctrl(struct v4l2_subdev *sd, + struct v4l2_queryctrl *qc) +{ + struct ov5630_motor *config = to_motor_config(sd); + + DBG_entering; + + if (qc->id != V4L2_CID_FOCUS_ABSOLUTE) + return -EINVAL; + + dprintk(1, "got focus range of %d", config->max_step); + if (config->max_step != 0) + qc->maximum = config->max_step; + DBG_leaving; + return 0; +} +static const struct v4l2_subdev_core_ops ov5630_motor_core_ops = { + /* + .queryctrl = ov5630_queryctrl, + .g_ctrl = ov5630_g_ctrl, + */ + .g_ctrl = ov5630_motor_g_ctrl, + .s_ctrl = ov5630_motor_s_ctrl, + .queryctrl = ov5630_motor_queryctrl, +}; + +static const struct v4l2_subdev_ops ov5630_motor_ops = { + .core = &ov5630_motor_core_ops, +}; + +static int ov5630_motor_detect(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int adap_id = i2c_adapter_id(adapter); + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { + eprintk("error i2c check func"); + return -ENODEV; + } + + if (adap_id != 1) { + eprintk("adap_id != 1"); + return -ENODEV; + } + + /* if (ov5630_motor_wakeup()) */ + /* return -ENODEV; */ + ov5630_motor_wakeup(); + ssleep(1); + + /* + ov5630_motor_read(client, (u32)OV5630_PID_H, &value); + if ((u8)value != 0x56) { + eprintk("PID != 0x56, but %x", value); + dprintk(2, "client->addr = %x", client->addr); + return -ENODEV; + } + */ + + return 0; +} + +static int ov5630_motor_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ov5630_motor *info; + struct v4l2_subdev *sd; + int ret = -1; +/* struct i2c_client *motor; */ + + DBG_entering; + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + /* + * Setup sensor configuration structure + */ + info = kzalloc(sizeof(struct ov5630_motor), GFP_KERNEL); + if (!info) { + eprintk("fail to malloc for ci_motor"); + ret = -ENOMEM; + goto out; + } + + ret = ov5630_motor_detect(client); + if (ret) { + eprintk("error ov5630_motor_detect"); + goto out_free; + } + + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &ov5630_motor_ops); + + /* + * Initialization OV5630 + * then turn into standby mode + */ + /* ret = ov5630_motor_standby(); */ + ret = ov5630_motor_init(client, info); + if (ret) { + eprintk("error calling ov5630_motor_init"); + goto out_free; + } + + ret = 0; + goto out; + +out_free: + kfree(info); + DBG_leaving; +out: + return ret; +} + +/* + * XXX: Need to be checked + */ +static int ov5630_motor_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + DBG_entering; + + v4l2_device_unregister_subdev(sd); + kfree(to_motor_config(sd)); + + DBG_leaving; + return 0; +} + +static const struct i2c_device_id ov5630_motor_id[] = { + {"ov5630_motor", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ov5630_motor_id); + +static struct v4l2_i2c_driver_data v4l2_i2c_data = { + .name = "ov5630_motor", + .probe = ov5630_motor_probe, + .remove = ov5630_motor_remove, + /* .suspend = ov5630_suspend, + * .resume = ov5630_resume, */ + .id_table = ov5630_motor_id, +}; +MODULE_AUTHOR("Xiaolin Zhang "); +MODULE_DESCRIPTION("A low-level driver for OmniVision 5630 sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mrstci/mrstov5630_motor/ov5630_motor.h b/drivers/media/video/mrstci/mrstov5630_motor/ov5630_motor.h new file mode 100644 index 0000000..302c218 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov5630_motor/ov5630_motor.h @@ -0,0 +1,86 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#include + +/* VCM start current (mA) */ +#define MOTOR_INFIN_CUR 15 +/* VCM max current for Macro (mA) */ +#define MOTOR_MACRO_CUR 90 +/* DAC output max current (mA) */ +#define MOTOR_DAC_MAX_CUR 100 +/* DAC output min current (mA) */ +#define MOTOR_DAC_MIN_CUR 3 + +#define MOTOR_DAC_BIT_RES 10 +#define MOTOR_DAC_MAX_CODE ((1 << MOTOR_DAC_BIT_RES) - 1) + +#define MOTOR_STEP_SHIFT 4 + +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +/* DAC register related define */ +#define MOTOR_POWER_DOWN (1 << 7) +#define PD_ENABLE (1 << 7) +#define PD_DISABLE (0) + +#define MOTOR_DAC_CODE_H(x) ((x >> 4) & 0x3f) +#define MOTOR_DAC_CODE_L(x) ((x << 4) & 0xf0) + +#define MOTOR_DAC_CTRL_MODE_0 0x00 +#define MOTOR_DAC_CTRL_MODE_1(x) (x & 0x07) +#define MOTOR_DAC_CTRL_MODE_2(x) ((x & 0x07) | 0x08) + +#define SUB_MODE_1 0x01 +#define SUB_MODE_2 0x02 +#define SUB_MODE_3 0x03 +#define SUB_MODE_4 0x04 +#define SUB_MODE_5 0x05 +#define SUB_MODE_6 0x06 +#define SUB_MODE_7 0x07 + +#define OV5630_MOTOR_ADDR (0x18 >> 1) +#define POWER_EN_PIN 7 +#define GPIO_AF_PD 95 + +struct ov5630_motor{ + unsigned int infin_cur; + unsigned int infin_code; + unsigned int macro_cur; + unsigned int macro_code; + unsigned int max_step; + unsigned int cur_code; + struct v4l2_subdev sd; +}; + +extern int ov5630_motor_init(struct i2c_client *client, struct ov5630_motor + *config); +extern int ov5630_motor_standby(void); +extern int ov5630_motor_wakeup(void); +extern int ov5630_motor_set_focus(struct i2c_client *c, int step, + struct ov5630_motor *config); +extern int ov5630_motor_get_focus(struct i2c_client *c, unsigned int *step, + struct ov5630_motor *config); +extern int ov5630_motor_max_step(struct i2c_client *c, unsigned int *max_code, + struct ov5630_motor *config); diff --git a/drivers/media/video/mrstci/mrstov9665/Kconfig b/drivers/media/video/mrstci/mrstov9665/Kconfig new file mode 100644 index 0000000..ba9b692 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov9665/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_MRST_OV9665 + tristate "Moorestown OV9665 SoC Sensor" + depends on I2C && VIDEO_MRST_ISP + + ---help--- + Say Y here if your platform support OV9665 SoC Sensor. + + To compile this driver as a module, choose M here: the + module will be called mrstov9665.ko. diff --git a/drivers/media/video/mrstci/mrstov9665/Makefile b/drivers/media/video/mrstci/mrstov9665/Makefile new file mode 100644 index 0000000..871b6bf --- /dev/null +++ b/drivers/media/video/mrstci/mrstov9665/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_VIDEO_MRST_OV9665) += mrstov9665.o + +EXTRA_CFLAGS += -I$(src)/../include diff --git a/drivers/media/video/mrstci/mrstov9665/mrstov9665.c b/drivers/media/video/mrstci/mrstov9665/mrstov9665.c new file mode 100644 index 0000000..04e553a --- /dev/null +++ b/drivers/media/video/mrstci/mrstov9665/mrstov9665.c @@ -0,0 +1,972 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ci_sensor_common.h" +#include "ov9665.h" + +static int mrstov9665_debug; +module_param(mrstov9665_debug, int, 0644); +MODULE_PARM_DESC(mrstov9665_debug, "Debug level (0-1)"); + +#define dprintk(level, fmt, arg...) do { \ + if (mrstov9665_debug >= level) \ + printk(KERN_DEBUG "mrstisp@%s: " fmt "\n", \ + __func__, ## arg); } \ + while (0) + +#define eprintk(fmt, arg...) \ + printk(KERN_ERR "mrstisp@%s: line %d: " fmt "\n", \ + __func__, __LINE__, ## arg); + +#define DBG_entering dprintk(2, "entering"); +#define DBG_leaving dprintk(2, "leaving"); +#define DBG_line dprintk(2, " line: %d", __LINE__); + +static inline struct ci_sensor_config *to_sensor_config(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ci_sensor_config, sd); +} + +static struct ov9665_format_struct { + __u8 *desc; + __u32 pixelformat; + struct regval_list *regs; +} ov9665_formats[] = { + { + .desc = "YUYV 4:2:2", + .pixelformat = SENSOR_MODE_BT601, + .regs = NULL, + }, +}; +#define N_OV9665_FMTS ARRAY_SIZE(ov9665_formats) + +static struct ov9665_res_struct { + __u8 *desc; + int res; + int width; + int height; + /* FIXME: correct the fps values.. */ + int fps; + bool used; + struct regval_list *regs; +} ov9665_res[] = { + { + .desc = "SXGA", + .res = SENSOR_RES_SXGA, + .width = 1280, + .height = 1024, + .fps = 15, + .used = 0, + .regs = ov9665_res_sxga, + }, + { + .desc = "VGA", + .res = SENSOR_RES_VGA, + .width = 640, + .height = 480, + .fps = 15, + .used = 0, + .regs = ov9665_res_vga, + }, +}; +#define N_RES (ARRAY_SIZE(ov9665_res)) + +/* + * I2C Read & Write stuff + */ +static int ov9665_read(struct i2c_client *c, unsigned char reg, + unsigned char *value) +{ + int ret; + + ret = i2c_smbus_read_byte_data(c, reg); + if (ret >= 0) { + *value = (unsigned char) ret; + ret = 0; + } + return ret; +} + +static int ov9665_write(struct i2c_client *c, unsigned char reg, + unsigned char value) +{ + int ret = i2c_smbus_write_byte_data(c, reg, value); + if (reg == 0x12 && (value & 0x80)) + msleep(2); /* Wait for reset to run */ + return ret; +} + +/* + * Write a list of register settings; ff/ff stops the process. + */ +static int ov9665_write_array(struct i2c_client *c, struct regval_list *vals) +{ + struct regval_list *p; + u8 read_val = 0; + int err_num = 0; + int i = 0; + p = vals; + while (p->reg_num != 0xff) { + ov9665_write(c, p->reg_num, p->value); + ov9665_read(c, p->reg_num, &read_val); + if (read_val != p->value) + err_num++; + p++; + i++; + } + + return 0; +} + +static int ov9665_set_data_pin_in(struct i2c_client *client) +{ + int ret = 0; + + ret += ov9665_write(client, 0xd5, 0x00); + ret += ov9665_write(client, 0xd6, 0x00); + + return ret; +} + +static int ov9665_set_data_pin_out(struct i2c_client *client) +{ + int ret = 0; + + ret += ov9665_write(client, 0xd5, 0xff); + ret += ov9665_write(client, 0xd6, 0xff); + + return ret; +} +/* + * Sensor specific helper function + */ +static int ov9665_standby(void) +{ + /* Pull the pin to high to hardware standby */ + gpio_set_value(GPIO_STDBY_PIN, 1); + dprintk(1, "PM: standby called\n"); + return 0; +} + +static int ov9665_wakeup(void) +{ + /* Pull the pin to low*/ + gpio_set_value(GPIO_STDBY_PIN, 0); + dprintk(1, "PM: wakeup called\n"); + msleep(10); + return 0; +} + +static int ov9665_s_power(struct v4l2_subdev *sd, u32 val) +{ + if (val == 1) + ov9665_standby(); + if (val == 0) + ov9665_wakeup(); + return 0; +} + +static int ov9665_init(struct i2c_client *c) +{ + int ret; + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct ci_sensor_config *info = to_sensor_config(sd); + u8 reg = 0; + + /* Fill the configuration structure */ + /* Note this default configuration value */ + info->mode = ov9665_formats[0].pixelformat; + info->res = ov9665_res[0].res; + info->type = SENSOR_TYPE_SOC; + info->bls = SENSOR_BLS_OFF; + info->gamma = SENSOR_GAMMA_ON; + info->cconv = SENSOR_CCONV_ON; + info->blc = SENSOR_BLC_AUTO; + info->agc = SENSOR_AGC_AUTO; + info->awb = SENSOR_AWB_AUTO; + info->aec = SENSOR_AEC_AUTO; + info->bus_width = SENSOR_BUSWIDTH_8BIT_ZZ; + info->ycseq = SENSOR_YCSEQ_YCBYCR; + info->conv422 = SENSOR_CONV422_COSITED; + info->bpat = SENSOR_BPAT_GRGRBGBG; + info->field_inv = SENSOR_FIELDINV_NOSWAP; + info->field_sel = SENSOR_FIELDSEL_BOTH; + info->hpol = SENSOR_HPOL_REFPOS; + info->vpol = SENSOR_VPOL_POS; + info->edge = SENSOR_EDGE_FALLING; + info->flicker_freq = SENSOR_FLICKER_100; + info->cie_profile = 0; + memcpy(info->name, "ov9665", 7); + + ret = ov9665_write(c, 0x12, 0x80); + /* Set registers into default config value */ + ret += ov9665_write_array(c, ov9665_def_reg); + + ov9665_read(c, 0x09, ®); + reg = reg | 0x10; + ov9665_write(c, 0x09, reg); + ov9665_set_data_pin_in(c); + ssleep(1); + + return ret; +} + +static int distance(struct ov9665_res_struct *res, u32 w, u32 h) +{ + int ret; + if (res->width < w || res->height < h) + return -1; + + ret = ((res->width - w) + (res->height - h)); + return ret; +} +static int ov9665_try_res(u32 *w, u32 *h) +{ + struct ov9665_res_struct *res_index, *p = NULL; + int dis, last_dis = ov9665_res->width + ov9665_res->height; + + dprintk(1, "&&&&& before %dx%d", *w, *h); + for (res_index = ov9665_res; + res_index < ov9665_res + N_RES; + res_index++) { + if ((res_index->width <= *w) && (res_index->height <= *h)) + break; + dis = distance(res_index, *w, *h); + if (dis < last_dis) { + last_dis = dis; + p = res_index; + } + } + if ((res_index->width < *w) || (res_index->height < *h)) { + if (res_index != ov9665_res) + res_index--; + } + + /* + if (p == NULL) { + p = ov2650_res; + } + + if ((w != NULL) && (h != NULL)) { + *w = p->width; + *h = p->height; + } + */ + if (res_index == ov9665_res + N_RES) + res_index = ov9665_res + N_RES - 1; + + *w = res_index->width; + *h = res_index->height; + + dprintk(1, "&&&&& after %dx%d", *w, *h); + return 0; +} + +static struct ov9665_res_struct *ov9665_to_res(u32 w, u32 h) +{ + struct ov9665_res_struct *res_index; + + for (res_index = ov9665_res; + res_index < ov9665_res + N_RES; + res_index++) + if ((res_index->width == w) && (res_index->height == h)) + break; + + if (res_index >= ov9665_res + N_RES) + res_index--; /* Take the bigger one */ + + return res_index; +} + +static int ov9665_try_fmt(struct v4l2_subdev *sd, + struct v4l2_format *fmt) +{ + DBG_entering; + return ov9665_try_res(&fmt->fmt.pix.width, &fmt->fmt.pix.height); + DBG_leaving; +} + +static int ov9665_get_fmt(struct v4l2_subdev *sd, + struct v4l2_format *fmt) +{ + struct ci_sensor_config *info = to_sensor_config(sd); + unsigned short width, height; + int index; + + ci_sensor_res2size(info->res, &width, &height); + + /* Marked the current sensor res as being "used" */ + for (index = 0; index < N_RES; index++) { + if ((width == ov9665_res[index].width) && + (height == ov9665_res[index].height)) { + ov9665_res[index].used = 1; + continue; + } + ov9665_res[index].used = 0; + } + + fmt->fmt.pix.width = width; + fmt->fmt.pix.height = height; + return 0; +} + +static int ov9665_set_fmt(struct v4l2_subdev *sd, struct v4l2_format *fmt) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + struct ci_sensor_config *info = to_sensor_config(sd); + int ret = 0; + struct ov9665_res_struct *res_index; + u32 width, height; + int index; + + DBG_entering; + + width = fmt->fmt.pix.width; + height = fmt->fmt.pix.height; + + ret = ov9665_try_res(&width, &height); + res_index = ov9665_to_res(width, height); + + ov9665_wakeup(); + /* if ((info->res != res_index->res) && (res_index->regs)) { */ + if ( res_index->regs) { + ret = ov9665_write(c, 0x12, 0x80); + ret += ov9665_write_array(c, ov9665_def_reg); + ret += ov9665_write_array(c, res_index->regs); + /* Add delay here to get better image */ + + for (index = 0; index < N_RES; index++) { + if ((width == ov9665_res[index].width) && + (height == ov9665_res[index].height)) { + ov9665_res[index].used = 1; + continue; + } + ov9665_res[index].used = 0; + } + + for (index = 0; index < N_RES; index++) + dprintk(2, "index = %d, used = %d\n", index, + ov9665_res[index].used); + + } + info->res = res_index->res; + + DBG_leaving; + return ret; +} + +static int ov9665_q_hflip(struct v4l2_subdev *sd, __s32 *value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + unsigned char v = 0; + + ret = ov9665_read(client, 0x04, &v); + *value = ((v & 0x80) == 0x80); + return ret; +} + +static int ov9665_t_hflip(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char v = 0; + int ret; + + value = value >= 1 ? 1 : 0; + ret = ov9665_read(client, 0x33, &v); + if (value) + v |= 0x08; + else + v &= ~0x08; + ret += ov9665_write(client, 0x33, v); + + ret += ov9665_read(client, 0x04, &v); + if (value) + v |= 0x80; + else + v &= ~0x80; + ret += ov9665_write(client, 0x04, v); + msleep(10); /* FIXME */ + return ret; +} + +static int ov9665_q_vflip(struct v4l2_subdev *sd, __s32 *value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + unsigned char v = 0; + + ret = ov9665_read(client, 0x04, &v); + *value = ((v & 0x40) == 0x40); + return ret; +} + +static int ov9665_t_vflip(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char v = 0; + int ret; + + value = value >= 1 ? 1 : 0; + ret = ov9665_read(client, 0x04, &v); + if (value) + v |= 0x40; + else + v &= ~0x40; + ret += ov9665_write(client, 0x04, v); + msleep(10); /* FIXME */ + return ret; +} + +static struct ov9665_control { + struct v4l2_queryctrl qc; + int (*query)(struct v4l2_subdev *sd, __s32 *value); + int (*tweak)(struct v4l2_subdev *sd, int value); +} ov9665_controls[] = { + { + .qc = { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + .tweak = ov9665_t_vflip, + .query = ov9665_q_vflip, + }, + { + .qc = { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Horizontal mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + .tweak = ov9665_t_hflip, + .query = ov9665_q_hflip, + }, +}; +#define N_CONTROLS (ARRAY_SIZE(ov9665_controls)) + +static struct ov9665_control *ov9665_find_control(__u32 id) +{ + int i; + + for (i = 0; i < N_CONTROLS; i++) + if (ov9665_controls[i].qc.id == id) + return ov9665_controls + i; + return NULL; +} + +static int ov9665_queryctrl(struct v4l2_subdev *sd, + struct v4l2_queryctrl *qc) +{ + struct ov9665_control *ctrl = ov9665_find_control(qc->id); + + if (ctrl == NULL) + return -EINVAL; + *qc = ctrl->qc; + return 0; +} + +static int ov9665_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct ov9665_control *octrl = ov9665_find_control(ctrl->id); + int ret; + + if (octrl == NULL) + return -EINVAL; + ret = octrl->query(sd, &ctrl->value); + if (ret >= 0) + return 0; + return ret; +} + +static int ov9665_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct ov9665_control *octrl = ov9665_find_control(ctrl->id); + int ret; + + if (octrl == NULL) + return -EINVAL; + ret = octrl->tweak(sd, ctrl->value); + if (ret >= 0) + return 0; + return ret; +} + +#if 0 +static int ov9665_get_caps(struct i2c_client *c, struct ci_sensor_caps *caps) +{ + if (caps == NULL) + return -EIO; + + caps->bus_width = SENSOR_BUSWIDTH_8BIT_ZZ; + caps->mode = SENSOR_MODE_BT601; + caps->field_inv = SENSOR_FIELDINV_NOSWAP; + caps->field_sel = SENSOR_FIELDSEL_BOTH; + caps->ycseq = SENSOR_YCSEQ_YCBYCR; + caps->conv422 = SENSOR_CONV422_COSITED; + caps->bpat = SENSOR_BPAT_GRGRBGBG; + caps->hpol = SENSOR_HPOL_REFPOS; + caps->vpol = SENSOR_VPOL_POS; + caps->edge = SENSOR_EDGE_FALLING; + caps->bls = SENSOR_BLS_OFF; + caps->gamma = SENSOR_GAMMA_ON; + caps->cconv = SENSOR_CCONV_ON; + caps->res = SENSOR_RES_SXGA | SENSOR_RES_VGA; + caps->blc = SENSOR_BLC_AUTO; + caps->agc = SENSOR_AGC_AUTO; + caps->awb = SENSOR_AWB_AUTO; + caps->aec = SENSOR_AEC_AUTO; + caps->cie_profile = 0; + caps->flicker_freq = SENSOR_FLICKER_100 | SENSOR_FLICKER_120; + caps->type = SENSOR_TYPE_SOC; + /* caps->name = "ov9665"; */ + strcpy(caps->name, "ov9665"); + + return 0; +} + +static int ov9665_get_config(struct i2c_client *c, + struct ci_sensor_config *config) +{ + struct ci_sensor_config *info = i2c_get_clientdata(c); + + if (config == NULL) { + printk(KERN_WARNING "sensor_get_config: NULL pointer\n"); + return -EIO; + } + + memset(config, 0, sizeof(struct ci_sensor_config *)); + memcpy(config, info, sizeof(struct ci_sensor_config)); + + return 0; +} + +static int ov9665_setup(struct i2c_client *c, + const struct ci_sensor_config *config) +{ + int ret; + struct ov9665_res_struct *res_index; + struct ci_sensor_config *info = i2c_get_clientdata(c); + u16 width, high; + + /* Soft reset camera first*/ + ret = ov9665_write(c, 0x12, 0x80); + + /* Set registers into default config value */ + ret += ov9665_write_array(c, ov9665_def_reg); + + /* set image resolution */ + ci_sensor_res2size(config->res, &width, &high); + ret += ov9665_try_res(c, &width, &high); + res_index = ov9665_find_res(width, high); + if (res_index->regs) + ret += ov9665_write_array(c, res_index->regs); + if (!ret) + info->res = res_index->res; + + /* Add some delay here to get a better image*/ + ssleep(1); + + return ret; +} + +static int ov9665_set_data_pin_in(struct i2c_client *client) +{ + int ret = 0; + + ret += ov9665_write(client, 0xd5, 0x00); + ret += ov9665_write(client, 0xd6, 0x00); + + return ret; +} + +static int ov9665_set_data_pin_out(struct i2c_client *client) +{ + int ret = 0; + + ret += ov9665_write(client, 0xd5, 0xff); + ret += ov9665_write(client, 0xd6, 0xff); + + return ret; +} +/* + * File operation functions + */ +static int ov9665_open(struct i2c_setting *c, void *priv) +{ + struct i2c_client *client = c->sensor_client; + int ret = 0; + u8 reg = 0; + /* Just wake up sensor */ + if (ov9665_wakeup()) + return -EIO; + + ov9665_init(client); + ret = ov9665_read(client, 0x09, ®); + reg = reg | 0x10; + ret += ov9665_write(client, 0x09, reg); + + if (ov9665_set_data_pin_in(client)) + return EIO; +/* + if (ov9665_standby()) + return EIO; +*/ + return ret; +} + +static int ov9665_release(struct i2c_setting *c, void *priv) +{ + /* Just suspend the sensor */ + if (ov9665_standby()) + return EIO; + return 0; +} + +static int ov9665_on(struct i2c_setting *c) +{ + struct i2c_client *client = c->sensor_client; + int ret = 0; + u8 reg = 0; + + ret = ov9665_read(client, 0x09, ®); + reg = reg & ~0x10; + ret = ov9665_write(client, 0x09, reg); + + if (ov9665_set_data_pin_out(client)) + return EIO; + + return ret; +} + +static int ov9665_off(struct i2c_setting *c) +{ + struct i2c_client *client = c->sensor_client; + int ret = 0; + u8 reg = 0; +/* + ret = ov9665_read(client, 0x09, ®); + reg = reg | 0x10; + ret += ov9665_write(client, 0x09, reg); +*/ + if (ov9665_set_data_pin_in(client)) + return EIO; + + return ret; +} + +static struct sensor_device ov9665 = { + .name = "OV9665", + .type = SENSOR_TYPE_SOC, + .minor = -1, + .open = ov9665_open, + .release = ov9665_release, + .on = ov9665_on, + .off = ov9665_off, + .querycap = ov9665_get_caps, + .get_config = ov9665_get_config, + .set_config = ov9665_setup, + .enum_parm = ov9665_queryctrl, + .get_parm = ov9665_g_ctrl, + .set_parm = ov9665_s_ctrl, + .try_res = ov9665_try_res, + .set_res = ov9665_set_res, + .suspend = ov9665_standby, + .resume = ov9665_wakeup, + .get_ls_corr_config = NULL, + .set_awb = NULL, + .set_aec = NULL, + .set_blc = NULL, + /* TBC */ +}; +#endif + +static int ov9665_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 reg = 0; + + DBG_entering; + if (enable) { + ov9665_read(client, 0x09, ®); + reg = reg & ~0x10; + ov9665_write(client, 0x09, reg); + ov9665_set_data_pin_out(client); + ssleep(1); + + } else { + ov9665_read(client, 0x09, ®); + reg = reg | 0x10; + ov9665_write(client, 0x09, reg); + ov9665_set_data_pin_in(client); + } + + DBG_leaving; + return 0; +} + +static int ov9665_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_frmsizeenum *fsize) +{ + unsigned int index = fsize->index; + + DBG_entering; + + if (index >= N_RES) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = ov9665_res[index].width; + fsize->discrete.height = ov9665_res[index].height; + fsize->reserved[0] = ov9665_res[index].used; + + DBG_leaving; + + return 0; +} + +static int ov9665_enum_frameintervals(struct v4l2_subdev *sd, + struct v4l2_frmivalenum *fival) +{ + unsigned int index = fival->index; + + DBG_entering; + + if (index >= N_RES) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1; + fival->discrete.denominator = ov9665_res[index].fps; + + DBG_leaving; + + return 0; +} + +static int ov9665_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + +#define V4L2_IDENT_OV9665 8246 + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_OV9665, 0); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov9665_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char val = 0; + int ret; + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ret = ov9665_read(client, reg->reg & 0xffff, &val); + reg->val = val; + reg->size = 1; + return ret; +} + +static int ov9665_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ov9665_write(client, reg->reg & 0xffff, reg->val & 0xff); + return 0; +} +#endif + +static const struct v4l2_subdev_video_ops ov9665_video_ops = { + .try_fmt = ov9665_try_fmt, + .s_fmt = ov9665_set_fmt, + .g_fmt = ov9665_get_fmt, + .s_stream = ov9665_s_stream, + .enum_framesizes = ov9665_enum_framesizes, + .enum_frameintervals = ov9665_enum_frameintervals, +}; + +static const struct v4l2_subdev_core_ops ov9665_core_ops = { + .g_chip_ident = ov9665_g_chip_ident, + .queryctrl = ov9665_queryctrl, + .g_ctrl = ov9665_g_ctrl, + .s_ctrl = ov9665_s_ctrl, + .s_gpio = ov9665_s_power, + /*.g_ext_ctrls = ov9665_g_ext_ctrls,*/ + /*.s_ext_ctrls = ov9665_s_ext_ctrls,*/ +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov9665_g_register, + .s_register = ov9665_s_register, +#endif +}; + +static const struct v4l2_subdev_ops ov9665_ops = { + .core = &ov9665_core_ops, + .video = &ov9665_video_ops, +}; +/* + * Basic i2c stuff + */ +/* +static unsigned short normal_i2c[] = {0x30, I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +static struct i2c_driver ov9665_driver; +*/ +static int ov9665_detect(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int adap_id = i2c_adapter_id(adapter); + u8 config = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (adap_id != 1) + return -ENODEV; + + ov9665_wakeup(); + + ov9665_read(client, 0x0a, &config); + if (config != 0x96) + return -ENODEV; + + ov9665_read(client, 0x0b, &config); + if (config != 0x63) + return -ENODEV; + + return 0; +} + +static int ov9665_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ci_sensor_config *info; + struct v4l2_subdev *sd; + int ret = -1; + + DBG_entering; + /* + * Setup sensor configuration structure + */ + info = kzalloc(sizeof(struct ci_sensor_config), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = ov9665_detect(client); + if (ret) { + kfree(info); + return -ENODEV; + } + + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &ov9665_ops); + + /* + * Initialization OV9665 + * then turn into standby mode + */ + /* ret = ov9665_standby(); */ + ret = ov9665_init(client); + if (ret) { + eprintk("error init ov9665"); + goto err_1; + } + + ov9665_standby(); + printk(KERN_INFO "Init ov9665 sensor success\n"); + DBG_leaving; + return 0; + +err_1: + kfree(info); + return ret; +} + +/* + * XXX: Need to be checked + */ +static int ov9665_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + kfree(to_sensor_config(sd)); + + return 0; +} + +static const struct i2c_device_id ov9665_id[] = { + {"ov9665", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ov9665_id); + +static struct v4l2_i2c_driver_data v4l2_i2c_data = { + .name = "ov9665", + .probe = ov9665_probe, + .remove = ov9665_remove, + .id_table = ov9665_id, +}; + +MODULE_AUTHOR("Xiaolin Zhang "); +MODULE_DESCRIPTION("A low-level driver for OmniVision 9665 sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mrstci/mrstov9665/ov9665.h b/drivers/media/video/mrstci/mrstov9665/ov9665.h new file mode 100644 index 0000000..6fc9d12 --- /dev/null +++ b/drivers/media/video/mrstci/mrstov9665/ov9665.h @@ -0,0 +1,263 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#define I2C_OV9665 0x60 +/* Should add to kernel source */ +#define I2C_DRIVERID_OV9665 1047 +/* GPIO pin on Moorestown */ +#define GPIO_SCLK_25 44 +#define GPIO_STB_PIN 47 +#define GPIO_STDBY_PIN 48 +#define GPIO_RESET_PIN 50 + +struct regval_list { + u8 reg_num; + u8 value; +}; + +/* + * Default register value + * 1280x1024 YUV + */ +static struct regval_list ov9665_def_reg[] = { + {0x3E, 0x80}, + {0x12, 0x80}, + + {0xd5, 0xff}, + {0xd6, 0x3f}, + + {0x3d, 0x3c}, + {0x11, 0x81}, + {0x2a, 0x00}, + {0x2b, 0x00}, + + {0x3a, 0xf1}, + {0x3b, 0x00}, + {0x3c, 0x58}, + {0x3e, 0x50}, + {0x71, 0x00}, + + {0x15, 0x00}, + {0x6a, 0x24}, + {0x85, 0xe7}, + + {0x63, 0x01}, + + {0x17, 0x0c}, + {0x18, 0x5c}, + {0x19, 0x01}, + {0x1a, 0x82}, + {0x03, 0x03}, + {0x2b, 0x00}, + + {0x36, 0xb4}, + {0x65, 0x10}, + {0x70, 0x02}, + {0x71, 0x9f}, + {0x64, 0x24}, + + {0x43, 0x00}, + {0x5D, 0x55}, + {0x5E, 0x57}, + {0x5F, 0x21}, + + {0x24, 0x3e}, + {0x25, 0x38}, + {0x26, 0x72}, + + {0x14, 0x68}, + {0x0C, 0x3a}, /* Auto detect for 50/60 */ + {0x4F, 0x9E}, + {0x50, 0x84}, + {0x5A, 0x67}, + + {0x7d, 0x30}, + {0x7e, 0x00}, + {0x82, 0x03}, + {0x7f, 0x00}, + {0x83, 0x07}, + {0x80, 0x03}, + {0x81, 0x04}, + + {0x96, 0xf0}, + {0x97, 0x00}, + {0x92, 0x33}, + {0x94, 0x5a}, + {0x93, 0x3a}, + {0x95, 0x48}, + {0x91, 0xfc}, + {0x90, 0xff}, + {0x8e, 0x4e}, + {0x8f, 0x4e}, + {0x8d, 0x13}, + {0x8c, 0x0c}, + {0x8b, 0x0c}, + {0x86, 0x9e}, + {0x87, 0x11}, + {0x88, 0x22}, + {0x89, 0x05}, + {0x8a, 0x03}, + + {0x9b, 0x0e}, + {0x9c, 0x1c}, + {0x9d, 0x34}, + {0x9e, 0x5a}, + {0x9f, 0x68}, + {0xa0, 0x76}, + {0xa1, 0x82}, + {0xa2, 0x8e}, + {0xa3, 0x98}, + {0xa4, 0xa0}, + {0xa5, 0xb0}, + {0xa6, 0xbe}, + {0xa7, 0xd2}, + {0xa8, 0xe2}, + {0xa9, 0xee}, + {0xaa, 0x18}, + + {0xAB, 0xe7}, + {0xb0, 0x43}, + {0xac, 0x04}, + {0x84, 0x40}, + + {0xad, 0x84}, + {0xd9, 0x24}, + {0xda, 0x00}, + {0xae, 0x10}, + + {0xab, 0xe7}, + {0xb9, 0xa0}, + {0xba, 0x80}, + {0xbb, 0xa0}, + {0xbc, 0x80}, + + {0xbd, 0x08}, + {0xbe, 0x19}, + {0xbf, 0x02}, + {0xc0, 0x08}, + {0xc1, 0x2a}, + {0xc2, 0x34}, + {0xc3, 0x2d}, + {0xc4, 0x2d}, + {0xc5, 0x00}, + {0xc6, 0x98}, + {0xc7, 0x18}, + {0x69, 0x48}, + + {0x74, 0xc0}, + + {0x7c, 0x18}, + {0x65, 0x11}, + {0x66, 0x00}, + {0x41, 0xa0}, + {0x5b, 0x28}, + {0x60, 0x84}, + {0x05, 0x07}, + {0x03, 0x03}, + {0xd2, 0x8c}, + + {0xc7, 0x90}, + {0xc8, 0x06}, + {0xcb, 0x40}, + {0xcc, 0x40}, + {0xcf, 0x00}, + {0xd0, 0x20}, + {0xd1, 0x00}, + {0xc7, 0x18}, + + {0x0d, 0x82}, + {0x0d, 0x80}, + + {0x09, 0x01}, + + {0xff, 0xff}, +}; + +/* 1280x1024 */ +static struct regval_list ov9665_res_sxga[] = { + {0x0c, 0xbc}, /* note this */ + {0xff, 0xff}, +}; + +/* 640x480 */ +static struct regval_list ov9665_res_vga[] = { + /* Fclk/4 */ + {0x11, 0x80}, + {0x63, 0x00}, + + {0x12, 0x40}, /*VGA format*/ + {0x14, 0x30}, /*4x*/ + {0x0c, 0xbc}, + {0x4d, 0x09}, + {0x5c, 0x80}, /* Full average AEC */ + + /* Windows setting */ + {0x17, 0x0c}, + {0x18, 0x5c}, + {0x19, 0x02}, + {0x1a, 0x3f}, + {0x03, 0x03}, + {0x32, 0xad}, + + /* 50/60Hz AEC */ + {0x5a, 0x23}, + {0x2b, 0x00}, + + {0x64, 0xa4}, + /* + {0x4F, 0x4f}, + {0x50, 0x42}, + */ + {0x4F, 0x9e}, + {0x50, 0x84}, + {0x97, 0x0a}, + {0xad, 0x82}, + {0xd9, 0x11}, + + /* Scale window */ + {0xb9, 0x50}, + {0xba, 0x3c}, + {0xbb, 0x50}, + {0xbc, 0x3c}, + + {0xad, 0x80}, + {0xd9, 0x00}, + {0xac, 0x0f}, + {0x84, 0x86}, + + /*This is for Color Matrix*/ + {0xbd, 0x05}, + {0xbe, 0x16}, + {0xbf, 0x05}, + {0xc0, 0x07}, + {0xc1, 0x18}, + {0xc2, 0x1f}, + {0xc3, 0x2b}, + {0xc4, 0x2b}, + {0xc5, 0x00}, + + {0x0d, 0x92}, + {0x0d, 0x90}, + + {0xff, 0xff}, +}; diff --git a/drivers/media/video/mrstci/mrsts5k4e1/Kconfig b/drivers/media/video/mrstci/mrsts5k4e1/Kconfig new file mode 100755 index 0000000..7dee787 --- /dev/null +++ b/drivers/media/video/mrstci/mrsts5k4e1/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_MRST_S5K4E1 + tristate "Moorestown s5k4e1 RAW Sensor" + depends on I2C && VIDEO_MRST_ISP + + ---help--- + Say Y here if your platform support s5k4e1 RAW Sensor. + + To compile this driver as a module, choose M here: the + module will be called mrstov2650.ko. diff --git a/drivers/media/video/mrstci/mrsts5k4e1/Makefile b/drivers/media/video/mrstci/mrsts5k4e1/Makefile new file mode 100644 index 0000000..8733fa8 --- /dev/null +++ b/drivers/media/video/mrstci/mrsts5k4e1/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_VIDEO_MRST_S5K4E1) += mrsts5k4e1.o + +EXTRA_CFLAGS += -I$(src)/../include diff --git a/drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.c b/drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.c new file mode 100755 index 0000000..f644531 --- /dev/null +++ b/drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.c @@ -0,0 +1,1024 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ci_sensor_common.h" +#include "mrsts5k4e1.h" +/* #include "priv.h" */ +/* extern const struct DumpRegs regs_d[]; */ + +static int s5k4e1_debug; +module_param(s5k4e1_debug, int, 0644); +MODULE_PARM_DESC(s5k4e1_debug, "Debug level (0-1)"); + +#define dprintk(level, fmt, arg...) \ + do { \ + if (s5k4e1_debug >= level) \ + printk(KERN_DEBUG "mrstisp@%s: " fmt "\n", \ + __func__, ## arg);\ + } while (0) + +#define eprintk(fmt, arg...) \ + printk(KERN_ERR "mrstisp@%s:" fmt "\n", \ + __func__, ## arg); + +#define DBG_entering dprintk(1, "entering"); +#define DBG_leaving dprintk(1, "leaving"); +#define DBG_line dprintk(1, " line: %d", __LINE__); + +static inline struct ci_sensor_config *to_sensor_config(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ci_sensor_config, sd); +} + +static struct s5k4e1_format_struct { + __u8 *desc; + __u32 pixelformat; + struct regval_list *regs; +} s5k4e1_formats[] = { + { + .desc = "Raw RGB Bayer", + .pixelformat = SENSOR_MODE_MIPI, + .regs = NULL, + }, +}; +#define N_S5K4E1_FMTS ARRAY_SIZE(s5k4e1_formats) + +static struct s5k4e1_res_struct { + __u8 *desc; + int res; + int width; + int height; + /* FIXME: correct the fps values.. */ + int fps; + bool used; + struct regval_list *regs; +} s5k4e1_res[] = { + { + .desc = "QSXGA_PLUS4", + .res = SENSOR_RES_QXGA_PLUS, + .width = 2592, + .height = 1944, + .fps = 15, + .used = 0, + .regs = s5k4e1_res_qsxga_plus4, + }, + { + .desc = "1080P", + .res = SENSOR_RES_1080P, + .width = 1920, + .height = 1080, + .fps = 25, + .used = 0, + .regs = s5k4e1_res_1080p, + }, + { + .desc = "VGA_PLUS", + .res = SENSOR_RES_VGA_PLUS, + .width = 1304, + .height = 980, + .fps = 30, + .used = 0, + .regs = s5k4e1_res_vga_ac04_bill, + }, + { + .desc = "720p", + .res = SENSOR_RES_720P, + .width = 1280, + .height = 720, + .fps = 30, + .used = 0, + .regs = s5k4e1_res_720p, + }, + { + .desc = "VGA", + .res = SENSOR_RES_VGA, + .width = 640, + .height = 480, + .used = 0, + .fps = 40, + .regs = s5k4e1_res_vga_ac04_bill, + }, +}; + +#define N_RES (ARRAY_SIZE(s5k4e1_res)) + +/* + * I2C Read & Write stuff + */ +static int s5k4e1_read(struct i2c_client *c, u32 reg, u32 *value) +{ + int ret; + int i; + struct i2c_msg msg[2]; + u8 msgbuf[2]; + u8 ret_val = 0; + *value = 0; + /* Read needs two message to go */ + memset(&msg, 0, sizeof(msg)); + msgbuf[0] = 0; + msgbuf[1] = 0; + i = 0; + + msgbuf[i++] = ((u16)reg) >> 8; + msgbuf[i++] = ((u16)reg) & 0xff; + msg[0].addr = c->addr; + msg[0].buf = msgbuf; + msg[0].len = i; + + msg[1].addr = c->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = &ret_val; + msg[1].len = 1; + + ret = i2c_transfer(c->adapter, &msg[0], 2); + *value = ret_val; + + ret = (ret == 2) ? 0 : -1; + dprintk(2, "reg:0x%8x, value:0x%8x - %s", reg, *value, + (ret ? "failed" : "succesfully")); + return ret; +} + +static int s5k4e1_write(struct i2c_client *c, u32 reg, u32 value) +{ + int ret, i; + struct i2c_msg msg; + u8 msgbuf[3]; + + /* Writing only needs one message */ + memset(&msg, 0, sizeof(msg)); + i = 0; + msgbuf[i++] = ((u16)reg) >> 8; + msgbuf[i++] = (u16)reg & 0xff; + msgbuf[i++] = (u8)value; + + msg.addr = c->addr; + msg.flags = 0; + msg.buf = msgbuf; + msg.len = i; + + ret = i2c_transfer(c->adapter, &msg, 1); + + /* If this is a reset register, wait for 1ms */ + if (reg == 0x0103 && (value & 0x01)) + /*Note here, check if this is needed */ + msleep(4); + + ret = (ret == 1) ? 0 : -1; + dprintk(2, "reg:0x%8x, value:0x%8x - %s", reg, value, + (ret ? "failed" : "successfully")); + return ret; +} + +static int s5k4e1_write_array(struct i2c_client *c, struct regval_list *vals) +{ + struct regval_list *p; + u32 read_val = 0; + int err_num = 0; + int i = 0; + + DBG_entering; + + p = vals; + while (p->reg_num != 0xffff) { + s5k4e1_write(c, (u32)p->reg_num, (u32)p->value); + s5k4e1_read(c, (u32)p->reg_num, &read_val); + /* msleep(100);*/ + if (read_val != p->value) { + eprintk("0x%x write error:should be 0x%x, but 0x%x", + p->reg_num, p->value, read_val); + err_num++; + } + p++; + i++; + } + dprintk(1, "sucessfully wrote %d registers, err is %d", i, + err_num); + return 0; +} + +/* + * Sensor specific helper function + */ +static int s5k4e1_standby(void) +{ + gpio_set_value(GPIO_STDBY_PIN, 1); + dprintk(1, "PM: standby called\n"); + return 0; +} + +static int s5k4e1_wakeup(void) +{ + gpio_set_value(GPIO_STDBY_PIN, 0); + dprintk(1, "PM: wakeup called\n"); + return 0; +} + +static int s5k4e1_s_power(struct v4l2_subdev *sd, u32 val) +{ + if (val == 1) + s5k4e1_standby(); + if (val == 0) + s5k4e1_wakeup(); + return 0; +} + +static int s5k4e1_set_img_ctrl(struct i2c_client *c, + const struct ci_sensor_config *config) +{ + int err = 0; + + DBG_entering; + + switch (config->blc) { + /* only SENSOR_BLC_AUTO supported */ + case SENSOR_BLC_AUTO: + break; + default: + dprintk(1, "BLC not supported,\ + set to BLC_AUTO by default."); + } + + switch (config->bls) { + /* only SENSOR_BLS_OFF supported */ + case SENSOR_BLS_OFF: + break; + default: + dprintk(1, "Black level not supported,\ + set to BLS_OFF by default."); + } + + switch (config->agc) { + /* only SENSOR_AGC_OFF supported */ + case SENSOR_AGC_OFF: + break; + default: + dprintk(1, "AGC not supported,\ + set to AGC_OFF by default."); + } + + switch (config->awb) { + /* only SENSOR_AWB_OFF supported */ + case SENSOR_AWB_OFF: + break; + default: + dprintk(1, "AWB not supported,\ + set to AWB_OFF by default."); + } + + switch (config->aec) { + /* only SENSOR_AEC_OFF supported */ + case SENSOR_AEC_OFF: + break; + default: + dprintk(1, "AEC not supported,\ + set to AEC_OFF by default."); + } + + DBG_leaving; + + return err; +} +static int s5k4e1_init(struct i2c_client *c) +{ + int ret = 0; + struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct ci_sensor_config *info = to_sensor_config(sd); + char *name = ""; + + DBG_entering; + + /* Fill the configuration structure */ + /* Note this default configuration value */ + info->mode = s5k4e1_formats[0].pixelformat; + info->res = s5k4e1_res[0].res; + info->type = SENSOR_TYPE_RAW; + info->bls = SENSOR_BLS_OFF; + info->gamma = SENSOR_GAMMA_OFF; + info->cconv = SENSOR_CCONV_OFF; + info->blc = SENSOR_BLC_AUTO; + info->agc = SENSOR_AGC_OFF; + info->awb = SENSOR_AWB_OFF; + info->aec = SENSOR_AEC_OFF; + /*info->bus_width = SENSOR_BUSWIDTH_10BIT_ZZ;*/ + info->bus_width = SENSOR_BUSWIDTH_12BIT; + info->ycseq = SENSOR_YCSEQ_YCBYCR; + info->conv422 = SENSOR_CONV422_COSITED; + /*info->conv422 = SENSOR_CONV422_NOCOSITED;*/ + info->bpat = SENSOR_BPAT_GRGRBGBG; + info->field_inv = SENSOR_FIELDINV_NOSWAP; + info->field_sel = SENSOR_FIELDSEL_BOTH; + info->hpol = SENSOR_HPOL_REFPOS; + info->vpol = SENSOR_VPOL_NEG; + info->edge = SENSOR_EDGE_RISING; + info->flicker_freq = SENSOR_FLICKER_100; + info->cie_profile = SENSOR_CIEPROF_F11; + info->mipi_mode = SENSOR_MIPI_MODE_RAW_10; + name = "s5k4e1"; + memcpy(info->name, name, 7); + + /* Reset sensor hardware, and implement the setting*/ + ret += s5k4e1_write(c, 0x0100, (u32)0x00); + /*TODO: See if we can ignore this*/ + ret = s5k4e1_write(c, 0x0103, (u32)0x01); + + /* sw reset -- delay 3.1ms */ + msleep(4); + + /* Set registers into default config value */ + /* ret += s5k4e1_write_array(c, s5k4e1_def_reg); */ + + /* Set MIPI interface */ +#ifdef S5K4E1_MIPI + ret += s5k4e1_write_array(c, s5k4e1_mipi); +#endif + + ret += s5k4e1_set_img_ctrl(c, info); /*FIXME*/ + + /* streaming */ + /* ret += s5k4e1_write(c, 0x0100, (u32)0x01); */ + ret += s5k4e1_write(c, 0x0100, (u32)0x00); + + msleep(1); + + DBG_leaving; + + return ret; +} + +static int distance(struct s5k4e1_res_struct *res, u32 w, u32 h) +{ + int ret; + + DBG_entering; + + if (res->width < w || res->height < h) + return -1; + + ret = ((res->width - w) + (res->height - h)); + + DBG_leaving; + + return ret; +} + +static int s5k4e1_try_res(u32 *w, u32 *h) +{ + struct s5k4e1_res_struct *res_index, *p = NULL; + int dis, last_dis = s5k4e1_res->width + s5k4e1_res->height; + + DBG_entering; + + for (res_index = s5k4e1_res; + res_index < s5k4e1_res + N_RES; + res_index++) { + if ((res_index->width < *w) || (res_index->height < *h)) + break; + dis = distance(res_index, *w, *h); + if (dis < last_dis) { + last_dis = dis; + p = res_index; + } + } + + if (p == NULL) + p = s5k4e1_res; + else if ((p->width < *w) || (p->height < *h)) { + if (p != s5k4e1_res) + p--; + } + + if ((w != NULL) && (h != NULL)) { + *w = p->width; + *h = p->height; + } + + DBG_leaving; + return 0; +} + +static struct s5k4e1_res_struct *s5k4e1_to_res(u32 w, u32 h) +{ + struct s5k4e1_res_struct *res_index; + + DBG_entering; + + for (res_index = s5k4e1_res; + res_index < s5k4e1_res + N_RES; + res_index++) + if ((res_index->width == w) && (res_index->height == h)) + break; + + if (res_index >= s5k4e1_res + N_RES) + res_index--; /* Take the bigger one */ + + DBG_leaving; + + return res_index; +} + +static int s5k4e1_try_fmt(struct v4l2_subdev *sd, + struct v4l2_format *fmt) +{ + DBG_entering; + return s5k4e1_try_res(&fmt->fmt.pix.width, &fmt->fmt.pix.height); + DBG_leaving; +} + +static int s5k4e1_get_fmt(struct v4l2_subdev *sd, + struct v4l2_format *fmt) +{ + struct ci_sensor_config *info = to_sensor_config(sd); + unsigned short width, height; + int index; + + ci_sensor_res2size(info->res, &width, &height); + + /* Marked the current sensor res as being "used" */ + for (index = 0; index < N_RES; index++) { + if ((width == s5k4e1_res[index].width) && + (height == s5k4e1_res[index].height)) { + s5k4e1_res[index].used = 1; + continue; + } + s5k4e1_res[index].used = 0; + } + + fmt->fmt.pix.width = width; + fmt->fmt.pix.height = height; + return 0; + +} + +#if 0 +/* chuanxiao add, to dump regs */ +static int s5k4e1_dump_regs(struct i2c_client *c) +{ + /*struct i2c_client *c = v4l2_get_subdevdata(sd);*/ + const struct DumpRegs *p = regs_d; + u32 value; + u32 value1, value2, value3, value4; + while (p->ulFlags != eTableEnd) { + if (p->ulFlags & eFourBytes) { + s5k4e1_read(c, (u32)p->ulAddr, &value1); + s5k4e1_read(c, (u32)p->ulAddr+1, &value2); + s5k4e1_read(c, (u32)p->ulAddr+2, &value3); + s5k4e1_read(c, (u32)p->ulAddr+3, &value4); + value = value1<<24 | value2<<16 | value3<<8 | value4; + } else if (p->ulFlags & eTwoBytes) { + s5k4e1_read(c, (u32)p->ulAddr, &value1); + s5k4e1_read(c, (u32)p->ulAddr+1, &value2); + value = value1<<8 | value2; + } else + s5k4e1_read(c, (u32)p->ulAddr, &value); + /* + if (value == p->ulDefaultValue) + dprintk(0, "%s\t @ 0x%x = 0x%lx (= default value)\n", + p->pszName, p->ulAddr, value); + else + dprintk(0, "%s\t @ 0x%x = 0x%lx (default was 0x%lx)\n", + p->pszName, p->ulAddr, value, p->ulDefaultValue); + */ + dprintk(0, "%-30s @ 0x%04X = 0x%08X", p->pszName, + p->ulAddr, value); + p++; + } + return 0; +} +#endif + +static int s5k4e1_set_fmt(struct v4l2_subdev *sd, struct v4l2_format *fmt) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + struct ci_sensor_config *info = to_sensor_config(sd); + int ret = 0; + struct s5k4e1_res_struct *res_index; + u32 width, height; + int index; + + DBG_entering; + + width = fmt->fmt.pix.width; + height = fmt->fmt.pix.height; + + dprintk(1, "was told to set fmt (%d x %d) ", width, height); + ret = s5k4e1_try_res(&width, &height); + + res_index = s5k4e1_to_res(width, height); + + s5k4e1_wakeup(); + DBG_line; + if (res_index->regs) { + /* software sleep/standby */ + ret += s5k4e1_write(c, 0x0100, (u32)0x00); + + /* Soft reset camera first*/ + /*TODO: See if we can ignore this*/ + ret = s5k4e1_write(c, 0x0103, (u32)0xff); + + /* Set registers into default config value */ + /* ret += s5k4e1_write_array(c, s5k4e1_def_reg);*/ + + /* set image resolution */ + ret += s5k4e1_write_array(c, res_index->regs); + + ret += s5k4e1_set_img_ctrl(c, info); + + /* XXX setup with unknow meaning ... */ + /* ret += s5k4e1_write(c, 0x30b0, 0xfe); */ + + /* Set MIPI interface */ +#ifdef S5K4E1_MIPI + ret += s5k4e1_write_array(c, s5k4e1_mipi); +#endif + + /* streaming */ + ret = s5k4e1_write(c, 0x0100, (u32)0x01); + msleep(1); + + info->res = res_index->res; + + /* Marked current sensor res as being "used" */ + for (index = 0; index < N_RES; index++) { + if ((width == s5k4e1_res[index].width) && + (height == s5k4e1_res[index].height)) { + s5k4e1_res[index].used = 1; + continue; + } + s5k4e1_res[index].used = 0; + } + + for (index = 0; index < N_RES; index++) + dprintk(2, "index = %d, used = %d\n", index, + s5k4e1_res[index].used); + + DBG_line; + } else { + eprintk("no res for (%d x %d)", width, height); + } + + DBG_leaving; + return ret; +} + +static int s5k4e1_t_gain(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + DBG_entering; + + s5k4e1_write(client, 0x0104, 1); /*hold*/ + + /* analog gain */ + s5k4e1_write(client, 0x0204, value >> 8); + + s5k4e1_write(client, 0x0205, value & 0xff); + + s5k4e1_write(client, 0x0104, 0); /*unhold*/ + + dprintk(1, "gain %x was writen to 0x0204/5", value); + + DBG_leaving; + return 0; +} + +static int s5k4e1_t_exposure(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + DBG_entering; + + s5k4e1_write(client, 0x0104, 1); /*hold*/ + + /* fine integration time */ + s5k4e1_write(client, 0x0200, value >> 24); + + s5k4e1_write(client, 0x0201, (value >> 16) & 0xff); + + /* coarse integration time */ + s5k4e1_write(client, 0x0202, (value & 0xff00) >> 8); + + s5k4e1_write(client, 0x0203, value & 0xff); + + s5k4e1_write(client, 0x0104, 0); /*unhold*/ + + dprintk(1, "exposure %x was writen to 0x0200/1/2/3", value); + + DBG_leaving; + return 0; +} + +static struct s5k4e1_control { + struct v4l2_queryctrl qc; + int (*query)(struct v4l2_subdev *sd, __s32 *value); + int (*tweak)(struct v4l2_subdev *sd, int value); +} s5k4e1_controls[] = { + { + .qc = { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x0, + .maximum = 0xFFFF, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + .tweak = s5k4e1_t_gain, + }, + { + .qc = { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x0, + .maximum = 0xFFFF, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + .tweak = s5k4e1_t_exposure, + }, +}; +#define N_CONTROLS (ARRAY_SIZE(s5k4e1_controls)) + +static struct s5k4e1_control *s5k4e1_find_control(__u32 id) +{ + int i; + + DBG_entering; + for (i = 0; i < N_CONTROLS; i++) + if (s5k4e1_controls[i].qc.id == id) + return s5k4e1_controls + i; + DBG_leaving; + return NULL; +} + +static int s5k4e1_queryctrl(struct v4l2_subdev *sd, + struct v4l2_queryctrl *qc) +{ + struct s5k4e1_control *ctrl = s5k4e1_find_control(qc->id); + + DBG_entering; + if (ctrl == NULL) + return -EINVAL; + *qc = ctrl->qc; + + DBG_leaving; + return 0; +} + +static int s5k4e1_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ +/* + struct s5k4e1_control *octrl = s5k4e1_find_control(parm->index); + int ret; + + if (octrl == NULL) + return -EINVAL; + ret = octrl->query(client, &parm->value); + if (ret >= 0) + return 0; +*/ + return 0; +} + +static int s5k4e1_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct s5k4e1_control *octrl = s5k4e1_find_control(ctrl->id); + int ret; + + DBG_entering; + + if (octrl == NULL) + return -EINVAL; + ret = octrl->tweak(sd, ctrl->value); + if (ret >= 0) + return 0; + + DBG_leaving; + return ret; +} + +static int s5k4e1_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + DBG_entering; + + if (enable) { + s5k4e1_write(client, (u32)0x0100, 0x01); + /*chuanxiao add, dump s5k4e1 regs*/ + /* s5k4e1_dump_regs(client); */ + } else + s5k4e1_write(client, (u32)0x0100, 0x00); + + /*msleep(1);*/ + + DBG_leaving; + return 0; +} + +static int s5k4e1_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_frmsizeenum *fsize) +{ + unsigned int index = fsize->index; + + DBG_entering; + + if (index >= N_RES) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = s5k4e1_res[index].width; + fsize->discrete.height = s5k4e1_res[index].height; + fsize->reserved[0] = s5k4e1_res[index].used; + + DBG_leaving; + + return 0; +} + +static int s5k4e1_enum_frameintervals(struct v4l2_subdev *sd, + struct v4l2_frmivalenum *fival) +{ + unsigned int index = fival->index; + + DBG_entering; + + if (index >= N_RES) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1; + fival->discrete.denominator = s5k4e1_res[index].fps; + + DBG_leaving; + + return 0; +} + +static int s5k4e1_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + DBG_entering; + +#define V4L2_IDENT_S5K4E1 8250 + DBG_leaving; + + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_S5K4E1, 0); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int s5k4e1_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char val = 0; + int ret; + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ret = s5k4e1_read(client, reg->reg & 0xffff, &val); + reg->val = val; + reg->size = 1; + return ret; +} + +static int s5k4e1_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + s5k4e1_write(client, reg->reg & 0xffff, reg->val & 0xff); + return 0; +} +#endif + +static const struct v4l2_subdev_video_ops s5k4e1_video_ops = { + .try_fmt = s5k4e1_try_fmt, + .s_fmt = s5k4e1_set_fmt, + .g_fmt = s5k4e1_get_fmt, + .s_stream = s5k4e1_s_stream, + .enum_framesizes = s5k4e1_enum_framesizes, + .enum_frameintervals = s5k4e1_enum_frameintervals, +}; + +static const struct v4l2_subdev_core_ops s5k4e1_core_ops = { + .g_chip_ident = s5k4e1_g_chip_ident, + .queryctrl = s5k4e1_queryctrl, + .g_ctrl = s5k4e1_g_ctrl, + .s_ctrl = s5k4e1_s_ctrl, + .s_gpio = s5k4e1_s_power, + /*.g_ext_ctrls = s5k4e1_g_ext_ctrls,*/ + /*.s_ext_ctrls = s5k4e1_s_ext_ctrls,*/ +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = s5k4e1_g_register, + .s_register = s5k4e1_s_register, +#endif +}; + +static const struct v4l2_subdev_ops s5k4e1_ops = { + .core = &s5k4e1_core_ops, + .video = &s5k4e1_video_ops, +}; + +/* + * Basic i2c stuff + */ +/* +static unsigned short normal_i2c[] = {0x36, I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +static struct i2c_driver i2c_driver_s5k4e1_sensor; +*/ +static int s5k4e1_detect(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int adap_id = i2c_adapter_id(adapter); + u32 value; + + DBG_entering; + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { + eprintk("error i2c check func"); + return -ENODEV; + } + + if (adap_id != 1) { + eprintk("adap_id != 1"); + return -ENODEV; + } + + if (s5k4e1_wakeup()) { + eprintk("sensor wakeup failed"); + return -EIO; + } + + s5k4e1_read(client, 0x0003, &value); + dprintk(1, "Read from 0x0003: %x", value); + if ((value != 0x09)) + return -ENODEV; + + s5k4e1_read(client, 0x0000, &value); + dprintk(1, "Read from 0x0000: %x", value); + if ((value != 0x4e) && (value != 0x10)) + return -ENODEV; + + s5k4e1_read(client, 0x0001, &value); + dprintk(1, "Read from 0x0001: %x", value); + if ((value != 0x4e) && (value != 0x10)) + return -ENODEV; + + /*TODO EVT3 detect*/ + s5k4e1_read(client, 0x0002, &value); + dprintk(1, "Read from 0x0002: %x", value); + if (value == 0x0010) { + dprintk(1, "EVT3 module not supported!"); + return -ENODEV; + } + + DBG_leaving; + return 0; +} + +static int s5k4e1_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ci_sensor_config *info; + struct v4l2_subdev *sd; + int ret = -1; + + DBG_entering; + + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + + /* + * Setup sensor configuration structure + */ + info = kzalloc(sizeof(struct ci_sensor_config), GFP_KERNEL); + if (!info) { + dprintk(0, "fail to malloc for ci_sensor_config"); + ret = -ENOMEM; + goto out; + } + + ret = s5k4e1_detect(client); + if (ret) { + dprintk(0, "error s5k4e1_detect"); + goto out_free; + } + + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &s5k4e1_ops); + + /* + * Initialization S5K4E1 + * then turn into standby mode + */ + ret = s5k4e1_init(client); + if (ret) { + dprintk(0, "error calling s5k4e1_init"); + goto out_free; + } + + s5k4e1_standby(); + dprintk(0, "Init s5k4e1 sensor successfully"); + + ret = 0; + goto out; + +out_free: + kfree(info); + DBG_leaving; +out: + + DBG_leaving; + return ret; +} + + +static int s5k4e1_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + DBG_entering; + + v4l2_device_unregister_subdev(sd); + kfree(to_sensor_config(sd)); + + DBG_leaving; + return 0; +} + +/** + * i2c_driver for s5k4e1_sensor + */ +static const struct i2c_device_id s5k4e1_id[] = { + {"s5k4e1", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, s5k4e1_id); + +static struct v4l2_i2c_driver_data v4l2_i2c_data = { + .name = "s5k4e1", + .probe = s5k4e1_probe, + .remove = s5k4e1_remove, + /* .suspend = s5k4e1_suspend, + * .resume = s5k4e1_resume, */ + .id_table = s5k4e1_id, +}; + +MODULE_AUTHOR("Xiaolin Zhang "); +MODULE_DESCRIPTION("A low-level driver for Samsung S5K4E1 sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.h b/drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.h new file mode 100755 index 0000000..d722035 --- /dev/null +++ b/drivers/media/video/mrstci/mrsts5k4e1/mrsts5k4e1.h @@ -0,0 +1,662 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#define I2C_S5K4E1 0x6C +/* Should add to kernel source */ +#define I2C_DRIVERID_S5K4E1 1046 +/* GPIO pin on Moorestown */ +#define GPIO_SCLK_25 44 +#define GPIO_STB_PIN 47 +#define GPIO_STDBY_PIN 49 +#define GPIO_RESET_PIN 50 + +struct regval_list { + u16 reg_num; + u8 value; +}; + +/* + * Default register value + * 5Mega Pixel, 2592x1944 + */ +/* MIPI register are removed by Wen */ + +/* 2592x1944 */ +static struct regval_list s5k4e1_res_qsxga_plus4[] = { + /* Reset for operation */ + {0x0100, 0x00}, /* stream off */ + {0x0103, 0x01}, /* software reset */ + +/* + * Analog Setting + * This register is for FACTORY ONLY. + * If you change it without prior notification, + * You are RESPONSIBLE for the FAILURE that will happen in the future. + */ + +/* CDS timing setting ... */ + {0x3000, 0x04}, /* ct_ld_start (default = 07h) */ + {0x3001, 0x02}, /* ct_sl_start (default = 05h) */ + {0x3002, 0x0C}, /* ct_rx_start (default = 21h) */ + {0x3003, 0x0E}, /* ct_cds_start (default = 23h) */ + {0x3004, 0x2C}, /* ct_smp_width (default = 60h) */ + {0x3005, 0x0D}, /* ct_az_width (default = 28h) */ + {0x3006, 0x39}, /* ct_s1r_width (default = 88h) */ + {0x3007, 0x02}, /* ct_tx_start (default = 06h) */ + {0x3008, 0x3C}, /* ct_tx_width 1.5us (default = 7Ch) */ + {0x3009, 0x3C}, /* ct_stx_width 1.5us (default = 7Ch) */ + {0x300A, 0x28}, /* ct_dtx_width 1us (default = 3Eh) */ + {0x300B, 0x15}, /* ct_rmp_rst_start (default = 44h) */ + {0x300C, 0x15}, /* ct_rmp_sig_start (default = 48h) */ + {0x300D, 0x02}, /* ct_rmp_lat (default = 02h) */ + {0x300E, 0xA9}, /* D-Shut en[7], CLP On[5], LD high[4] */ + +/* CDS option setting ... */ + {0x3010, 0x00}, /* smp_en[2]=0(00) 1(04) row_id[1:0] = 00 */ + {0x3011, 0x7A}, /* RST_MX (288), SIG_MX (1024+352) */ + {0x3012, 0x30}, /* SIG offset1 48 code */ + {0x3013, 0xA0}, /* RST offset1 160 code */ + {0x3014, 0x00}, /* SIG offset2 */ + {0x3015, 0x00}, /* RST offset2 */ + {0x3016, 0x02}, /* ADC_SAT (510mV) */ + {0x3017, 0x94}, /* RMP_INIT[3:0](RMP_REG) 1.8V MS[6:4]=1 */ + {0x3018, 0x78}, /* rmp option - ramp connect[MSB] +RMP INIT DAC MIN */ + {0x301D, 0xD4}, /* CLP level (default = 0Fh) */ + + {0x3021, 0x02}, /* inrush ctrl[1] off */ + {0x3022, 0x44}, /* pump ring oscillator set [7:4]=CP, [3:0]=NCP */ + {0x3024, 0x40}, /* pix voltage 2.8V (default = 88h) */ + {0x3027, 0x08}, /* ntg voltage (default = 04h) */ + +/* Pixel option setting ... */ + {0x301C, 0x05}, /* Pixel Bias [3:0] (default = 03h) */ + {0x30D8, 0x3F}, /* All tx off 2f, on 3f */ + +/* ADLC setting ... */ + {0x3070, 0x5F}, /* [6]L-ADLC BPR, [4]ch sel, [3]L-ADLC, [2]F-ADLC */ + {0x3071, 0x00}, /* F&L-adlc max 127 (default = 11h, max 255) */ + {0x3080, 0x04}, /* F-ADLC filter A (default = 10h) */ + {0x3081, 0x38}, /* F-ADLC filter B (default = 20h) */ + +/* Integration setting ... */ + {0x0202, 0x03}, /* coarse integration time */ + {0x0203, 0xCF}, + {0x0204, 0x00}, /* analog gain[msb] 0100 x8 0080 x4 */ + {0x0205, 0x80}, /* analog gain[lsb] 0040 x2 0020 x1 */ + +/* Frame Length */ + {0x0340, 0x07}, /* Capture 07B4(1960[# of row]+12[V-blank]) */ + {0x0341, 0xA4}, /* Preview 03E0(980[# of row]+12[V-blank]) */ + +/* Line Length */ + {0x0342, 0x0A}, /* 2738 */ + {0x0343, 0xB2}, /* (Same as sensor default) */ + +/* embedded 2-line OFF setting ... */ +/* 2608 x 1960 */ + {0x3084, 0x15}, /* SYNC Mode */ + +/* (3) MIPI 2-lane Serial(TST = 0000b or TST = 0010b), 30 fps */ + + {0x30A9, 0x01}, + {0x0387, 0x01}, + + {0x30BD, 0x00}, /* SEL_CCP[0] */ + {0x30B2, 0x08}, /* PLL P = 8 */ + {0x30B3, 0x00}, /* PLL M[8] = 0 */ + {0x30B5, 0x01}, /* PLL S = 0 */ + {0x30BE, 0x1A}, /* M_PCLKDIV_AUTO[4], M_DIV_PCLK[3:0] */ + + {0x30BF, 0xAB}, + {0x30C0, 0x00}, /* video_offset[7:4] 3240%12 */ + {0x30C1, 0x01}, /* pack video enable [0] */ + {0x30C8, 0x0C}, /* video_data_length 3260 = 2608 * 1.25 */ + {0x30C9, 0xA8}, + {0x30E2, 0x02}, /* num lanes[1:0] = 2 */ + {0x30EE, 0x02}, /* DPHY enable [1] */ + {0x30F1, 0x70}, /* DPHY BANDCTRL 800MHz=80.6MHz */ + {0x3111, 0x86}, /* Embedded data off [5] */ + + {0x034C, 0x0A}, + {0x034D, 0x20}, + {0x044E, 0x07}, + {0x034F, 0x98}, + + {0x0344, 0x00}, + {0x0345, 0x08}, + {0x0346, 0x00}, + {0x0347, 0x08}, + {0x0348, 0x0A}, + {0x0349, 0x27}, + {0x034A, 0x07}, + {0x034B, 0x9F}, + + /* This is to set FRAME_NUM > 0 */ + {0x30d9, 0x00}, + + /* Add this setting according to Bill's test */ + {0x0305, 0x05}, + {0x0306, 0x00}, + {0x0307, 0x3c}, + {0x30b5, 0x02}, + + {0x020E, 0x01}, /* Gr Digital Gain */ + {0x020F, 0x00}, + {0x0210, 0x01}, /* Red Digital Gain */ + {0x0211, 0x00}, + {0x0212, 0x01}, /* Blue Digital Gain */ + {0x0213, 0x00}, + {0x0214, 0x01}, /* Gb Digital Gain */ + {0x0215, 0x00}, + {0x0204, 0x00}, + {0x0205, 0x80}, + +#if 1 + /*Apply Bill's setting*/ + {0x30E2, 0x02}, + {0x0305, 0x05}, + {0x0306, 0x00}, + {0x0307, 0x50}, /* vcc_out = 80 */ + {0x30B5, 0x01}, /* pll_s = 1 */ + {0x30B4, 0x50}, + + {0x30B2, 0x05}, + + {0x30BE, 0x1A}, /* DIV_M_PCLK = 5 */ + + {0x0100, 0x01}, /* stream on */ + {0xffff, 0xff}, +#endif +}; + +/* 1920x1080 */ +static struct regval_list s5k4e1_res_1080p[] = { +/* Reset for operation ... */ + {0x0100, 0x00}, /* stream off */ + {0x0103, 0x01}, /* software reset */ + +/* + * Analog Setting + * This register is for FACTORY ONLY. + * If you change it without prior notification, + * You are RESPONSIBLE for the FAILURE that will happen in the future. + */ + +/* CDS timing setting ... */ + {0x3000, 0x04}, /* ct_ld_start (default = 07h) */ + {0x3001, 0x02}, /* ct_sl_start (default = 05h) */ + {0x3002, 0x0C}, /* ct_rx_start (default = 21h) */ + {0x3003, 0x0E}, /* ct_cds_start (default = 23h) */ + {0x3004, 0x2C}, /* ct_smp_width (default = 60h) */ + {0x3005, 0x0D}, /* ct_az_width (default = 28h) */ + {0x3006, 0x39}, /* ct_s1r_width (default = 88h) */ + {0x3007, 0x02}, /* ct_tx_start (default = 06h) */ + {0x3008, 0x3C}, /* ct_tx_width 1.5us (default = 7Ch) */ + {0x300A, 0x28}, /* ct_dtx_width 1us (default = 3Eh) */ + {0x300B, 0x15}, /* ct_rmp_rst_start (default = 44h) */ + {0x300C, 0x15}, /* ct_rmp_sig_start (default = 48h) */ + {0x300D, 0x02}, /* ct_rmp_lat (default = 02h) */ + {0x300E, 0xA9}, /* D-Shut en[7], CLP On[5], LD high[4] */ + +/* CDS option setting ... */ + {0x3010, 0x00}, /* smp_en[2]=0(00) 1(04) row_id[1:0] = 00 */ + {0x3011, 0x7A}, /* RST_MX (288), SIG_MX (1024+352) */ + {0x3012, 0x30}, /* SIG offset1 48 code */ + {0x3013, 0xA0}, /* RST offset1 160 code */ + {0x3014, 0x00}, /* SIG offset2 */ + {0x3015, 0x00}, /* RST offset2 */ + {0x3016, 0x0A}, /* ADC_SAT (510mV) */ + {0x3017, 0x94}, /* RMP_INIT[3:0](RMP_REG) 1.8V MS[6:4]=1 */ + {0x3018, 0x78}, /* rmp option - ramp connect[MSB] +RMP INIT DAC MIN */ + + {0x301D, 0xD4}, /* CLP level (default = 0Fh) */ + + {0x3021, 0x02}, /* inrush ctrl[1] off */ + {0x3022, 0x41}, /* pump ring oscillator set [7:4]=CP, [3:0]=NCP */ + {0x3024, 0x08}, /* pix voltage 2.8V (default = 88h) */ + {0x3027, 0x08}, /* ntg voltage (default = 04h) */ + +/* Pixel option setting ... */ + {0x301C, 0x05}, /* Pixel Bias [3:0] (default = 03h) */ + {0x30D8, 0x3F}, /* All tx off 2f, on 3f */ + +/* ADLC setting ... */ + {0x3070, 0x5F}, /* [6]L-ADLC BPR, [4]ch sel, [3]L-ADLC, [2]F-ADLC */ + {0x3071, 0x00}, /* F&L-adlc max 127 (default = 11h, max 255) */ + {0x3080, 0x04}, /* F-ADLC filter A (default = 10h) */ + {0x3081, 0x38}, /* F-ADLC filter B (default = 20h) */ + +/* Integration setting ... */ + {0x0202, 0x03}, /* coarse integration time */ + {0x0203, 0xCD}, + {0x0204, 0x00}, /* analog gain[msb] 0100 x8 0080 x4 */ + {0x0205, 0x80}, /* analog gain[lsb] 0040 x2 0020 x1 */ + +/* Frame Length */ + {0x0340, 0x04}, /*Capture 07B4(1960[# of row]+12[V-blank]) */ + {0x0341, 0x44}, /*Preview 03E0(980[# of row]+12[V-blank]) */ + +/* Line Length */ + {0x0342, 0x0A}, /* 2738 */ + {0x0343, 0xB2}, /*(Same as sensor default) */ + +/* embedded 2-line OFF setting ... */ +/* 1920 x 1080 */ + {0x3084, 0x15}, /* SYNC Mode */ + +/* PLL & MIPI setting ... */ +/* input clock 25MHz */ + +/* (3) MIPI 2-lane Serial(TST = 0000b or TST = 0010b), 30 fps */ + {0x30BD, 0x00}, /* SEL_CCP[0] */ + {0x30B2, 0x08}, /* PLL P = 8 */ + {0x30B3, 0x00}, /* PLL M[8] = 0 */ + {0x30B4, 0x78}, /* PLL M = 129 */ + {0x30B5, 0x00}, /* PLL S = 0 */ + {0x30BE, 0x1A}, /* M_PCLKDIV_AUTO[4], M_DIV_PCLK[3:0] */ + + {0x30BF, 0xAB}, + {0x30C0, 0x00}, /* video_offset[7:4] 2400%12 */ + {0x30C1, 0x01}, /* pack video enable [0] */ + {0x30C8, 0x09}, /* video_data_length 2400 = 1920 * 1.25 */ + {0x30C9, 0x60}, + {0x30E2, 0x02}, /* num lanes[1:0] = 2 */ + {0x30EE, 0x02}, /* DPHY enable [1] */ + {0x30F1, 0x70}, /* DPHY BANDCTRL 800MHz=80.6MHz */ + {0x3111, 0x86}, /* Embedded data off [5] */ + + {0x30b4, 0x20}, + {0x30b5, 0x01}, + + {0x30A9, 0x01}, + {0x0387, 0x01}, + {0x0344, 0x01}, /*x_addr_start 344 */ + {0x0345, 0x58}, + {0x0348, 0x08}, /*x_addr_end 2263 */ + {0x0349, 0xD7}, + {0x0346, 0x01}, /*y_addr_start 440 */ + {0x0347, 0xB8}, + {0x034A, 0x05}, /*y_addr_end 1519 */ + {0x034B, 0xEF}, + + {0x034C, 0x07}, /*x_output_size 1920 */ + {0x034D, 0x80}, + {0x034E, 0x04}, /*y_output_size 1080 */ + {0x034F, 0x38}, + + {0x30d9, 0x00}, + + {0x020E, 0x01}, /*Gr Digital Gain */ + {0x020F, 0x00}, + {0x0210, 0x01}, /*Red Digital Gain */ + {0x0211, 0x00}, + {0x0212, 0x01}, /*Blue Digital Gain */ + {0x0213, 0x00}, + {0x0214, 0x01}, /*Gb Digital Gain */ + {0x0215, 0x00}, + {0x0204, 0x00}, + {0x0205, 0x80}, + + + /*Apply Bill's setting*/ + {0x30E2, 0x02}, + {0x0305, 0x05}, + {0x0306, 0x00}, + {0x0307, 0x50}, /*vcc_out = 80 */ + {0x30B5, 0x01}, /*pll_s = 1 */ + {0x30B4, 0x50}, + + {0x30B2, 0x05}, + + {0x30BE, 0x1A}, /*DIV_M_PCLK = 5 */ + + {0x0383, 0x01}, + + {0x0100, 0x01}, /* stream on */ + {0xffff, 0xff}, + +}; + +/* 1280x720, V1F2 & H1F2 */ +static struct regval_list s5k4e1_res_720p[] = { + {0x0100, 0x00}, /* stream off */ + {0x0103, 0x01}, /* software reset */ + +/* CDS timing setting ... */ + {0x3000, 0x04}, + {0x3001, 0x02}, + {0x3002, 0x0C}, + {0x3003, 0x0E}, + {0x3004, 0x2C}, + {0x3005, 0x0D}, + {0x3006, 0x39}, + {0x3007, 0x02}, + {0x3008, 0x3C}, + {0x3009, 0x3C}, + {0x300A, 0x28}, + {0x300B, 0x15}, + {0x300C, 0x15}, + {0x300D, 0x02}, + {0x300E, 0xAB}, + +/* CDS option setting ... */ + {0x3010, 0x00}, + {0x3011, 0x7A}, + {0x3012, 0x30}, + {0x3013, 0x90}, + {0x3014, 0x00}, + {0x3015, 0x00}, + {0x3016, 0x0A}, + {0x3017, 0x84}, + {0x3018, 0x78}, + {0x301D, 0xD4}, + + {0x3021, 0x02}, + {0x3022, 0x41}, + {0x3024, 0x08}, + {0x3027, 0x08}, + +/* Pixel option setting ... */ + {0x301C, 0x05}, /* Pixel Bias [3:0] (default = 03h) */ + {0x30D8, 0x3F}, /* All tx off 2f, on 3f */ + +/* ADLC setting ... */ + {0x3070, 0x5F}, + {0x3071, 0x00}, + {0x3080, 0x04}, + {0x3081, 0x38}, + +/* Integration setting ... */ + {0x0202, 0x03}, + {0x0203, 0xD8}, + {0x0204, 0x00}, + {0x0205, 0x80}, + +/*Frame Length*/ + {0x0340, 0x02}, + {0x0341, 0xDC}, + +/* Line Length */ + {0x0342, 0x0A}, /*2738 */ + {0x0343, 0xB2}, + +/* Average Sub-sampling */ + {0x0387, 0x03}, + {0x30a9, 0x02}, + +/* embedded 2-line OFF setting ... */ +/* 1280 x 720 */ + {0x3084, 0x15}, + +/* PLL & MIPI setting ... */ + +/* (3) MIPI 2-lane Serial(TST = 0000b or TST = 0010b), 60 fps */ + {0x30BD, 0x00}, + {0x30B2, 0x08}, + {0x30B3, 0x00}, + {0x30B4, 0x78}, + {0x30B5, 0x00}, + {0x30BE, 0x1A}, + + {0x30BF, 0xAB}, + {0x30C0, 0x40}, + {0x30C1, 0x01}, + {0x30C8, 0x06}, + {0x30C9, 0x40}, + + {0x30E2, 0x02}, + + {0x30b4, 0x20}, + {0x30b5, 0x01}, + + {0x30EE, 0x02}, + {0x30F1, 0x70}, + {0x3111, 0x86}, + +/* MIPI Size Setting ... */ +/* 1304 x 980 */ + {0x0344, 0x00}, + {0x0345, 0x18}, + {0x0348, 0x0A}, + {0x0349, 0x17}, + {0x0346, 0x01}, + {0x0347, 0x04}, + {0x034A, 0x06}, + {0x034B, 0xA3}, + + {0x0380, 0x00}, + {0x0381, 0x01}, + {0x0382, 0x00}, + {0x0383, 0x01}, + {0x0384, 0x00}, + {0x0385, 0x01}, + {0x0386, 0x00}, + {0x0387, 0x03}, + + {0x034C, 0x05}, /* x_output_size = 1280 */ + {0x034D, 0x00}, + {0x034E, 0x02}, /* y_output_size = 720 */ + {0x034F, 0xD0}, + + {0x30d9, 0x00}, + + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x0210, 0x01}, + {0x0211, 0x00}, + {0x0212, 0x01}, + {0x0213, 0x00}, + {0x0214, 0x01}, + {0x0215, 0x00}, + {0x0204, 0x01}, + {0x0205, 0x00}, + + /*Apply Bill's setting*/ + {0x30E2, 0x02}, + {0x0305, 0x05}, + {0x0306, 0x00}, + {0x0307, 0x50}, /*vcc_out = 80 */ + {0x30B5, 0x01}, /*pll_s = 1 */ + {0x30B4, 0x50}, + + {0x30B2, 0x05}, + + {0x30BE, 0x15}, /*DIV_M_PCLK = 5 */ + + {0x0100, 0x01}, /* stream on */ + {0xffff, 0xff}, +}; + +/*VGA*/ +static struct regval_list s5k4e1_res_vga_ac04_bill[] = { + {0x0100, 0x00}, /* stream off */ + {0x0103, 0x01}, /* software reset */ + + {0x3000, 0x04}, + {0x3001, 0x02}, + {0x3002, 0x0C}, + {0x3003, 0x0E}, + {0x3004, 0x2C}, + {0x3005, 0x0D}, + {0x3006, 0x39}, + {0x3007, 0x02}, + {0x3008, 0x3C}, + {0x3009, 0x3C}, + {0x300A, 0x28}, + {0x300B, 0x15}, + {0x300C, 0x15}, + {0x300D, 0x02}, + {0x300E, 0xA8}, + + {0x3010, 0x00}, + {0x3011, 0x7A}, + {0x3012, 0x30}, + {0x3013, 0xA0}, + {0x3014, 0x00}, + {0x3015, 0x00}, + {0x3016, 0x0A}, + {0x3017, 0x94}, + {0x3018, 0x78}, + + {0x301D, 0xD4}, + + {0x3021, 0x02}, + {0x3022, 0x41}, + {0x3024, 0x08}, + {0x3027, 0x08}, + + {0x301C, 0x05}, + {0x30D8, 0x3F}, + + {0x3070, 0x5F}, + {0x3071, 0x00}, + {0x3080, 0x04}, + {0x3081, 0x38}, + + {0x0202, 0x03}, + {0x0203, 0xD4}, + {0x0204, 0x00}, + {0x0205, 0x20}, + + {0x0340, 0x03}, + {0x0341, 0xE0}, + + {0x0342, 0x0A}, + {0x0343, 0xB2}, + + {0x0344, 0x00}, + {0x0345, 0x18}, + {0x0348, 0x0A}, + {0x0349, 0x17}, + {0x0346, 0x00}, + {0x0347, 0x14}, + {0x034A, 0x07}, + {0x034B, 0x93}, + + {0x034C, 0x02}, + {0x034D, 0x80}, + {0x034E, 0x01}, + {0x034F, 0xE0}, + + {0x0380, 0x00}, + {0x0381, 0x01}, + {0x0382, 0x00}, + {0x0383, 0x07}, + {0x0384, 0x00}, + {0x0385, 0x01}, + {0x0386, 0x00}, + {0x0387, 0x07}, + + {0x3084, 0x15}, + + {0x30BD, 0x00}, + + + {0x30b3, 0x00}, + {0x30b4, 0x57}, + {0x30b5, 0x01}, + {0x30f1, 0x70}, + + {0x30BE, 0x1A}, + + {0x30BF, 0xAB}, + {0x30C0, 0x80}, + {0x30C1, 0x01}, + {0x30C8, 0x03}, + {0x30C9, 0x20}, + + {0x30b2, 0x06}, + {0x30E2, 0x02}, + + {0x30EE, 0x02}, + + {0x3111, 0x86}, + + {0x30d9, 0x00}, + + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x0210, 0x01}, + {0x0211, 0x00}, + {0x0212, 0x01}, + {0x0213, 0x00}, + {0x0214, 0x01}, + {0x0215, 0x00}, + {0x0204, 0x01}, + {0x0205, 0x00}, + +#if 1 + /* Apply Bill's setting */ + {0x30E2, 0x02}, + {0x0305, 0x05}, + {0x0306, 0x00}, + {0x0307, 0x50}, + {0x30B5, 0x01}, + {0x30B4, 0x50}, + + {0x30B2, 0x05}, + + {0x30BE, 0x15}, + + /* {0x0100, 0x01}, */ + /* {0xffff, 0xff}, */ +#endif + +#if 1 + /* 1304x980 */ + {0x3013, 0x90}, + {0x3017, 0x84}, + {0x30A9, 0x02}, + {0x300E, 0xAB}, + + {0x0387, 0x03}, + {0x0344, 0x00}, /* x_addr_start = 0 */ + {0x0345, 0x00}, + {0x0348, 0x0A}, /* x_addr_end = 2607 */ + {0x0349, 0x2F}, + {0x0346, 0x00}, /* y_addr_start = 0 */ + {0x0347, 0x00}, + {0x034A, 0x07}, /* y_addr_end = 1959 */ + {0x034B, 0xA7}, + {0x0380, 0x00}, + {0x0381, 0x01}, + {0x0382, 0x00}, + {0x0383, 0x01}, + {0x0384, 0x00}, + {0x0385, 0x01}, + {0x0386, 0x00}, + {0x0387, 0x03}, + {0x034c, 0x05}, /* x_output_size = 1304 */ + {0x034d, 0x18}, + {0x034e, 0x03}, /* y_output_size = 980 */ + {0x034f, 0xd4}, + {0x30BF, 0xAB}, + {0x30c0, 0xa0}, + {0x30C8, 0x06}, /* x_output_size * 1.25 */ + {0x30c9, 0x5e}, + + {0x0100, 0x01}, + {0xffff, 0xff}, + +#endif +}; diff --git a/drivers/media/video/mrstci/mrsts5k4e1_motor/Kconfig b/drivers/media/video/mrstci/mrsts5k4e1_motor/Kconfig new file mode 100755 index 0000000..27cb730 --- /dev/null +++ b/drivers/media/video/mrstci/mrsts5k4e1_motor/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_MRST_S5K4E1_MOTOR + tristate "Moorestown s5k4e1 motor" + depends on I2C && VIDEO_MRST_ISP && VIDEO_MRST_S5K4E1 + + ---help--- + Say Y here if your platform support s5k4e1 motor. + + To compile this driver as a module, choose M here: the + module will be called mrstov2650.ko. diff --git a/drivers/media/video/mrstci/mrsts5k4e1_motor/Makefile b/drivers/media/video/mrstci/mrsts5k4e1_motor/Makefile new file mode 100644 index 0000000..68c9fbc --- /dev/null +++ b/drivers/media/video/mrstci/mrsts5k4e1_motor/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_VIDEO_MRST_S5K4E1_MOTOR) += mrsts5k4e1_motor.o + +EXTRA_CFLAGS += -I$(src)/../include diff --git a/drivers/media/video/mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.c b/drivers/media/video/mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.c new file mode 100644 index 0000000..cd2813b --- /dev/null +++ b/drivers/media/video/mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.c @@ -0,0 +1,430 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mrsts5k4e1_motor.h" + +static int s5k4e1_motor_debug; +module_param(s5k4e1_motor_debug, int, 0644); +MODULE_PARM_DESC(s5k4e1_motor_debug, "Debug level (0-1)"); + +#define dprintk(level, fmt, arg...) \ + do { \ + if (s5k4e1_motor_debug >= level) \ + printk(KERN_DEBUG "mrstisp@%s: " fmt "\n", __func__, ## arg); \ + } while (0) + +#define eprintk(fmt, arg...) \ + printk(KERN_ERR "mrstisp@%s: line %d: " fmt "\n", \ + __func__, __LINE__, ## arg); + +#define DBG_entering dprintk(1, "entering"); +#define DBG_leaving dprintk(1, "leaving"); +#define DBG_line dprintk(1, " line: %d", __LINE__); + +static inline struct s5k4e1_motor *to_motor_config(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s5k4e1_motor, sd); +} + +/*static struct s5k4e1_motor *config; */ +static int motor_read(struct i2c_client *c, u32 *reg) +{ + int ret; + struct i2c_msg msg; + u8 msgbuf[3]; + + msgbuf[0] = 0; + msgbuf[1] = 0; + msgbuf[2] = 0; + + memset(&msg, 0, sizeof(msg)); + msg.addr = c->addr; + msg.buf = msgbuf; + msg.len = 3; + msg.flags = I2C_M_RD; + + ret = i2c_transfer(c->adapter, &msg, 1); + + *reg = (msgbuf[0] << 16 | msgbuf[1] << 8 | msgbuf[2]); + + ret = (ret == 1) ? 0 : -1; + return ret; +} + +static int motor_write(struct i2c_client *c, u32 reg) +{ + int ret; + struct i2c_msg msg; + u8 msgbuf[3]; + + memset(&msg, 0, sizeof(msg)); + msgbuf[0] = (reg & 0x00FFFFFFFF) >> 16; + msgbuf[1] = (reg & 0x0000FFFF) >> 8 ; + msgbuf[2] = reg; + + msg.addr = c->addr; + msg.flags = 0; + msg.buf = msgbuf; + msg.len = 3; + + ret = i2c_transfer(c->adapter, &msg, 1); + + ret = (ret == 1) ? 0 : -1; + return ret; +} + +static int s5k4e1_motor_goto_position(struct i2c_client *c, + unsigned short code, + struct s5k4e1_motor *config, + unsigned int step) +{ + int max_code, min_code; + int timeout = 25; /*TODO: check the timeout time */ + u8 cmdh, cmdl, finished; + u32 cmd = 0, val = 0; + + max_code = config->macro_code; + min_code = config->infin_code; + + if (code > max_code) + code = max_code; + if (code < min_code) + code = min_code; + + cmdh = MOTOR_DAC_CTRL_MODE_1 | (code >> 8); /* PS EN x x M W TD9 TD8*/ + cmdl = code; /* TD7 ~ TD0 */ + cmd |= (cmdh << 16) | (cmdl << 8); + + dprintk(1, "cmdh: %x, cmdl: %x, cmd: %x", cmdh, cmdl, cmd); + dprintk(1, "DAC code: %x", code); + + motor_write(c, cmd); + finished = 0; + while ((!finished) && timeout--) { + msleep(1); + motor_read(c, &val); + cmdh = val >> 16; + cmdl = val >> 8; + + dprintk(1, "cmdh & MOTOR_F = %x", cmdh & MOTOR_F); + finished = cmdh & MOTOR_F; + finished = (finished) ? 0 : 1; + }; + + if (finished) { + dprintk(1, "Moving from code %x to code %x takes %d ms.", + config->cur_code, code, 25-timeout); + return 0; + } else { + eprintk("Unable to move motor to step %d, TIMEOUT!!", step); + return -1; + } + +} + +int s5k4e1_motor_wakeup(struct i2c_client *client) +{ + /* hardware wakeup: set PS = 1 */ + return motor_write(client, 0xC00000); +} + +int s5k4e1_motor_standby(struct i2c_client *client) +{ + /* hardware standby: set PS = 0 */ + return motor_write(client, 0x400000); +} + +int s5k4e1_motor_init(struct i2c_client *client, struct s5k4e1_motor *config) +{ + + int ret; + int infin_cur, macro_cur; + int step_res, step_time; + int val; + + DBG_entering; + infin_cur = MAX(MOTOR_INFIN_CUR, MOTOR_DAC_MIN_CUR); + macro_cur = MIN(MOTOR_MACRO_CUR, MOTOR_DAC_MAX_CUR); + step_res = 1 << MOTOR_STEP_SHIFT; + step_time = MOTOR_STEP_TIME; + + /*config->motor = client;*/ + config->infin_cur = infin_cur; + config->macro_cur = macro_cur; + + config->infin_code = MOTOR_INFIN_CODE; + config->macro_code = MOTOR_MACRO_CODE; + + config->max_step = ((config->macro_code - config->infin_code) + >> MOTOR_STEP_SHIFT) + 1; + config->step_res = step_res; + config->step_time = step_time; + + dprintk(1, "max_step: %d, step_res: %d, step_time: %d", + config->max_step, step_res, step_time); + + /* Set motor step time and resolution */ + val = (MOTOR_DAC_CTRL_MODE_0 << 16) | (step_res << 8) | step_time; + ret = motor_write(client, val); + + /* Note here, maybe macro_code */ + ret |= s5k4e1_motor_goto_position(client, config->infin_code, + config, 0); + if (!ret) { + config->cur_code = config->infin_code; + dprintk(1, "Motor initialization success!"); + } else + eprintk("Error while initializing motor!!!"); + + return ret; +} + +int s5k4e1_motor_set_focus(struct i2c_client *c, + unsigned int step, + struct s5k4e1_motor *config) +{ + int s_code, ret; + int max_step = config->max_step; + unsigned int val = step; + + if (val > max_step) + val = max_step; + + s_code = (val << MOTOR_STEP_SHIFT); + s_code += config->infin_code; + + ret = s5k4e1_motor_goto_position(c, s_code, config, step); + if (!ret) + config->cur_code = s_code; + + return ret; +} + +static int s5k4e1_motor_g_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + struct s5k4e1_motor *config = to_motor_config(sd); + int ret; + + DBG_entering; + ret = s5k4e1_motor_get_focus(c, &ctrl->value, config); + if (ret) { + eprintk("error call s5k4e1_motor_get_focue"); + return ret; + } + DBG_leaving; + return 0; +} + +static int s5k4e1_motor_s_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct i2c_client *c = v4l2_get_subdevdata(sd); + struct s5k4e1_motor *config = to_motor_config(sd); + int ret; + + DBG_entering; + ret = s5k4e1_motor_set_focus(c, ctrl->value, config); + if (ret) { + eprintk("error call s5k4e1_motor_set_focue"); + return ret; + } + DBG_leaving; + return 0; +} + +int s5k4e1_motor_get_focus(struct i2c_client *c, + unsigned int *step, + struct s5k4e1_motor *config) +{ + int ret_step; + + ret_step = ((config->cur_code - config->infin_code) + >> MOTOR_STEP_SHIFT); + + if (ret_step <= config->max_step) + *step = ret_step; + else + *step = config->max_step; + + return 0; +} + +int s5k4e1_motor_max_step(struct i2c_client *c, + unsigned int *max_code, + struct s5k4e1_motor *config) +{ + if (config->max_step != 0) + *max_code = config->max_step; + return 0; + +} + +static int s5k4e1_motor_queryctrl(struct v4l2_subdev *sd, + struct v4l2_queryctrl *qc) +{ + struct s5k4e1_motor *config = to_motor_config(sd); + + DBG_entering; + dprintk(1, "got focus range of %d", config->max_step); + if (config->max_step != 0) + qc->maximum = config->max_step; + DBG_leaving; + return 0; +} + +static const struct v4l2_subdev_core_ops s5k4e1_motor_core_ops = { + .g_ctrl = s5k4e1_motor_g_ctrl, + .s_ctrl = s5k4e1_motor_s_ctrl, + .queryctrl = s5k4e1_motor_queryctrl, +}; + +static const struct v4l2_subdev_ops s5k4e1_motor_ops = { + .core = &s5k4e1_motor_core_ops, +}; + +static int s5k4e1_motor_detect(struct i2c_client *client) +{ + struct i2c_adapter *adapter = client->adapter; + int adap_id = i2c_adapter_id(adapter); + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { + eprintk("error i2c check func"); + return -ENODEV; + } + + if (adap_id != 1) { + eprintk("adap_id != 1"); + return -ENODEV; + } + + if (s5k4e1_motor_wakeup(client)) + eprintk("unable to wakeup s5k4e1 motor."); + + return 0; +} + +static int s5k4e1_motor_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct s5k4e1_motor *info; + struct v4l2_subdev *sd; + int ret = -1; +/* struct i2c_client *motor; */ + + DBG_entering; + v4l_info(client, "chip found @ 0x%x (%s)\n", + client->addr << 1, client->adapter->name); + /* + * Setup sensor configuration structure + */ + info = kzalloc(sizeof(struct s5k4e1_motor), GFP_KERNEL); + if (!info) { + eprintk("fail to malloc for ci_motor"); + ret = -ENOMEM; + goto out; + } + + ret = s5k4e1_motor_detect(client); + if (ret) { + eprintk("error s5k4e1_motor_detect"); + goto out_free; + } + + sd = &info->sd; + v4l2_i2c_subdev_init(sd, client, &s5k4e1_motor_ops); + + /* + * Initialization S5K4E1 + * then turn into standby mode + */ + ret = s5k4e1_motor_init(client, info); + if (ret) { + eprintk("error calling s5k4e1_motor_init"); + goto out_free; + } + + ret = 0; + goto out; + +out_free: + kfree(info); + DBG_leaving; +out: + return ret; +} + +/* + * XXX: Need to be checked + */ +static int s5k4e1_motor_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + DBG_entering; + + v4l2_device_unregister_subdev(sd); + kfree(to_motor_config(sd)); + + DBG_leaving; + return 0; +} + +static const struct i2c_device_id s5k4e1_motor_id[] = { + {"s5k4e1_motor", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, s5k4e1_motor_id); + +static struct v4l2_i2c_driver_data v4l2_i2c_data = { + .name = "s5k4e1_motor", + .probe = s5k4e1_motor_probe, + .remove = s5k4e1_motor_remove, + /* .suspend = ov5630_suspend, + * .resume = ov5630_resume, */ + .id_table = s5k4e1_motor_id, +}; +MODULE_AUTHOR("Xiaolin Zhang "); +MODULE_DESCRIPTION("A low-level driver for Samsung S5K4E1 sensor motor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.h b/drivers/media/video/mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.h new file mode 100644 index 0000000..04f9436 --- /dev/null +++ b/drivers/media/video/mrstci/mrsts5k4e1_motor/mrsts5k4e1_motor.h @@ -0,0 +1,102 @@ +/* + * Support for Moorestown Langwell Camera Imaging ISP subsystem. + * + * Copyright (c) 2009 Intel Corporation. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Xiaolin Zhang + */ + +#include + +/* DAC output max current (mA) */ +#define MOTOR_DAC_MAX_CUR 125 +/* DAC output min current (mA) */ +#define MOTOR_DAC_MIN_CUR 1 +/* DAC max code (Hex) */ +#define MOTOR_DAC_CODE_MAX 0x3FF +/* DAC min code (Hex) */ +#define MOTOR_DAC_CODE_MIN 0x0 + +/* VCM start code (Hex) */ +#define MOTOR_INFIN_CODE 0x120 +/* VCM stop code (Hex) */ +#define MOTOR_MACRO_CODE 0x205 + +#define MOTOR_STEP_SHIFT 4 /* Step res = 2^4 = 10H */ +#define MOTOR_STEP_TIME 20 /* Step time = 50us x 20d = 1ms */ + +/* VCM start current (mA) */ +#define MOTOR_INFIN_CUR ((MOTOR_DAC_MAX_CUR / MOTOR_DAC_CODE_MAX) \ + * MOTOR_INFIN_CODE + 1) +/* VCM max current for Macro (mA) */ +#define MOTOR_MACRO_CUR ((MOTOR_DAC_MAX_CUR / MOTOR_DAC_CODE_MAX) \ + * MOTOR_MACRO_CODE + 1) + + +#define MOTOR_DAC_BIT_RES 10 +#define MOTOR_DAC_MAX_CODE ((1 << MOTOR_DAC_BIT_RES) - 1) + +#define MOTOR_STEP_SHIFT 4 + +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +/* DAC register related define */ +#define MOTOR_PS (1 << 7) /* power save */ +#define MOTOR_EN (1 << 6) /* out pin status*/ +#define MOTOR_M (1 << 3) /* mode select */ +#define MOTOR_W (1 << 2) /* register address */ +#define MOTOR_F (1 << 4) /* finish flag */ + +#define MOTOR_DAC_CODE_L(x) (x & 0xff) +#define MOTOR_DAC_CODE_H(x) ((x >> 8) & 0xf3) + +/* Step mode setting */ +#define MOTOR_DAC_CTRL_MODE_0 0xCC +/* DAC code setting */ +#define MOTOR_DAC_CTRL_MODE_1 0xC8 + +#define S5K4E1_MOTOR_ADDR (0x18 >> 1) +/*#define POWER_EN_PIN 7*/ +#define GPIO_AF_PD 95 + +#define DEBUG 0 + +struct s5k4e1_motor{ + /*struct i2c_client *motor;*/ + unsigned int infin_cur; + unsigned int infin_code; + unsigned int macro_cur; + unsigned int macro_code; + unsigned int max_step; + unsigned int cur_code; + unsigned int step_res; + unsigned int step_time; + struct v4l2_subdev sd; +}; + +extern int s5k4e1_motor_init(struct i2c_client *client, + struct s5k4e1_motor *config); +extern int s5k4e1_motor_standby(struct i2c_client *client); +extern int s5k4e1_motor_wakeup(struct i2c_client *client); +extern int s5k4e1_motor_set_focus(struct i2c_client *c, unsigned int step, + struct s5k4e1_motor *config); +extern int s5k4e1_motor_get_focus(struct i2c_client *c, unsigned int *step, + struct s5k4e1_motor *config); +extern int s5k4e1_motor_max_step(struct i2c_client *c, unsigned int *max_code, + struct s5k4e1_motor *config); -- 1.6.0.6