summaryrefslogtreecommitdiff
path: root/drivers/media/video/tegra/ov5640.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/video/tegra/ov5640.c')
-rw-r--r--drivers/media/video/tegra/ov5640.c491
1 files changed, 491 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/ov5640.c b/drivers/media/video/tegra/ov5640.c
new file mode 100644
index 000000000000..b20c036bb06c
--- /dev/null
+++ b/drivers/media/video/tegra/ov5640.c
@@ -0,0 +1,491 @@
+/*
+ * ov5640.c - ov5640 sensor driver
+ *
+ * Copyright (c) 2011 - 2012, NVIDIA, All Rights Reserved.
+ *
+ * Contributors:
+ * Abhinav Sinha <absinha@nvidia.com>
+ *
+ * Leverage soc380.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/ov5640.h>
+
+#include "ov5640_tables.h"
+
+/* Focuser single step & full scale transition time truth table
+ * in the format of:
+ * index mode single step transition full scale transition
+ * 0 0 0 0
+ * 1 1 50uS 51.2mS
+ * 2 1 100uS 102.3mS
+ * 3 1 200uS 204.6mS
+ * 4 1 400uS 409.2mS
+ * 5 1 800uS 818.4mS
+ * 6 1 1600uS 1637.0mS
+ * 7 1 3200uS 3274.0mS
+ * 8 0 0 0
+ * 9 2 50uS 1.1mS
+ * A 2 100uS 2.2mS
+ * B 2 200uS 4.4mS
+ * C 2 400uS 8.8mS
+ * D 2 800uS 17.6mS
+ * E 2 1600uS 35.2mS
+ * F 2 3200uS 70.4mS
+ */
+
+/* pick up the mode index setting and its settle time from the above table */
+#define OV5640_VCM_DACMODE 0x3602
+#define OV5640_TRANSITION_MODE 0x0B
+#define SETTLETIME_MS 5
+
+#define POS_LOW (0)
+#define POS_HIGH (1023)
+#define FPOS_COUNT 1024
+#define FOCAL_LENGTH (10.0f)
+#define FNUMBER (2.8f)
+
+#define SIZEOF_I2C_TRANSBUF 64
+
+struct ov5640_info {
+ int mode;
+ struct miscdevice miscdev_info;
+ struct i2c_client *i2c_client;
+ struct ov5640_platform_data *pdata;
+ struct ov5640_config focuser;
+ int af_fw_loaded;
+ struct kobject *kobj;
+ struct device *dev;
+ u8 i2c_trans_buf[SIZEOF_I2C_TRANSBUF];
+};
+
+static int ov5640_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)
+ return -EINVAL;
+
+ *val = data[2];
+
+ return 0;
+}
+
+static int ov5640_write_reg(struct i2c_client *client, u8 addr, u8 value)
+{
+ int count;
+ struct i2c_msg msg[1];
+ unsigned char data[4];
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ data[0] = addr;
+ data[1] = (u8) (addr & 0xff);
+ data[2] = value;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].len = 3;
+ msg[0].buf = data;
+
+ count = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (count == ARRAY_SIZE(msg))
+ return 0;
+ dev_err(&client->dev,
+ "ov5840: i2c transfer failed, addr: %x, value: %02x\n",
+ addr, (u32)value);
+ return -EIO;
+}
+
+static int ov5640_write_bulk_reg(struct i2c_client *client, u8 *data, int len)
+{
+ int err;
+ struct i2c_msg msg;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg.addr = client->addr;
+ msg.flags = 0;
+ msg.len = len;
+ msg.buf = data;
+
+ err = i2c_transfer(client->adapter, &msg, 1);
+ if (err == 1)
+ return 0;
+
+ dev_err(&client->dev, "ov5640: i2c transfer failed at %x\n",
+ (int)data[0] << 8 | data[1]);
+
+ return err;
+}
+
+static int ov5640_write_table(struct ov5640_info *info,
+ struct ov5640_reg table[],
+ struct ov5640_reg override_list[],
+ int num_override_regs)
+{
+ int err;
+ struct ov5640_reg *next, *n_next;
+ u8 *b_ptr = info->i2c_trans_buf;
+ unsigned int buf_filled = 0;
+ int i;
+ u16 val;
+
+ for (next = table; next->addr != OV5640_TABLE_END; next++) {
+ if (next->addr == OV5640_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;
+ }
+ }
+ }
+
+ if (!buf_filled) {
+ b_ptr = info->i2c_trans_buf;
+ *b_ptr++ = next->addr >> 8;
+ *b_ptr++ = next->addr & 0xff;
+ buf_filled = 2;
+ }
+ *b_ptr++ = val;
+ buf_filled++;
+
+ n_next = next + 1;
+ if (n_next->addr != OV5640_TABLE_END &&
+ n_next->addr != OV5640_TABLE_WAIT_MS &&
+ buf_filled < SIZEOF_I2C_TRANSBUF &&
+ n_next->addr == next->addr + 1) {
+ continue;
+ }
+
+ err = ov5640_write_bulk_reg(info->i2c_client,
+ info->i2c_trans_buf, buf_filled);
+ if (err)
+ return err;
+
+ buf_filled = 0;
+ }
+ return 0;
+}
+
+static int ov5640_set_mode(struct ov5640_info *info, struct ov5640_mode *mode)
+{
+ int sensor_mode;
+ int err;
+
+ dev_info(info->dev, "%s: xres %u yres %u\n",
+ __func__, mode->xres, mode->yres);
+ if (!info->af_fw_loaded) {
+ err = ov5640_write_table(info, tbl_af_firmware, NULL, 0);
+ if (err)
+ return err;
+ info->af_fw_loaded = 1;
+ }
+
+ if (mode->xres == 2592 && mode->yres == 1944)
+ sensor_mode = OV5640_MODE_2592x1944;
+ else if (mode->xres == 1920 && mode->yres == 1080)
+ sensor_mode = OV5640_MODE_1920x1080;
+ else if (mode->xres == 1296 && mode->yres == 964)
+ sensor_mode = OV5640_MODE_1296x972;
+ else {
+ dev_info(info->dev, "%s: invalid resolution: %d %d\n",
+ __func__, mode->xres, mode->yres);
+ return -EINVAL;
+ }
+
+ err = ov5640_write_table(info, mode_table[sensor_mode],
+ NULL, 0);
+ if (err)
+ return err;
+
+ info->mode = sensor_mode;
+ return 0;
+}
+
+static int ov5640_set_af_mode(struct ov5640_info *info, u8 mode)
+{
+ dev_info(info->dev, "%s: mode %d\n", __func__, mode);
+ if (mode == OV5640_AF_INIFINITY)
+ return ov5640_write_table(info, tbl_release_focus, NULL, 0);
+
+ if (mode == OV5640_AF_TRIGGER)
+ return ov5640_write_table(info, tbl_single_focus, NULL, 0);
+
+ return -EINVAL;
+}
+
+static int ov5640_get_af_status(struct ov5640_info *info, u8 *val)
+{
+ int err;
+
+ err = ov5640_read_reg(info->i2c_client, 0x3023, val);
+ if (err)
+ return -EINVAL;
+
+ dev_info(info->dev, "%s: value %02x\n", __func__, (u32)val);
+ return 0;
+}
+
+static int ov5640_set_position(struct ov5640_info *info, u32 position)
+{
+ u8 data[4];
+
+ if (position < info->focuser.pos_low ||
+ position > info->focuser.pos_high)
+ return -EINVAL;
+
+ data[0] = (OV5640_VCM_DACMODE >> 8) & 0xff;
+ data[1] = OV5640_VCM_DACMODE & 0xff;
+ data[2] = ((position & 0xf) << 4) | OV5640_TRANSITION_MODE;
+ data[3] = (position * 0x3f0) >> 4;
+ return ov5640_write_bulk_reg(info->i2c_client, data, 4);
+}
+
+static int ov5640_set_power(struct ov5640_info *info, u32 level)
+{
+ switch (level) {
+ case OV5640_POWER_LEVEL_OFF:
+ case OV5640_POWER_LEVEL_SUS:
+ if (info->pdata && info->pdata->power_off)
+ info->pdata->power_off();
+ info->af_fw_loaded = 0;
+ info->mode = 0;
+ break;
+ case OV5640_POWER_LEVEL_ON:
+ if (info->pdata && info->pdata->power_on)
+ info->pdata->power_on();
+ break;
+ default:
+ dev_err(info->dev, "unknown power level %d.\n", level);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static long ov5640_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct ov5640_info *info = file->private_data;
+
+ switch (cmd) {
+ case OV5640_IOCTL_SET_SENSOR_MODE:
+ {
+ struct ov5640_mode mode;
+ if (copy_from_user(&mode,
+ (const void __user *)arg,
+ sizeof(struct ov5640_mode))) {
+ return -EFAULT;
+ }
+
+ return ov5640_set_mode(info, &mode);
+ }
+ case OV5640_IOCTL_GET_CONFIG:
+ {
+ if (copy_to_user((void __user *) arg,
+ &info->focuser,
+ sizeof(info->focuser))) {
+ dev_err(info->dev, "%s: 0x%x\n", __func__, __LINE__);
+ return -EFAULT;
+ }
+
+ break;
+ }
+ case OV5640_IOCTL_GET_AF_STATUS:
+ {
+ int err;
+ u8 val;
+
+ if (!info->af_fw_loaded) {
+ dev_err(info->dev, "OV5640 AF fw not loaded!\n");
+ break;
+ }
+
+ err = ov5640_get_af_status(info, &val);
+ if (err)
+ return err;
+
+ if (copy_to_user((void __user *) arg,
+ &val, sizeof(val))) {
+ dev_err(info->dev, "%s: 0x%x\n", __func__, __LINE__);
+ return -EFAULT;
+ }
+ break;
+ }
+ case OV5640_IOCTL_SET_AF_MODE:
+ if (!info->af_fw_loaded) {
+ dev_err(info->dev, "OV5640 AF fw not loaded!\n");
+ break;
+ }
+ return ov5640_set_af_mode(info, (u8)arg);
+ case OV5640_IOCTL_POWER_LEVEL:
+ return ov5640_set_power(info, (u32)arg);
+ case OV5640_IOCTL_SET_FPOSITION:
+ return ov5640_set_position(info, (u32)arg);
+ case OV5640_IOCTL_GET_SENSOR_STATUS:
+ {
+ u8 status = 0;
+ if (copy_to_user((void __user *)arg, &status,
+ 1)) {
+ dev_info(info->dev, "%s %d\n", __func__, __LINE__);
+ return -EFAULT;
+ }
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ov5640_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *miscdev = file->private_data;
+ struct ov5640_info *info;
+
+ pr_info("%s\n", __func__);
+ if (!miscdev) {
+ pr_err("miscdev == NULL\n");
+ return -1;
+ }
+ info = container_of(miscdev, struct ov5640_info, miscdev_info);
+ file->private_data = info;
+
+ return 0;
+}
+
+int ov5640_release(struct inode *inode, struct file *file)
+{
+ file->private_data = NULL;
+ pr_info("%s\n", __func__);
+ return 0;
+}
+
+static const struct file_operations ov5640_fileops = {
+ .owner = THIS_MODULE,
+ .open = ov5640_open,
+ .unlocked_ioctl = ov5640_ioctl,
+ .release = ov5640_release,
+};
+
+static struct miscdevice ov5640_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "ov5640",
+ .fops = &ov5640_fileops,
+};
+
+static int ov5640_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ov5640_info *info;
+ int err;
+
+ dev_info(&client->dev, "ov5640: probing sensor.\n");
+
+ info = devm_kzalloc(&client->dev,
+ sizeof(struct ov5640_info), GFP_KERNEL);
+ if (!info) {
+ dev_err(&client->dev, "ov5640: Unable to allocate memory!\n");
+ return -ENOMEM;
+ }
+
+ memcpy(&(info->miscdev_info),
+ &ov5640_device,
+ sizeof(struct miscdevice));
+
+ err = misc_register(&(info->miscdev_info));
+ if (err) {
+ dev_err(&client->dev,
+ "ov5640: Unable to register misc device!\n");
+ devm_kfree(&client->dev, info);
+ return err;
+ }
+
+ info->dev = &client->dev;
+ info->pdata = client->dev.platform_data;
+ info->i2c_client = client;
+ info->focuser.settle_time = SETTLETIME_MS;
+ info->focuser.focal_length = FOCAL_LENGTH;
+ info->focuser.fnumber = FNUMBER;
+ info->focuser.pos_low = POS_LOW;
+ info->focuser.pos_high = POS_HIGH;
+
+ i2c_set_clientdata(client, info);
+ return 0;
+}
+
+static int ov5640_remove(struct i2c_client *client)
+{
+ struct ov5640_info *info;
+ info = i2c_get_clientdata(client);
+ misc_deregister(&ov5640_device);
+ return 0;
+}
+
+static const struct i2c_device_id ov5640_id[] = {
+ { "ov5640", 0 },
+ { },
+};
+
+MODULE_DEVICE_TABLE(i2c, ov5640_id);
+
+static struct i2c_driver ov5640_i2c_driver = {
+ .driver = {
+ .name = "ov5640",
+ .owner = THIS_MODULE,
+ },
+ .probe = ov5640_probe,
+ .remove = ov5640_remove,
+ .id_table = ov5640_id,
+};
+
+module_i2c_driver(ov5640_i2c_driver);