From e4a6422877d29a9dc49ea0945ea4309621924e1e Mon Sep 17 00:00:00 2001 From: Charlie Huang Date: Wed, 29 Jun 2011 10:44:58 -0700 Subject: media: video: tegra: enable ov9726 sensor initial support for sensor ov9726 bug 829399 Original-Change-Id: I67315dfce7baf2c14f49bf526c10fe19cf32d1d3 Reviewed-on: http://git-master/r/40472 Reviewed-by: Daniel Willemsen Reviewed-by: Chonglei Huang Tested-by: Chonglei Huang Rebase-Id: R31312b2b0fda96348bb14461651ccb27aa30b0de --- drivers/media/video/tegra/Kconfig | 7 + drivers/media/video/tegra/Makefile | 1 + drivers/media/video/tegra/ov9726.c | 649 +++++++++++++++++++++++++++++++++++++ include/media/ov9726.h | 68 ++++ 4 files changed, 725 insertions(+) create mode 100644 drivers/media/video/tegra/ov9726.c create mode 100644 include/media/ov9726.h diff --git a/drivers/media/video/tegra/Kconfig b/drivers/media/video/tegra/Kconfig index 3ab84283d076..33117ec4d256 100644 --- a/drivers/media/video/tegra/Kconfig +++ b/drivers/media/video/tegra/Kconfig @@ -17,6 +17,13 @@ config VIDEO_OV5650 This is a driver for the Omnivision OV5650 5MP camera sensor for use with the tegra isp. +config VIDEO_OV9726 + tristate "OV9726 camera sensor support" + depends on I2C && ARCH_TEGRA + ---help--- + This is a driver for the Omnivision OV9726 camera sensor + for use with the tegra isp. + config VIDEO_OV2710 tristate "OV2710 camera sensor support" depends on I2C && ARCH_TEGRA diff --git a/drivers/media/video/tegra/Makefile b/drivers/media/video/tegra/Makefile index 07d9e3ccbf30..d97cf3059a9f 100644 --- a/drivers/media/video/tegra/Makefile +++ b/drivers/media/video/tegra/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_TEGRA_MEDIASERVER) += mediaserver/ obj-$(CONFIG_TEGRA_CAMERA) += tegra_camera.o obj-$(CONFIG_VIDEO_AR0832) += ar0832_main.o obj-$(CONFIG_VIDEO_OV5650) += ov5650.o +obj-$(CONFIG_VIDEO_OV9726) += ov9726.o obj-$(CONFIG_VIDEO_OV2710) += ov2710.o obj-$(CONFIG_VIDEO_SOC380) += soc380.o obj-$(CONFIG_TORCH_SSL3250A) += ssl3250a.o diff --git a/drivers/media/video/tegra/ov9726.c b/drivers/media/video/tegra/ov9726.c new file mode 100644 index 000000000000..390ab10cb7d8 --- /dev/null +++ b/drivers/media/video/tegra/ov9726.c @@ -0,0 +1,649 @@ +/* + * ov9726.c - ov9726 sensor driver + * + * Copyright (c) 2011, NVIDIA, All Rights Reserved. + * + * Contributors: + * Charlie Huang + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct ov9726_power_rail { + struct regulator *sen_1v8_reg; + struct regulator *sen_2v8_reg; +}; + +struct ov9726_devinfo { + struct miscdevice miscdev_info; + struct i2c_client *i2c_client; + struct ov9726_platform_data *pdata; + struct ov9726_power_rail power_rail; + atomic_t in_use; + __u32 mode; +}; + +static inline void +msleep_range(unsigned int delay_base) +{ + usleep_range(delay_base*1000, delay_base*1000 + 500); +} + +static inline int +ov9726_power_init(struct ov9726_devinfo *dev) +{ + struct i2c_client *i2c_client = dev->i2c_client; + int err = 0; + + dev->power_rail.sen_1v8_reg = regulator_get(&i2c_client->dev, "dovdd"); + if (IS_ERR_OR_NULL(dev->power_rail.sen_1v8_reg)) { + dev_err(&i2c_client->dev, "%s: failed to get vdd\n", + __func__); + err = PTR_ERR(dev->power_rail.sen_1v8_reg); + goto ov9726_power_init_end; + } + + dev->power_rail.sen_2v8_reg = regulator_get(&i2c_client->dev, "avdd"); + if (IS_ERR_OR_NULL(dev->power_rail.sen_2v8_reg)) { + dev_err(&i2c_client->dev, "%s: failed to get vaa\n", + __func__); + err = PTR_ERR(dev->power_rail.sen_2v8_reg); + + regulator_put(dev->power_rail.sen_1v8_reg); + dev->power_rail.sen_1v8_reg = NULL; + } + +ov9726_power_init_end: + return err; +} + +static inline void +ov9726_power_release(struct ov9726_devinfo *dev) +{ + regulator_put(dev->power_rail.sen_1v8_reg); + regulator_put(dev->power_rail.sen_2v8_reg); +} + +static int +ov9726_power(struct ov9726_devinfo *dev, bool pwr_on) +{ + struct i2c_client *i2c_client = dev->i2c_client; + int rst_active_state = dev->pdata->rst_low_active ? 0 : 1; + int pwdn_active_state = dev->pdata->pwdn_low_active ? 0 : 1; + int ret = 0; + + dev_info(&i2c_client->dev, "%s %s\n", __func__, pwr_on ? "on" : "off"); + + if (pwr_on) { + /* pull low the RST pin of ov9726 first */ + gpio_set_value(dev->pdata->gpio_rst, rst_active_state); + msleep_range(1); + /* Plug 1.8V and 2.8V power to sensor */ + ret = regulator_enable(dev->power_rail.sen_1v8_reg); + if (ret) { + dev_err(&i2c_client->dev, "%s: failed to enable vdd\n", + __func__); + goto fail_regulator_1v8_reg; + } + + msleep_range(20); + + ret = regulator_enable(dev->power_rail.sen_2v8_reg); + if (ret) { + dev_err(&i2c_client->dev, "%s: failed to enable vaa\n", + __func__); + goto fail_regulator_2v8_reg; + } + msleep_range(1); + /* turn on ov9726 */ + gpio_set_value(dev->pdata->gpio_pwdn, !pwdn_active_state); + + msleep_range(5); + /* release RST pin */ + gpio_set_value(dev->pdata->gpio_rst, !rst_active_state); + msleep_range(20); + + /* Board specific power-on sequence */ + dev->pdata->power_on(); + } else { + /* pull low the RST pin of ov9726 */ + gpio_set_value(dev->pdata->gpio_rst, rst_active_state); + msleep_range(1); + /* turn off ov9726 */ + gpio_set_value(dev->pdata->gpio_pwdn, pwdn_active_state); + msleep_range(1); + + /* Unplug 1.8V and 2.8V power from sensor */ + regulator_disable(dev->power_rail.sen_2v8_reg); + regulator_disable(dev->power_rail.sen_1v8_reg); + + /* Board specific power-down sequence */ + dev->pdata->power_off(); + } + + return 0; + +fail_regulator_2v8_reg: + regulator_put(dev->power_rail.sen_2v8_reg); + dev->power_rail.sen_2v8_reg = NULL; + regulator_disable(dev->power_rail.sen_1v8_reg); +fail_regulator_1v8_reg: + regulator_put(dev->power_rail.sen_1v8_reg); + dev->power_rail.sen_1v8_reg = NULL; + return ret; +} + +/* 2 regs to program frame length */ +static inline void +ov9726_get_frame_length_regs(struct ov9726_reg *regs, u32 frame_length) +{ + regs->addr = OV9726_REG_FRAME_LENGTH_HI; + regs->val = (frame_length >> 8) & 0xff; + regs++; + regs->addr = OV9726_REG_FRAME_LENGTH_LO; + regs->val = frame_length & 0xff; +} + +/* 3 regs to program coarse time */ +static inline void +ov9726_get_coarse_time_regs(struct ov9726_reg *regs, u32 coarse_time) +{ + regs->addr = OV9726_REG_COARSE_TIME_HI; + regs->val = (coarse_time >> 8) & 0xff; + regs++; + regs->addr = OV9726_REG_COARSE_TIME_LO; + regs->val = coarse_time & 0xff; +} + +/* 1 reg to program gain */ +static inline void +ov9726_get_gain_reg(struct ov9726_reg *regs, u16 gain) +{ + regs->addr = OV9726_REG_GAIN_HI; + regs->val = (gain >> 8) & 0xff; + regs++; + regs->addr = OV9726_REG_GAIN_LO; + regs->val = gain & 0xff; +} + +static int +ov9726_read_reg8(struct i2c_client *client, u16 addr, u8 *val) +{ + int err; + struct i2c_msg msg[2]; + unsigned char data[3]; + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 2; + msg[0].buf = data; + + /* high byte goes out first */ + data[0] = (u8)(addr >> 8); + data[1] = (u8)(addr & 0xff); + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = data + 2; + + err = i2c_transfer(client->adapter, msg, 2); + + if (err != 2) + err = -EINVAL; + else { + *val = data[2]; + err = 0; + } + + return err; +} + +static int +ov9726_write_reg8(struct i2c_client *client, u16 addr, u8 val) +{ + int err; + struct i2c_msg msg; + unsigned char data[3]; + int retry = 0; + + if (!client->adapter) + return -ENODEV; + + data[0] = (u8)(addr >> 8); + data[1] = (u8)(addr & 0xff); + data[2] = (u8)(val & 0xff); + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 3; + msg.buf = data; + + do { + err = i2c_transfer(client->adapter, &msg, 1); + if (err == 1) + break; + + retry++; + dev_err(&client->dev, + "ov9726: i2c transfer failed, retrying %x %x\n", + addr, val); + msleep_range(3); + } while (retry <= OV9726_MAX_RETRIES); + + return (err != 1); +} + +static int +ov9726_write_reg16(struct i2c_client *client, u16 addr, u16 val) +{ + int count; + struct i2c_msg msg; + unsigned char data[4]; + int retry = 0; + + if (!client->adapter) + return -ENODEV; + + data[0] = (u8)(addr >> 8); + data[1] = (u8)(addr & 0xff); + data[2] = (u8)(val >> 8); + data[3] = (u8)(val & 0xff); + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 4; + msg.buf = data; + + do { + count = i2c_transfer(client->adapter, &msg, 1); + if (count == 1) + return 0; + + retry++; + dev_err(&client->dev, + "ov9726: i2c transfer failed, retrying %x %x %x\n", + addr, val, count); + msleep_range(3); + } while (retry <= OV9726_MAX_RETRIES); + + return -EIO; +} + +static int +ov9726_write_table( + struct i2c_client *client, + struct ov9726_reg table[], + struct ov9726_reg override_list[], + int num_override_regs) +{ + const struct ov9726_reg *next; + int err = 0; + int i; + u16 val; + + dev_info(&client->dev, "ov9726_write_table\n"); + + for (next = table; next->addr != OV9726_TABLE_END; next++) { + + if (next->addr == OV9726_TABLE_WAIT_MS) { + msleep_range(next->val); + continue; + } + + val = next->val; + + /* When an override list is passed in, replace the reg */ + /* value to write if the reg is in the list */ + if (override_list) { + for (i = 0; i < num_override_regs; i++) { + if (next->addr == override_list[i].addr) { + val = override_list[i].val; + break; + } + } + } + + err = ov9726_write_reg8(client, next->addr, val); + if (err) + break; + } + + return err; +} + +static int +ov9726_set_frame_length(struct i2c_client *i2c_client, u32 frame_length) +{ + int ret; + + dev_info(&i2c_client->dev, "[%s] (0x%08x)\n", __func__, frame_length); + /* hold register value */ + ret = ov9726_write_reg8(i2c_client, 0x104, 0x01); + if (ret) + return ret; + + ret = ov9726_write_reg16(i2c_client, + OV9726_REG_FRAME_LENGTH_HI, + frame_length); + + /* release hold, update register value */ + ret |= ov9726_write_reg8(i2c_client, 0x104, 0x00); + + return ret; +} + +static int +ov9726_set_coarse_time(struct i2c_client *i2c_client, u32 coarse_time) +{ + int ret; + + dev_info(&i2c_client->dev, "[%s] (0x%08x)\n", __func__, coarse_time); + /* hold register value */ + ret = ov9726_write_reg8(i2c_client, 0x104, 0x01); + if (ret) + return ret; + + ret = ov9726_write_reg16(i2c_client, + OV9726_REG_COARSE_TIME_HI, + coarse_time); + + /* release hold, update register value */ + ret |= ov9726_write_reg8(i2c_client, 0x104, 0x00); + + return ret; +} + +static int ov9726_set_gain(struct i2c_client *i2c_client, u16 gain) +{ + int ret; + + /* hold register value */ + ret = ov9726_write_reg8(i2c_client, 0x104, 0x01); + if (ret) + return ret; + + ret = ov9726_write_reg16(i2c_client, OV9726_REG_GAIN_HI, gain); + + /* release hold, update register value */ + ret |= ov9726_write_reg8(i2c_client, 0x104, 0x00); + + return ret; +} + +static int ov9726_get_status(struct i2c_client *i2c_client, u8 *status) +{ + int err; + + err = ov9726_read_reg8(i2c_client, 0x003, status); + *status = 0; + return err; +} + +static int +ov9726_set_mode( + struct ov9726_devinfo *dev, + struct ov9726_mode *mode, + struct ov9726_reg *reg_list) +{ + struct i2c_client *i2c_client = dev->i2c_client; + int err = 0; + + dev_info(&i2c_client->dev, "%s.\n", __func__); + + if (!reg_list) { + dev_err(&i2c_client->dev, "%s: empty reg list\n", __func__); + return -EINVAL; + } + + if (dev->mode != mode->mode_id) { + dev_info(&i2c_client->dev, + "%s: xres %u yres %u framelen %u coarse %u gain %u\n", + __func__, mode->xres, mode->yres, mode->frame_length, + mode->coarse_time, mode->gain); + + err = ov9726_write_table(i2c_client, reg_list, NULL, 0); + if (err) + goto ov9726_set_mode_exit; + + err = ov9726_set_frame_length(i2c_client, mode->frame_length); + if (err) + goto ov9726_set_mode_exit; + + err = ov9726_set_coarse_time(i2c_client, mode->coarse_time); + if (err) + goto ov9726_set_mode_exit; + + dev->mode = mode->mode_id; + } + + err = ov9726_set_gain(i2c_client, mode->gain); + +ov9726_set_mode_exit: + return err; +} + +static long +ov9726_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ov9726_devinfo *dev = file->private_data; + struct i2c_client *i2c_client = dev->i2c_client; + int err = 0; + + switch (cmd) { + case OV9726_IOCTL_SET_MODE: + { + struct ov9726_cust_mode cust_mode; + struct ov9726_reg *reg_list = NULL; + + if (copy_from_user(&cust_mode, + (const void __user *)arg, + sizeof(struct ov9726_cust_mode))) { + err = -EFAULT; + break; + } + + reg_list = kzalloc( + sizeof(struct ov9726_reg) * cust_mode.reg_num, + GFP_KERNEL); + + if (!reg_list) { + dev_err(&i2c_client->dev, + "ov9726: Unable to allocate memory!\n"); + err = -ENOMEM; + break; + } + + if (copy_from_user(reg_list, + (const void __user *)cust_mode.reg_seq, + sizeof(struct ov9726_reg) * cust_mode.reg_num)) + err = -EFAULT; + + if (!err) + err = ov9726_set_mode(dev, &(cust_mode.mode), reg_list); + + kfree(reg_list); + break; + } + + case OV9726_IOCTL_SET_FRAME_LENGTH: + err = ov9726_set_frame_length(i2c_client, (u32)arg); + break; + + case OV9726_IOCTL_SET_COARSE_TIME: + err = ov9726_set_coarse_time(i2c_client, (u32)arg); + break; + + case OV9726_IOCTL_SET_GAIN: + err = ov9726_set_gain(i2c_client, (u16)arg); + break; + + case OV9726_IOCTL_GET_STATUS: + { + u8 status; + + err = ov9726_get_status(i2c_client, &status); + if (!err) { + if (copy_to_user((void __user *)arg, + &status, sizeof(status))) + err = -EFAULT; + } + break; + } + + default: + err = -EINVAL; + break; + } + + return err; +} + +static int ov9726_open(struct inode *inode, struct file *file) +{ + struct miscdevice *miscdev = file->private_data; + struct ov9726_devinfo *dev; + + dev = container_of(miscdev, struct ov9726_devinfo, miscdev_info); + /* check if device is in use */ + if (atomic_xchg(&dev->in_use, 1)) + return -EBUSY; + dev->mode = (__u32)-1; + file->private_data = dev; + + ov9726_power(dev, true); + + return 0; +} + +int ov9726_release(struct inode *inode, struct file *file) +{ + struct ov9726_devinfo *dev; + + dev = file->private_data; + file->private_data = NULL; + + ov9726_power(dev, false); + + /* warn if device already released */ + WARN_ON(!atomic_xchg(&dev->in_use, 0)); + return 0; +} + +static const struct file_operations ov9726_fileops = { + .owner = THIS_MODULE, + .open = ov9726_open, + .unlocked_ioctl = ov9726_ioctl, + .release = ov9726_release, +}; + +static struct miscdevice ov9726_device = { + .name = "ov9726", + .minor = MISC_DYNAMIC_MINOR, + .fops = &ov9726_fileops, +}; + +static int +ov9726_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct ov9726_devinfo *dev; + int err = 0; + + dev_info(&client->dev, "ov9726: probing sensor.\n"); + + dev = kzalloc(sizeof(struct ov9726_devinfo), GFP_KERNEL); + if (!dev) { + dev_err(&client->dev, "ov9726: Unable to allocate memory!\n"); + err = -ENOMEM; + goto probe_end; + } + + memcpy(&(dev->miscdev_info), + &ov9726_device, + sizeof(struct miscdevice)); + + err = misc_register(&(dev->miscdev_info)); + if (err) { + dev_err(&client->dev, "ov9726: Unable to register misc device!\n"); + goto probe_end; + } + + dev->pdata = client->dev.platform_data; + dev->i2c_client = client; + atomic_set(&dev->in_use, 0); + i2c_set_clientdata(client, dev); + + err = ov9726_power_init(dev); + +probe_end: + if (err) { + kfree(dev); + dev_err(&client->dev, "failed.\n"); + } + + return err; +} + +static int ov9726_remove(struct i2c_client *client) +{ + struct ov9726_devinfo *dev; + + dev = i2c_get_clientdata(client); + i2c_set_clientdata(client, NULL); + misc_deregister(&ov9726_device); + ov9726_power_release(dev); + kfree(dev); + + return 0; +} + +static const struct i2c_device_id ov9726_id[] = { + {"ov9726", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov9726_id); + +static struct i2c_driver ov9726_i2c_driver = { + .driver = { + .name = "ov9726", + .owner = THIS_MODULE, + }, + .probe = ov9726_probe, + .remove = ov9726_remove, + .id_table = ov9726_id, +}; + +static int __init ov9726_init(void) +{ + pr_info("ov9726 sensor driver loading\n"); + return i2c_add_driver(&ov9726_i2c_driver); +} + +static void __exit ov9726_exit(void) +{ + i2c_del_driver(&ov9726_i2c_driver); +} + +module_init(ov9726_init); +module_exit(ov9726_exit); diff --git a/include/media/ov9726.h b/include/media/ov9726.h new file mode 100644 index 000000000000..34678fb0708f --- /dev/null +++ b/include/media/ov9726.h @@ -0,0 +1,68 @@ +/* +* ov9726.h +* +* Copyright (c) 2011, NVIDIA, All Rights Reserved. +* +* This file is licensed under the terms of the GNU General Public License +* version 2. This program is licensed "as is" without any warranty of any +* kind, whether express or implied. +*/ + +#ifndef __OV9726_H__ +#define __OV9726_H__ + +#include + +#define OV9726_I2C_ADDR 0x20 + +#define OV9726_IOCTL_SET_MODE _IOW('o', 1, struct ov9726_cust_mode) +#define OV9726_IOCTL_SET_FRAME_LENGTH _IOW('o', 2, __u32) +#define OV9726_IOCTL_SET_COARSE_TIME _IOW('o', 3, __u32) +#define OV9726_IOCTL_SET_GAIN _IOW('o', 4, __u16) +#define OV9726_IOCTL_GET_STATUS _IOR('o', 5, __u8) + +struct ov9726_mode { + int mode_id; + int xres; + int yres; + __u32 frame_length; + __u32 coarse_time; + __u16 gain; +}; + +struct ov9726_reg { + __u16 addr; + __u16 val; +}; + +struct ov9726_cust_mode { + struct ov9726_mode mode; + __u16 reg_num; + struct ov9726_reg *reg_seq; +}; + +#define OV9726_TABLE_WAIT_MS 0 +#define OV9726_TABLE_END 1 + +#ifdef __KERNEL__ +#define OV9726_REG_FRAME_LENGTH_HI 0x340 +#define OV9726_REG_FRAME_LENGTH_LO 0x341 +#define OV9726_REG_COARSE_TIME_HI 0x202 +#define OV9726_REG_COARSE_TIME_LO 0x203 +#define OV9726_REG_GAIN_HI 0x204 +#define OV9726_REG_GAIN_LO 0x205 + +#define OV9726_MAX_RETRIES 3 + +struct ov9726_platform_data { + int (*power_on)(void); + int (*power_off)(void); + unsigned gpio_rst; + bool rst_low_active; + unsigned gpio_pwdn; + bool pwdn_low_active; +}; +#endif /* __KERNEL__ */ + +#endif /* __OV9726_H__ */ + -- cgit v1.2.3