blob: efbc7d302a2ed65f56d8e1b10a08f038464c4d7f [file] [log] [blame]
/*
* soc380.c - soc380 sensor driver
*
* Copyright (c) 2011-2013, NVIDIA CORPORATION. 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/clk.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/module.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;
struct clk *mclk;
};
#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},
{0x0010, 0x021c},
{0x0012, 0x0000},
{0x0014, 0x244B},
{SOC380_TABLE_WAIT_MS, 10},
{0x0014, 0x304B},
{SOC380_TABLE_WAIT_MS, 50},
{0x0014, 0xB04A},
{0x098C, 0x2703},
{0x0990, 0x0280},
{0x098C, 0x2705},
{0x0990, 0x01E0},
{0x098C, 0x2707},
{0x0990, 0x0280},
{0x098C, 0x2709},
{0x0990, 0x01E0},
{0x098C, 0x270D},
{0x0990, 0x0000},
{0x098C, 0x270F},
{0x0990, 0x0000},
{0x098C, 0x2711},
{0x0990, 0x01E7},
{0x098C, 0x2713},
{0x0990, 0x0287},
{0x098C, 0x2715},
{0x0990, 0x0001},
{0x098C, 0x2717},
{0x0990, 0x0026},
{0x098C, 0x2719},
{0x0990, 0x001A},
{0x098C, 0x271B},
{0x0990, 0x006B},
{0x098C, 0x271D},
{0x0990, 0x006B},
{0x098C, 0x271F},
{0x0990, 0x022A},
{0x098C, 0x2721},
{0x0990, 0x034A},
{0x098C, 0x2723},
{0x0990, 0x0000},
{0x098C, 0x2725},
{0x0990, 0x0000},
{0x098C, 0x2727},
{0x0990, 0x01E7},
{0x098C, 0x2729},
{0x0990, 0x0287},
{0x098C, 0x272B},
{0x0990, 0x0001},
{0x098C, 0x272D},
{0x0990, 0x0026},
{0x098C, 0x272F},
{0x0990, 0x001A},
{0x098C, 0x2731},
{0x0990, 0x006B},
{0x098C, 0x2733},
{0x0990, 0x006B},
{0x098C, 0x2735},
{0x0990, 0x022A},
{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, 0x0021},
{0x098C, 0xA409},
{0x0990, 0x0023},
{0x098C, 0xA40A},
{0x0990, 0x0028},
{0x098C, 0xA40B},
{0x0990, 0x002A},
{0x098C, 0x2411},
{0x0990, 0x008B},
{0x098C, 0x2413},
{0x0990, 0x00A6},
{0x098C, 0x2415},
{0x0990, 0x008B},
{0x098C, 0x2417},
{0x0990, 0x00A6},
{0x098C, 0xA404},
{0x0990, 0x0010},
{0x098C, 0xA40D},
{0x0990, 0x0002},
{0x098C, 0xA40E},
{0x0990, 0x0003},
{0x098C, 0xA410},
{0x0990, 0x000A},
{0x098C, 0xA215},
{0x0990, 0x0003},
{0x098C, 0xA20C},
{0x0990, 0x0003},
{0x098C, 0xA103},
{0x0990, 0x0006},
{SOC380_TABLE_WAIT_MS, 100},
{0x098C, 0xA103},
{0x0990, 0x0005},
{SOC380_TABLE_WAIT_MS, 50},
{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 void soc380_mclk_disable(struct soc380_info *info)
{
dev_dbg(&info->i2c_client->dev, "%s: disable MCLK\n", __func__);
clk_disable_unprepare(info->mclk);
}
static int soc380_mclk_enable(struct soc380_info *info)
{
int err;
unsigned long mclk_init_rate = 24000000;
dev_dbg(&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;
}
static int soc380_open(struct inode *inode, struct file *file)
{
struct soc380_status dev_status;
int err = soc380_mclk_enable(info);
if (err < 0)
goto fail_mclk;
file->private_data = info;
if (info->pdata && info->pdata->power_on) {
err = info->pdata->power_on(&info->i2c_client->dev);
if (err < 0)
goto fail_power_on;
}
dev_status.data = 0;
dev_status.status = 0;
err = soc380_get_status(info, &dev_status);
if (err < 0)
goto fail_status;
return 0;
fail_status:
if (info->pdata && info->pdata->power_off)
info->pdata->power_off(&info->i2c_client->dev);
fail_power_on:
soc380_mclk_disable(info);
fail_mclk:
file->private_data = NULL;
return err;
}
static int soc380_release(struct inode *inode, struct file *file)
{
if (info->pdata && info->pdata->power_off)
info->pdata->power_off(&info->i2c_client->dev);
soc380_mclk_disable(info);
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;
const char *mclk_name;
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;
}
info->pdata = client->dev.platform_data;
info->i2c_client = client;
mclk_name = info->pdata && 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);
kfree(info);
return PTR_ERR(info->mclk);
}
i2c_set_clientdata(client, info);
err = misc_register(&soc380_device);
if (err) {
pr_err("soc380: Unable to register misc device!\n");
kfree(info);
return err;
}
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);
MODULE_LICENSE("GPL v2");