diff options
author | Hu He <hhe@nvidia.com> | 2014-11-25 14:59:07 -0800 |
---|---|---|
committer | Winnie Hsu <whsu@nvidia.com> | 2015-02-09 17:06:22 -0800 |
commit | 231a96efcdf47e971ce70412332ad436b1ffb619 (patch) | |
tree | 59a9c03c74592fbe7272bed1575a78cef8bc8fc7 /drivers/media/platform | |
parent | c017b032a284d7cb1974f7e80a6c3613089b9c0e (diff) |
Kernel: add ov4689 kernel driver
Bug 1600299
Change-Id: I63f4d597bcc8b960407a7291fde6aa2ddb5bec25
Signed-off-by: Ming Wong <miwong@nvidia.com>
Reviewed-on: http://git-master/r/674099
Reviewed-by: Winnie Hsu <whsu@nvidia.com>
GVS: Gerrit_Virtual_Submit
Diffstat (limited to 'drivers/media/platform')
-rw-r--r-- | drivers/media/platform/tegra/Kconfig | 7 | ||||
-rw-r--r-- | drivers/media/platform/tegra/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/platform/tegra/ov4689.c | 993 | ||||
-rw-r--r-- | drivers/media/platform/tegra/ov4689_tables.h | 565 |
4 files changed, 1566 insertions, 0 deletions
diff --git a/drivers/media/platform/tegra/Kconfig b/drivers/media/platform/tegra/Kconfig index 2b95077a2f2b..1bcd293e6260 100644 --- a/drivers/media/platform/tegra/Kconfig +++ b/drivers/media/platform/tegra/Kconfig @@ -127,6 +127,13 @@ config VIDEO_OV7695 This is a driver for the OV7695 YUV camera sensor device This sensor has external ISP, it isn't using tegra ISP. +config VIDEO_OV4689 + tristate "OV4689 camera sensor support" + depends on I2C && ARCH_TEGRA + ---help--- + This is a driver for the OV4689 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/platform/tegra/Makefile b/drivers/media/platform/tegra/Makefile index 7a3b491cd7db..6c205b2ed0e1 100644 --- a/drivers/media/platform/tegra/Makefile +++ b/drivers/media/platform/tegra/Makefile @@ -43,4 +43,5 @@ obj-$(CONFIG_VIDEO_OV5693) += ov5693.o obj-$(CONFIG_VIDEO_AD5823) += ad5823.o obj-$(CONFIG_VIDEO_OV7695) += ov7695.o obj-$(CONFIG_VIDEO_MT9M114) += mt9m114.o +obj-$(CONFIG_VIDEO_OV4689) += ov4689.o obj-$(CONFIG_VIDEO_CAMERA) += camera.o diff --git a/drivers/media/platform/tegra/ov4689.c b/drivers/media/platform/tegra/ov4689.c new file mode 100644 index 000000000000..435322f69cc5 --- /dev/null +++ b/drivers/media/platform/tegra/ov4689.c @@ -0,0 +1,993 @@ +/* + * ov4682.c - ov4682 sensor driver + * + * Copyright (c) 2015, 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/clk.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/regulator/consumer.h> +#include <linux/regmap.h> +#include <media/ov4689.h> +#include <linux/gpio.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> + +#include "nvc_utilities.h" + +#include "ov4689_tables.h" + +#define MAX_BUFFER_SIZE 32 + +/* Frame length: R0x380e~R0x380f */ +#define OV4689_NUM_BYTES_FL (2) +/* Coarse time: R0x3500~R0x3502 */ +#define OV4689_NUM_BYTES_CT (3) +/* Gain: R0x3508~R0x3509 */ +#define OV4689_NUM_BYTES_GAIN (2) +/* Num of regs in override list */ +#define OV4689_NUM_BYTES_OVERRIDES (OV4689_NUM_BYTES_FL + \ + OV4689_NUM_BYTES_CT + \ + OV4689_NUM_BYTES_GAIN) + +#define OV4689_SUPPORT_SENSORID (1) +#define OV4689_SUPPORT_EEPROM (0) +#define OV4689_SUPPORT_DEBUGFS (1) + +struct ov4689_info { + struct miscdevice miscdev_info; + int mode; + struct ov4689_power_rail power; + struct ov4689_sensordata sensor_data; + struct i2c_client *i2c_client; + struct ov4689_platform_data *pdata; + struct clk *mclk; + struct regmap *regmap; +#if OV4689_SUPPORT_DEBUGFS + struct dentry *debugdir; +#endif + atomic_t in_use; +#if OV4689_SUPPORT_EEPROM + struct ov4689_eeprom_data eeprom[ov4689_EEPROM_NUM_BLOCKS]; + u8 eeprom_buf[ov4689_EEPROM_SIZE]; +#endif +}; + +static const struct regmap_config sensor_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +static inline void +msleep_range(unsigned int delay_base) +{ + usleep_range(delay_base*1000, delay_base*1000+500); +} + +static inline void ov4689_get_frame_length_regs(struct ov4689_reg *regs, + u32 frame_length) +{ + /* 2 registers for FL, i.e., 2-byte FL */ + regs->addr = 0x380e; + regs->val = (frame_length >> 8) & 0xff; + (regs + 1)->addr = 0x380f; + (regs + 1)->val = (frame_length) & 0xff; +} + +static inline void ov4689_get_coarse_time_regs(struct ov4689_reg *regs, + u32 coarse_time) +{ + /* 3 registers for CT, i.e., 3-byte CT */ + regs->addr = 0x3500; + regs->val = (coarse_time >> 12) & 0xff; + (regs + 1)->addr = 0x3501; + (regs + 1)->val = (coarse_time >> 4) & 0xff; + (regs + 2)->addr = 0x3502; + (regs + 2)->val = (coarse_time & 0xf) << 4; +} + +static inline void ov4689_get_gain_reg(struct ov4689_reg *regs, + u16 gain) +{ + /* 2 register for gain, i.e., 2-byte gain */ + regs->addr = 0x3508; + regs->val = (gain >> 8) & 0xff; + (regs + 1)->addr = 0x3509; + (regs + 1)->val = gain & 0xff; +} + +static inline int ov4689_read_reg(struct ov4689_info *info, + u16 addr, u8 *val) +{ + return regmap_read(info->regmap, addr, (unsigned int *) val); +} + +static int ov4689_write_reg(struct ov4689_info *info, u16 addr, u8 val) +{ + int err; + + err = regmap_write(info->regmap, addr, val); + + if (err) + pr_err("%s:i2c write failed, %x = %x\n", + __func__, addr, val); + + return err; +} + +static int ov4689_write_table(struct ov4689_info *info, + const struct ov4689_reg table[], + const struct ov4689_reg override_list[], + int num_override_regs) +{ + int err; + const struct ov4689_reg *next; + int i; + u16 val; + + for (next = table; next->addr != OV4689_TABLE_END; next++) { + if (next->addr == OV4689_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 = ov4689_write_reg(info, next->addr, val); + if (err) { + pr_err("%s:ov4689_write_table:%d", __func__, err); + return err; + } + } + return 0; +} + +static int ov4689_set_mode(struct ov4689_info *info, struct ov4689_mode *mode) +{ + int sensor_mode; + int err; + struct ov4689_reg reg_list[OV4689_NUM_BYTES_OVERRIDES]; + + if ((mode->xres == 2688 && mode->yres == 1520) || + (mode->xres == 2688 && mode->yres == 1504)) { + sensor_mode = OV4689_MODE_2688x1520; + pr_info("ov4689_set_mode 2688x1520\n"); + } else if (mode->xres == 1920 && mode->yres == 1080) { + sensor_mode = OV4689_MODE_1920x1080; + pr_info("ov4689_set_mode 1920x1080\n"); + } else { + pr_err("There is no this resolution no support %dX%d!!", + mode->xres, mode->yres); + return 1; + } + + /* get a list of override regs for the asking frame length, */ + /* coarse integration time, and gain. */ + ov4689_get_frame_length_regs(reg_list, mode->frame_length); + ov4689_get_coarse_time_regs(reg_list + OV4689_NUM_BYTES_FL, + mode->coarse_time); + ov4689_get_gain_reg(reg_list + OV4689_NUM_BYTES_FL + + OV4689_NUM_BYTES_CT, mode->gain); + + err = ov4689_write_table(info, mode_table[sensor_mode], + reg_list, OV4689_NUM_BYTES_OVERRIDES); + + info->mode = sensor_mode; + + pr_info("[ov4689]: stream on.\n"); + return 0; +} + +static int ov4689_get_status(struct ov4689_info *info, u8 *dev_status) +{ + *dev_status = 0; + return 0; +} + +static int ov4689_set_frame_length(struct ov4689_info *info, u32 frame_length, + bool group_hold) +{ + struct ov4689_reg reg_list[OV4689_NUM_BYTES_FL]; + int i = 0; + int ret; + + ov4689_get_frame_length_regs(reg_list, frame_length); + + if (group_hold) { + ret = ov4689_write_reg(info, 0x3208, 0x00); + if (ret) + return ret; + } + + for (i = 0; i < OV4689_NUM_BYTES_FL; i++) { + ret = ov4689_write_reg(info, reg_list[i].addr, + reg_list[i].val); + if (ret) + return ret; + } + + if (group_hold) { + ret = ov4689_write_reg(info, 0x3208, 0x10); + if (ret) + return ret; + ret = ov4689_write_reg(info, 0x3208, 0xA0); + if (ret) + return ret; + } + + return 0; +} + +static int ov4689_set_coarse_time(struct ov4689_info *info, u32 coarse_time, + bool group_hold) +{ + int ret; + + struct ov4689_reg reg_list[OV4689_NUM_BYTES_CT]; + int i = 0; + + ov4689_get_coarse_time_regs(reg_list, coarse_time); + + if (group_hold) { + ret = ov4689_write_reg(info, 0x3208, 0x00); + if (ret) + return ret; + } + + for (i = 0; i < OV4689_NUM_BYTES_CT; i++) { + ret = ov4689_write_reg(info, reg_list[i].addr, + reg_list[i].val); + if (ret) + return ret; + } + + if (group_hold) { + ret = ov4689_write_reg(info, 0x3208, 0x10); + if (ret) + return ret; + ret = ov4689_write_reg(info, 0x3208, 0xA0); + if (ret) + return ret; + } + return 0; +} + +static int ov4689_set_gain(struct ov4689_info *info, u16 gain, + bool group_hold) +{ + int ret, i; + struct ov4689_reg reg_list[OV4689_NUM_BYTES_GAIN]; + + ov4689_get_gain_reg(reg_list, gain); + + if (group_hold) { + ret = ov4689_write_reg(info, 0x3208, 0x00); + if (ret) + return ret; + } + + /* writing 1-byte gain */ + for (i = 0; i < OV4689_NUM_BYTES_GAIN; i++) { + ret = ov4689_write_reg(info, reg_list[i].addr, + reg_list[i].val); + if (ret) + return ret; + } + + if (group_hold) { + ret = ov4689_write_reg(info, 0x3208, 0x10); + if (ret) + return ret; + ret = ov4689_write_reg(info, 0x3208, 0xA0); + if (ret) + return ret; + } + return 0; +} + +static int ov4689_set_group_hold(struct ov4689_info *info, + + struct ov4689_grouphold *ae) +{ + int ret; + int count = 0; + bool group_hold_enabled = false; + + if (ae->gain_enable) + count++; + if (ae->coarse_time_enable) + count++; + if (ae->frame_length_enable) + count++; + if (count >= 2) + group_hold_enabled = true; + + if (group_hold_enabled) { + ret = ov4689_write_reg(info, 0x3208, 0x00); + if (ret) + return ret; + } + + if (ae->gain_enable) + ov4689_set_gain(info, ae->gain, false); + if (ae->coarse_time_enable) + ov4689_set_coarse_time(info, ae->coarse_time, false); + if (ae->frame_length_enable) + ov4689_set_frame_length(info, ae->frame_length, false); + + if (group_hold_enabled) { + ret = ov4689_write_reg(info, 0x3208, 0x10); + if (ret) + return ret; + ret = ov4689_write_reg(info, 0x3208, 0xA0); + if (ret) + return ret; + } + + return 0; +} + +#if OV4689_SUPPORT_SENSORID +static int ov4689_get_sensor_id(struct ov4689_info *info) +{ + int ret = 0; + int i; + u8 bak = 0; + + pr_info("%s\n", __func__); + if (info->sensor_data.fuse_id_size) + return 0; + + /* select bank 31 */ + ov4689_write_reg(info, 0x3d84, 31); + for (i = 0; i < 8; i++) { + ret |= ov4689_read_reg(info, 0x300A + i, &bak); + info->sensor_data.fuse_id[i] = bak; + } + + if (!ret) + info->sensor_data.fuse_id_size = 2; + + return ret; +} +#endif + +static void ov4689_mclk_disable(struct ov4689_info *info) +{ + dev_dbg(&info->i2c_client->dev, "%s: disable MCLK\n", __func__); + clk_disable_unprepare(info->mclk); +} + +static int ov4689_mclk_enable(struct ov4689_info *info) +{ + int err; + unsigned long mclk_init_rate = 24000000; + + dev_info(&info->i2c_client->dev, "%s: enable MCLK with %lu Hz\n", + __func__, mclk_init_rate); + + err = clk_set_rate(info->mclk, mclk_init_rate); + if (!err) + err = clk_prepare_enable(info->mclk); + return err; +} + +#if OV4689_SUPPORT_EEPROM +static int ov4689_eeprom_device_release(struct ov4689_info *info) +{ + int i; + + for (i = 0; i < ov4689_EEPROM_NUM_BLOCKS; i++) { + if (info->eeprom[i].i2c_client != NULL) { + i2c_unregister_device(info->eeprom[i].i2c_client); + info->eeprom[i].i2c_client = NULL; + } + } + + return 0; +} + +static int ov4689_eeprom_device_init(struct ov4689_info *info) +{ + char *dev_name = "eeprom_ov4689"; + static struct regmap_config eeprom_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + }; + int i; + int err; + + for (i = 0; i < ov4689_EEPROM_NUM_BLOCKS; i++) { + info->eeprom[i].adap = i2c_get_adapter( + info->i2c_client->adapter->nr); + memset(&info->eeprom[i].brd, 0, sizeof(info->eeprom[i].brd)); + strncpy(info->eeprom[i].brd.type, dev_name, + sizeof(info->eeprom[i].brd.type)); + info->eeprom[i].brd.addr = ov4689_EEPROM_ADDRESS + i; + info->eeprom[i].i2c_client = i2c_new_device( + info->eeprom[i].adap, &info->eeprom[i].brd); + + info->eeprom[i].regmap = devm_regmap_init_i2c( + info->eeprom[i].i2c_client, &eeprom_regmap_config); + if (IS_ERR(info->eeprom[i].regmap)) { + err = PTR_ERR(info->eeprom[i].regmap); + ov4689_eeprom_device_release(info); + return err; + } + } + + return 0; +} + +static int ov4689_read_eeprom(struct ov4689_info *info, + u8 reg, u16 length, u8 *buf) +{ + return regmap_raw_read(info->eeprom[0].regmap, reg, &buf[reg], length); +} + +static int ov4689_write_eeprom(struct ov4689_info *info, + u16 addr, u8 val) +{ + return regmap_write(info->eeprom[addr >> 8].regmap, addr & 0xFF, val); +} +#endif + +static long OV4689_IOCTL(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct ov4689_info *info = file->private_data; + + switch (_IOC_NR(cmd)) { + case _IOC_NR(OV4689_IOCTL_SET_POWER): + if (!info->pdata) + break; + if (arg && info->pdata->power_on) { + err = ov4689_mclk_enable(info); + if (!err) + err = info->pdata->power_on(&info->power); + if (err < 0) + ov4689_mclk_disable(info); + } + if (!arg && info->pdata->power_off) { + info->pdata->power_off(&info->power); + ov4689_mclk_disable(info); + } + break; + case _IOC_NR(OV4689_IOCTL_SET_MODE): + { + struct ov4689_mode mode; + if (copy_from_user(&mode, (const void __user *)arg, + sizeof(struct ov4689_mode))) { + pr_err("%s:Failed to get mode from user.\n", __func__); + return -EFAULT; + } + return ov4689_set_mode(info, &mode); + } + case _IOC_NR(OV4689_IOCTL_SET_FRAME_LENGTH): + return ov4689_set_frame_length(info, (u32)arg, true); + case _IOC_NR(OV4689_IOCTL_SET_COARSE_TIME): + return ov4689_set_coarse_time(info, (u32)arg, true); + case _IOC_NR(OV4689_IOCTL_SET_GAIN): + return ov4689_set_gain(info, (u16)arg, true); + case _IOC_NR(OV4689_IOCTL_GET_STATUS): + { + u8 status; + + err = ov4689_get_status(info, &status); + if (err) + return err; + if (copy_to_user((void __user *)arg, &status, 1)) { + pr_err("%s:Failed to copy status to user\n", __func__); + return -EFAULT; + } + return 0; + } +#if OV4689_SUPPORT_SENSORID + case _IOC_NR(OV4689_IOCTL_GET_SENSORDATA): + { + err = ov4689_get_sensor_id(info); + + if (err) { + pr_err("%s:Failed to get fuse id info.\n", __func__); + return err; + } + if (copy_to_user((void __user *)arg, &info->sensor_data, + sizeof(struct ov4689_sensordata))) { + pr_info("%s:Failed to copy fuse id to user space\n", + __func__); + return -EFAULT; + } + return 0; + } +#endif + case _IOC_NR(OV4689_IOCTL_SET_GROUP_HOLD): + { + struct ov4689_grouphold grouphold; + if (copy_from_user(&grouphold, (const void __user *)arg, + sizeof(struct ov4689_grouphold))) { + pr_info("%s:fail group hold\n", __func__); + return -EFAULT; + } + return ov4689_set_group_hold(info, &grouphold); + } +#if OV4689_SUPPORT_EEPROM + /* there is actually one of them really in use + NVC_IOCTL_GET_EEPROM_DATA + or + OV4689_IOCTL_GET_SENSORDATA (legacy?) + */ + case _IOC_NR(NVC_IOCTL_GET_EEPROM_DATA): + { + ov4689_read_eeprom(info, + 0, + ov4689_EEPROM_SIZE, + info->eeprom_buf); + + if (copy_to_user((void __user *)arg, + info->eeprom_buf, ov4689_EEPROM_SIZE)) { + dev_err(&info->i2c_client->dev, + "%s:Failed to copy status to user\n", + __func__); + return -EFAULT; + } + return 0; + } + + case _IOC_NR(NVC_IOCTL_SET_EEPROM_DATA): + { + int i; + if (copy_from_user(info->eeprom_buf, + (const void __user *)arg, ov4689_EEPROM_SIZE)) { + dev_err(&info->i2c_client->dev, + "%s:Failed to read from user buffer\n", + __func__); + return -EFAULT; + } + for (i = 0; i < ov4689_EEPROM_SIZE; i++) { + ov4689_write_eeprom(info, + i, + info->eeprom_buf[i]); + msleep(20); + } + return 0; + } +#endif + + default: + pr_err("%s:unknown cmd.\n", __func__); + err = -EINVAL; + } + + return err; +} + +#if OV4689_SUPPORT_DEBUGFS +static int ov4689_debugfs_show(struct seq_file *s, void *unused) +{ + struct ov4689_info *dev = s->private; + + dev_dbg(&dev->i2c_client->dev, "%s: ++\n", __func__); + + return 0; +} + +static ssize_t ov4689_debugfs_write(struct file *file, char const __user *buf, + size_t count, loff_t *offset) +{ + struct ov4689_info *dev = + ((struct seq_file *)file->private_data)->private; + struct i2c_client *i2c_client = dev->i2c_client; + int ret = 0; + char buffer[MAX_BUFFER_SIZE]; + u32 address; + u32 data; + u8 readback; + + dev_dbg(&i2c_client->dev, "%s: ++\n", __func__); + + if (copy_from_user(&buffer, buf, sizeof(buffer))) + goto debugfs_write_fail; + + if (sscanf(buf, "0x%x 0x%x", &address, &data) == 2) + goto set_attr; + if (sscanf(buf, "0X%x 0X%x", &address, &data) == 2) + goto set_attr; + if (sscanf(buf, "%d %d", &address, &data) == 2) + goto set_attr; + + if (sscanf(buf, "0x%x 0x%x", &address, &data) == 1) + goto read; + if (sscanf(buf, "0X%x 0X%x", &address, &data) == 1) + goto read; + if (sscanf(buf, "%d %d", &address, &data) == 1) + goto read; + + dev_err(&i2c_client->dev, "SYNTAX ERROR: %s\n", buf); + return -EFAULT; + +set_attr: + dev_info(&i2c_client->dev, + "new address = %x, data = %x\n", address, data); + ret |= ov4689_write_reg(dev, address, data); +read: + ret |= ov4689_read_reg(dev, address, &readback); + dev_dbg(&i2c_client->dev, + "wrote to address 0x%x with value 0x%x\n", + address, readback); + + if (ret) + goto debugfs_write_fail; + + return count; + +debugfs_write_fail: + dev_err(&i2c_client->dev, + "%s: test pattern write failed\n", __func__); + return -EFAULT; +} + +static int ov4689_debugfs_open(struct inode *inode, struct file *file) +{ + struct ov4689_info *dev = inode->i_private; + struct i2c_client *i2c_client = dev->i2c_client; + + dev_dbg(&i2c_client->dev, "%s: ++\n", __func__); + + return single_open(file, ov4689_debugfs_show, inode->i_private); +} + +static const struct file_operations ov4689_debugfs_fops = { + .open = ov4689_debugfs_open, + .read = seq_read, + .write = ov4689_debugfs_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static void ov4689_remove_debugfs(struct ov4689_info *dev) +{ + struct i2c_client *i2c_client = dev->i2c_client; + + dev_dbg(&i2c_client->dev, "%s: ++\n", __func__); + + debugfs_remove_recursive(dev->debugdir); + dev->debugdir = NULL; +} + +static void ov4689_create_debugfs(struct ov4689_info *dev) +{ + struct dentry *ret; + struct i2c_client *i2c_client = dev->i2c_client; + + dev_dbg(&i2c_client->dev, "%s\n", __func__); + + dev->debugdir = + debugfs_create_dir(dev->miscdev_info.this_device->kobj.name, + NULL); + if (!dev->debugdir) + goto remove_debugfs; + + ret = debugfs_create_file("d", + S_IWUSR | S_IRUGO, + dev->debugdir, dev, + &ov4689_debugfs_fops); + if (!ret) + goto remove_debugfs; + + return; +remove_debugfs: + dev_err(&i2c_client->dev, "couldn't create debugfs\n"); + ov4689_remove_debugfs(dev); +} +#endif + +static int ov4689_power_on(struct ov4689_power_rail *pw) +{ + struct ov4689_info *info = container_of(pw, struct ov4689_info, power); + + gpio_set_value(info->pdata->reset_gpio, 0); + usleep_range(10, 20); + gpio_set_value(info->pdata->reset_gpio, 1); + usleep_range(1000, 1100); + + return 0; +} + +static int ov4689_power_off(struct ov4689_power_rail *pw) +{ + struct ov4689_info *info = container_of(pw, struct ov4689_info, power); + + gpio_set_value(info->pdata->reset_gpio, 0); + return 0; +} + +static int ov4689_open(struct inode *inode, struct file *file) +{ + struct miscdevice *miscdev = file->private_data; + struct ov4689_info *info; + + info = container_of(miscdev, struct ov4689_info, miscdev_info); + /* check if the device is in use */ + if (atomic_xchg(&info->in_use, 1)) { + pr_info("%s:BUSY!\n", __func__); + return -EBUSY; + } + + file->private_data = info; + + return 0; +} + +static int ov4689_release(struct inode *inode, struct file *file) +{ + struct ov4689_info *info = file->private_data; + + file->private_data = NULL; + + /* warn if device is already released */ + WARN_ON(!atomic_xchg(&info->in_use, 0)); + + return 0; +} + +static int ov4689_power_put(struct ov4689_power_rail *pw) +{ + if (unlikely(!pw)) + return -EFAULT; + + if (likely(pw->avdd)) + regulator_put(pw->avdd); + + if (likely(pw->iovdd)) + regulator_put(pw->iovdd); + + if (likely(pw->dvdd)) + regulator_put(pw->dvdd); + + pw->avdd = NULL; + pw->iovdd = NULL; + pw->dvdd = NULL; + + return 0; +} + +static int ov4689_regulator_get(struct ov4689_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 ov4689_power_get(struct ov4689_info *info) +{ + struct ov4689_power_rail *pw = &info->power; + int err = 0; + + err |= ov4689_regulator_get(info, &pw->avdd, "vana"); /* ananlog 2.7v */ + err |= ov4689_regulator_get(info, &pw->dvdd, "vdig"); /* digital 1.2v */ + err |= ov4689_regulator_get(info, &pw->iovdd, "vif"); /* IO 1.8v */ + + return err; +} + +static const struct file_operations ov4689_fileops = { + .owner = THIS_MODULE, + .open = ov4689_open, + .unlocked_ioctl = OV4689_IOCTL, +#ifdef CONFIG_COMPAT + .compat_ioctl = OV4689_IOCTL, +#endif + .release = ov4689_release, +}; + +static struct miscdevice ov4689_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ov4689", + .fops = &ov4689_fileops, +}; + +static struct ov4689_platform_data *ov4689_parse_dt(struct i2c_client *client) +{ + struct device_node *np = client->dev.of_node; + struct ov4689_platform_data *board_info_pdata; + + board_info_pdata = devm_kzalloc(&client->dev, sizeof(*board_info_pdata), + GFP_KERNEL); + if (!board_info_pdata) { + dev_err(&client->dev, "Failed to allocate pdata\n"); + return NULL; + } + + of_property_read_string(np, "clocks", &board_info_pdata->mclk_name); + board_info_pdata->reset_gpio = of_get_named_gpio(np, "reset-gpios", 0); + + board_info_pdata->power_on = ov4689_power_on; + board_info_pdata->power_off = ov4689_power_off; + + return board_info_pdata; +} + +static int ov4689_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ov4689_info *info; + int err; + const char *mclk_name; + + pr_info("[ov4689]: probing sensor.\n"); + + info = devm_kzalloc(&client->dev, + sizeof(struct ov4689_info), GFP_KERNEL); + if (!info) { + pr_err("%s:Unable to allocate memory!\n", __func__); + return -ENOMEM; + } + + info->regmap = devm_regmap_init_i2c(client, &sensor_regmap_config); + if (IS_ERR(info->regmap)) { + dev_err(&client->dev, + "regmap init failed: %ld\n", PTR_ERR(info->regmap)); + return -ENODEV; + } + + if (client->dev.of_node) + info->pdata = ov4689_parse_dt(client); + else + info->pdata = client->dev.platform_data; + + if (!info->pdata) { + pr_err("[ov4689]:%s:Unable to get platform data\n", __func__); + return -EFAULT; + } + + gpio_request_one(info->pdata->reset_gpio, GPIOF_OUT_INIT_LOW, + "reset_gpio"); + + info->i2c_client = client; + atomic_set(&info->in_use, 0); + info->mode = -1; + + mclk_name = info->pdata->mclk_name ? + info->pdata->mclk_name : "default_mclk"; + info->mclk = devm_clk_get(&client->dev, mclk_name); + if (IS_ERR(info->mclk)) { + dev_err(&client->dev, "%s: unable to get clock %s\n", + __func__, mclk_name); + return PTR_ERR(info->mclk); + } + + ov4689_power_get(info); + + memcpy(&info->miscdev_info, + &ov4689_device, + sizeof(struct miscdevice)); + + err = misc_register(&info->miscdev_info); + if (err) { + pr_err("%s:Unable to register misc device!\n", __func__); + goto ov4689_probe_fail; + } + + i2c_set_clientdata(client, info); + +#if OV4689_SUPPORT_EEPROM + /* eeprom interface */ + err = ov4689_eeprom_device_init(info); + if (err) { + dev_err(&client->dev, + "Failed to allocate eeprom register map: %d\n", err); + return err; + } +#endif + +#if OV4689_SUPPORT_DEBUGFS + /* create debugfs interface */ + ov4689_create_debugfs(info); +#endif + return 0; + +ov4689_probe_fail: + ov4689_power_put(&info->power); + + return err; +} + +static int ov4689_remove(struct i2c_client *client) +{ + struct ov4689_info *info; + info = i2c_get_clientdata(client); + misc_deregister(&ov4689_device); + + ov4689_power_put(&info->power); + +#if OV4689_SUPPORT_DEBUGFS + ov4689_remove_debugfs(info); +#endif + +#if OV4689_SUPPORT_EEPROM + ov4689_eeprom_device_release(info); +#endif + return 0; +} + +static const struct i2c_device_id ov4689_id[] = { + { "ov4689", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, ov4689_id); + +static struct i2c_driver ov4689_i2c_driver = { + .driver = { + .name = "ov4689", + .owner = THIS_MODULE, + }, + .probe = ov4689_probe, + .remove = ov4689_remove, + .id_table = ov4689_id, +}; + + +static int __init +ov4689_init(void) +{ + pr_info("[ov4689] sensor driver loading\n"); + return i2c_add_driver(&ov4689_i2c_driver); +} + +static void __exit +ov4689_exit(void) +{ + i2c_del_driver(&ov4689_i2c_driver); +} + +module_init(ov4689_init); +module_exit(ov4689_exit); diff --git a/drivers/media/platform/tegra/ov4689_tables.h b/drivers/media/platform/tegra/ov4689_tables.h new file mode 100644 index 000000000000..7233d60ad311 --- /dev/null +++ b/drivers/media/platform/tegra/ov4689_tables.h @@ -0,0 +1,565 @@ +/* + * ov4689_tables.h + * + * Copyright (c) 2015, 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/>. + */ + +#ifndef OV4689_I2C_TABLES +#define OV4689_I2C_TABLES + +struct ov4689_reg { + u16 addr; + u16 val; +}; + +#define OV4689_TABLE_WAIT_MS 0 +#define OV4689_TABLE_END 1 + +static struct ov4689_reg ov4689_mode_2688x1520_initial[] = { + {0x0103, 0x01}, + {0x3638, 0x00}, + {0x0300, 0x00}, + /* 1008Mbps MIPI_CLK */ + {0x0302, 0x2a}, + {0x0304, 0x03}, + {0x030b, 0x00}, + {0x030d, 0x1e}, + {0x030e, 0x04}, + {0x030f, 0x01}, + {0x0312, 0x01}, + {0x031e, 0x00}, + {0x3000, 0x20}, + {0x3002, 0x00}, + {0x3018, 0x72}, + {0x3020, 0x93}, + {0x3021, 0x03}, + {0x3022, 0x01}, + {0x3031, 0x0a}, + {0x303f, 0x0c}, + {0x3305, 0xf1}, + {0x3307, 0x04}, + {0x3309, 0x29}, + {0x3500, 0x00}, + {0x3501, 0x60}, + {0x3502, 0x00}, + {0x3503, 0x04}, + {0x3504, 0x00}, + {0x3505, 0x00}, + {0x3506, 0x00}, + {0x3507, 0x00}, + {0x3508, 0x00}, + {0x3509, 0x80}, + {0x350a, 0x00}, + {0x350b, 0x00}, + {0x350c, 0x00}, + {0x350d, 0x00}, + {0x350e, 0x00}, + {0x350f, 0x80}, + {0x3510, 0x00}, + {0x3511, 0x00}, + {0x3512, 0x00}, + {0x3513, 0x00}, + {0x3514, 0x00}, + {0x3515, 0x80}, + {0x3516, 0x00}, + {0x3517, 0x00}, + {0x3518, 0x00}, + {0x3519, 0x00}, + {0x351a, 0x00}, + {0x351b, 0x80}, + {0x351c, 0x00}, + {0x351d, 0x00}, + {0x351e, 0x00}, + {0x351f, 0x00}, + {0x3520, 0x00}, + {0x3521, 0x80}, + {0x3522, 0x08}, + {0x3524, 0x08}, + {0x3526, 0x08}, + {0x3528, 0x08}, + {0x352a, 0x08}, + {0x3602, 0x00}, + {0x3603, 0x40}, + {0x3604, 0x02}, + {0x3605, 0x00}, + {0x3606, 0x00}, + {0x3607, 0x00}, + {0x3609, 0x12}, + {0x360a, 0x40}, + {0x360c, 0x08}, + {0x360f, 0xe5}, + {0x3608, 0x8f}, + {0x3611, 0x00}, + {0x3613, 0xf7}, + {0x3616, 0x58}, + {0x3619, 0x99}, + {0x361b, 0x60}, + {0x361c, 0x7a}, + {0x361e, 0x79}, + {0x361f, 0x02}, + {0x3632, 0x00}, + {0x3633, 0x10}, + {0x3634, 0x10}, + {0x3635, 0x10}, + {0x3636, 0x15}, + {0x3646, 0x86}, + {0x364a, 0x0b}, + {0x3700, 0x17}, + {0x3701, 0x22}, + {0x3703, 0x10}, + {0x370a, 0x37}, + {0x3705, 0x00}, + {0x3706, 0x63}, + {0x3709, 0x3c}, + {0x370b, 0x01}, + {0x370c, 0x30}, + {0x3710, 0x24}, + {0x3711, 0x0c}, + {0x3716, 0x00}, + {0x3720, 0x28}, + {0x3729, 0x7b}, + {0x372a, 0x84}, + {0x372b, 0xbd}, + {0x372c, 0xbc}, + {0x372e, 0x52}, + {0x373c, 0x0e}, + {0x373e, 0x33}, + {0x3743, 0x10}, + {0x3744, 0x88}, + {0x3745, 0xc0}, + {0x374a, 0x43}, + {0x374c, 0x00}, + {0x374e, 0x23}, + {0x3751, 0x7b}, + {0x3752, 0x84}, + {0x3753, 0xbd}, + {0x3754, 0xbc}, + {0x3756, 0x52}, + {0x375c, 0x00}, + {0x3760, 0x00}, + {0x3761, 0x00}, + {0x3762, 0x00}, + {0x3763, 0x00}, + {0x3764, 0x00}, + {0x3767, 0x04}, + {0x3768, 0x04}, + {0x3769, 0x08}, + {0x376a, 0x08}, + {0x376b, 0x20}, + {0x376c, 0x00}, + {0x376d, 0x00}, + {0x376e, 0x00}, + {0x3773, 0x00}, + {0x3774, 0x51}, + {0x3776, 0xbd}, + {0x3777, 0xbd}, + {0x3781, 0x18}, + {0x3783, 0x25}, + {0x3798, 0x1b}, + {0x3800, 0x00}, + {0x3801, 0x08}, + {0x3802, 0x00}, + {0x3803, 0x04}, + {0x3804, 0x0a}, + {0x3805, 0x97}, + {0x3806, 0x05}, + {0x3807, 0xfb}, + {0x3808, 0x0a}, + {0x3809, 0x80}, + {0x380a, 0x05}, + {0x380b, 0xf0}, + {0x380c, 0x03}, + {0x380d, 0x5c}, + {0x380e, 0x06}, + {0x380f, 0x12}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x04}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3819, 0x01}, + {0x3820, 0x00}, + {0x3821, 0x06}, + {0x3829, 0x00}, + {0x382a, 0x01}, + {0x382b, 0x01}, + {0x382d, 0x7f}, + {0x3830, 0x04}, + {0x3836, 0x01}, + {0x3837, 0x00}, + {0x3841, 0x02}, + {0x3846, 0x08}, + {0x3847, 0x07}, + {0x3d85, 0x36}, + {0x3d8c, 0x71}, + {0x3d8d, 0xcb}, + {0x3f0a, 0x00}, + {0x4000, 0xf1}, + {0x4001, 0x40}, + {0x4002, 0x04}, + {0x4003, 0x14}, + {0x400e, 0x00}, + {0x4011, 0x00}, + {0x401a, 0x00}, + {0x401b, 0x00}, + {0x401c, 0x00}, + {0x401d, 0x00}, + {0x401f, 0x00}, + {0x4020, 0x00}, + {0x4021, 0x10}, + {0x4022, 0x07}, + {0x4023, 0xcf}, + {0x4024, 0x09}, + {0x4025, 0x60}, + {0x4026, 0x09}, + {0x4027, 0x6f}, + {0x4028, 0x00}, + {0x4029, 0x02}, + {0x402a, 0x06}, + {0x402b, 0x04}, + {0x402c, 0x02}, + {0x402d, 0x02}, + {0x402e, 0x0e}, + {0x402f, 0x04}, + {0x4302, 0xff}, + {0x4303, 0xff}, + {0x4304, 0x00}, + {0x4305, 0x00}, + {0x4306, 0x00}, + {0x4308, 0x02}, + {0x4500, 0x6c}, + {0x4501, 0xc4}, + {0x4502, 0x40}, + {0x4503, 0x01}, + {0x4600, 0x00}, + {0x4601, 0xA7}, + /* bit[5]=0 as MIPI continuous clock mode */ + {0x4800, 0x04}, + {0x4813, 0x08}, + {0x481f, 0x40}, + {0x4829, 0x78}, + /* global timing for 360Mbps */ + {0x4837, 0x10}, + {0x4b00, 0x2a}, + {0x4b0d, 0x00}, + {0x4d00, 0x04}, + {0x4d01, 0x42}, + {0x4d02, 0xd1}, + {0x4d03, 0x93}, + {0x4d04, 0xf5}, + {0x4d05, 0xc1}, + {0x5000, 0xf3}, + {0x5001, 0x11}, + {0x5004, 0x00}, + {0x500a, 0x00}, + {0x500b, 0x00}, + {0x5032, 0x00}, + {0x5040, 0x00}, + {0x5050, 0x0c}, + {0x5500, 0x00}, + {0x5501, 0x10}, + {0x5502, 0x01}, + {0x5503, 0x0f}, + {0x8000, 0x00}, + {0x8001, 0x00}, + {0x8002, 0x00}, + {0x8003, 0x00}, + {0x8004, 0x00}, + {0x8005, 0x00}, + {0x8006, 0x00}, + {0x8007, 0x00}, + {0x8008, 0x00}, + {0x3638, 0x00}, + + {0x0100, 0x01}, + + {OV4689_TABLE_END, OV4689_TABLE_END} + +}; + +static struct ov4689_reg ov4689_mode_1920x1080_initial[] = { + {0x0103, 0x01}, + {0x3638, 0x00}, + {0x0300, 0x00}, + /* 792 Mbps MIPI_CLK */ + {0x0302, 0x21}, + {0x0304, 0x03}, + {0x030b, 0x00}, + {0x030d, 0x1e}, + {0x030e, 0x04}, + {0x030f, 0x01}, + {0x0312, 0x01}, + {0x031e, 0x00}, + {0x3000, 0x20}, + {0x3002, 0x00}, + {0x3018, 0x72}, + {0x3020, 0x93}, + {0x3021, 0x03}, + {0x3022, 0x01}, + {0x3031, 0x0a}, + {0x303f, 0x0c}, + {0x3305, 0xf1}, + {0x3307, 0x04}, + {0x3309, 0x29}, + {0x3500, 0x01}, + {0x3501, 0x21}, + {0x3502, 0x40}, + {0x3503, 0x04}, + {0x3504, 0x00}, + {0x3505, 0x00}, + {0x3506, 0x00}, + {0x3507, 0x00}, + {0x3508, 0x00}, + {0x3509, 0x80}, + {0x350a, 0x00}, + {0x350b, 0x00}, + {0x350c, 0x00}, + {0x350d, 0x00}, + {0x350e, 0x00}, + {0x350f, 0x80}, + {0x3510, 0x00}, + {0x3511, 0x00}, + {0x3512, 0x00}, + {0x3513, 0x00}, + {0x3514, 0x00}, + {0x3515, 0x80}, + {0x3516, 0x00}, + {0x3517, 0x00}, + {0x3518, 0x00}, + {0x3519, 0x00}, + {0x351a, 0x00}, + {0x351b, 0x80}, + {0x351c, 0x00}, + {0x351d, 0x00}, + {0x351e, 0x00}, + {0x351f, 0x00}, + {0x3520, 0x00}, + {0x3521, 0x80}, + {0x3522, 0x08}, + {0x3524, 0x08}, + {0x3526, 0x08}, + {0x3528, 0x08}, + {0x352a, 0x08}, + {0x3602, 0x00}, + {0x3603, 0x40}, + {0x3604, 0x02}, + {0x3605, 0x00}, + {0x3606, 0x00}, + {0x3607, 0x00}, + {0x3609, 0x12}, + {0x360a, 0x40}, + {0x360c, 0x08}, + {0x360f, 0xe5}, + {0x3608, 0x8f}, + {0x3611, 0x00}, + {0x3613, 0xf7}, + {0x3616, 0x58}, + {0x3619, 0x99}, + {0x361b, 0x60}, + {0x361c, 0x7a}, + {0x361e, 0x79}, + {0x361f, 0x02}, + {0x3632, 0x00}, + {0x3633, 0x10}, + {0x3634, 0x10}, + {0x3635, 0x10}, + {0x3636, 0x15}, + {0x3646, 0x86}, + {0x364a, 0x0b}, + {0x3700, 0x17}, + {0x3701, 0x22}, + {0x3703, 0x10}, + {0x370a, 0x37}, + {0x3705, 0x00}, + {0x3706, 0x63}, + {0x3709, 0x3c}, + {0x370b, 0x01}, + {0x370c, 0x30}, + {0x3710, 0x24}, + {0x3711, 0x0c}, + {0x3716, 0x00}, + {0x3720, 0x28}, + {0x3729, 0x7b}, + {0x372a, 0x84}, + {0x372b, 0xbd}, + {0x372c, 0xbc}, + {0x372e, 0x52}, + {0x373c, 0x0e}, + {0x373e, 0x33}, + {0x3743, 0x10}, + {0x3744, 0x88}, + {0x3745, 0xc0}, + {0x374a, 0x43}, + {0x374c, 0x00}, + {0x374e, 0x23}, + {0x3751, 0x7b}, + {0x3752, 0x84}, + {0x3753, 0xbd}, + {0x3754, 0xbc}, + {0x3756, 0x52}, + {0x375c, 0x00}, + {0x3760, 0x00}, + {0x3761, 0x00}, + {0x3762, 0x00}, + {0x3763, 0x00}, + {0x3764, 0x00}, + {0x3767, 0x04}, + {0x3768, 0x04}, + {0x3769, 0x08}, + {0x376a, 0x08}, + {0x376b, 0x20}, + {0x376c, 0x00}, + {0x376d, 0x00}, + {0x376e, 0x00}, + {0x3773, 0x00}, + {0x3774, 0x51}, + {0x3776, 0xbd}, + {0x3777, 0xbd}, + {0x3781, 0x18}, + {0x3783, 0x25}, + {0x3798, 0x1b}, + {0x3800, 0x01}, + {0x3801, 0x88}, + {0x3802, 0x00}, + {0x3803, 0xe0}, + {0x3804, 0x09}, + {0x3805, 0x17}, + {0x3806, 0x05}, + {0x3807, 0x1f}, + {0x3808, 0x07}, + {0x3809, 0x80}, + {0x380a, 0x04}, + {0x380b, 0x38}, + {0x380c, 0x03}, + {0x380d, 0x5c}, + {0x380e, 0x12}, + {0x380f, 0x1e}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x04}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3819, 0x01}, + {0x3820, 0x00}, + {0x3821, 0x06}, + {0x3829, 0x00}, + {0x382a, 0x01}, + {0x382b, 0x01}, + {0x382d, 0x7f}, + {0x3830, 0x04}, + {0x3836, 0x01}, + {0x3837, 0x00}, + {0x3841, 0x02}, + {0x3846, 0x08}, + {0x3847, 0x07}, + {0x3d85, 0x36}, + {0x3d8c, 0x71}, + {0x3d8d, 0xcb}, + {0x3f0a, 0x00}, + {0x4000, 0xf1}, + {0x4001, 0x40}, + {0x4002, 0x04}, + {0x4003, 0x14}, + {0x400e, 0x00}, + {0x4011, 0x00}, + {0x401a, 0x00}, + {0x401b, 0x00}, + {0x401c, 0x00}, + {0x401d, 0x00}, + {0x401f, 0x00}, + {0x4020, 0x00}, + {0x4021, 0x10}, + {0x4022, 0x06}, + {0x4023, 0x13}, + {0x4024, 0x07}, + {0x4025, 0x40}, + {0x4026, 0x07}, + {0x4027, 0x50}, + {0x4028, 0x00}, + {0x4029, 0x02}, + {0x402a, 0x06}, + {0x402b, 0x04}, + {0x402c, 0x02}, + {0x402d, 0x02}, + {0x402e, 0x0e}, + {0x402f, 0x04}, + {0x4302, 0xff}, + {0x4303, 0xff}, + {0x4304, 0x00}, + {0x4305, 0x00}, + {0x4306, 0x00}, + {0x4308, 0x02}, + {0x4500, 0x6c}, + {0x4501, 0xc4}, + {0x4502, 0x40}, + {0x4503, 0x01}, + {0x4600, 0x00}, + {0x4601, 0x77}, + /* bit[5]=0 as MIPI continuous clock mode */ + {0x4800, 0x04}, + {0x4813, 0x08}, + {0x481f, 0x40}, + {0x4829, 0x78}, + {0x4837, 0x14}, + {0x4b00, 0x2a}, + {0x4b0d, 0x00}, + {0x4d00, 0x04}, + {0x4d01, 0x42}, + {0x4d02, 0xd1}, + {0x4d03, 0x93}, + {0x4d04, 0xf5}, + {0x4d05, 0xc1}, + {0x5000, 0xf3}, + {0x5001, 0x11}, + {0x5004, 0x00}, + {0x500a, 0x00}, + {0x500b, 0x00}, + {0x5032, 0x00}, + {0x5040, 0x00}, + {0x5050, 0x0c}, + {0x5500, 0x00}, + {0x5501, 0x10}, + {0x5502, 0x01}, + {0x5503, 0x0f}, + {0x8000, 0x00}, + {0x8001, 0x00}, + {0x8002, 0x00}, + {0x8003, 0x00}, + {0x8004, 0x00}, + {0x8005, 0x00}, + {0x8006, 0x00}, + {0x8007, 0x00}, + {0x8008, 0x00}, + {0x3638, 0x00}, + + {0x0100, 0x01}, + + + {OV4689_TABLE_END, OV4689_TABLE_END} +}; + +enum { + OV4689_MODE_2688x1520, + OV4689_MODE_1920x1080, +}; + +static struct ov4689_reg *mode_table[] = { + [OV4689_MODE_2688x1520] = ov4689_mode_2688x1520_initial, + [OV4689_MODE_1920x1080] = ov4689_mode_1920x1080_initial, +}; + +#endif |