summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Huang <chahuang@nvidia.com>2011-06-29 10:44:58 -0700
committerJeremy Wyman <jwyman@nvidia.com>2011-06-30 17:17:27 -0700
commitbcad42d1a0aadaebaaf56fbe4b480cad63fadb3a (patch)
tree94bc85ae3f35e401440aa7636cb44b32b088e143
parentcc44260f85612ff49db0738a5b7c46e8e5193fa9 (diff)
media: video: tegra: enable ov9726 sensor
bug 829399 - add front sensor ov9726 for enterprise board Change-Id: Id775f8d529206c326dbe8c552e049eb49f76fa55 Reviewed-on: http://git-master/r/39005 Reviewed-by: Jihoon Bang <jbang@nvidia.com> Tested-by: Jihoon Bang <jbang@nvidia.com> Reviewed-by: Chonglei Huang <chahuang@nvidia.com> Reviewed-by: Jeremy Wyman <jwyman@nvidia.com>
-rw-r--r--drivers/media/video/tegra/Kconfig7
-rw-r--r--drivers/media/video/tegra/Makefile1
-rw-r--r--drivers/media/video/tegra/ov9726.c479
-rw-r--r--include/media/ov9726.h64
4 files changed, 551 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/Kconfig b/drivers/media/video/tegra/Kconfig
index 5b6ffd50ad25..63865f884eb0 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 9710f3d659d7..7bdbac38ce29 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..25054f42e42a
--- /dev/null
+++ b/drivers/media/video/tegra/ov9726.c
@@ -0,0 +1,479 @@
+/*
+ * ov9726.c - ov9726 sensor driver
+ *
+ * Copyright (c) 2011, NVIDIA, All Rights Reserved.
+ *
+ * Contributors:
+ * Charlie Huang <chahuang@nvidia.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/i2c.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <mach/iomap.h>
+#include <asm/atomic.h>
+
+#include <media/ov9726.h>
+
+struct ov9726_devinfo {
+ struct miscdevice miscdev_info;
+ struct i2c_client *i2c_client;
+ struct ov9726_platform_data *pdata;
+ atomic_t in_use;
+ __u32 mode;
+};
+
+/* 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 inline void
+msleep_range(unsigned int delay_base)
+{
+ usleep_range(delay_base*1000, delay_base*1000 + 500);
+}
+
+static int
+ov9726_read_reg(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_reg(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 shift = (sizeof(u16)/sizeof(u8) - 1) * 8;
+ int i, ret;
+
+ for (i = 0; i < sizeof(u16)/sizeof(u8); i++) {
+ ret = ov9726_write_reg(client, addr, (u8)((val >> shift) & 0xff));
+ if (ret)
+ break;
+
+ addr++;
+ shift -= 8;
+ }
+
+ return ret;
+}
+
+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_reg(client, next->addr, val);
+ if (err)
+ break;
+ }
+
+ 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: xres %u yres %u framelength %u coarsetime %u gain %u\n",
+ __func__, mode->xres, mode->yres, mode->frame_length,
+ mode->coarse_time, mode->gain);
+
+ if (!reg_list) {
+ dev_err(&i2c_client->dev, "%s: empty reg list\n", __func__);
+ return -EINVAL;
+ }
+
+ if (dev->mode != mode->mode_id) {
+ err = ov9726_write_table(i2c_client, reg_list, NULL, 0);
+ if (!err)
+ dev->mode = mode->mode_id;
+ }
+
+ return err;
+}
+
+static int
+ov9726_set_frame_length(struct ov9726_devinfo *dev, u32 frame_length)
+{
+ return ov9726_write_reg16(dev->i2c_client,
+ OV9726_REG_FRAME_LENGTH_HI,
+ frame_length);
+}
+
+static int
+ov9726_set_coarse_time(struct ov9726_devinfo *dev,u32 coarse_time)
+{
+ int ret, ret_hold;
+
+ // hold register value
+ ret_hold = ov9726_write_reg(dev->i2c_client, 0x104, 0x01);
+ if (ret_hold)
+ return ret_hold;
+
+ ret = ov9726_write_reg16(dev->i2c_client,
+ OV9726_REG_COARSE_TIME_HI,
+ coarse_time);
+
+ // release hold, update register value
+ ret_hold = ov9726_write_reg(dev->i2c_client, 0x104, 0x00);
+
+ if (ret)
+ return ret;
+ else
+ return ret_hold;
+}
+
+static int ov9726_set_gain(struct ov9726_devinfo *dev, u16 gain)
+{
+ return ov9726_write_reg16(dev->i2c_client, OV9726_REG_GAIN_HI, gain);
+}
+
+static int ov9726_get_status(struct ov9726_devinfo *dev, u8 *status)
+{
+ int err;
+
+ err = ov9726_read_reg(dev->i2c_client, 0x003, status);
+ *status = 0;
+ 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;
+
+ if (err)
+ break;
+
+ reg_list = (struct ov9726_reg *)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;
+ }
+
+ if (err)
+ 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);
+ }
+
+ if (reg_list)
+ kfree(reg_list);
+ break;
+ }
+
+ case OV9726_IOCTL_SET_FRAME_LENGTH:
+ err = ov9726_set_frame_length(dev, (u32)arg);
+ break;
+
+ case OV9726_IOCTL_SET_COARSE_TIME:
+ err = ov9726_set_coarse_time(dev, (u32)arg);
+ break;
+
+ case OV9726_IOCTL_SET_GAIN:
+ err = ov9726_set_gain(dev, (u16)arg);
+ break;
+
+ case OV9726_IOCTL_GET_STATUS:
+ {
+ u8 status;
+
+ err = ov9726_get_status(dev, &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_info;
+
+ dev_info =container_of(miscdev, struct ov9726_devinfo, miscdev_info);
+ // check if device is in use
+ if (atomic_xchg(&dev_info->in_use, 1))
+ return -EBUSY;
+
+ if (dev_info && dev_info->pdata && dev_info->pdata->power_on)
+ dev_info->pdata->power_on();
+ file->private_data = dev_info;
+
+ return 0;
+}
+
+int ov9726_release(struct inode *inode, struct file *file)
+{
+ struct ov9726_devinfo *dev_info;
+
+ dev_info = file->private_data;
+ if (dev_info && dev_info->pdata && dev_info->pdata->power_off)
+ dev_info->pdata->power_off();
+
+ file->private_data = NULL;
+ // warn if device already released
+ WARN_ON(!atomic_xchg(&dev_info->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_info;
+ int err = 0;
+
+ dev_info(&client->dev, "ov9726: probing sensor.\n");
+
+ dev_info = kzalloc(sizeof(struct ov9726_devinfo), GFP_KERNEL);
+ if (!dev_info) {
+ dev_err(&client->dev, "ov9726: Unable to allocate memory!\n");
+ err = -ENOMEM;
+ goto probe_end;
+ }
+
+ memcpy(&(dev_info->miscdev_info), &ov9726_device, sizeof(struct miscdevice));
+ err = misc_register(&(dev_info->miscdev_info));
+ if (err) {
+ dev_err(&client->dev, "ov9726: Unable to register misc device!\n");
+ goto probe_end;
+ }
+
+ dev_info->pdata = client->dev.platform_data;
+ dev_info->i2c_client = client;
+ atomic_set(&dev_info->in_use, 0);
+ dev_info->mode = (__u32)-1;
+ i2c_set_clientdata(client, dev_info);
+
+probe_end:
+ if (err) {
+ if (dev_info)
+ kfree(dev_info);
+ dev_err(&client->dev, "failed.\n");
+ }
+
+ return err;
+}
+
+static int ov9726_remove(struct i2c_client *client)
+{
+ struct ov9726_devinfo *dev_info;
+
+ dev_info = i2c_get_clientdata(client);
+ i2c_set_clientdata(client, NULL);
+ misc_deregister(&ov9726_device);
+ kfree(dev_info);
+
+ 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..75672393e860
--- /dev/null
+++ b/include/media/ov9726.h
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2011 NVIDIA Corporation. All rights reserved.
+ *
+ * NVIDIA Corporation and its licensors retain all intellectual property
+ * and proprietary rights in and to this software and related documentation
+ * and any modifications thereto. Any use, reproduction, disclosure or
+ * distribution of this software and related documentation without an express
+ * license agreement from NVIDIA Corporation is strictly prohibited.
+ */
+
+#ifndef __OV9726_H__
+#define __OV9726_H__
+
+#include <linux/ioctl.h>
+
+#define OV9726_I2C_ADDR 0x20
+
+#define OV9726_IOCTL_SET_MODE _IOW('o', 1, struct ov9726_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);
+};
+#endif /* __KERNEL__ */
+
+#endif /* __OV9726_H__ */
+