aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/media/video/tegra/ad5820.c
blob: 19d35bca5b0b10788f386146ad31ac2baa250356 (plain) (tree)






































































































































































































































                                                                             
/*
 * 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 <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);