--- /dev/null
+/*
+ * Samsung Exynos5 SoC series Actuator driver
+ *
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ *
+ * 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.
+ */
+
+#include <linux/i2c.h>
+#include <linux/time.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/videodev2.h>
+#include <linux/videodev2_exynos_camera.h>
+
+#include "fimc-is-actuator-lc898217.h"
+#include "fimc-is-device-sensor.h"
+#include "fimc-is-device-sensor-peri.h"
+#include "fimc-is-core.h"
+
+#include "fimc-is-helper-i2c.h"
+
+#include "interface/fimc-is-interface-library.h"
+
+#define ACTUATOR_NAME "LC898217"
+
+#define DEF_LC898217_FIRST_POSITION 120
+#define DEF_LC898217_FIRST_DELAY 30
+
+/* register definition */
+#define REG_TARGET_HIGH 0x84
+#define REG_TARGET_LOW 0x85
+#define REG_SRV_STATE1 0xB0
+#define REG_TGT_CNVTIM 0xB2
+
+extern struct fimc_is_lib_support gPtr_lib_support;
+extern struct fimc_is_sysfs_actuator sysfs_actuator;
+
+static int sensor_lc898217_write_position(struct i2c_client *client, u32 val)
+{
+ int ret = 0;
+ u8 val_high = 0, val_low = 0;
+
+ BUG_ON(!client);
+
+ if (!client->adapter) {
+ err("Could not find adapter!\n");
+ ret = -ENODEV;
+ goto p_err;
+ }
+
+ if (val > LC898217_POS_MAX_SIZE) {
+ err("Invalid af position(position : %d, Max : %d).\n",
+ val, LC898217_POS_MAX_SIZE);
+ ret = -EINVAL;
+ goto p_err;
+ }
+
+ val_high = (val >> 8) & 0xFF;
+ val_low = val & 0xFF;
+
+ ret = fimc_is_sensor_addr8_write8(client, REG_TARGET_HIGH, val_high);
+ if (ret < 0)
+ goto p_err;
+ ret = fimc_is_sensor_addr8_write8(client, REG_TARGET_LOW, val_low);
+
+p_err:
+ return ret;
+}
+
+int sensor_lc898217_actuator_init(struct v4l2_subdev *subdev, u32 val)
+{
+ int ret = 0;
+ u8 product_id = 0;
+ struct fimc_is_actuator *actuator;
+ struct i2c_client *client = NULL;
+#ifdef DEBUG_ACTUATOR_TIME
+ struct timeval st, end;
+ do_gettimeofday(&st);
+#endif
+
+ long cal_addr;
+ u32 cal_data;
+
+ int first_position = DEF_LC898217_FIRST_POSITION;
+
+ BUG_ON(!subdev);
+
+ dbg_actuator("%s\n", __func__);
+
+ actuator = (struct fimc_is_actuator *)v4l2_get_subdevdata(subdev);
+ if (!actuator) {
+ err("actuator is not detect!\n");
+ goto p_err;
+ }
+
+ client = actuator->client;
+ if (unlikely(!client)) {
+ err("client is NULL");
+ ret = -EINVAL;
+ goto p_err;
+ }
+
+ I2C_MUTEX_LOCK(actuator->i2c_lock);
+ ret = fimc_is_sensor_addr8_read8(client, 0x03, &product_id);
+ if (ret < 0)
+ goto p_err_mutex;
+
+#if 0
+ if (product_id != LC898217_PRODUCT_ID) {
+ err("LC898217 is not detected(%d), Slave: %d\n", product_id, client->addr);
+ goto p_err_mutex;
+ }
+#endif
+
+ /* EEPROM AF calData address */
+ if (gPtr_lib_support.binary_load_flg) {
+ /* get pan_focus */
+ cal_addr = gPtr_lib_support.minfo->kvaddr_rear_cal + EEPROM_OEM_BASE;
+ memcpy((void *)&cal_data, (void *)cal_addr, sizeof(cal_data));
+
+ if (cal_data > 0)
+ first_position = cal_data;
+ } else {
+ warn("SDK library is not loaded");
+ }
+
+ ret = sensor_lc898217_write_position(client, first_position);
+ if (ret < 0)
+ goto p_err_mutex;
+ actuator->position = first_position;
+
+ /* Go active mode */
+ ret = fimc_is_sensor_addr8_write8(client, 0x02, 0);
+ if (ret < 0)
+ goto p_err_mutex;
+
+ dbg_actuator("initial position: %d\n", first_position);
+
+ mdelay(DEF_LC898217_FIRST_DELAY);
+
+#ifdef DEBUG_ACTUATOR_TIME
+ do_gettimeofday(&end);
+ pr_info("[%s] time %lu us", __func__, (end.tv_sec - st.tv_sec) * 1000000 + (end.tv_usec - st.tv_usec));
+#endif
+
+p_err_mutex:
+ I2C_MUTEX_UNLOCK(actuator->i2c_lock);
+
+p_err:
+ return ret;
+}
+
+int sensor_lc898217_actuator_get_status(struct v4l2_subdev *subdev, u32 *info)
+{
+ int ret = 0;
+ struct fimc_is_actuator *actuator = NULL;
+ struct i2c_client *client = NULL;
+ enum fimc_is_actuator_status status = ACTUATOR_STATUS_NO_BUSY;
+#ifdef DEBUG_ACTUATOR_TIME
+ struct timeval st, end;
+ do_gettimeofday(&st);
+#endif
+
+ dbg_actuator("%s\n", __func__);
+
+ BUG_ON(!subdev);
+ BUG_ON(!info);
+
+ actuator = (struct fimc_is_actuator *)v4l2_get_subdevdata(subdev);
+ BUG_ON(!actuator);
+
+ client = actuator->client;
+ if (unlikely(!client)) {
+ err("client is NULL");
+ ret = -EINVAL;
+ goto p_err;
+ }
+
+ /*
+ * The info is busy flag.
+ * But, this module can't get busy flag.
+ */
+ status = ACTUATOR_STATUS_NO_BUSY;
+ *info = status;
+
+#ifdef DEBUG_ACTUATOR_TIME
+ do_gettimeofday(&end);
+ pr_info("[%s] time %lu us", __func__, (end.tv_sec - st.tv_sec) * 1000000 + (end.tv_usec - st.tv_usec));
+#endif
+
+p_err:
+ return ret;
+}
+
+int sensor_lc898217_actuator_set_position(struct v4l2_subdev *subdev, u32 *info)
+{
+ int ret = 0;
+ struct fimc_is_actuator *actuator;
+ struct i2c_client *client;
+ u32 position = 0;
+#ifdef DEBUG_ACTUATOR_TIME
+ struct timeval st, end;
+ do_gettimeofday(&st);
+#endif
+
+ BUG_ON(!subdev);
+ BUG_ON(!info);
+
+ actuator = (struct fimc_is_actuator *)v4l2_get_subdevdata(subdev);
+ BUG_ON(!actuator);
+
+ client = actuator->client;
+ if (unlikely(!client)) {
+ err("client is NULL");
+ ret = -EINVAL;
+ goto p_err;
+ }
+
+ I2C_MUTEX_LOCK(actuator->i2c_lock);
+ position = *info;
+ if (position > LC898217_POS_MAX_SIZE) {
+ err("Invalid af position(position : %d, Max : %d).\n",
+ position, LC898217_POS_MAX_SIZE);
+ ret = -EINVAL;
+ goto p_err;
+ }
+
+ /* debug option : fixed position testing */
+ if (sysfs_actuator.enable_fixed)
+ position = sysfs_actuator.fixed_position;
+
+ /* position Set */
+ ret = sensor_lc898217_write_position(client, position);
+ if (ret < 0)
+ goto p_err;
+ actuator->position = position;
+
+ dbg_actuator("%s: position(%d)\n", __func__, position);
+
+#ifdef DEBUG_ACTUATOR_TIME
+ do_gettimeofday(&end);
+ pr_info("[%s] time %lu us", __func__, (end.tv_sec - st.tv_sec) * 1000000 + (end.tv_usec - st.tv_usec));
+#endif
+p_err:
+ I2C_MUTEX_UNLOCK(actuator->i2c_lock);
+ return ret;
+}
+
+static int sensor_lc898217_actuator_g_ctrl(struct v4l2_subdev *subdev, struct v4l2_control *ctrl)
+{
+ int ret = 0;
+ u32 val = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_ACTUATOR_GET_STATUS:
+ ret = sensor_lc898217_actuator_get_status(subdev, &val);
+ if (ret < 0) {
+ err("err!!! ret(%d), actuator status(%d)", ret, val);
+ ret = -EINVAL;
+ goto p_err;
+ }
+ break;
+ default:
+ err("err!!! Unknown CID(%#x)", ctrl->id);
+ ret = -EINVAL;
+ goto p_err;
+ }
+
+ ctrl->value = val;
+
+p_err:
+ return ret;
+}
+
+static int sensor_lc898217_actuator_s_ctrl(struct v4l2_subdev *subdev, struct v4l2_control *ctrl)
+{
+ int ret = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_ACTUATOR_SET_POSITION:
+ ret = sensor_lc898217_actuator_set_position(subdev, &ctrl->value);
+ if (ret) {
+ err("failed to actuator set position: %d, (%d)\n", ctrl->value, ret);
+ ret = -EINVAL;
+ goto p_err;
+ }
+ break;
+ default:
+ err("err!!! Unknown CID(%#x)", ctrl->id);
+ ret = -EINVAL;
+ goto p_err;
+ }
+
+p_err:
+ return ret;
+}
+
+static const struct v4l2_subdev_core_ops core_ops = {
+ .init = sensor_lc898217_actuator_init,
+ .g_ctrl = sensor_lc898217_actuator_g_ctrl,
+ .s_ctrl = sensor_lc898217_actuator_s_ctrl,
+};
+
+static const struct v4l2_subdev_ops subdev_ops = {
+ .core = &core_ops,
+};
+
+static int sensor_lc898217_actuator_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret = 0;
+ struct fimc_is_core *core = NULL;
+ struct v4l2_subdev *subdev_actuator = NULL;
+ struct fimc_is_actuator *actuator = NULL;
+ struct fimc_is_device_sensor *device = NULL;
+ u32 sensor_id = 0;
+ u32 place = 0;
+ struct device *dev;
+ struct device_node *dnode;
+
+ BUG_ON(!fimc_is_dev);
+ BUG_ON(!client);
+
+ core = (struct fimc_is_core *)dev_get_drvdata(fimc_is_dev);
+ if (!core) {
+ err("core device is not yet probed");
+ ret = -EPROBE_DEFER;
+ goto p_err;
+ }
+
+ dev = &client->dev;
+ dnode = dev->of_node;
+
+ ret = of_property_read_u32(dnode, "id", &sensor_id);
+ if (ret) {
+ err("id read is fail(%d)", ret);
+ goto p_err;
+ }
+
+ ret = of_property_read_u32(dnode, "place", &place);
+ if (ret) {
+ pr_info("place read is fail(%d)", ret);
+ place = 0;
+ }
+ probe_info("%s sensor_id(%d) actuator_place(%d)\n", __func__, sensor_id, place);
+
+ device = &core->sensor[sensor_id];
+ if (!test_bit(FIMC_IS_SENSOR_PROBE, &device->state)) {
+ err("sensor device is not yet probed");
+ ret = -EPROBE_DEFER;
+ goto p_err;
+ }
+
+ actuator = kzalloc(sizeof(struct fimc_is_actuator), GFP_KERNEL);
+ if (!actuator) {
+ err("actuator is NULL");
+ ret = -ENOMEM;
+ goto p_err;
+ }
+
+ subdev_actuator = kzalloc(sizeof(struct v4l2_subdev), GFP_KERNEL);
+ if (!subdev_actuator) {
+ err("subdev_actuator is NULL");
+ ret = -ENOMEM;
+ goto p_err;
+ }
+
+ /* This name must is match to sensor_open_extended actuator name */
+ actuator->id = ACTUATOR_NAME_LC898217;
+ actuator->subdev = subdev_actuator;
+ actuator->device = sensor_id;
+ actuator->client = client;
+ actuator->position = 0;
+ actuator->max_position = LC898217_POS_MAX_SIZE;
+ actuator->pos_size_bit = LC898217_POS_SIZE_BIT;
+ actuator->pos_direction = LC898217_POS_DIRECTION;
+ actuator->i2c_lock = NULL;
+ actuator->need_softlanding = 0;
+ actuator->actuator_ops = NULL;
+
+ device->subdev_actuator[place] = subdev_actuator;
+ device->actuator[place] = actuator;
+
+ v4l2_i2c_subdev_init(subdev_actuator, client, &subdev_ops);
+ v4l2_set_subdevdata(subdev_actuator, actuator);
+ v4l2_set_subdev_hostdata(subdev_actuator, device);
+
+ snprintf(subdev_actuator->name, V4L2_SUBDEV_NAME_SIZE, "actuator-subdev.%d", actuator->id);
+
+ probe_info("%s done\n", __func__);
+ return ret;
+
+p_err:
+ if (!actuator)
+ kzfree(actuator);
+
+ if (!subdev_actuator)
+ kzfree(subdev_actuator);
+
+ return ret;
+}
+
+static const struct of_device_id sensor_actuator_lc898217_match[] = {
+ {
+ .compatible = "samsung,exynos5-fimc-is-actuator-lc898217",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sensor_actuator_lc898217_match);
+
+static const struct i2c_device_id sensor_actuator_lc898217_idt[] = {
+ { ACTUATOR_NAME, 0 },
+ {},
+};
+
+static struct i2c_driver sensor_actuator_lc898217_driver = {
+ .probe = sensor_lc898217_actuator_probe,
+ .driver = {
+ .name = ACTUATOR_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = sensor_actuator_lc898217_match,
+ .suppress_bind_attrs = true,
+ },
+ .id_table = sensor_actuator_lc898217_idt,
+};
+
+static int __init sensor_actuator_lc898217_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&sensor_actuator_lc898217_driver);
+ if (ret)
+ err("failed to add %s driver: %d\n",
+ sensor_actuator_lc898217_driver.driver.name, ret);
+
+ return ret;
+}
+late_initcall_sync(sensor_actuator_lc898217_init);