summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbhinav Sinha <absinha@nvidia.com>2011-03-31 13:56:27 -0700
committerVarun Colbert <vcolbert@nvidia.com>2011-04-15 18:35:43 -0700
commitdee4b6ad92b696fca78ca292d2e63a09ec8a7233 (patch)
treeade011f5f9e3e772f3ada72bbb35bacbfb9af6da
parenta56f81030b5013d1f84c91025c595525d9824bd6 (diff)
camera: Add driver for soc380 front sensor
Bug: 783488 Change-Id: I0d70ec5bb0fd880b167e9ced0e002829abeccdbf Reviewed-on: http://git-master/r/24969 Reviewed-by: Varun Colbert <vcolbert@nvidia.com> Tested-by: Varun Colbert <vcolbert@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/soc380.c496
-rw-r--r--include/media/soc380.h38
4 files changed, 542 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/Kconfig b/drivers/media/video/tegra/Kconfig
index fd1908ba781b..afcbec6b25ab 100644
--- a/drivers/media/video/tegra/Kconfig
+++ b/drivers/media/video/tegra/Kconfig
@@ -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
diff --git a/drivers/media/video/tegra/Makefile b/drivers/media/video/tegra/Makefile
index 60d2b5041974..872a3ce0d30a 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_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
index 000000000000..1304cd58b53d
--- /dev/null
+++ b/drivers/media/video/tegra/soc380.c
@@ -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
index 000000000000..0cf1744dd022
--- /dev/null
+++ b/include/media/soc380.h
@@ -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__ */
+