| /* |
| * AD5820 focuser driver. |
| * |
| * Copyright (C) 2010-2011 NVIDIA Corporation. |
| * |
| * Contributors: |
| * Sachin Nikam <snikam@nvidia.com> |
| * |
| * Based on ov5650.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. |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/fs.h> |
| #include <linux/i2c.h> |
| #include <linux/miscdevice.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/module.h> |
| |
| #include <media/ad5820.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 1636.8mS |
| * 7 1 3200uS 3273.6mS |
| * 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 AD5820_TRANSITION_MODE 0x0B |
| #define SETTLETIME_MS 5 |
| |
| #define POS_LOW (0) |
| #define POS_HIGH (1023) |
| #define FOCAL_LENGTH (4.507f) |
| #define FNUMBER (2.8f) |
| #define FPOS_COUNT 1024 |
| |
| struct ad5820_info { |
| struct i2c_client *i2c_client; |
| struct regulator *regulator; |
| struct ad5820_config config; |
| }; |
| |
| static int ad5820_write(struct i2c_client *client, u32 value) |
| { |
| int count; |
| struct i2c_msg msg[1]; |
| unsigned char data[2]; |
| |
| if (!client->adapter) |
| return -ENODEV; |
| |
| data[0] = (u8) ((value >> 4) & 0x3F); |
| data[1] = (u8) ((value & 0xF) << 4) | AD5820_TRANSITION_MODE; |
| |
| msg[0].addr = client->addr; |
| msg[0].flags = 0; |
| msg[0].len = ARRAY_SIZE(data); |
| msg[0].buf = data; |
| |
| count = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); |
| if (count == ARRAY_SIZE(msg)) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| static int ad5820_set_position(struct ad5820_info *info, u32 position) |
| { |
| if (position < info->config.pos_low || |
| position > info->config.pos_high) |
| return -EINVAL; |
| |
| return ad5820_write(info->i2c_client, position); |
| } |
| |
| static long ad5820_ioctl(struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| struct ad5820_info *info = file->private_data; |
| |
| switch (cmd) { |
| case AD5820_IOCTL_GET_CONFIG: |
| { |
| if (copy_to_user((void __user *) arg, |
| &info->config, |
| sizeof(info->config))) { |
| pr_err("%s: 0x%x\n", __func__, __LINE__); |
| return -EFAULT; |
| } |
| |
| break; |
| } |
| case AD5820_IOCTL_SET_POSITION: |
| return ad5820_set_position(info, (u32) arg); |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| struct ad5820_info *info; |
| |
| static int ad5820_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = info; |
| if (info->regulator) |
| regulator_enable(info->regulator); |
| return 0; |
| } |
| |
| int ad5820_release(struct inode *inode, struct file *file) |
| { |
| if (info->regulator) |
| regulator_disable(info->regulator); |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| |
| static const struct file_operations ad5820_fileops = { |
| .owner = THIS_MODULE, |
| .open = ad5820_open, |
| .unlocked_ioctl = ad5820_ioctl, |
| .release = ad5820_release, |
| }; |
| |
| static struct miscdevice ad5820_device = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "ad5820", |
| .fops = &ad5820_fileops, |
| }; |
| |
| static int ad5820_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int err; |
| |
| pr_info("ad5820: probing sensor.\n"); |
| |
| info = kzalloc(sizeof(struct ad5820_info), GFP_KERNEL); |
| if (!info) { |
| pr_err("ad5820: Unable to allocate memory!\n"); |
| return -ENOMEM; |
| } |
| |
| err = misc_register(&ad5820_device); |
| if (err) { |
| pr_err("ad5820: Unable to register misc device!\n"); |
| kfree(info); |
| return err; |
| } |
| |
| info->regulator = regulator_get(&client->dev, "vdd_vcore_af"); |
| if (IS_ERR_OR_NULL(info->regulator)) { |
| dev_err(&client->dev, "unable to get regulator %s\n", |
| dev_name(&client->dev)); |
| info->regulator = NULL; |
| } else { |
| regulator_enable(info->regulator); |
| } |
| |
| info->i2c_client = client; |
| info->config.settle_time = SETTLETIME_MS; |
| info->config.focal_length = FOCAL_LENGTH; |
| info->config.fnumber = FNUMBER; |
| info->config.pos_low = POS_LOW; |
| info->config.pos_high = POS_HIGH; |
| i2c_set_clientdata(client, info); |
| return 0; |
| } |
| |
| static int ad5820_remove(struct i2c_client *client) |
| { |
| struct ad5820_info *info; |
| info = i2c_get_clientdata(client); |
| misc_deregister(&ad5820_device); |
| kfree(info); |
| return 0; |
| } |
| |
| static const struct i2c_device_id ad5820_id[] = { |
| { "ad5820", 0 }, |
| { }, |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, ad5820_id); |
| |
| static struct i2c_driver ad5820_i2c_driver = { |
| .driver = { |
| .name = "ad5820", |
| .owner = THIS_MODULE, |
| }, |
| .probe = ad5820_probe, |
| .remove = ad5820_remove, |
| .id_table = ad5820_id, |
| }; |
| |
| static int __init ad5820_init(void) |
| { |
| pr_info("ad5820 sensor driver loading\n"); |
| return i2c_add_driver(&ad5820_i2c_driver); |
| } |
| |
| static void __exit ad5820_exit(void) |
| { |
| i2c_del_driver(&ad5820_i2c_driver); |
| } |
| |
| module_init(ad5820_init); |
| module_exit(ad5820_exit); |
| MODULE_LICENSE("GPL v2"); |