diff options
author | Abhinav Sinha <absinha@nvidia.com> | 2011-03-31 13:56:27 -0700 |
---|---|---|
committer | Varun Colbert <vcolbert@nvidia.com> | 2011-04-15 18:35:43 -0700 |
commit | dee4b6ad92b696fca78ca292d2e63a09ec8a7233 (patch) | |
tree | ade011f5f9e3e772f3ada72bbb35bacbfb9af6da /drivers | |
parent | a56f81030b5013d1f84c91025c595525d9824bd6 (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>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/media/video/tegra/Kconfig | 7 | ||||
-rw-r--r-- | drivers/media/video/tegra/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/video/tegra/soc380.c | 496 |
3 files changed, 504 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); |