From cb0a27a5fbb9e68a4ea00d8eb371eed2df890755 Mon Sep 17 00:00:00 2001
From: Andrei Warkentin <andreiw@motorola.com>
Date: Mon, 20 Sep 2010 15:32:56 -0500
Subject: [PATCH] media: video: tegra: dw9714l: Make focuser driver more
 generic

Exposes more parameters and settings to focuser HAL,
putting all specifics in the driver, queryable from the HAL.

Change-Id: Id3d8764b7f9f1853982ca9c0d403fb0e455c7185
Signed-off-by: Andrei Warkentin <andreiw@motorola.com>
---
 drivers/media/video/tegra/dw9714l.c | 148 +++++++++++++++++++++++-----
 include/media/dw9714l.h             |  26 ++++-
 2 files changed, 149 insertions(+), 25 deletions(-)

diff --git a/drivers/media/video/tegra/dw9714l.c b/drivers/media/video/tegra/dw9714l.c
index 7ac4f3c13fa8..26c36c3a0803 100644
--- a/drivers/media/video/tegra/dw9714l.c
+++ b/drivers/media/video/tegra/dw9714l.c
@@ -19,15 +19,49 @@
 #include <linux/miscdevice.h>
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
+#include <linux/uaccess.h>
 #include <media/dw9714l.h>
 
-#define DW9714L_MAX_RETRIES (3)
+#define POS_LOW (144)
+#define POS_HIGH (520)
+#define SETTLETIME_MS (50)
+#define FOCAL_LENGTH (4.42f)
+#define FNUMBER (2.8f)
+#define DEFAULT_MODE (MODE_LSC);
+
+#define PROT_OFF (0xECA3)
+#define PROT_ON (0xDC51)
+#define DLC_MCLK (0x2)
+#define DLC_TSRC (0x17)
+#define LSC_MCLK (0x1)
+#define LSC_S10 (0x3)
+#define LSC_S32 (0x3)
+#define LSC_TSRC (0x3)
+#define LSC_GOAL(pos) ((pos << 4) | (LSC_S32 << 2) | LSC_S10)
+#define GOAL(pos) (pos << 4)
 
-#define POS_PER_STEP (3)
+#define DW9714L_MAX_RETRIES (3)
 
 struct dw9714l_info {
 	struct i2c_client *i2c_client;
 	struct regulator *regulator;
+	struct dw9714l_config config;
+};
+
+static u16 dlc_pre_set_pos[] =
+{
+	PROT_OFF,
+	0xA10C | DLC_MCLK,
+	0xF200 | (DLC_TSRC << 3),
+	PROT_ON
+};
+
+static u16 lsc_pre_set_pos[] =
+{
+	PROT_OFF,
+	0xA104 | LSC_MCLK,
+	0xF200 | (LSC_TSRC << 3),
+	PROT_ON
 };
 
 static int dw9714l_write(struct i2c_client *client, u16 value)
@@ -60,31 +94,66 @@ static int dw9714l_write(struct i2c_client *client, u16 value)
 	return -EIO;
 }
 
-static int dw9714l_set_position(struct dw9714l_info *info, u32 position)
+static int dw9714l_write_many(struct i2c_client *client,
+			      u16 *values,
+			      size_t count)
 {
-	int ret;
+	int ix = 0;
+	int ret = 0;
+	while (ix < count && ret == 0)
+		ret = dw9714l_write(client, values[ix++]);
 
-	/* Protection off. */
-	ret = dw9714l_write(info->i2c_client, 0xECA3);
-	if (ret)
-		return ret;
+	return ret;
+}
 
