camera: Add driver for soc380 front sensor
Abhinav Sinha [Thu, 31 Mar 2011 20:56:27 +0000 (13:56 -0700)]
Bug: 783488
Original-Change-Id: I0d70ec5bb0fd880b167e9ced0e002829abeccdbf
Reviewed-on: http://git-master/r/24969
Reviewed-by: Varun Colbert <vcolbert@nvidia.com>
Tested-by: Varun Colbert <vcolbert@nvidia.com>

Rebase-Id: R83f2674bdf0300e8fba97c50e55649ff68ad18a2

drivers/media/video/tegra/Kconfig
drivers/media/video/tegra/Makefile
drivers/media/video/tegra/soc380.c [new file with mode: 0644]
include/media/soc380.h [new file with mode: 0644]

index fd1908b..afcbec6 100644 (file)
@@ -24,6 +24,13 @@ config VIDEO_OV2710
           This is a driver for the Omnivision OV2710 camera sensor
           for use with the tegra isp.
 
+config VIDEO_SOC380
+        tristate "SOC380 camera sensor support"
+        depends on I2C && ARCH_TEGRA
+        ---help---
+          This is a driver for the Semco soc380 camera sensor
+          for use with the tegra isp.
+
 config TORCH_SSL3250A
         tristate "SSL3250A flash/torch support"
         depends on I2C && ARCH_TEGRA
index 6fe7963..516fd7e 100644 (file)
@@ -3,6 +3,7 @@ obj-$(CONFIG_TEGRA_MEDIASERVER)         += mediaserver/
 obj-$(CONFIG_TEGRA_CAMERA)             += tegra_camera.o
 obj-$(CONFIG_VIDEO_OV5650)             += ov5650.o
 obj-$(CONFIG_VIDEO_OV2710)             += ov2710.o
+obj-$(CONFIG_VIDEO_SOC380)             += soc380.o
 obj-$(CONFIG_TORCH_SSL3250A)           += ssl3250a.o
 obj-$(CONFIG_VIDEO_SH532U)             += sh532u.o
 obj-$(CONFIG_VIDEO_AD5820)             += ad5820.o
