media: video: tegra: Add AR0261 support
Amey Asgaonkar [Wed, 26 Jun 2013 21:19:55 +0000 (14:19 -0700)]
Adds driver support for ar0261 camera sensor.

Bug 1256440

Change-Id: Ib764afc2c183d5a26e48eeb38b10d85fefdd4e77
Signed-off-by: Amey Asgaonkar <aasgaonkar@nvidia.com>
Reviewed-on: http://git-master/r/242574
Reviewed-by: Sachin Nikam <snikam@nvidia.com>

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

index 8316754..ecc76f1 100644 (file)
@@ -77,6 +77,12 @@ config VIDEO_IMX135
           maximum of 13MP (4208x3120) resolution with HDR
           capability.
 
+config VIDEO_AR0261
+        tristate "AR0261 camera sensor support"
+        depends on I2C && ARCH_TEGRA
+        ---help---
+          This is a driver for the AR0261 camera sensor
+
 config VIDEO_IMX132
         tristate "IMX132 camera sensor support"
         depends on I2C && ARCH_TEGRA
index 2beff42..6d9706c 100644 (file)
@@ -31,6 +31,7 @@ obj-$(CONFIG_VIDEO_AD5816)    += ad5816.o
 obj-$(CONFIG_VIDEO_IMX091)     += imx091.o nvc_utilities.o
 obj-$(CONFIG_VIDEO_IMX135)     += imx135.o
 obj-$(CONFIG_VIDEO_IMX132)     += imx132.o
+obj-$(CONFIG_VIDEO_AR0261)     += ar0261.o
 obj-$(CONFIG_DEBUG_FS)         += nvc_debugfs.o
 obj-$(CONFIG_VIDEO_OV5693)     += ov5693.o
 obj-$(CONFIG_VIDEO_AD5823)     += ad5823.o
