atomisp: lc898211: HVCA Semco actuator driver
authorDavid Cohen <david.a.cohen@intel.com>
Wed, 28 Mar 2012 13:52:36 +0000 (16:52 +0300)
committerbuildbot <buildbot@intel.com>
Mon, 7 May 2012 16:50:31 +0000 (09:50 -0700)
BZ: 27947

This patch adds LC898211 IC driver to control Hybrid VCA Semco actuator
driver.

Change-Id: I22d7345fb8bb82be9ed61e52768f2bc6cc412610
Signed-off-by: David Cohen <david.a.cohen@intel.com>
Reviewed-on: http://android.intel.com:8080/42535
Reviewed-by: Kruger, Jozef <jozef.kruger@intel.com>
Reviewed-by: Samurov, Vitali <vitali.samurov@intel.com>
Reviewed-by: Koskinen, Ilkka <ilkka.koskinen@intel.com>
Tested-by: Koski, Anttu <anttu.koski@intel.com>
Reviewed-by: buildbot <buildbot@intel.com>
Tested-by: buildbot <buildbot@intel.com>
drivers/media/video/Kconfig
drivers/media/video/Makefile
drivers/media/video/lc898211.c [new file with mode: 0644]
drivers/media/video/lc898211.h [new file with mode: 0644]

index 878d233..c77fb75 100644 (file)
@@ -332,6 +332,18 @@ config VIDEO_MT9E013
 
          It currently only works with the atomisp driver.
 
+config VIDEO_LC898211
+       tristate "LC898211 IC driver for Semco HVCA actuator/EEPROM modules"
+       depends on I2C && VIDEO_V4L2 && VIDEO_ATOMISP
+       ---help---
+         This is a Video4Linux2 driver for LC898211 IC to control
+         Semco Hybrid VCA and EEPROM modules.
+
+         To compile this driver as a module, choose M here: the
+         module will be called lc898211.
+
+         It currently only works with the atomisp driver.
+
 config VIDEO_OV8830
        tristate "Omnivision ov8830 sensor support"
        depends on I2C && VIDEO_V4L2 && VIDEO_ATOMISP
index beae10c..0d2ef98 100644 (file)
@@ -80,6 +80,8 @@ obj-$(CONFIG_VIDEO_OV8830)   += ov8830.o
 CFFLAGS_ov8830.o = -Werror
 obj-$(CONFIG_VIDEO_MT9M114)  += mt9m114.o
 CFFLAGS_mt9m114.o = -Werror
+obj-$(CONFIG_VIDEO_LC898211) += lc898211.o
+CFFLAGS_lc898211.o = -Werror
 
 obj-$(CONFIG_SOC_CAMERA_IMX074)                += imx074.o
 obj-$(CONFIG_SOC_CAMERA_MT9M001)       += mt9m001.o
