From: David Cohen Date: Wed, 28 Mar 2012 13:52:36 +0000 (+0300) Subject: atomisp: lc898211: HVCA Semco actuator driver X-Git-Tag: 2.1b_release~833 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=68106272af3a22e7adcea182137809e8a17ab497;p=kernel%2Fkernel-mfld-blackbay.git atomisp: lc898211: HVCA Semco actuator driver BZ: 27947 This patch adds LC898211 IC driver to control Hybrid VCA Semco actuator driver. Change-Id: I22d7345fb8bb82be9ed61e52768f2bc6cc412610 Signed-off-by: David Cohen Reviewed-on: http://android.intel.com:8080/42535 Reviewed-by: Kruger, Jozef Reviewed-by: Samurov, Vitali Reviewed-by: Koskinen, Ilkka Tested-by: Koski, Anttu Reviewed-by: buildbot Tested-by: buildbot --- diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 878d233..c77fb75 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -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 diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index beae10c..0d2ef98 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -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 index 0000000..888e69c --- /dev/null +++ b/drivers/media/video/lc898211.c @@ -0,0 +1,972 @@ +/* + * Support for lc898211 actuator. + * + * Copyright (c) 2012 Intel Corporation. All Rights Reserved. + * + * Author: David Cohen + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = ®, + }, { + .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 "); +MODULE_LICENSE("GPL"); + diff --git a/drivers/media/video/lc898211.h b/drivers/media/video/lc898211.h new file mode 100644 index 0000000..4b00e17 --- /dev/null +++ b/drivers/media/video/lc898211.h @@ -0,0 +1,152 @@ +/* + * Support for Semco LC898211 IC. + * + * Copyright (c) 2012 Intel Corporation. All Rights Reserved. + * + * Author: David Cohen + * + * 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 +#include +#include +#include + +#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