diff --git a/drivers/media/video/tegra/soc380.c b/drivers/media/video/tegra/soc380.c
new file mode 100644 (file)
index 0000000..1304cd5
--- /dev/null
@@ -0,0 +1,496 @@
+/*
+ * soc380.c - soc380 sensor driver
+ *
+ * Copyright (c) 2011, NVIDIA, All Rights Reserved.
+ *
+ * Contributors:
+ *      Abhinav Sinha <absinha@nvidia.com>
+ *
+ * Leverage OV2710.c
+ *
+ * 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.
+ */
+
+/**
+ * SetMode Sequence for 640x480. Phase 0. Sensor Dependent.
+ * This sequence should put sensor in streaming mode for 640x480
+ * This is usually given by the FAE or the sensor vendor.
+ */
+
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/i2c.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <media/soc380.h>
+
+struct soc380_reg {
+       u16 addr;
+       u16 val;
+};
+
+struct soc380_info {
+       int mode;
+       struct i2c_client *i2c_client;
+       struct soc380_platform_data *pdata;
+};
+
+#define SOC380_TABLE_WAIT_MS 0
+#define SOC380_TABLE_END 1
+#define SOC380_MAX_RETRIES 3
+
+static struct soc380_reg mode_640x480[] = {
+       {0x001A, 0x0011},
+
+       {SOC380_TABLE_WAIT_MS, 1},
+
+       {0x001A, 0x0010},
+
+       {SOC380_TABLE_WAIT_MS, 1},
+
+       {0x0018, 0x4028},
+       {0x001A, 0x0210},
+       {0x001E, 0x0777},
+       {0x0016, 0x42DF},
+       {0x0010, 0x0217},
+       {0x0012, 0x0000},
+       {0x0014, 0x2147},
+
+       {SOC380_TABLE_WAIT_MS, 50},
+
+       {0x0014, 0x2047},
+
+       {SOC380_TABLE_WAIT_MS, 10},
+
+       {0x0014, 0xA046},
+
+       {SOC380_TABLE_WAIT_MS, 10},
+
+       {0x3040, 0x0027},
+       {0x301A, 0x1218},
+
+       {SOC380_TABLE_WAIT_MS, 10},
+
+       {0x301A, 0x121C},
+       {0x098C, 0x2703},
+       {0x0990, 0x0280},
+       {0x098C, 0x2705},
+       {0x0990, 0x01E0},
+       {0x098C, 0x2707},
+       {0x0990, 0x0280},
+       {0x098C, 0x2709},
+       {0x0990, 0x01E0},
+       {0x098C, 0x270D},
+       {0x0990, 0x0004},
+       {0x098C, 0x270F},
+       {0x0990, 0x0004},
+       {0x098C, 0x2711},
+       {0x0990, 0x01EB},
+       {0x098C, 0x2713},
+       {0x0990, 0x028B},
+       {0x098C, 0x2715},
+       {0x0990, 0x0001},
+       {0x098C, 0x2717},
+       {0x0990, 0x0026},
+       {0x098C, 0x2719},
+       {0x0990, 0x001A},
+       {0x098C, 0x271B},
+       {0x0990, 0x006B},
+       {0x098C, 0x271D},
+       {0x0990, 0x006B},
+       {0x098C, 0x271F},
+       {0x0990, 0x046F},
+       {0x098C, 0x2721},
+       {0x0990, 0x034A},
+       {0x098C, 0x2723},
+       {0x0990, 0x0004},
+       {0x098C, 0x2725},
+       {0x0990, 0x0004},
+       {0x098C, 0x2727},
+       {0x0990, 0x01EB},
+       {0x098C, 0x2729},
+       {0x0990, 0x028B},
+       {0x098C, 0x272B},
+       {0x0990, 0x0001},
+       {0x098C, 0x272D},
+       {0x0990, 0x0026},
+       {0x098C, 0x272F},
+       {0x0990, 0x001A},
+       {0x098C, 0x2731},
+       {0x0990, 0x006B},
+       {0x098C, 0x2733},
+       {0x0990, 0x006B},
+       {0x098C, 0x2735},
+       {0x0990, 0x046F},
+       {0x098C, 0x2737},
+       {0x0990, 0x034A},
+       {0x098C, 0x2739},
+       {0x0990, 0x0000},
+       {0x098C, 0x273B},
+       {0x0990, 0x027F},
+       {0x098C, 0x273D},
+       {0x0990, 0x0000},
+       {0x098C, 0x273F},
+       {0x0990, 0x01DF},
+       {0x098C, 0x2747},
+       {0x0990, 0x0000},
+       {0x098C, 0x2749},
+       {0x0990, 0x027F},
+       {0x098C, 0x274B},
+       {0x0990, 0x0000},
+       {0x098C, 0x274D},
+       {0x0990, 0x01DF},
+       {0x098C, 0x222D},
+       {0x0990, 0x008B},
+       {0x098C, 0xA408},
+       {0x0990, 0x001F},
+       {0x098C, 0xA409},
+       {0x0990, 0x0022},
+       {0x098C, 0xA40A},
+       {0x0990, 0x0019},
+       {0x098C, 0xA40B},
+       {0x0990, 0x001C},
+       {0x098C, 0x2411},
+       {0x0990, 0x008B},
+       {0x098C, 0x2413},
+       {0x0990, 0x00A6},
+       {0x098C, 0x2415},
+       {0x0990, 0x008B},
+       {0x098C, 0x2417},
+       {0x0990, 0x00A6},
+       {0x098C, 0xA40D},
+       {0x0990, 0x0002},
+       {0x098C, 0xA410},
+       {0x0990, 0x0001},
+       {0x098C, 0xA103},
+       {0x0990, 0x0006},
+
+       {SOC380_TABLE_WAIT_MS, 50},
+
+       {0x098C, 0xA103},
+       {0x0990, 0x0005},
+
+       {0x3012, 0x0384},
+       {0x098C, 0x2115},
+       {0x0990, 0x0002},
+       {0x321C, 0x0003},
+       {0x3330, 0x0000},
+       {0x098C, 0x2103},
+       {0x0990, 0x0002},
+       {0x321C, 0x0003},
+       {0x3330, 0x0000},
+       {0x3330, 0x0000},
+       {0x321C, 0x0003},
+       {0x098C, 0xA103},
+       {0x0990, 0x0000},
+       {0x098C, 0xA104},
+       {0x0990, 0x0007},
+
+       {0x098C, 0xA115},
+       {0x0990, 0x0002},
+       {0x098C, 0xA103},
+       {0x0990, 0x0002},
+
+       {SOC380_TABLE_WAIT_MS, 500},
+       {SOC380_TABLE_END, 0x0000}
+};
+
+enum {
+       SOC380_MODE_680x480,
+};
+
+static struct soc380_reg *mode_table[] = {
+       [SOC380_MODE_680x480] = mode_640x480,
+};
+
+static int soc380_read_reg(struct i2c_client *client, u16 addr, u16 *val)
+{
+       int err;
+       struct i2c_msg msg[2];
+       unsigned char data[4];
+
+       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 = 2;
+       msg[1].buf = data + 2;
+
+       err = i2c_transfer(client->adapter, msg, 2);
+
+       if (err != 2)
+               return -EINVAL;
+
+       *val = data[2] << 8 | data[3];
+
+       return 0;
+}
+
+static int soc380_write_reg(struct i2c_client *client, u16 addr, u16 val)
+{
+       int err;
+       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 {
+               err = i2c_transfer(client->adapter, &msg, 1);
+               if (err == 1)
+                       return 0;
+               retry++;
+               pr_err("soc380: i2c transfer failed, retrying %x %x\n",
+                      addr, val);
+               msleep(3);
+       } while (retry <= SOC380_MAX_RETRIES);
+
+       return err;
+}
+
+static int soc380_write_table(struct i2c_client *client,
+                             const struct soc380_reg table[],
+                             const struct soc380_reg override_list[],
+                             int num_override_regs)
+{
+       int err;
+       const struct soc380_reg *next;
+       int i;
+       u16 val;
+
+       for (next = table; next->addr != SOC380_TABLE_END; next++) {
+               if (next->addr == SOC380_TABLE_WAIT_MS) {
+                       msleep(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 = soc380_write_reg(client, next->addr, val);
+               if (err)
+                       return err;
+       }
+       return 0;
+}
+
+static int soc380_set_mode(struct soc380_info *info, struct soc380_mode *mode)
+{
+       int sensor_mode;
+       int err;
+
+       pr_info("%s: xres %u yres %u\n", __func__, mode->xres, mode->yres);
+       if (mode->xres == 640 && mode->yres == 480)
+               sensor_mode = SOC380_MODE_680x480;
+       else {
+               pr_err("%s: invalid resolution supplied to set mode %d %d\n",
+                      __func__, mode->xres, mode->yres);
+               return -EINVAL;
+       }
+
+       err = soc380_write_table(info->i2c_client, mode_table[sensor_mode],
+               NULL, 0);
+       if (err)
+               return err;
+
+       info->mode = sensor_mode;
+       return 0;
+}
+
+static int soc380_get_status(struct soc380_info *info,
+               struct soc380_status *dev_status)
+{
+       int err;
+
+       err = soc380_write_reg(info->i2c_client, 0x98C, dev_status->data);
+       if (err)
+               return err;
+
+       err = soc380_read_reg(info->i2c_client, 0x0990,
+               (u16 *) &dev_status->status);
+       if (err)
+               return err;
+
+       return err;
+}
+
+static long soc380_ioctl(struct file *file,
+                        unsigned int cmd, unsigned long arg)
+{
+       int err;
+       struct soc380_info *info = file->private_data;
+
+       switch (cmd) {
+       case SOC380_IOCTL_SET_MODE:
+       {
+               struct soc380_mode mode;
+               if (copy_from_user(&mode,
+                                  (const void __user *)arg,
+                                  sizeof(struct soc380_mode))) {
+                       return -EFAULT;
+               }
+
+               return soc380_set_mode(info, &mode);
+       }
+       case SOC380_IOCTL_GET_STATUS:
+       {
+               struct soc380_status dev_status;
+               if (copy_from_user(&dev_status,
+                                  (const void __user *)arg,
+                                  sizeof(struct soc380_status))) {
+                       return -EFAULT;
+               }
+
+               err = soc380_get_status(info, &dev_status);
+               if (err)
+                       return err;
+               if (copy_to_user((void __user *)arg, &dev_status,
+                                sizeof(struct soc380_status))) {
+                       return -EFAULT;
+               }
+               return 0;
+       }
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static struct soc380_info *info;
+
+static int soc380_open(struct inode *inode, struct file *file)
+{
+       struct soc380_status dev_status;
+       int err;
+
+       file->private_data = info;
+       if (info->pdata && info->pdata->power_on)
+               info->pdata->power_on();
+
+       dev_status.data = 0;
+       dev_status.status = 0;
+       err = soc380_get_status(info, &dev_status);
+       return err;
+}
+
+int soc380_release(struct inode *inode, struct file *file)
+{
+       if (info->pdata && info->pdata->power_off)
+               info->pdata->power_off();
+       file->private_data = NULL;
+       return 0;
+}
+
+static const struct file_operations soc380_fileops = {
+       .owner = THIS_MODULE,
+       .open = soc380_open,
+       .unlocked_ioctl = soc380_ioctl,
+       .release = soc380_release,
+};
+
+static struct miscdevice soc380_device = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = "soc380",
+       .fops = &soc380_fileops,
+};
+
+static int soc380_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       int err;
+
+       pr_info("soc380: probing sensor.\n");
+
+       info = kzalloc(sizeof(struct soc380_info), GFP_KERNEL);
+       if (!info) {
+               pr_err("soc380: Unable to allocate memory!\n");
+               return -ENOMEM;
+       }
+
+       err = misc_register(&soc380_device);
+       if (err) {
+               pr_err("soc380: Unable to register misc device!\n");
+               kfree(info);
+               return err;
+       }
+
+       info->pdata = client->dev.platform_data;
+       info->i2c_client = client;
+
+       i2c_set_clientdata(client, info);
+       return 0;
+}
+
+static int soc380_remove(struct i2c_client *client)
+{
+       struct soc380_info *info;
+       info = i2c_get_clientdata(client);
+       misc_deregister(&soc380_device);
+       kfree(info);
+       return 0;
+}
+
+static const struct i2c_device_id soc380_id[] = {
+       { "soc380", 0 },
+       { },
+};
+
+MODULE_DEVICE_TABLE(i2c, soc380_id);
+
+static struct i2c_driver soc380_i2c_driver = {
+       .driver = {
+               .name = "soc380",
+               .owner = THIS_MODULE,
+       },
+       .probe = soc380_probe,
+       .remove = soc380_remove,
+       .id_table = soc380_id,
+};
+
+static int __init soc380_init(void)
+{
+       pr_info("soc380 sensor driver loading\n");
+       return i2c_add_driver(&soc380_i2c_driver);
+}
+
+static void __exit soc380_exit(void)
+{
+       i2c_del_driver(&soc380_i2c_driver);
+}
+
+module_init(soc380_init);
+module_exit(soc380_exit);
diff --git a/include/media/soc380.h b/include/media/soc380.h
new file mode 100644 (file)
index 0000000..0cf1744
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * 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 __SOC380_H__
+#define __SOC380_H__
+
+#include <linux/ioctl.h>  /* For IOCTL macros */
+
+#define SOC380_IOCTL_SET_MODE          _IOW('o', 1, struct soc380_mode)
+#define SOC380_IOCTL_GET_STATUS                _IOR('o', 2, struct soc380_status)
+
+struct soc380_mode {
+       int xres;
+       int yres;
+};
+
+struct soc380_status {
+       int data;
+       int status;
+};
+
+#ifdef __KERNEL__
+struct soc380_platform_data {
+       int (*power_on)(void);
+       int (*power_off)(void);
+
+};
+#endif /* __KERNEL__ */
+
+#endif  /* __SOC380_H__ */
+