diff --git a/drivers/media/video/lc898211.c b/drivers/media/video/lc898211.c
new file mode 100644 (file)
index 0000000..888e69c
--- /dev/null
@@ -0,0 +1,972 @@
+/*
+ * Support for lc898211 actuator.
+ *
+ * Copyright (c) 2012 Intel Corporation. All Rights Reserved.
+ *
+ * Author: David Cohen <david.a.cohen@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#include <linux/atomisp.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-chip-ident.h>
+
+#include "lc898211.h"
+
+#define to_lc898211_dev(sd) container_of(sd, struct lc898211_dev, sd)
+
+static int
+__lc898211_read_reg(struct i2c_client *client, u16 addr, u16 len, u8 reg,
+                   u8 *val)
+{
+       int err;
+       struct i2c_msg msg[2] = {
+               {
+                       .addr = addr,
+                       .flags = 0,
+                       .len = sizeof(reg),
+                       .buf = &reg,
+               }, {
+                       .addr = addr,
+                       .len = len,
+                       .flags = I2C_M_RD,
+                       .buf = val,
+               }
+       };
+
+       if (!client->adapter) {
+               v4l2_err(client, "%s: error, no client->adapter\n", __func__);
+               return -ENODEV;
+       }
+
+       if (len > LC898211_MAX_I2C_MSG_LEN) {
+               v4l2_err(client, "%s: error, too big message length\n",
+                        __func__);
+               return -EINVAL;
+       }
+
+       err = i2c_transfer(client->adapter, msg, 2);
+       if (err != ARRAY_SIZE(msg)) {
+               if (err >= 0)
+                       err = -EIO;
+               goto error;
+       }
+
+       return 0;
+
+error:
+       v4l2_err(client, "%s: read from offset 0x%x error %d", __func__, reg,
+                err);
+       return err;
+}
+
+static int
+lc898211_read_reg(struct i2c_client *client, u16 len, u8 reg, void *val)
+{
+       return __lc898211_read_reg(client, client->addr, len, reg, val);
+}
+
+static int
+lc898211_eep_read_reg(struct i2c_client *client, u16 len, u16 reg, void *val)
+{
+       return __lc898211_read_reg(client, LC898211_EEP_GET_ID(reg), len,
+                                  (u8)reg, val);
+}
+
+static int
+lc898211_eep_read_reg_array(struct i2c_client *client, u16 size, u16 addr,
+                           void *data)
+{
+       u8 *buf = data;
+       u16 index = 0;
+       int ret = 0;
+
+       while (index < size) {
+               /*
+                * Cannot read different pages in same I2C command.
+                * One msg size should be limited by end of page.
+                */
+               const int max_size = LC898211_EEP_PAGE_SIZE -
+                               ((addr + index) & ~LC898211_EEP_PAGE_MASK);
+               int msg_size = index + LC898211_MAX_I2C_MSG_LEN < size ?
+                              LC898211_MAX_I2C_MSG_LEN : size - index;
+
+               if (msg_size > max_size)
+                       msg_size = max_size;
+
+               ret = lc898211_eep_read_reg(client, msg_size, addr + index,
+                                           &buf[index]);
+               if (ret)
+                       return ret;
+
+               index += msg_size;
+       }
+
+       return ret;
+}
+
+static int
+lc898211_i2c_write(struct i2c_client *client, u16 len, u8 *data)
+{
+       struct i2c_msg msg;
+       const int num_msg = 1;
+       int ret;
+
+       msg.addr = client->addr;
+       msg.flags = 0;
+       msg.len = len;
+       msg.buf = data;
+
+       ret = i2c_transfer(client->adapter, &msg, 1);
+
+       if (ret == num_msg)
+               return 0;
+
+       return ret > 0 ? -EIO : ret;
+}
+
+static int
+__lc898211_write_reg(struct i2c_client *client, u16 data_length, u8 reg,
+                    u8 *val)
+{
+       u8 data[3];
+       const u16 len = sizeof(u8) + data_length; /* 8-bit address + data */
+
+       if (!client->adapter) {
+               v4l2_err(client, "%s: error, no client->adapter\n", __func__);
+               return -ENODEV;
+       }
+       if (data_length > 2) {
+               v4l2_err(client, "%s: write error: invalid length type %d\n",
+                        __func__, data_length);
+               return -EINVAL;
+       }
+
+       data[0] = reg;
+       data[1] = val[0];
+       if (data_length == 2)
+               data[2] = val[1];
+
+       return lc898211_i2c_write(client, len, data);
+}
+
+static int
+lc898211_write_reg8(struct i2c_client *client, u8 reg, u8 val)
+{
+       int ret;
+
+       ret = __lc898211_write_reg(client, sizeof(u8), reg, &val);
+       if (ret)
+               v4l2_err(client, "%s: write error: wrote 0x%x to offset 0x%x "
+                                "error %d\n", __func__, val, reg, ret);
+
+       return ret;
+}
+
+static int
+lc898211_write_reg16(struct i2c_client *client, u8 reg, u16 val)
+{
+       int ret;
+       u8 data[2] = { (u8)(val >> 8), (u8)val };
+
+       ret = __lc898211_write_reg(client, sizeof(u16), reg, data);
+       if (ret)
+               v4l2_err(client, "%s: write error: wrote 0x%x to offset 0x%x "
+                                "error %d\n", __func__, val, reg, ret);
+
+       return ret;
+}
+
+static int lc898211_wait_focus(struct v4l2_subdev *sd);
+static int lc898211_q_focus_abs(struct v4l2_subdev *sd, s32 *value);
+static int __lc898211_q_focus_abs(struct v4l2_subdev *sd, s16 *pos);
+
+static int lc898211_t_focus_abs(struct v4l2_subdev *sd, s32 value)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       int ret;
+       s16 step;
+       s16 focus;
+
+       /* Correct value */
+       value = clamp_t(s16, value, dev->af_tun.focus_abs_max,
+                       dev->af_tun.focus_abs_min);
+
+       ret = __lc898211_q_focus_abs(sd, &focus);
+       if (ret < 0) {
+               v4l2_err(client, "%s: error when requesting position\n",
+                        __func__);
+               return ret;
+       }
+       step = focus < value ? dev->step : -dev->step;
+       ret = lc898211_write_reg16(client, LC898211_REG16_MS1Z12, step);
+       if (ret) {
+               v4l2_err(client, "%s: error when setting slew\n",
+                        __func__);
+               return ret;
+       }
+
+       ret = lc898211_write_reg8(client, LC898211_REG_STMVINT, dev->timing);
+       if (ret < 0) {
+               v4l2_err(client, "%s: error when setting timing\n",
+                        __func__);
+               return ret;
+       }
+
+       ret = lc898211_write_reg16(client, LC898211_REG16_STMVEND, value << 6);
+       if (ret < 0) {
+               v4l2_err(client, "%s: error when setting new position\n",
+                        __func__);
+               return ret;
+       }
+
+       ret = lc898211_write_reg8(client, LC898211_REG_STMVEN,
+                                 LC898211_REG_STMVEN_MOVE_BUSY);
+       if (ret < 0) {
+               v4l2_err(client, "%s: error when enabling moving\n",
+                        __func__);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int lc898211_wait_focus(struct v4l2_subdev *sd)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       int retry, ret;
+       u8 val;
+
+       retry = LC898211_VCM_SLEW_RETRY_MAX;
+       while (--retry) {
+               s16 pos;
+               usleep_range(1000, 1500);
+               ret = __lc898211_q_focus_abs(sd, &pos);
+               if (ret < 0) {
+                       v4l2_err(client, "%s: error when checking current "
+                                        "position\n", __func__);
+                       return ret;
+               }
+
+               if (pos == LC898211_VCM_INVALID_MIN_POS ||
+                   pos == LC898211_VCM_INVALID_MAX_POS) {
+                       v4l2_err(client, "%s: invalid position\n", __func__);
+                       return ret;
+               }
+
+               ret = lc898211_read_reg(client, LC898211_8BIT,
+                                       LC898211_REG_STMVEN, &val);
+               if (ret < 0) {
+                       v4l2_err(client, "%s: error when checking if new "
+                                        "position is ready\n", __func__);
+                       return ret;
+               }
+               if (!(val & LC898211_REG_STMVEN_MOVE_BUSY))
+                       break;
+       }
+       if (!retry) {
+               v4l2_err(client, "%s: timeout when setting new position\n",
+                        __func__);
+               /* Clear move bit */
+               lc898211_write_reg8(client, LC898211_REG_STMVEN, 0);
+               return -ETIMEDOUT;
+       }
+
+       usleep_range(5000, 5500);
+
+       retry = LC898211_VCM_STAB_RETRY_MAX;
+       while (--retry) {
+               usleep_range(1000, 1500);
+               ret = lc898211_read_reg(client, LC898211_8BIT,
+                                       LC898211_REG_MSSET, &val);
+               if (ret < 0) {
+                       v4l2_err(client, "%s: error when checking if new "
+                                        "position is ready\n", __func__);
+                       return ret;
+               }
+               if (!(val & LC898211_REG_MSSET_BUSY))
+                       break;
+       }
+       if (!retry) {
+               v4l2_err(client, "%s: timeout when waiting for lens "
+                        "stabilization\n", __func__);
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+static int lc898211_t_focus_rel(struct v4l2_subdev *sd, s32 value)
+{
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       s16 focus;
+       int ret;
+
+       ret = __lc898211_q_focus_abs(sd, &focus);
+       if (ret < 0) {
+               v4l2_info(client, "%s: invalid focus position, assuming "
+                                 "default\n", __func__);
+               focus = (dev->af_tun.focus_abs_max +
+                                       dev->af_tun.focus_abs_min) / 2;
+       } else {
+               /* Normalizing focus value */
+               focus >>= 6;
+       }
+
+       return lc898211_t_focus_abs(sd, focus + value);
+}
+
+static int lc898211_q_focus_status(struct v4l2_subdev *sd, s32 *value)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       int busy;
+       int ret;
+       u8 val;
+
+       ret = lc898211_read_reg(client, LC898211_8BIT,
+                       LC898211_REG_STMVEN, &val);
+       if (ret < 0) {
+               v4l2_err(client, "%s: error when checking if new "
+                                "position is ready\n", __func__);
+               return ret;
+       }
+       busy = val & LC898211_REG_STMVEN_MOVE_BUSY;
+       if (busy)
+               goto out;
+
+       ret = lc898211_read_reg(client, LC898211_8BIT,
+                       LC898211_REG_MSSET, &val);
+       if (ret < 0) {
+               v4l2_err(client, "%s: error when checking if lens "
+                                "stabilization is ready\n", __func__);
+               return ret;
+       }
+       busy = val & LC898211_REG_MSSET_BUSY;
+
+out:
+       if (busy) {
+               *value = ATOMISP_FOCUS_STATUS_MOVING |
+                        ATOMISP_FOCUS_HP_IN_PROGRESS;
+       } else {
+               *value = ATOMISP_FOCUS_STATUS_ACCEPTS_NEW_MOVE |
+                        ATOMISP_FOCUS_HP_COMPLETE;
+       }
+
+       return 0;
+}
+
+static int __lc898211_q_focus_abs(struct v4l2_subdev *sd, s16 *pos)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       int ret;
+
+       ret = lc898211_read_reg(client, LC898211_16BIT, LC898211_REG16_RZ,
+                               pos);
+       if (ret < 0) {
+               v4l2_err(client, "%s: failed to read lens position\n",
+                        __func__);
+               return ret;
+       }
+
+       *pos = be16_to_cpu(*pos);
+       if (*pos == LC898211_VCM_INVALID_MIN_POS ||
+           *pos == LC898211_VCM_INVALID_MAX_POS)
+               return -EINVAL;
+       *pos >>= 6;
+
+       return 0;
+}
+
+static int lc898211_q_focus_abs(struct v4l2_subdev *sd, s32 *value)
+{
+       s16 focus;
+       int ret;
+
+       ret = __lc898211_q_focus_abs(sd, &focus);
+       if (ret < 0)
+               return ret;
+
+       *value = focus;
+
+       return 0;
+}
+
+static int lc898211_t_vcm_slew(struct v4l2_subdev *sd, s32 value)
+{
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+
+       if (value & ~LC898211_VCM_SLEW_MASK || value > LC898211_VCM_SLEW_MAX)
+               return -EINVAL;
+
+       dev->step = value;
+
+       return 0;
+}
+
+static int lc898211_t_vcm_timing(struct v4l2_subdev *sd, s32 value)
+{
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+
+       if (value < 0 || value > LC898211_VCM_SLEW_TIME_MAX)
+               return -EINVAL;
+
+       dev->timing = value;
+
+       return 0;
+}
+
+static struct lc898211_control lc898211_controls[] = {
+       {
+               .qc = {
+                       .id = V4L2_CID_FOCUS_ABSOLUTE,
+                       .type = V4L2_CTRL_TYPE_INTEGER,
+                       .name = "focus move absolute",
+                       .step = 1,
+                       .default_value = 0,
+                       .flags = 0,
+               },
+               .tweak = lc898211_t_focus_abs,
+               .query = lc898211_q_focus_abs,
+       },
+       {
+               .qc = {
+                       .id = V4L2_CID_FOCUS_RELATIVE,
+                       .type = V4L2_CTRL_TYPE_INTEGER,
+                       .name = "focus move relative",
+                       .step = 1,
+                       .default_value = 0,
+                       .flags = 0,
+               },
+               .tweak = lc898211_t_focus_rel,
+       },
+       {
+               .qc = {
+                       .id = V4L2_CID_FOCUS_STATUS,
+                       .type = V4L2_CTRL_TYPE_INTEGER,
+                       .name = "focus status",
+                       .step = 1,
+                       .default_value = 0,
+                       .flags = 0,
+               },
+               .query = lc898211_q_focus_status,
+       },
+       {
+               .qc = {
+                       .id = V4L2_CID_VCM_SLEW,
+                       .type = V4L2_CTRL_TYPE_INTEGER,
+                       .name = "vcm slew",
+                       .step = 1,
+                       .default_value = LC898211_VCM_SLEW_DEFAULT,
+                       .flags = 0,
+               },
+               .tweak = lc898211_t_vcm_slew,
+       },
+       {
+               .qc = {
+                       .id = V4L2_CID_VCM_TIMEING,
+                       .type = V4L2_CTRL_TYPE_INTEGER,
+                       .name = "vcm step time",
+                       .step = 1,
+                       .default_value = 0,
+                       .flags = 0,
+               },
+               .tweak = lc898211_t_vcm_timing,
+       },
+};
+#define N_CONTROLS (ARRAY_SIZE(lc898211_controls))
+
+static struct lc898211_control *lc898211_find_control(u32 id)
+{
+       int i;
+
+       for (i = 0; i < N_CONTROLS; i++)
+               if (lc898211_controls[i].qc.id == id)
+                       return &lc898211_controls[i];
+       return NULL;
+}
+
+static int lc898211_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc)
+{
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       struct lc898211_control *ctrl = lc898211_find_control(qc->id);
+
+       if (ctrl == NULL)
+               return -EINVAL;
+
+       mutex_lock(&dev->input_lock);
+       *qc = ctrl->qc;
+       mutex_unlock(&dev->input_lock);
+
+       return 0;
+}
+
+static int lc898211_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
+{
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       struct lc898211_control *s_ctrl;
+       int ret;
+
+       s_ctrl = lc898211_find_control(ctrl->id);
+       if ((s_ctrl == NULL) || (s_ctrl->query == NULL))
+               return -EINVAL;
+
+       mutex_lock(&dev->input_lock);
+       ret = s_ctrl->query(sd, &ctrl->value);
+       mutex_unlock(&dev->input_lock);
+
+       return ret;
+}
+
+static int lc898211_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
+{
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       struct lc898211_control *octrl = lc898211_find_control(ctrl->id);
+       int ret;
+
+       if ((octrl == NULL) || (octrl->tweak == NULL))
+               return -EINVAL;
+
+       mutex_lock(&dev->input_lock);
+       ret = octrl->tweak(sd, ctrl->value);
+       mutex_unlock(&dev->input_lock);
+
+       return ret;
+}
+
+static int lc898211_get_eeprom_data(struct v4l2_subdev *sd)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+
+       /* Cache EEPROM data */
+       return lc898211_eep_read_reg_array(client, LC898211_EEP_SIZE,
+                                          LC898211_EEP_START_ADDR,
+                                          dev->eeprom);
+}
+
+/*
+ * Normalize AF tuning values in 2 steps:
+ * 1: Values come in big enddian format
+ * 2: Values are s10 written in the most significant 10 bits of s16.
+ */
+static void __normalize_af_tun_value(s16 *value)
+{
+       *value = (s16)be16_to_cpu(*value);
+       *value >>= 6;
+}
+
+static int lc898211_get_af_tuning(struct v4l2_subdev *sd)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       int ret;
+
+       ret = lc898211_eep_read_reg_array(client, LC898211_EEP_AF_TUN_SIZE,
+                                         LC898211_EEP_AF_TUN_START,
+                                         &dev->af_tun);
+       if (ret < 0)
+               return ret;
+
+       __normalize_af_tun_value(&dev->af_tun.focus_abs_min);
+       __normalize_af_tun_value(&dev->af_tun.focus_abs_max);
+       __normalize_af_tun_value(&dev->af_tun.inf_pos);
+       __normalize_af_tun_value(&dev->af_tun.mac_pos);
+
+       return 0;
+}
+static int lc898211_init_registers(struct v4l2_subdev *sd)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       struct lc898211_eeprom_data *edata;
+       int i;
+
+       /* Read actuator initialization registers list from EEPROM */
+       for (i = 0, edata = &dev->eeprom[i]; i < LC898211_EEP_NUM_DATA;
+            i++, edata++) {
+               int len;
+               int ret;
+               u8 dest_data[2];
+
+               if (edata->addr == LC898211_EEP_ADDR_EOF)
+                       break;
+
+               if (edata->addr == LC898211_EEP_ADDR_DELAY) {
+                       u16 delay = be16_to_cpu(*(u16 *)edata->data);
+                       usleep_range(delay * 1000, delay * 1500);
+                       continue;
+               }
+
+               len = edata->size;
+               if (len > LC898211_EEP_DATA_SIZE_MAX) {
+                       v4l2_err(client, "%s: malformed initialization data "
+                                "from EEPROM: data size too long.\n",
+                                __func__);
+                       return -EINVAL;
+               }
+
+               switch (edata->type) {
+               case LC898211_EEP_DATA_DIRECT:
+                       dest_data[0] = edata->data[0];
+                       dest_data[1] = edata->data[1];
+                       break;
+               case LC898211_EEP_DATA_INDIRECT_EEP:
+                       ret = lc898211_eep_read_reg(client, len, edata->data[0],
+                                                   dest_data);
+                       if (ret < 0) {
+                               v4l2_err(client, "%s: error reading indirect "
+                                        "data from EEPROM.\n", __func__);
+                               return ret;
+                       }
+                       break;
+               case LC898211_EEP_DATA_INDIRECT_HVCA:
+                       ret = lc898211_read_reg(client, len, edata->data[0],
+                                               dest_data);
+                       if (ret < 0) {
+                               v4l2_err(client, "%s: error reading indirect "
+                                                "data from HVCA.\n", __func__);
+                               return ret;
+                       }
+                       break;
+               case LC898211_EEP_DATA_MASK_AND:
+                       ret = lc898211_read_reg(client, len, edata->addr,
+                                               dest_data);
+                       if (ret < 0) {
+                               v4l2_err(client, "%s: error reading indirect "
+                                                "data from HVCA.\n", __func__);
+                               return ret;
+                       }
+                       dest_data[0] &= edata->data[0];
+                       dest_data[1] &= edata->data[1];
+                       break;
+               case LC898211_EEP_DATA_MASK_OR:
+                       ret = lc898211_read_reg(client, len, edata->addr,
+                                               dest_data);
+                       if (ret < 0) {
+                               v4l2_err(client, "%s: error reading indirect "
+                                                "data from HVCA.\n", __func__);
+                               return ret;
+                       }
+                       dest_data[0] |= edata->data[0];
+                       dest_data[1] |= edata->data[1];
+                       break;
+               default:
+                       v4l2_err(client, "%s: malformed initialization data "
+                                "from EEPROM: unknown data type.\n", __func__);
+                       return -EINVAL;
+               }
+
+               ret = __lc898211_write_reg(client, edata->size, edata->addr,
+                                          dest_data);
+               if (ret < 0) {
+                       v4l2_err(client, "%s: error writing initialization "
+                                        "data.\n", __func__);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int lc898211_warmup(struct v4l2_subdev *sd)
+{
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       const int bottom_step = dev->af_tun.focus_abs_min /
+                                                       LC898211_WARMUP_STEP;
+       const int top_step = dev->af_tun.focus_abs_max / LC898211_WARMUP_STEP;
+       int target_pos;
+       int i;
+       int ret;
+
+       /* Warmup bottom direction */
+       for (i = LC898211_WARMUP_POS_START; i <= LC898211_WARMUP_POS_END; i++) {
+               target_pos = i * bottom_step;
+               ret = lc898211_t_focus_abs(sd, target_pos);
+               if (ret < 0) {
+                       v4l2_err(client, "%s: failed to warmup to bottom "
+                                        "direction [%d / %d]\n", __func__,
+                                        i, LC898211_WARMUP_POS_END);
+                       return ret;
+               }
+               ret = lc898211_wait_focus(sd);
+               if (ret)
+                       return ret;
+               usleep_range(1000, 1500);
+       }
+
+       /* Warmup top direction */
+       for (i = LC898211_WARMUP_POS_START; i <= LC898211_WARMUP_POS_END; i++) {
+               target_pos = i * top_step;
+               ret = lc898211_t_focus_abs(sd, target_pos);
+               if (ret < 0) {
+                       v4l2_err(client, "%s: failed to warmup to top "
+                                        "direction [%d / %d]\n", __func__,
+                                        i, LC898211_WARMUP_POS_END);
+                       return ret;
+               }
+               ret = lc898211_wait_focus(sd);
+               if (ret)
+                       return ret;
+               if (i < LC898211_WARMUP_POS_END)
+                       usleep_range(1000, 1500);
+       }
+
+       return 0;
+}
+
+static int lc898211_init(struct v4l2_subdev *sd, u32 val)
+{
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       int ret;
+
+       mutex_lock(&dev->input_lock);
+       /* set inital registers */
+       ret = lc898211_t_vcm_slew(sd, LC898211_VCM_SLEW_DEFAULT);
+       if (ret)
+               goto out;
+       ret = lc898211_init_registers(sd);
+       if (ret)
+               goto out;
+       ret = lc898211_t_vcm_timing(sd, LC898211_VCM_SLEW_TIME_DEFAULT);
+       if (ret)
+               goto out;
+       ret = lc898211_warmup(sd);
+       if (ret)
+               goto out;
+
+       /* set VCM to home position */
+       ret = lc898211_t_focus_abs(sd,
+                       (dev->af_tun.inf_pos + dev->af_tun.mac_pos) / 2);
+
+out:
+       mutex_unlock(&dev->input_lock);
+       return ret;
+}
+
+static int lc898211_power_up(struct v4l2_subdev *sd)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       int ret;
+
+       /* power control */
+       ret = dev->platform_data->power_ctrl(sd, 1);
+       if (ret)
+               goto fail_power;
+
+       /* flis clock control */
+       ret = dev->platform_data->flisclk_ctrl(sd, 1);
+       if (ret)
+               goto fail_clk;
+
+       /* gpio ctrl */
+       ret = dev->platform_data->gpio_ctrl(sd, 1);
+       if (ret)
+               dev_err(&client->dev, "gpio failed 1\n");
+       msleep(20);
+
+       return 0;
+
+fail_clk:
+       dev->platform_data->flisclk_ctrl(sd, 0);
+fail_power:
+       dev->platform_data->power_ctrl(sd, 0);
+       dev_err(&client->dev, "sensor power-up failed\n");
+
+       return ret;
+}
+
+static int lc898211_power_down(struct v4l2_subdev *sd)
+{
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       int ret;
+
+       ret = dev->platform_data->flisclk_ctrl(sd, 0);
+       if (ret)
+               dev_err(&client->dev, "flisclk failed\n");
+
+       /* gpio ctrl */
+       ret = dev->platform_data->gpio_ctrl(sd, 0);
+       if (ret)
+               dev_err(&client->dev, "gpio failed 1\n");
+
+       /* power control */
+       ret = dev->platform_data->power_ctrl(sd, 0);
+       if (ret)
+               dev_err(&client->dev, "vprog failed.\n");
+
+       return ret;
+}
+
+static int lc898211_g_priv_int_data(struct v4l2_subdev *sd,
+                                   struct v4l2_private_int_data *priv)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(sd);
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+       u32 read_size = priv->size;
+       const int af_tun_size = sizeof(dev->af_tun);
+       int ret;
+
+       /* Return correct size */
+       priv->size = af_tun_size;
+
+       /* No need to copy data if size is 0 */
+       if (!read_size)
+               return 0;
+
+       /* Correct read_size value only if bigger than maximum */
+       if (read_size > af_tun_size)
+               read_size = af_tun_size;
+
+       ret = copy_to_user(priv->data, &dev->af_tun, read_size);
+       if (ret) {
+               v4l2_err(client, "%s: failed to copy EEPROM data to user\n",
+                        __func__);
+               return -EFAULT;
+       }
+
+       return 0;
+}
+
+static long lc898211_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
+{
+       switch (cmd) {
+       case ATOMISP_IOC_G_MOTOR_PRIV_INT_DATA:
+               return lc898211_g_priv_int_data(sd, arg);
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static const struct v4l2_subdev_core_ops lc898211_core_ops = {
+       .queryctrl = lc898211_queryctrl,
+       .g_ctrl = lc898211_g_ctrl,
+       .s_ctrl = lc898211_s_ctrl,
+       .init = lc898211_init,
+       .ioctl = lc898211_ioctl,
+};
+
+static const struct v4l2_subdev_ops lc898211_ops = {
+       .core = &lc898211_core_ops,
+};
+
+static int lc898211_remove(struct i2c_client *client)
+{
+       struct v4l2_subdev *sd = i2c_get_clientdata(client);
+       struct lc898211_dev *dev = to_lc898211_dev(sd);
+
+       v4l2_device_unregister_subdev(sd);
+       kfree(dev);
+
+       return 0;
+}
+
+static int lc898211_probe(struct i2c_client *client,
+                        const struct i2c_device_id *id)
+{
+       struct lc898211_dev *dev;
+       int ret;
+
+       /* allocate sensor device & init sub device */
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (!dev) {
+               v4l2_err(client, "%s: out of memory\n", __func__);
+               return -ENOMEM;
+       }
+
+       mutex_init(&dev->input_lock);
+
+       v4l2_i2c_subdev_init(&dev->sd, client, &lc898211_ops);
+
+       dev->platform_data = client->dev.platform_data;
+       if (!dev->platform_data)
+               v4l2_info(client, "%s: driver has no platform data\n",
+                         __func__);
+
+       lc898211_power_up(&dev->sd);
+       ret = lc898211_get_eeprom_data(&dev->sd);
+       if (ret) {
+               v4l2_err(client, "%s: failed to read EEPROM data\n",
+                        __func__);
+               goto err;
+       }
+
+       ret = lc898211_get_af_tuning(&dev->sd);
+       if (ret) {
+               v4l2_err(client, "%s: failed to read AF tuning data\n",
+                        __func__);
+               goto err;
+       }
+
+       lc898211_power_down(&dev->sd);
+
+       v4l2_info(client, "LC898211 actuator successfully initialized\n");
+
+       return 0;
+
+err:
+       lc898211_remove(client);
+       return ret;
+}
+
+static const struct i2c_device_id lc898211_id[] = {
+       {LC898211_NAME, 0},
+       {}
+};
+
+MODULE_DEVICE_TABLE(i2c, lc898211_id);
+
+static struct i2c_driver lc898211_driver = {
+       .driver = {
+               .owner = THIS_MODULE,
+               .name = LC898211_NAME,
+       },
+       .probe = lc898211_probe,
+       .remove = lc898211_remove,
+       .id_table = lc898211_id,
+};
+
+static __init int init_lc898211(void)
+{
+       return i2c_add_driver(&lc898211_driver);
+}
+
+static __exit void exit_lc898211(void)
+{
+       i2c_del_driver(&lc898211_driver);
+}
+
+module_init(init_lc898211);
+module_exit(exit_lc898211);
+
+MODULE_DESCRIPTION("A low-level driver for LC898211 actuator");
+MODULE_AUTHOR("David Cohen <david.a.cohen@intel.com>");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/media/video/lc898211.h b/drivers/media/video/lc898211.h
new file mode 100644 (file)
index 0000000..4b00e17
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Support for Semco LC898211 IC.
+ *
+ * Copyright (c) 2012 Intel Corporation. All Rights Reserved.
+ *
+ * Author: David Cohen <david.a.cohen@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#ifndef __LC898211_H__
+#define __LC898211_H__
+
+#include <linux/atomisp_platform.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <media/v4l2-subdev.h>
+
+#define        LC898211_NAME                   "lc898211"
+#define LC898211_MAX_I2C_MSG_LEN       30
+#define LC898211_I2C_MSG_LENGTH                2
+
+#define LC898211_16BIT                 2
+#define LC898211_8BIT                  1
+
+/*
+ * EEPROM: I2C ID <-> ADDRESS
+ *
+ * PAGE 0: 0xa0 <-> 0x000-0x0ff
+ * PAGE 1: 0xa1 <-> 0x100-0x1ff
+ * PAGE 2: 0xa2 <-> 0x200-0x2ff
+ * PAGE 3: 0xa3 <-> 0x300-0x3ff
+ */
+#define        LC898211_EEP_ID_BASE            0xa0
+#define LC898211_EEP_PAGE_SIZE         0x100
+#define LC898211_EEP_PAGE_MASK         (~(LC898211_EEP_PAGE_SIZE - 1))
+#define LC898211_EEP_ID(page)          ((LC898211_EEP_ID_BASE | ((page) << 1)) >> 1)
+#define LC898211_EEP_GET_PAGE(addr)    (((addr) & 0x100) >> 8)
+#define LC898211_EEP_GET_ID(addr)      LC898211_EEP_ID(LC898211_EEP_GET_PAGE(addr))
+
+#define LC898211_EEP_START_ADDR                0x030
+#define LC898211_EEP_END_ADDR          0x1bf
+#define LC898211_EEP_SIZE              (LC898211_EEP_END_ADDR - \
+                                        LC898211_EEP_START_ADDR + 1)
+#define LC898211_EEP_ADDR_EOF          0xff
+#define LC898211_EEP_ADDR_DELAY                0xdd
+
+#define LC898211_EEP_DATA_DIRECT       0x0
+#define LC898211_EEP_DATA_INDIRECT_EEP 0x1
+#define LC898211_EEP_DATA_INDIRECT_HVCA        0x2
+#define LC898211_EEP_DATA_MASK_AND     0x7
+#define LC898211_EEP_DATA_MASK_OR      0x8
+#define LC898211_EEP_DATA_SIZE_MAX     2
+
+#define LC898211_EEP_INF1_H            0x20
+#define LC898211_EEP_INF1_L            0x21
+#define LC898211_EEP_MAC1_H            0x22
+#define LC898211_EEP_MAC1_L            0x23
+#define LC898211_EEP_INF2_H            0x24
+#define LC898211_EEP_INF2_L            0x25
+#define LC898211_EEP_MAC2_H            0x26
+#define LC898211_EEP_MAC2_L            0x27
+#define LC898211_EEP_AF_TUN_START      LC898211_EEP_INF1_H
+#define LC898211_EEP_AF_TUN_END                LC898211_EEP_MAC2_L
+#define LC898211_EEP_AF_TUN_SIZE       (LC898211_EEP_AF_TUN_END - \
+                                        LC898211_EEP_AF_TUN_START + 1)
+
+struct lc898211_eeprom_data {
+       u8 addr;
+       unsigned size:4;
+       unsigned type:4;
+       u8 data[2];
+};
+
+#define LC898211_EEP_DATA_SIZE         sizeof(struct lc898211_eeprom_data)
+#define LC898211_EEP_NUM_DATA          (LC898211_EEP_SIZE /            \
+                                                       LC898211_EEP_DATA_SIZE)
+
+struct lc898211_eeprom_af_tun {
+       s16 focus_abs_min;
+       s16 focus_abs_max;
+       s16 inf_pos;
+       s16 mac_pos;
+};
+
+
+#define LC898211_VCM_SLEW_MASK         0x7fa0
+/*
+ * FIXME: SLEW_MAX was defined base on valid register value, but a smaller
+ * sane value must be defined.
+ */
+#define LC898211_VCM_SLEW_MAX          0x7fa0
+#define LC898211_VCM_SLEW_MIN          0x280
+#define LC898211_VCM_SLEW_DEFAULT      LC898211_VCM_SLEW_MIN
+#define LC898211_VCM_ABS_MASK          0xffa0
+
+#define LC898211_VCM_SLEW_TIME_MAX     0xff
+#define LC898211_VCM_SLEW_TIME_DEFAULT 0x2
+#define LC898211_VCM_SLEW_RETRY_MAX    50
+#define LC898211_VCM_STAB_RETRY_MAX    15
+#define LC898211_VCM_HOME_POS          0
+#define LC898211_VCM_INVALID_MIN_POS   0x8001
+#define LC898211_VCM_INVALID_MAX_POS   0x7fff
+
+#define LC898211_WARMUP_STEP           10      /* 10% */
+#define LC898211_WARMUP_POS_START      1
+#define LC898211_WARMUP_POS_END                6
+
+#define LC898211_REG16_MS1Z12          0x16    /* VCM slew */
+#define LC898211_REG_STMVINT           0xa0    /* VCM step timing */
+#define LC898211_REG16_STMVEND         0xa1    /* target position */
+#define LC898211_REG16_RZ              0x04    /* current position */
+
+#define LC898211_REG_STMVEN            0x8a
+#define LC898211_REG_STMVEN_MOVE_BUSY  (1 << 0)        /* moving? */
+
+#define LC898211_REG_MSSET             0x8f
+#define LC898211_REG_MSSET_BUSY                (1 << 0)        /* stabilized? */
+
+struct lc898211_dev {
+       struct v4l2_subdev sd;
+       struct mutex input_lock;        /* serialize access to priv data */
+       int step;
+       int timing;
+       int moving;
+       int power;
+
+       struct camera_sensor_platform_data *platform_data;
+       struct lc898211_eeprom_data eeprom[LC898211_EEP_NUM_DATA];
+       struct lc898211_eeprom_af_tun af_tun;
+};
+
+struct lc898211_control {
+       struct v4l2_queryctrl qc;
+       int (*query)(struct v4l2_subdev *sd, s32 *value);
+       int (*tweak)(struct v4l2_subdev *sd, s32 value);
+};
+
+#endif