-	ret = dw9714l_write(info->i2c_client, 0xF200 | (0x0F << 3));
-	if (ret)
-		return ret;
+static int dw9714l_set_position(struct dw9714l_info *info, u32 position)
+{
+	int ret;
 
-	/* Protection on. */
-	ret = dw9714l_write(info->i2c_client, 0xDC51);
-	if (ret)
-		return ret;
+	if (position < info->config.pos_low ||
+	    position > info->config.pos_high)
+		return -EINVAL;
 
-	ret = dw9714l_write(info->i2c_client,
-			    (position << 4) |
-			    (POS_PER_STEP << 2));
-	if (ret)
-		return ret;
+	/*
+	  As we calibrate the focuser, we might go back and forth on
+	  the actual mode of setting the position. To
+	  make this least painful, we'll make the mode a settable
+	  parameter exposed to the focuser HAL.
+	 */
+
+	switch(info->config.mode)
+	{
+	case MODE_LSC:
+		ret = dw9714l_write_many(info->i2c_client,
+					 lsc_pre_set_pos,
+					 ARRAY_SIZE(lsc_pre_set_pos));
+
+		if (ret)
+			return ret;
+
+		ret = dw9714l_write(info->i2c_client,
+				    LSC_GOAL(position));
+
+		break;
+	case MODE_DLC:
+		ret = dw9714l_write_many(info->i2c_client,
+					 dlc_pre_set_pos,
+					 ARRAY_SIZE(dlc_pre_set_pos));
+
+		if (ret)
+			return ret;
+
+		/* Fall through */
+	case MODE_DIRECT:
+		ret = dw9714l_write(info->i2c_client,
+				    GOAL(position));
+		break;
+	case MODE_INVALID:
+	default:
+		WARN_ON(info->config.mode);
+	}
 
-	return 0;
+	return ret;
 }
 
 static int dw9714l_ioctl(struct inode *inode, struct file *file,
@@ -93,11 +162,39 @@ static int dw9714l_ioctl(struct inode *inode, struct file *file,
 	struct dw9714l_info *info = file->private_data;
 
 	switch (cmd) {
+	case DW9714L_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 DW9714L_IOCTL_SET_CAL:
+	{
+		struct dw9714l_cal cal;
+		if (copy_from_user(&cal,
+				   (const void __user *) arg,
+				   sizeof(cal))) {
+			pr_err("%s: 0x%x\n", __func__, __LINE__);
+			return -EFAULT;
+		}
+
+		if (cal.mode >= MODE_INVALID)
+			return -EINVAL;
+
+		info->config.mode = cal.mode;
+		break;
+	}
 	case DW9714L_IOCTL_SET_POSITION:
 		return dw9714l_set_position(info, (u32) arg);
 	default:
 		return -EINVAL;
 	}
+
 	return 0;
 }
 
@@ -105,9 +202,7 @@ struct dw9714l_info *info = NULL;
 
 static int dw9714l_open(struct inode *inode, struct file *file)
 {
-	u8 status;
 
-	pr_info("%s\n", __func__);
 	file->private_data = info;
 	if (info->regulator)
 		regulator_enable(info->regulator);
@@ -116,7 +211,6 @@ static int dw9714l_open(struct inode *inode, struct file *file)
 
 int dw9714l_release(struct inode *inode, struct file *file)
 {
-	pr_info("%s\n", __func__);
 	if (info->regulator)
 		regulator_disable(info->regulator);
 	file->private_data = NULL;
@@ -167,6 +261,12 @@ static int dw9714l_probe(struct i2c_client *client,
 	}
 
 	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;
+	info->config.mode = DEFAULT_MODE;
 	i2c_set_clientdata(client, info);
 	return 0;
 }
diff --git a/include/media/dw9714l.h b/include/media/dw9714l.h
index 3d91140dd37f..cac2abb9537b 100644
--- a/include/media/dw9714l.h
+++ b/include/media/dw9714l.h
@@ -24,7 +24,31 @@
 
 #include <linux/ioctl.h>  /* For IOCTL macros */
 
-#define DW9714L_IOCTL_SET_POSITION		_IOW('o', 1, u32)
+#define DW9714L_IOCTL_GET_CONFIG   _IOR('o', 1, struct dw9714l_config)
+#define DW9714L_IOCTL_SET_CAL      _IOW('o', 2, struct dw9714l_cal)
+#define DW9714L_IOCTL_SET_POSITION _IOW('o', 3, u32)
+
+enum dw9714l_mode {
+	MODE_DIRECT,
+	MODE_LSC,
+	MODE_DLC,
+	MODE_INVALID
+};
+
+struct dw9714l_config
+{
+	__u32 settle_time;
+	float focal_length;
+	float fnumber;
+	__u32 pos_low;
+	__u32 pos_high;
+	enum dw9714l_mode mode;
+};
+
+struct dw9714l_cal
+{
+	enum dw9714l_mode mode;
+};
 
 #endif  /* __DW9714L_H__ */
 
-- 
2.34.1