diff --git a/drivers/media/video/tegra/ar0261.c b/drivers/media/video/tegra/ar0261.c
new file mode 100644 (file)
index 0000000..beef9ed
--- /dev/null
@@ -0,0 +1,954 @@
+/*
+ * ar0261.c - ar0261 sensor driver
+ *
+ * Copyright (c) 2013, NVIDIA Corporation. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 <linux/regulator/consumer.h>
+#include <media/ar0261.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+
+struct ar0261_reg {
+       u16 addr;
+       u16 val;
+};
+
+struct ar0261_info {
+       struct miscdevice miscdev_info;
+       struct ar0261_power_rail power;
+       struct ar0261_sensordata sensor_data;
+       struct i2c_client *i2c_client;
+       struct ar0261_platform_data *pdata;
+       atomic_t in_use;
+       int mode;
+};
+
+#define AR0261_TABLE_WAIT_MS 0
+#define AR0261_TABLE_END 1
+
+static struct ar0261_reg mode_1920x1080[] = {
+       {0x301A, 0x0019},
+       {AR0261_TABLE_WAIT_MS, 100},
+       {0x301A, 0x0218},
+       {0x31B0, 0x0062},
+       {0x31B2, 0x0046},
+       {0x31B4, 0x3248},
+       {0x31B6, 0x22A6},
+       {0x31B8, 0x1832},
+       {0x31BA, 0x1052},
+       {0x31BC, 0x0408},
+       {0x31AE, 0x0201},
+       {AR0261_TABLE_WAIT_MS, 1},
+       {0x3044, 0x0590},
+       {0x3EE6, 0x60AD},
+       {0x3EDC, 0xDBFA},
+       {0x301A, 0x0218},
+       {AR0261_TABLE_WAIT_MS, 100},
+       {0x3D00, 0x0481},
+       {0x3D02, 0xFFFF},
+       {0x3D04, 0xFFFF},
+       {0x3D06, 0xFFFF},
+       {0x3D08, 0x6600},
+       {0x3D0A, 0x0311},
+       {0x3D0C, 0x8C67},
+       {0x3D0E, 0x0808},
+       {0x3D10, 0x4380},
+       {0x3D12, 0x4343},
+       {0x3D14, 0x8043},
+       {0x3D16, 0x4330},
+       {0x3D18, 0x0543},
+       {0x3D1A, 0x4381},
+       {0x3D1C, 0x4C85},
+       {0x3D1E, 0x2022},
+       {0x3D20, 0x8020},
+       {0x3D22, 0xA093},
+       {0x3D24, 0x5A8A},
+       {0x3D26, 0x4C81},
+       {0x3D28, 0x5981},
+       {0x3D2A, 0x1E00},
+       {0x3D2C, 0x5F83},
+       {0x3D2E, 0x5C80},
+       {0x3D30, 0x5C81},
+       {0x3D32, 0x5F58},
+       {0x3D34, 0x6880},
+       {0x3D36, 0x1060},
+       {0x3D38, 0x8541},
+       {0x3D3A, 0xB350},
+       {0x3D3C, 0x5F10},
+       {0x3D3E, 0x6050},
+       {0x3D40, 0x5780},
+       {0x3D42, 0x6880},
+       {0x3D44, 0x2220},
+       {0x3D46, 0x805D},
+       {0x3D48, 0x8140},
+       {0x3D4A, 0x864B},
+       {0x3D4C, 0x8524},
+       {0x3D4E, 0x08A0},
+       {0x3D50, 0x55B8},
+       {0x3D52, 0x429C},
+       {0x3D54, 0x4281},
+       {0x3D56, 0x4081},
+       {0x3D58, 0x2808},
+       {0x3D5A, 0x2810},
+       {0x3D5C, 0x5727},
+       {0x3D5E, 0x1069},
+       {0x3D60, 0x4B52},
+       {0x3D62, 0x8265},
+       {0x3D64, 0x8A65},
+       {0x3D66, 0xA95E},
+       {0x3D68, 0x5080},
+       {0x3D6A, 0x5250},
+       {0x3D6C, 0x6080},
+       {0x3D6E, 0x6922},
+       {0x3D70, 0x2080},
+       {0x3D72, 0x5D80},
+       {0x3D74, 0x4080},
+       {0x3D76, 0x5681},
+       {0x3D78, 0x5781},
+       {0x3D7A, 0x4B86},
+       {0x3D7C, 0x2408},
+       {0x3D7E, 0x9345},
+       {0x3D80, 0x8144},
+       {0x3D82, 0x4481},
+       {0x3D84, 0x4586},
+       {0x3D86, 0x4E80},
+       {0x3D88, 0x4FCD},
+       {0x3D8A, 0x4685},
+       {0x3D8C, 0x0006},
+       {0x3D8E, 0x8143},
+       {0x3D90, 0x4380},
+       {0x3D92, 0x4343},
+       {0x3D94, 0x8043},
+       {0x3D96, 0x4380},
+       {0x3D98, 0x4343},
+       {0x3D9A, 0x8043},
+       {0x3D9C, 0x4380},
+       {0x3D9E, 0x4343},
+       {0x3DA0, 0x8648},
+       {0x3DA2, 0x4880},
+       {0x3DA4, 0x6B6B},
+       {0x3DA6, 0x814C},
+       {0x3DA8, 0x864D},
+       {0x3DAA, 0xA442},
+       {0x3DAC, 0x8641},
+       {0x3DAE, 0x804D},
+       {0x3DB0, 0x864C},
+       {0x3DB2, 0x8A45},
+       {0x3DB4, 0x8144},
+       {0x3DB6, 0x4481},
+       {0x3DB8, 0x4583},
+       {0x3DBA, 0x46B7},
+       {0x3DBC, 0x7386},
+       {0x3DBE, 0x4685},
+       {0x3DC0, 0x0006},
+       {0x3DC2, 0x8143},
+       {0x3DC4, 0x4380},
+       {0x3DC6, 0x4343},
+       {0x3DC8, 0x8043},
+       {0x3DCA, 0x4380},
+       {0x3DCC, 0x4343},
+       {0x3DCE, 0x8043},
+       {0x3DD0, 0x4380},
+       {0x3DD2, 0x4343},
+       {0x3DD4, 0x8648},
+       {0x3DD6, 0x4880},
+       {0x3DD8, 0x6A6A},
+       {0x3DDA, 0x814C},
+       {0x3DDC, 0x864D},
+       {0x3DDE, 0xA442},
+       {0x3DE0, 0x8641},
+       {0x3DE2, 0x804D},
+       {0x3DE4, 0x864C},
+       {0x3DE6, 0x8A45},
+       {0x3DE8, 0x8144},
+       {0x3DEA, 0x4481},
+       {0x3DEC, 0x4583},
+       {0x3DEE, 0x4686},
+       {0x3DF0, 0x73FF},
+       {0x3DF2, 0xD358},
+       {0x3DF4, 0x835B},
+       {0x3DF6, 0x825A},
+       {0x3DF8, 0x8153},
+       {0x3DFA, 0x5467},
+       {0x3DFC, 0x6363},
+       {0x3DFE, 0x2640},
+       {0x3E00, 0x6470},
+       {0x3E02, 0xFFFF},
+       {0x3E04, 0xFFFF},
+       {0x3E06, 0xFFED},
+       {0x3E08, 0x4580},
+       {0x3E0A, 0x4384},
+       {0x3E0C, 0x4380},
+       {0x3E0E, 0x0280},
+       {0x3E10, 0x8402},
+       {0x3E12, 0x8080},
+       {0x3E14, 0x6A84},
+       {0x3E16, 0x6A80},
+       {0x3E18, 0x4484},
+       {0x3E1A, 0x4480},
+       {0x3E1C, 0x4578},
+       {0x3E1E, 0x8270},
+       {0x3E20, 0x0000},
+       {0x3E22, 0x0000},
+       {0x3E24, 0x0000},
+       {0x3E26, 0x0000},
+       {0x3E28, 0x0000},
+       {0x3E2A, 0x0000},
+       {0x3E2C, 0x0000},
+       {0x3E2E, 0x0000},
+       {0x3E30, 0x0000},
+       {0x3E32, 0x0000},
+       {0x3E34, 0x0000},
+       {0x3E36, 0x0000},
+       {0x3E38, 0x0000},
+       {0x3E3A, 0x0000},
+       {0x3E3C, 0x0000},
+       {0x3E3E, 0x0000},
+       {0x3E40, 0x0000},
+       {0x3E42, 0x0000},
+       {0x3E44, 0x0000},
+       {0x3E46, 0x0000},
+       {0x3E48, 0x0000},
+       {0x3E4A, 0x0000},
+       {0x3E4C, 0x0000},
+       {0x3E4E, 0x0000},
+       {0x3E50, 0x0000},
+       {0x3E52, 0x0000},
+       {0x3E54, 0x0000},
+       {0x3E56, 0x0000},
+       {0x3E58, 0x0000},
+       {0x3E5A, 0x0000},
+       {0x3E5C, 0x0000},
+       {0x3E5E, 0x0000},
+       {0x3E60, 0x0000},
+       {0x3E62, 0x0000},
+       {0x3E64, 0x0000},
+       {0x3E66, 0x0000},
+       {0x3E68, 0x0000},
+       {0x3E6A, 0x0000},
+       {0x3E6C, 0x0000},
+       {0x3E6E, 0x0000},
+       {0x3E70, 0x0000},
+       {0x3E72, 0x0000},
+       {0x3E74, 0x0000},
+       {0x3E76, 0x0000},
+       {0x3E78, 0x0000},
+       {0x3E7A, 0x0000},
+       {0x3E7C, 0x0000},
+       {0x3E7E, 0x0000},
+       {0x3E80, 0x0000},
+       {0x3E82, 0x0000},
+       {0x3E84, 0x0000},
+       {0x3E86, 0x0000},
+       {0x3E88, 0x0000},
+       {0x3E8A, 0x0000},
+       {0x3E8C, 0x0000},
+       {0x3E8E, 0x0000},
+       {0x3E90, 0x0000},
+       {0x3E92, 0x0000},
+       {0x3E94, 0x0000},
+       {0x3E96, 0x0000},
+       {0x3E98, 0x0000},
+       {0x3E9A, 0x0000},
+       {0x3E9C, 0x0000},
+       {0x3E9E, 0x0000},
+       {0x3EA0, 0x0000},
+       {0x3EA2, 0x0000},
+       {0x3EA4, 0x0000},
+       {0x3EA6, 0x0000},
+       {0x3EA8, 0x0000},
+       {0x3EAA, 0x0000},
+       {0x3EAC, 0x0000},
+       {0x3EAE, 0x0000},
+       {0x3EB0, 0x0000},
+       {0x3EB2, 0x0000},
+       {0x3EB4, 0x0000},
+       {0x3EB6, 0x0000},
+       {0x3EB8, 0x0000},
+       {0x3EBA, 0x0000},
+       {0x3EBC, 0x0000},
+       {0x3EBE, 0x0000},
+       {0x3EC0, 0x0000},
+       {0x3EC2, 0x0000},
+       {0x3EC4, 0x0000},
+       {0x3EC6, 0x0000},
+       {0x3EC8, 0x0000},
+       {0x3ECA, 0x0000},
+       {0x301A, 0x021C},
+       {0x0342, 0x10CC},
+       {0x0340, 0x04A4},
+       {0x0202, 0x0496},
+       {0x0312, 0x045D},
+       {0x31AE, 0x0201},
+       {0x0300, 0x0005},
+       {0x0302, 0x0001},
+       {0x0304, 0x0202},
+       {0x0306, 0x4040},
+       {0x0308, 0x000A},
+       {0x030A, 0x0001},
+       {0x0344, 0x0008},
+       {0x0348, 0x0787},
+       {0x0346, 0x0008},
+       {0x034A, 0x043F},
+       {0x034C, 0x0780},
+       {0x034E, 0x0438},
+       {0x3040, 0x0041},
+       {0x0104, 0x0001},
+       {0x3ECC, 0x008F},
+       {0x3ECE, 0xA8F0},
+       {0x3ED0, 0xFFFF},
+       {0x3ED6, 0x7193},
+       {0x3ED8, 0x8A11},
+       {0x30D2, 0x0020},
+       {0x30D4, 0x0040},
+       {0x3180, 0x80FF},
+       {0x0104, 0x0000},
+       {0x301A, 0x001C},
+       {AR0261_TABLE_END, 0x0000}
+};
+
+enum {
+       AR0261_MODE_1920X1080,
+};
+
+static struct ar0261_reg *mode_table[] = {
+       [AR0261_MODE_1920X1080] = mode_1920x1080,
+};
+
+static inline void
+msleep_range(unsigned int delay_base)
+{
+       usleep_range(delay_base*1000, delay_base*1000+500);
+}
+
+static inline void
+ar0261_get_frame_length_regs(struct ar0261_reg *regs, u32 frame_length)
+{
+       regs->addr = AR0261_FRAME_LEN_LINES_15_8;
+       regs->val = (frame_length >> 8) & 0xff;
+       (regs + 1)->addr = AR0261_FRAME_LEN_LINES_7_0;
+       (regs + 1)->val = (frame_length) & 0xff;
+}
+
+static inline void
+ar0261_get_coarse_time_regs(struct ar0261_reg *regs, u32 coarse_time)
+{
+       regs->addr = AR0261_COARSE_INTEGRATION_TIME_15_8;
+       regs->val = (coarse_time >> 8) & 0xff;
+       (regs + 1)->addr = AR0261_COARSE_INTEGRATION_TIME_7_0;
+       (regs + 1)->val = (coarse_time) & 0xff;
+}
+
+static inline void
+ar0261_get_gain_reg(struct ar0261_reg *regs, u16 gain)
+{
+       regs->addr = AR0261_ANA_GAIN_GLOBAL;
+       regs->val = gain;
+}
+
+static int
+ar0261_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
+ar0261_write_reg(struct i2c_client *client, u16 addr, u16 val)
+{
+       int err;
+       struct i2c_msg msg;
+       unsigned char data[4];
+
+       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;
+
+       err = i2c_transfer(client->adapter, &msg, 1);
+       if (err == 1)
+               return 0;
+
+       dev_err(&client->dev, "%s:i2c write failed, %x = %x\n",
+                       __func__, addr, val);
+
+       return err;
+}
+
+static int ar0261_i2c_wr8(struct i2c_client *client, u16 reg, u8 val)
+{
+       struct i2c_msg msg;
+       u8 buf[3];
+
+       buf[0] = (reg >> 8);
+       buf[1] = (reg & 0xFF);
+       buf[2] = val;
+       msg.addr = client->addr;
+       msg.flags = 0;
+       msg.len = 3;
+       msg.buf = &buf[0];
+       if (i2c_transfer(client->adapter, &msg, 1) != 1)
+               return -EIO;
+
+       return 0;
+}
+
+static int
+ar0261_write_table(struct i2c_client *client,
+                        const struct ar0261_reg table[],
+                        const struct ar0261_reg override_list[],
+                        int num_override_regs)
+{
+       const struct ar0261_reg *next;
+       int err = 0;
+       int i;
+       u16 val;
+
+       dev_info(&client->dev, "ar0261_write_table\n");
+
+       for (next = table; next->addr != AR0261_TABLE_END; next++) {
+
+               if (next->addr == AR0261_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 = ar0261_write_reg(client, next->addr, val);
+               if (err)
+                       break;
+       }
+
+       return err;
+}
+
+static int
+ar0261_set_mode(struct ar0261_info *info, struct ar0261_mode *mode)
+{
+       struct device *dev = &info->i2c_client->dev;
+       int sensor_mode;
+       int err;
+       struct ar0261_reg reg_list[5];
+
+       dev_info(dev, "%s: res [%ux%u] framelen %u coarsetime %u gain %u\n",
+               __func__, mode->xres, mode->yres,
+               mode->frame_length, mode->coarse_time, mode->gain);
+
+       if ((mode->xres == 1920) && (mode->yres == 1080))
+               sensor_mode = AR0261_MODE_1920X1080;
+       else {
+               dev_err(dev, "%s: invalid resolution to set mode %d %d\n",
+                       __func__, mode->xres, mode->yres);
+               return -EINVAL;
+       }
+
+       /*
+        * get a list of override regs for the asking frame length,
+        * coarse integration time, and gain.
+        */
+       ar0261_get_frame_length_regs(reg_list, mode->frame_length);
+       ar0261_get_coarse_time_regs(reg_list + 2, mode->coarse_time);
+       ar0261_get_gain_reg(reg_list + 4, mode->gain);
+
+       err = ar0261_write_table(info->i2c_client, mode_table[sensor_mode],
+                       reg_list, 5);
+       if (err)
+               return err;
+
+       info->mode = sensor_mode;
+       dev_info(dev, "[ar0261]: stream on.\n");
+       return 0;
+}
+
+static int
+ar0261_get_status(struct ar0261_info *info, u8 *dev_status)
+{
+       /* TBD */
+       *dev_status = 0;
+       return 0;
+}
+
+static int
+ar0261_set_frame_length(struct ar0261_info *info,
+                               u32 frame_length,
+                               bool group_hold)
+{
+       struct ar0261_reg reg_list[2];
+       int i = 0;
+       int ret;
+
+       ar0261_get_frame_length_regs(reg_list, frame_length);
+
+       if (group_hold) {
+               ret = ar0261_i2c_wr8(info->i2c_client,
+                                       AR0261_GROUP_PARAM_HOLD, 0x01);
+               if (ret)
+                       return ret;
+       }
+
+       for (i = 0; i < NUM_OF_FRAME_LEN_REG; i++) {
+               ret = ar0261_i2c_wr8(info->i2c_client, reg_list[i].addr,
+                       reg_list[i].val);
+               if (ret)
+                       return ret;
+       }
+
+       if (group_hold) {
+               ret = ar0261_i2c_wr8(info->i2c_client,
+                                       AR0261_GROUP_PARAM_HOLD, 0x0);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int
+ar0261_set_coarse_time(struct ar0261_info *info,
+                               u32 coarse_time,
+                               bool group_hold)
+{
+       int ret;
+
+       struct ar0261_reg reg_list[2];
+       int i = 0;
+
+       ar0261_get_coarse_time_regs(reg_list, coarse_time);
+
+       if (group_hold) {
+               ret = ar0261_i2c_wr8(info->i2c_client,
+                                       AR0261_GROUP_PARAM_HOLD,
+                                       0x01);
+               if (ret)
+                       return ret;
+       }
+
+       for (i = 0; i < NUM_OF_COARSE_TIME_REG; i++) {
+               ret = ar0261_i2c_wr8(info->i2c_client, reg_list[i].addr,
+                       reg_list[i].val);
+               if (ret)
+                       return ret;
+       }
+
+       if (group_hold) {
+               ret = ar0261_i2c_wr8(info->i2c_client,
+                                       AR0261_GROUP_PARAM_HOLD, 0x0);
+               if (ret)
+                       return ret;
+       }
+       return 0;
+}
+
+static int
+ar0261_set_gain(struct ar0261_info *info, u16 gain, bool group_hold)
+{
+       int ret;
+       struct ar0261_reg reg_list;
+
+       ar0261_get_gain_reg(&reg_list, gain);
+
+       if (group_hold) {
+               ret = ar0261_i2c_wr8(info->i2c_client,
+                                       AR0261_GROUP_PARAM_HOLD, 0x1);
+               if (ret)
+                       return ret;
+       }
+
+       ret = ar0261_i2c_wr8(info->i2c_client, reg_list.addr, reg_list.val);
+       if (ret)
+               return ret;
+
+       if (group_hold) {
+               ret = ar0261_i2c_wr8(info->i2c_client,
+                                       AR0261_GROUP_PARAM_HOLD, 0x0);
+               if (ret)
+                       return ret;
+       }
+       return 0;
+}
+
+static int
+ar0261_set_group_hold(struct ar0261_info *info, struct ar0261_ae *ae)
+{
+       int ret;
+       int count = 0;
+       bool grouphold_enabled = false;
+
+       if (ae->gain_enable)
+               count++;
+       if (ae->coarse_time_enable)
+               count++;
+       if (ae->frame_length_enable)
+               count++;
+       if (count >= 2)
+               grouphold_enabled = true;
+
+       if (grouphold_enabled) {
+               ret = ar0261_i2c_wr8(info->i2c_client,
+                                       AR0261_GROUP_PARAM_HOLD, 0x1);
+               if (ret)
+                       return ret;
+       }
+
+
+       if (ae->gain_enable)
+               ar0261_set_gain(info, ae->gain, false);
+       if (ae->coarse_time_enable)
+               ar0261_set_coarse_time(info, ae->coarse_time, false);
+       if (ae->frame_length_enable)
+               ar0261_set_frame_length(info, ae->frame_length, false);
+
+       if (grouphold_enabled) {
+               ret = ar0261_i2c_wr8(info->i2c_client,
+                                       AR0261_GROUP_PARAM_HOLD, 0x0);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int ar0261_get_sensor_id(struct ar0261_info *info)
+{
+       int ret = 0;
+       int i;
+       u16 bak = 0;
+
+       if (info->sensor_data.fuse_id_size)
+               return 0;
+
+       /*
+        * TBD 1: If the sensor does not have power at this point
+        * Need to supply the power, e.g. by calling power on function
+        */
+       /*TODO: add code for reading fuse id.*/
+
+       return 0;
+}
+
+static long
+ar0261_ioctl(struct file *file,
+                        unsigned int cmd, unsigned long arg)
+{
+       int err;
+       struct ar0261_info *info = file->private_data;
+       struct device *dev = &info->i2c_client->dev;
+
+       switch (cmd) {
+       case AR0261_IOCTL_SET_MODE:
+       {
+               struct ar0261_mode mode;
+               if (copy_from_user(&mode,
+                       (const void __user *)arg,
+                       sizeof(struct ar0261_mode))) {
+                       dev_err(dev, "%s:Failed to get mode from user.\n",
+                       __func__);
+                       return -EFAULT;
+               }
+               return ar0261_set_mode(info, &mode);
+       }
+       case AR0261_IOCTL_SET_FRAME_LENGTH:
+               return ar0261_set_frame_length(info, (u32)arg, true);
+       case AR0261_IOCTL_SET_COARSE_TIME:
+               return ar0261_set_coarse_time(info, (u32)arg, true);
+       case AR0261_IOCTL_SET_GAIN:
+               return ar0261_set_gain(info, (u16)arg, true);
+       case AR0261_IOCTL_GET_STATUS:
+       {
+               u8 status;
+
+               err = ar0261_get_status(info, &status);
+               if (err)
+                       return err;
+               if (copy_to_user((void __user *)arg, &status, 1)) {
+                       dev_err(dev, "%s:Failed to copy status to user.\n",
+                       __func__);
+                       return -EFAULT;
+               }
+               return 0;
+       }
+       case AR0261_IOCTL_GET_SENSORDATA:
+       {
+               err = ar0261_get_sensor_id(info);
+
+               if (err) {
+                       dev_err(dev, "%s:Failed to get fuse id info.\n",
+                       __func__);
+                       return err;
+               }
+               if (copy_to_user((void __user *)arg,
+                               &info->sensor_data,
+                               sizeof(struct ar0261_sensordata))) {
+                       dev_info(dev, "%s:Fail copy fuse id to user space\n",
+                               __func__);
+                       return -EFAULT;
+               }
+               return 0;
+       }
+       case AR0261_IOCTL_SET_GROUP_HOLD:
+       {
+               struct ar0261_ae ae;
+               if (copy_from_user(&ae, (const void __user *)arg,
+                               sizeof(struct ar0261_ae))) {
+                       dev_info(dev, "%s:fail group hold\n", __func__);
+                       return -EFAULT;
+               }
+               return ar0261_set_group_hold(info, &ae);
+       }
+       default:
+               dev_err(dev, "%s:unknown cmd.\n", __func__);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int
+ar0261_open(struct inode *inode, struct file *file)
+{
+       struct miscdevice *miscdev = file->private_data;
+       struct ar0261_info *info;
+
+       info = container_of(miscdev, struct ar0261_info, miscdev_info);
+       /* check if the device is in use */
+       if (atomic_xchg(&info->in_use, 1)) {
+               dev_info(&info->i2c_client->dev, "%s:BUSY!\n", __func__);
+               return -EBUSY;
+       }
+
+       file->private_data = info;
+
+       if (info->pdata && info->pdata->power_on)
+               info->pdata->power_on(&info->power);
+       else {
+               dev_err(&info->i2c_client->dev,
+                       "%s:no valid power_on function.\n", __func__);
+               return -EEXIST;
+       }
+
+       return 0;
+}
+
+static int
+ar0261_release(struct inode *inode, struct file *file)
+{
+       struct ar0261_info *info = file->private_data;
+
+       if (info->pdata && info->pdata->power_off)
+               info->pdata->power_off(&info->power);
+       file->private_data = NULL;
+
+       /* warn if device is already released */
+       WARN_ON(!atomic_xchg(&info->in_use, 0));
+       return 0;
+}
+
+static int ar0261_power_put(struct ar0261_power_rail *pw)
+{
+       if (likely(pw->dvdd))
+               regulator_put(pw->dvdd);
+
+       if (likely(pw->avdd))
+               regulator_put(pw->avdd);
+
+       if (likely(pw->iovdd))
+               regulator_put(pw->iovdd);
+
+       pw->dvdd = NULL;
+       pw->avdd = NULL;
+       pw->iovdd = NULL;
+
+       return 0;
+}
+
+static int ar0261_regulator_get(struct ar0261_info *info,
+       struct regulator **vreg, char vreg_name[])
+{
+       struct regulator *reg = NULL;
+       int err = 0;
+
+       reg = regulator_get(&info->i2c_client->dev, vreg_name);
+       if (unlikely(IS_ERR(reg))) {
+               dev_err(&info->i2c_client->dev, "%s %s ERR: %d\n",
+                       __func__, vreg_name, (int)reg);
+               err = PTR_ERR(reg);
+               reg = NULL;
+       } else
+               dev_dbg(&info->i2c_client->dev, "%s: %s\n",
+                       __func__, vreg_name);
+
+       *vreg = reg;
+       return err;
+}
+
+static int ar0261_power_get(struct ar0261_info *info)
+{
+       struct ar0261_power_rail *pw = &info->power;
+
+       ar0261_regulator_get(info, &pw->dvdd, "vdig"); /* digital 1.2v */
+       ar0261_regulator_get(info, &pw->avdd, "vana"); /* analog 2.7v */
+       ar0261_regulator_get(info, &pw->iovdd, "vif"); /* interface 1.8v */
+
+       return 0;
+}
+
+static const struct file_operations ar0261_fileops = {
+       .owner = THIS_MODULE,
+       .open = ar0261_open,
+       .unlocked_ioctl = ar0261_ioctl,
+       .release = ar0261_release,
+};
+
+static struct miscdevice ar0261_device = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = "ar0261",
+       .fops = &ar0261_fileops,
+};
+
+static int
+ar0261_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       struct ar0261_info *info;
+       int err = 0;
+
+       pr_info("[ar0261]: probing sensor.\n");
+
+       info = devm_kzalloc(&client->dev,
+               sizeof(struct ar0261_info), GFP_KERNEL);
+       if (!info) {
+               pr_err("[ar0261]:%s:Unable to allocate memory!\n", __func__);
+               return -ENOMEM;
+       }
+
+       info->pdata = client->dev.platform_data;
+       info->i2c_client = client;
+       atomic_set(&info->in_use, 0);
+       info->mode = -1;
+
+       i2c_set_clientdata(client, info);
+
+       ar0261_power_get(info);
+
+       memcpy(&info->miscdev_info,
+               &ar0261_device,
+               sizeof(struct miscdevice));
+
+       err = misc_register(&info->miscdev_info);
+       if (err) {
+               ar0261_power_put(&info->power);
+               pr_err("[ar0261]:%s:Unable to register misc device!\n",
+               __func__);
+       }
+
+       return err;
+}
+
+static int
+ar0261_remove(struct i2c_client *client)
+{
+       struct ar0261_info *info = i2c_get_clientdata(client);
+
+       ar0261_power_put(&info->power);
+       misc_deregister(&ar0261_device);
+       return 0;
+}
+
+static const struct i2c_device_id ar0261_id[] = {
+       { "ar0261", 0 },
+};
+
+MODULE_DEVICE_TABLE(i2c, ar0261_id);
+
+static struct i2c_driver ar0261_i2c_driver = {
+       .driver = {
+               .name = "ar0261",
+               .owner = THIS_MODULE,
+       },
+       .probe = ar0261_probe,
+       .remove = ar0261_remove,
+       .id_table = ar0261_id,
+};
+
+static int __init
+ar0261_init(void)
+{
+       pr_info("[ar0261] sensor driver loading\n");
+       return i2c_add_driver(&ar0261_i2c_driver);
+}
+
+static void __exit
+ar0261_exit(void)
+{
+       i2c_del_driver(&ar0261_i2c_driver);
+}
+
+module_init(ar0261_init);
+module_exit(ar0261_exit);
+
diff --git a/include/media/ar0261.h b/include/media/ar0261.h
new file mode 100644 (file)
index 0000000..65ba55f
--- /dev/null
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2013 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 __AR0261_H__
+#define __AR0261_H__
+
+#include <linux/ioctl.h>  /* For IOCTL macros */
+
+#define AR0261_IOCTL_SET_MODE          _IOW('o', 1, struct ar0261_mode)
+#define AR0261_IOCTL_GET_STATUS                _IOR('o', 2, __u8)
+#define AR0261_IOCTL_SET_FRAME_LENGTH  _IOW('o', 3, __u32)
+#define AR0261_IOCTL_SET_COARSE_TIME   _IOW('o', 4, __u32)
+#define AR0261_IOCTL_SET_GAIN          _IOW('o', 5, __u16)
+#define AR0261_IOCTL_GET_SENSORDATA _IOR('o', 6, struct ar0261_sensordata)
+#define AR0261_IOCTL_SET_GROUP_HOLD    _IOW('o', 7, struct ar0261_ae)
+
+/* AR0261 registers */
+#define AR0261_GROUP_PARAM_HOLD                        (0x0104)
+#define AR0261_COARSE_INTEGRATION_TIME_15_8    (0x0202)
+#define AR0261_COARSE_INTEGRATION_TIME_7_0     (0x0203)
+#define AR0261_ANA_GAIN_GLOBAL                 (0x305F)
+#define AR0261_FRAME_LEN_LINES_15_8            (0x0340)
+#define AR0261_FRAME_LEN_LINES_7_0             (0x0341)
+
+#define NUM_OF_FRAME_LEN_REG           2
+#define NUM_OF_COARSE_TIME_REG         2
+#define NUM_OF_SENSOR_ID_SPECIFIC_REG  8
+struct ar0261_mode {
+       int xres;
+       int yres;
+       __u32 frame_length;
+       __u32 coarse_time;
+       __u16 gain;
+};
+
+struct ar0261_ae {
+       __u32 frame_length;
+       __u8  frame_length_enable;
+       __u32 coarse_time;
+       __u8  coarse_time_enable;
+       __s32 gain;
+       __u8  gain_enable;
+};
+
+struct ar0261_sensordata {
+       __u32 fuse_id_size;
+       __u16 fuse_id[16];
+};
+
+#ifdef __KERNEL__
+struct ar0261_power_rail {
+       struct regulator *dvdd;
+       struct regulator *avdd;
+       struct regulator *iovdd;
+};
+
+struct ar0261_platform_data {
+       int (*power_on)(struct ar0261_power_rail *pw);
+       int (*power_off)(struct ar0261_power_rail *pw);
+};
+#endif /* __KERNEL__ */
+
+#endif  /* __AR0261_H__ */