diff options
author | Frank Chen <frankc@nvidia.com> | 2012-12-27 15:28:27 -0800 |
---|---|---|
committer | John Sasinowski <jsasinowski@nvidia.com> | 2013-03-21 12:23:53 -0700 |
commit | 548ccc56539bc9eb80a670477cd6c7f73df1d420 (patch) | |
tree | f0da32030ebf8851d248a8dc8fd518027bc1546a /drivers/media | |
parent | 3b926a89b0db6fcf74c99f878d5913d84738d250 (diff) |
media: video: tegra: add DW9718 focuser
Add driver support for dw9718 focuser.
Bug 1250073
Change-Id: I313ef751d15f14cce4b5aef299f31f53c38f8114
Signed-off-by: Frank Chen <frankc@nvidia.com>
Reviewed-on: http://git-master/r/210061
Reviewed-by: Automatic_Commit_Validation_User
GVS: Gerrit_Virtual_Submit
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
Reviewed-by: John Sasinowski <jsasinowski@nvidia.com>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/video/tegra/Kconfig | 7 | ||||
-rw-r--r-- | drivers/media/video/tegra/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/video/tegra/dw9718.c | 1077 |
3 files changed, 1085 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/Kconfig b/drivers/media/video/tegra/Kconfig index ad28b4220954..3502d9bcc6f5 100644 --- a/drivers/media/video/tegra/Kconfig +++ b/drivers/media/video/tegra/Kconfig @@ -140,3 +140,10 @@ config VIDEO_AR0833 ---help--- This is a driver for the AR0833 camera sensor for use with the tegra isp. + +config VIDEO_DW9718 + tristate "DW9718 focuser support" + depends on I2C && ARCH_TEGRA + ---help--- + This is a driver for the DW9718 focuser + for use with the tegra isp. diff --git a/drivers/media/video/tegra/Makefile b/drivers/media/video/tegra/Makefile index 191f38965635..d9bc01c0ca73 100644 --- a/drivers/media/video/tegra/Makefile +++ b/drivers/media/video/tegra/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_TORCH_TPS61050) += tps61050.o obj-$(CONFIG_MAX77665_FLASH) += max77665-flash.o obj-$(CONFIG_TORCH_AS364X) += as364x.o obj-$(CONFIG_VIDEO_SH532U) += sh532u.o +obj-$(CONFIG_VIDEO_DW9718) += dw9718.o obj-$(CONFIG_VIDEO_AD5820) += ad5820.o obj-$(CONFIG_VIDEO_AD5816) += ad5816.o obj-$(CONFIG_VIDEO_IMX091) += imx091.o diff --git a/drivers/media/video/tegra/dw9718.c b/drivers/media/video/tegra/dw9718.c new file mode 100644 index 000000000000..c2044ae3ac63 --- /dev/null +++ b/drivers/media/video/tegra/dw9718.c @@ -0,0 +1,1077 @@ +/* + * dw9718.c - dw9718 focuser driver + * + * Copyright (C) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ +/* Implementation + * -------------- + * The board level details about the device need to be provided in the board + * file with the <device>_platform_data structure. + * Standard among NVC kernel drivers in this structure is: + * .cfg = Use the NVC_CFG_ defines that are in nvc.h. + * Descriptions of the configuration options are with the defines. + * This value is typically 0. + * .num = The number of the instance of the device. This should start at 1 and + * and increment for each device on the board. This number will be + * appended to the MISC driver name, Example: /dev/focuser.1 + * If not used or 0, then nothing is appended to the name. + * .sync = If there is a need to synchronize two devices, then this value is + * the number of the device instance (.num above) this device is to + * sync to. For example: + * Device 1 platform entries = + * .num = 1, + * .sync = 2, + * Device 2 platfrom entries = + * .num = 2, + * .sync = 1, + * The above example sync's device 1 and 2. + * To disable sync, set .sync = 0. Note that the .num = 0 device is not + * allowed to be synced to. + * This is typically used for stereo applications. + * .dev_name = The MISC driver name the device registers as. If not used, + * then the part number of the device is used for the driver name. + * If using the NVC user driver then use the name found in this + * driver under _default_pdata. + * .gpio_count = The ARRAY_SIZE of the nvc_gpio_pdata table. + * .gpio = A pointer to the nvc_gpio_pdata structure's platform GPIO data. + * The GPIO mechanism works by cross referencing the .gpio_type key + * among the nvc_gpio_pdata GPIO data and the driver's nvc_gpio_init + * GPIO data to build a GPIO table the driver can use. The GPIO's + * defined in the device header file's _gpio_type enum are the + * gpio_type keys for the nvc_gpio_pdata and nvc_gpio_init structures. + * These need to be present in the board file's nvc_gpio_pdata + * structure for the GPIO's that are used. + * The driver's GPIO logic uses assert/deassert throughout until the + * low level _gpio_wr/rd calls where the .assert_high is used to + * convert the value to the correct signal level. + * See the GPIO notes in nvc.h for additional information. + * + * The following is specific to NVC kernel focus drivers: + * .nvc = Pointer to the nvc_focus_nvc structure. This structure needs to + * be defined and populated if overriding the driver defaults. + * .cap = Pointer to the nvc_focus_cap structure. This structure needs to + * be defined and populated if overriding the driver defaults. + * + * The following is specific to this NVC kernel focus driver: + * .info = Pointer to the dw9718_pdata_info structure. This structure does + * not need to be defined and populated unless overriding ROM data. + * + * Power Requirements: + * The device's header file defines the voltage regulators needed with the + * enumeration <device>_vreg. The order these are enumerated is the order + * the regulators will be enabled when powering on the device. When the + * device is powered off the regulators are disabled in descending order. + * The <device>_vregs table in this driver uses the nvc_regulator_init + * structure to define the regulator ID strings that go with the regulators + * defined with <device>_vreg. These regulator ID strings (or supply names) + * will be used in the regulator_get function in the _vreg_init function. + * The board power file and <device>_vregs regulator ID strings must match. + */ + +#include <linux/fs.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include <linux/i2c.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/uaccess.h> +#include <linux/list.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <media/dw9718.h> + +#define ENABLE_DEBUGFS_INTERFACE + +#define dw9718_ID 0x04 +#define dw9718_FOCAL_LENGTH_FLOAT (4.570f) +#define dw9718_FNUMBER_FLOAT (2.8f) +#define dw9718_FOCAL_LENGTH (0x40923D71) /* 4.570f */ +#define dw9718_FNUMBER (0x40333333) /* 2.8f */ +#define dw9718_SLEW_RATE 0x0060 +#define dw9718_ACTUATOR_RANGE 1023 +#define dw9718_SETTLETIME 30 +#define dw9718_FOCUS_MACRO 620 +#define dw9718_FOCUS_INFINITY 70 +#define dw9718_POS_LOW_DEFAULT 0 +#define dw9718_POS_HIGH_DEFAULT 1023 +#define dw9718_POS_CLAMP 0x03ff +/* Need to decide exact value of VCM_THRESHOLD and its use */ +/* define dw9718_VCM_THRESHOLD 20 */ + +struct dw9718_info { + atomic_t in_use; + struct i2c_client *i2c_client; + struct dw9718_platform_data *pdata; + struct miscdevice miscdev; + struct list_head list; + int pwr_dev; + struct dw9718_power_rail power; + int status; + u32 cur_pos; + u8 s_mode; + bool reset_flag; + struct dw9718_info *s_info; + struct nvc_focus_nvc nvc; + struct nvc_focus_cap cap; + struct nv_focuser_config nv_config; +}; + +/** + * The following are default values + */ +static struct nvc_focus_cap dw9718_default_cap = { + .version = NVC_FOCUS_CAP_VER2, + .slew_rate = dw9718_SLEW_RATE, + .actuator_range = dw9718_ACTUATOR_RANGE, + .settle_time = dw9718_SETTLETIME, + .focus_macro = dw9718_FOCUS_MACRO, + .focus_infinity = dw9718_FOCUS_INFINITY, + .focus_hyper = dw9718_FOCUS_INFINITY, +}; + +static struct nvc_focus_nvc dw9718_default_nvc = { + .focal_length = dw9718_FOCAL_LENGTH, + .fnumber = dw9718_FNUMBER, + .max_aperature = dw9718_FNUMBER, +}; + +static struct dw9718_platform_data dw9718_default_pdata = { + .cfg = 0, + .num = 0, + .sync = 0, + .dev_name = "focuser", +}; +static LIST_HEAD(dw9718_info_list); +static DEFINE_SPINLOCK(dw9718_spinlock); + +static int dw9718_i2c_wr8(struct dw9718_info *info, u8 reg, u8 val) +{ + struct i2c_msg msg; + u8 buf[2]; + buf[0] = reg; + buf[1] = val; + msg.addr = info->i2c_client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = &buf[0]; + if (i2c_transfer(info->i2c_client->adapter, &msg, 1) != 1) + return -EIO; + return 0; +} + +static int dw9718_i2c_wr16(struct dw9718_info *info, u8 reg, u16 val) +{ + struct i2c_msg msg; + u8 buf[3]; + buf[0] = reg; + buf[1] = (u8)(val >> 8); + buf[2] = (u8)(val & 0xff); + msg.addr = info->i2c_client->addr; + msg.flags = 0; + msg.len = 3; + msg.buf = &buf[0]; + if (i2c_transfer(info->i2c_client->adapter, &msg, 1) != 1) + return -EIO; + return 0; +} + +/** + * Below are device specific functions. + */ +static int dw9718_position_wr(struct dw9718_info *info, s32 position) +{ + int err; + + dev_dbg(&info->i2c_client->dev, "%s %d\n", __func__, position); + position &= dw9718_POS_CLAMP; + err = dw9718_i2c_wr16(info, DW9718_VCM_CODE_MSB, position); + if (!err) + info->cur_pos = position; + else + dev_err(&info->i2c_client->dev, "%s: ERROR set position %d", + __func__, position); + return err; +} + +int dw9718_set_arc_mode(struct dw9718_info *info) +{ + int err; + u32 sr = info->nv_config.slew_rate; + + dev_dbg(&info->i2c_client->dev, "%s %x\n", __func__, sr); + /* set ARC enable */ + err = dw9718_i2c_wr8(info, DW9718_CONTROL, (sr >> 16) & 0xFF); + if (err) { + dev_err(&info->i2c_client->dev, + "%s: CONTROL reg write failed\n", __func__); + goto set_arc_mode_done; + } + usleep_range(80, 100); + + /* set the ARC RES2 */ + err = dw9718_i2c_wr8(info, DW9718_SWITCH_MODE, (sr >> 8) & 0xFF); + if (err) { + dev_err(&info->i2c_client->dev, + "%s: MODE write failed\n", __func__); + goto set_arc_mode_done; + } + + err = dw9718_i2c_wr8(info, DW9718_SACT, sr & 0XFF); + if (err) { + dev_err(&info->i2c_client->dev, + "%s: RES write failed\n", __func__); + goto set_arc_mode_done; + } + + err = dw9718_position_wr(info, 0); + +set_arc_mode_done: + return err; +} + +static int dw9718_pm_wr(struct dw9718_info *info, int pwr) +{ + int err = 0; + if ((info->pdata->cfg & (NVC_CFG_OFF2STDBY | NVC_CFG_BOOT_INIT)) && + (pwr == NVC_PWR_OFF || pwr == NVC_PWR_STDBY_OFF)) + pwr = NVC_PWR_STDBY; + + if (pwr == info->pwr_dev) + return 0; + + switch (pwr) { + case NVC_PWR_OFF_FORCE: + case NVC_PWR_OFF: + if (info->pdata && info->pdata->power_off) + info->pdata->power_off(&info->power); + break; + case NVC_PWR_STDBY_OFF: + case NVC_PWR_STDBY: + if (info->pdata && info->pdata->power_off) + info->pdata->power_off(&info->power); + break; + case NVC_PWR_COMM: + case NVC_PWR_ON: + if (info->pdata && info->pdata->power_on) + info->pdata->power_on(&info->power); + dw9718_set_arc_mode(info); + break; + default: + err = -EINVAL; + break; + } + + if (err < 0) { + dev_err(&info->i2c_client->dev, "%s err %d\n", __func__, err); + pwr = NVC_PWR_ERR; + } + + info->pwr_dev = pwr; + dev_dbg(&info->i2c_client->dev, "%s pwr_dev=%d\n", __func__, + info->pwr_dev); + + return err; +} + +static int dw9718_power_put(struct dw9718_power_rail *pw) +{ + if (unlikely(!pw)) + return -EFAULT; + + if (likely(pw->vdd)) + regulator_put(pw->vdd); + + if (likely(pw->vdd_i2c)) + regulator_put(pw->vdd_i2c); + + pw->vdd = NULL; + pw->vdd_i2c = NULL; + + return 0; +} + +static int dw9718_regulator_get(struct dw9718_info *info, + struct regulator **vreg, char vreg_name[]) +{ + struct regulator *reg = NULL; + int err = 0; + + reg = regulator_get(&info->i2c_client->dev, vreg_name); + if (unlikely(IS_ERR(reg))) { + dev_err(&info->i2c_client->dev, "%s %s ERR: %d\n", + __func__, vreg_name, (int)reg); + err = PTR_ERR(reg); + reg = NULL; + } else + dev_dbg(&info->i2c_client->dev, "%s: %s\n", + __func__, vreg_name); + + *vreg = reg; + return err; +} + +static int dw9718_power_get(struct dw9718_info *info) +{ + struct dw9718_power_rail *pw = &info->power; + + dw9718_regulator_get(info, &pw->vdd, "vdd"); + dw9718_regulator_get(info, &pw->vdd_i2c, "vdd_i2c"); + + return 0; +} + +static int dw9718_pm_dev_wr(struct dw9718_info *info, int pwr) +{ + if (pwr < info->pwr_dev) + pwr = info->pwr_dev; + return dw9718_pm_wr(info, pwr); +} + +static void dw9718_pm_exit(struct dw9718_info *info) +{ + dw9718_pm_wr(info, NVC_PWR_OFF_FORCE); + dw9718_power_put(&info->power); +} + +static int dw9718_reset(struct dw9718_info *info, u32 level) +{ + int err = 0; + + if (level == NVC_RESET_SOFT) { + err = dw9718_i2c_wr8(info, DW9718_POWER_DN, 0x01); + usleep_range(200, 220); + err |= dw9718_i2c_wr8(info, DW9718_POWER_DN, 0x00); + usleep_range(100, 120); + } else + err = dw9718_pm_wr(info, NVC_PWR_OFF_FORCE); + + return err; +} + +static void dw9718_get_focuser_capabilities(struct dw9718_info *info) +{ + memset(&info->nv_config, 0, sizeof(info->nv_config)); + + dev_dbg(&info->i2c_client->dev, "%s\n", __func__); + info->nv_config.focal_length = info->nvc.focal_length; + info->nv_config.fnumber = info->nvc.fnumber; + info->nv_config.max_aperture = info->nvc.fnumber; + info->nv_config.range_ends_reversed = 0; + + info->nv_config.pos_working_low = info->cap.focus_infinity; + info->nv_config.pos_working_high = info->cap.focus_macro; + info->nv_config.pos_actual_low = dw9718_POS_LOW_DEFAULT; + info->nv_config.pos_actual_high = dw9718_POS_HIGH_DEFAULT; + + info->nv_config.slew_rate = info->cap.slew_rate; + info->nv_config.circle_of_confusion = -1; + info->nv_config.num_focuser_sets = 1; + info->nv_config.focuser_set[0].macro = info->cap.focus_macro; + info->nv_config.focuser_set[0].hyper = info->cap.focus_hyper; + info->nv_config.focuser_set[0].inf = info->cap.focus_infinity; + info->nv_config.focuser_set[0].settle_time = info->cap.settle_time; +} + +static int dw9718_set_focuser_capabilities(struct dw9718_info *info, + struct nvc_param *params) +{ + dev_dbg(&info->i2c_client->dev, "%s\n", __func__); + if (copy_from_user(&info->nv_config, + (const void __user *)params->p_value, + sizeof(struct nv_focuser_config))) { + dev_err(&info->i2c_client->dev, + "%s Error: copy_from_user bytes %d\n", + __func__, sizeof(struct nv_focuser_config)); + return -EFAULT; + } + + /* set pre-set value, as currently ODM sets incorrect value */ + info->cap.settle_time = dw9718_SETTLETIME; + + dev_dbg(&info->i2c_client->dev, + "%s: copy_from_user bytes %d info->cap.settle_time %d\n", + __func__, sizeof(struct nv_focuser_config), + info->cap.settle_time); + + return 0; +} + +static int dw9718_param_rd(struct dw9718_info *info, unsigned long arg) +{ + struct nvc_param params; + const void *data_ptr = NULL; + u32 data_size = 0; + + dev_dbg(&info->i2c_client->dev, "%s %lx\n", __func__, arg); + if (copy_from_user(¶ms, + (const void __user *)arg, + sizeof(struct nvc_param))) { + dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n", + __func__, __LINE__); + return -EFAULT; + } + if (info->s_mode == NVC_SYNC_SLAVE) + info = info->s_info; + switch (params.param) { + case NVC_PARAM_LOCUS: + data_ptr = &info->cur_pos; + data_size = sizeof(info->cur_pos); + dev_dbg(&info->i2c_client->dev, "%s LOCUS: %d\n", + __func__, info->cur_pos); + break; + case NVC_PARAM_FOCAL_LEN: + info->nvc.focal_length = dw9718_FOCAL_LENGTH; + data_ptr = &info->nvc.focal_length; + data_size = sizeof(info->nvc.focal_length); + break; + case NVC_PARAM_MAX_APERTURE: + data_ptr = &info->nvc.max_aperature; + data_size = sizeof(info->nvc.max_aperature); + dev_dbg(&info->i2c_client->dev, "%s MAX_APERTURE: %x\n", + __func__, info->nvc.max_aperature); + break; + case NVC_PARAM_FNUMBER: + data_ptr = &info->nvc.fnumber; + data_size = sizeof(info->nvc.fnumber); + dev_dbg(&info->i2c_client->dev, "%s FNUMBER: %u\n", + __func__, info->nvc.fnumber); + break; + case NVC_PARAM_CAPS: + /* send back just what's requested or our max size */ + dw9718_get_focuser_capabilities(info); + data_ptr = &info->nv_config; + data_size = sizeof(info->nv_config); + dev_err(&info->i2c_client->dev, "%s CAPS\n", __func__); + break; + case NVC_PARAM_STS: + data_ptr = &info->status; + data_size = sizeof(info->status); + dev_dbg(&info->i2c_client->dev, "%s\n", __func__); + break; + case NVC_PARAM_STEREO: + data_ptr = &info->s_mode; + data_size = sizeof(info->s_mode); + dev_err(&info->i2c_client->dev, "%s STEREO: %d\n", __func__, + info->s_mode); + break; + default: + dev_err(&info->i2c_client->dev, + "%s unsupported parameter: %d\n", + __func__, params.param); + return -EINVAL; + } + if (params.sizeofvalue < data_size) { + dev_err(&info->i2c_client->dev, + "%s data size mismatch %d != %d Param: %d\n", + __func__, params.sizeofvalue, data_size, params.param); + return -EINVAL; + } + if (copy_to_user((void __user *)params.p_value, data_ptr, data_size)) { + dev_err(&info->i2c_client->dev, "%s copy_to_user err line %d\n", + __func__, __LINE__); + return -EFAULT; + } + return 0; +} + +static int dw9718_param_wr_s(struct dw9718_info *info, + struct nvc_param *params, s32 s32val) +{ + int err = 0; + + switch (params->param) { + case NVC_PARAM_LOCUS: + dev_dbg(&info->i2c_client->dev, "%s LOCUS: %d\n", + __func__, s32val); + err = dw9718_position_wr(info, s32val); + break; + case NVC_PARAM_RESET: + err = dw9718_reset(info, s32val); + dev_dbg(&info->i2c_client->dev, "%s RESET\n", __func__); + break; + case NVC_PARAM_SELF_TEST: + dev_dbg(&info->i2c_client->dev, "%s SELF_TEST\n", __func__); + break; + default: + dev_dbg(&info->i2c_client->dev, + "%s unsupported parameter: %d\n", + __func__, params->param); + err = -EINVAL; + break; + } + + if (err) + dev_err(&info->i2c_client->dev, "ERROR! %d\n", err); + return err; +} + +static int dw9718_param_wr(struct dw9718_info *info, unsigned long arg) +{ + struct nvc_param params; + u8 u8val; + s32 s32val; + int err = 0; + if (copy_from_user(¶ms, (const void __user *)arg, + sizeof(struct nvc_param))) { + dev_err(&info->i2c_client->dev, + "%s copy_from_user err line %d\n", + __func__, __LINE__); + return -EFAULT; + } + if (copy_from_user(&s32val, + (const void __user *)params.p_value, sizeof(s32val))) { + dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n", + __func__, __LINE__); + return -EFAULT; + } + u8val = (u8)s32val; + /* parameters independent of sync mode */ + switch (params.param) { + case NVC_PARAM_STEREO: + dev_dbg(&info->i2c_client->dev, "%s STEREO: %d\n", + __func__, u8val); + if (u8val == info->s_mode) + return 0; + switch (u8val) { + case NVC_SYNC_OFF: + info->s_mode = u8val; + break; + case NVC_SYNC_MASTER: + info->s_mode = u8val; + break; + case NVC_SYNC_SLAVE: + if (info->s_info != NULL) { + /* default slave lens position */ + err = dw9718_position_wr(info->s_info, + info->s_info->cap.focus_infinity); + if (!err) { + info->s_mode = u8val; + info->s_info->s_mode = u8val; + } else { + if (info->s_mode != NVC_SYNC_STEREO) + dw9718_pm_wr(info->s_info, + NVC_PWR_OFF); + err = -EIO; + } + } else { + err = -EINVAL; + } + break; + case NVC_SYNC_STEREO: + if (info->s_info != NULL) { + /* sync power */ + info->s_info->pwr_dev = info->pwr_dev; + /* move slave lens to master position */ + err = dw9718_position_wr(info->s_info, + (s32)info->cur_pos); + if (!err) { + info->s_mode = u8val; + info->s_info->s_mode = u8val; + } else { + if (info->s_mode != NVC_SYNC_SLAVE) + dw9718_pm_wr(info->s_info, + NVC_PWR_OFF); + err = -EIO; + } + } else { + err = -EINVAL; + } + break; + default: + err = -EINVAL; + } + if (info->pdata->cfg & NVC_CFG_NOERR) + return 0; + return err; + + case NVC_PARAM_CAPS: + if (dw9718_set_focuser_capabilities(info, ¶ms)) { + dev_err(&info->i2c_client->dev, + "%s: Error: copy_from_user bytes %d\n", + __func__, params.sizeofvalue); + return -EFAULT; + } + return 0; + + default: + /* parameters dependent on sync mode */ + switch (info->s_mode) { + case NVC_SYNC_OFF: + case NVC_SYNC_MASTER: + return dw9718_param_wr_s(info, ¶ms, s32val); + case NVC_SYNC_SLAVE: + return dw9718_param_wr_s(info->s_info, ¶ms, s32val); + case NVC_SYNC_STEREO: + err = dw9718_param_wr_s(info, ¶ms, s32val); + if (!(info->pdata->cfg & NVC_CFG_SYNC_I2C_MUX)) + err |= dw9718_param_wr_s(info->s_info, + ¶ms, s32val); + return err; + default: + dev_err(&info->i2c_client->dev, "%s %d internal err\n", + __func__, __LINE__); + return -EINVAL; + } + } +} + +static long dw9718_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + struct dw9718_info *info = file->private_data; + int pwr; + int err = 0; + switch (cmd) { + case NVC_IOCTL_PARAM_WR: + dw9718_pm_dev_wr(info, NVC_PWR_ON); + err = dw9718_param_wr(info, arg); + dw9718_pm_dev_wr(info, NVC_PWR_OFF); + return err; + case NVC_IOCTL_PARAM_RD: + dw9718_pm_dev_wr(info, NVC_PWR_ON); + err = dw9718_param_rd(info, arg); + dw9718_pm_dev_wr(info, NVC_PWR_OFF); + return err; + case NVC_IOCTL_PWR_WR: + /* This is a Guaranteed Level of Service (GLOS) call */ + pwr = (int)arg * 2; + dev_dbg(&info->i2c_client->dev, "%s PWR_WR: %d\n", + __func__, pwr); + err = dw9718_pm_dev_wr(info, pwr); + return err; + case NVC_IOCTL_PWR_RD: + if (info->s_mode == NVC_SYNC_SLAVE) + pwr = info->s_info->pwr_dev; + else + pwr = info->pwr_dev; + dev_dbg(&info->i2c_client->dev, "%s PWR_RD: %d\n", + __func__, pwr); + if (copy_to_user((void __user *)arg, + (const void *)&pwr, sizeof(pwr))) { + dev_err(&info->i2c_client->dev, + "%s copy_to_user err line %d\n", + __func__, __LINE__); + return -EFAULT; + } + return 0; + default: + dev_dbg(&info->i2c_client->dev, "%s unsupported ioctl: %x\n", + __func__, cmd); + } + return -EINVAL; +} + + +static void dw9718_sdata_init(struct dw9718_info *info) +{ + /* set defaults */ + memcpy(&info->nvc, &dw9718_default_nvc, sizeof(info->nvc)); + memcpy(&info->cap, &dw9718_default_cap, sizeof(info->cap)); + + /* set overrides if any */ + if (info->pdata->nvc) { + if (info->pdata->nvc->fnumber) + info->nvc.fnumber = info->pdata->nvc->fnumber; + if (info->pdata->nvc->focal_length) + info->nvc.focal_length = info->pdata->nvc->focal_length; + if (info->pdata->nvc->max_aperature) + info->nvc.max_aperature = + info->pdata->nvc->max_aperature; + } + + if (info->pdata->cap) { + if (info->pdata->cap->actuator_range) + info->cap.actuator_range = + info->pdata->cap->actuator_range; + if (info->pdata->cap->settle_time) + info->cap.settle_time = info->pdata->cap->settle_time; + if (info->pdata->cap->slew_rate) + info->cap.slew_rate = info->pdata->cap->slew_rate; + if (info->pdata->cap->focus_macro) + info->cap.focus_macro = info->pdata->cap->focus_macro; + if (info->pdata->cap->focus_hyper) + info->cap.focus_hyper = info->pdata->cap->focus_hyper; + if (info->pdata->cap->focus_infinity) + info->cap.focus_infinity = + info->pdata->cap->focus_infinity; + } +} + +static int dw9718_sync_en(unsigned num, unsigned sync) +{ + struct dw9718_info *master = NULL; + struct dw9718_info *slave = NULL; + struct dw9718_info *pos = NULL; + rcu_read_lock(); + list_for_each_entry_rcu(pos, &dw9718_info_list, list) { + if (pos->pdata->num == num) { + master = pos; + break; + } + } + pos = NULL; + list_for_each_entry_rcu(pos, &dw9718_info_list, list) { + if (pos->pdata->num == sync) { + slave = pos; + break; + } + } + rcu_read_unlock(); + if (master != NULL) + master->s_info = NULL; + if (slave != NULL) + slave->s_info = NULL; + if (!sync) + return 0; /* no err if sync disabled */ + if (num == sync) + return -EINVAL; /* err if sync instance is itself */ + if ((master != NULL) && (slave != NULL)) { + master->s_info = slave; + slave->s_info = master; + } + return 0; +} + +static int dw9718_sync_dis(struct dw9718_info *info) +{ + if (info->s_info != NULL) { + info->s_info->s_mode = 0; + info->s_info->s_info = NULL; + info->s_mode = 0; + info->s_info = NULL; + return 0; + } + return -EINVAL; +} + +static int dw9718_open(struct inode *inode, struct file *file) +{ + struct dw9718_info *info = NULL; + struct dw9718_info *pos = NULL; + int err; + rcu_read_lock(); + list_for_each_entry_rcu(pos, &dw9718_info_list, list) { + if (pos->miscdev.minor == iminor(inode)) { + info = pos; + break; + } + } + rcu_read_unlock(); + if (!info) + return -ENODEV; + err = dw9718_sync_en(info->pdata->num, info->pdata->sync); + if (err == -EINVAL) + dev_err(&info->i2c_client->dev, + "%s err: invalid num (%u) and sync (%u) instance\n", + __func__, info->pdata->num, info->pdata->sync); + if (atomic_xchg(&info->in_use, 1)) + return -EBUSY; + if (info->s_info != NULL) { + if (atomic_xchg(&info->s_info->in_use, 1)) + return -EBUSY; + } + file->private_data = info; + dw9718_pm_dev_wr(info, NVC_PWR_ON); + dw9718_position_wr(info, info->cap.focus_infinity); + dw9718_pm_dev_wr(info, NVC_PWR_OFF); + dev_dbg(&info->i2c_client->dev, "%s\n", __func__); + + return 0; +} + +static int dw9718_release(struct inode *inode, struct file *file) +{ + struct dw9718_info *info = file->private_data; + dev_dbg(&info->i2c_client->dev, "%s\n", __func__); + dw9718_pm_wr(info, NVC_PWR_OFF); + file->private_data = NULL; + WARN_ON(!atomic_xchg(&info->in_use, 0)); + if (info->s_info != NULL) + WARN_ON(!atomic_xchg(&info->s_info->in_use, 0)); + dw9718_sync_dis(info); + return 0; +} + +static const struct file_operations dw9718_fileops = { + .owner = THIS_MODULE, + .open = dw9718_open, + .unlocked_ioctl = dw9718_ioctl, + .release = dw9718_release, +}; + +static void dw9718_del(struct dw9718_info *info) +{ + dw9718_pm_exit(info); + if ((info->s_mode == NVC_SYNC_SLAVE) || + (info->s_mode == NVC_SYNC_STEREO)) + dw9718_pm_exit(info->s_info); + + dw9718_sync_dis(info); + spin_lock(&dw9718_spinlock); + list_del_rcu(&info->list); + spin_unlock(&dw9718_spinlock); + synchronize_rcu(); +} + +static int dw9718_remove(struct i2c_client *client) +{ + struct dw9718_info *info = i2c_get_clientdata(client); + dev_dbg(&info->i2c_client->dev, "%s\n", __func__); + misc_deregister(&info->miscdev); + dw9718_del(info); + return 0; +} + +static int nvc_debugfs_init(const char *dir_name, + struct dentry **d_entry, struct dentry **f_entry, void *info); + +static int dw9718_probe( + struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct dw9718_info *info; + char dname[16]; + + dev_dbg(&client->dev, "%s\n", __func__); + pr_info("dw9718: probing focuser.\n"); + + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (info == NULL) { + dev_err(&client->dev, "%s: kzalloc error\n", __func__); + return -ENOMEM; + } + info->i2c_client = client; + if (client->dev.platform_data) { + info->pdata = client->dev.platform_data; + } else { + info->pdata = &dw9718_default_pdata; + dev_dbg(&client->dev, "%s No platform data. Using defaults.\n", + __func__); + } + + i2c_set_clientdata(client, info); + INIT_LIST_HEAD(&info->list); + spin_lock(&dw9718_spinlock); + list_add_rcu(&info->list, &dw9718_info_list); + spin_unlock(&dw9718_spinlock); + dw9718_power_get(info); + dw9718_sdata_init(info); + + if (info->pdata->dev_name != 0) + strcpy(dname, info->pdata->dev_name); + else + strcpy(dname, "dw9718"); + + if (info->pdata->num) + snprintf(dname, sizeof(dname), + "%s.%u", dname, info->pdata->num); + + info->miscdev.name = dname; + info->miscdev.fops = &dw9718_fileops; + info->miscdev.minor = MISC_DYNAMIC_MINOR; + if (misc_register(&info->miscdev)) { + dev_err(&client->dev, "%s unable to register misc device %s\n", + __func__, dname); + dw9718_del(info); + return -ENODEV; + } + + nvc_debugfs_init( + info->miscdev.this_device->kobj.name, NULL, NULL, info); + + return 0; +} + +#ifdef ENABLE_DEBUGFS_INTERFACE +static int nvc_status_show(struct seq_file *s, void *data) +{ + struct dw9718_info *k_info = s->private; + struct nv_focuser_config *pcfg = &k_info->nv_config; + struct nvc_focus_cap *pcap = &k_info->cap; + + pr_info("%s\n", __func__); + + seq_printf(s, "focuser status:\n" + " Limit = (%04d - %04d)\n" + " Range = (%04d - %04d)\n" + " Current Pos = %04d\n" + " Settle time = %04d\n" + " Macro = %04d\n" + " Infinity = %04d\n" + " Hyper = %04d\n" + " SlewRate = 0x%06x\n" + , + pcfg->pos_actual_low, pcfg->pos_actual_high, + pcfg->pos_working_low, pcfg->pos_working_high, + k_info->cur_pos, + pcap->settle_time, + pcap->focus_macro, + pcap->focus_infinity, + pcap->focus_hyper, + pcfg->slew_rate + ); + + return 0; +} + +static ssize_t nvc_attr_set(struct file *s, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct dw9718_info *k_info = + ((struct seq_file *)s->private_data)->private; + struct nv_focuser_config *pcfg = &k_info->nv_config; + char buf[24]; + int buf_size; + int err; + u32 val = 0; + + pr_info("%s (%d)\n", __func__, count); + + if (!user_buf || count <= 1) + return -EFAULT; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf + 1, "0x%x", &val) == 1) + goto set_attr; + if (sscanf(buf + 1, "0X%x", &val) == 1) + goto set_attr; + if (sscanf(buf + 1, "%d", &val) == 1) + goto set_attr; + + pr_err("SYNTAX ERROR: %s\n", buf); + return -EFAULT; + +set_attr: + pr_info("new data = %x\n", val); + switch (buf[0]) { + case 'p': + pr_info("new pos = %d\n", val); + err = dw9718_position_wr(k_info, val); + if (err) + pr_err("ERROR set position %x\n", val); + break; + case 'h': + if (val <= pcfg->pos_working_low || val >= 1024) { + pr_info("new pos_high(%d) out of range\n", + val); + break; + } + pr_info("new pos_high = %d\n", val); + pcfg->pos_working_high = val; + break; + case 'l': + if (val >= pcfg->pos_working_high) { + pr_info("new pos_low(%d) out of range\n", + val); + break; + } + pr_info("new pos_low = %d\n", val); + pcfg->pos_working_low = val; + break; + case 'm': + pr_info("new vcm mode = %x\n", val); + pcfg->slew_rate = val; + dw9718_set_arc_mode(k_info); + break; + } + + return count; +} + +static int nvc_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, nvc_status_show, inode->i_private); +} + +static const struct file_operations nvc_debugfs_fops = { + .open = nvc_debugfs_open, + .read = seq_read, + .write = nvc_attr_set, + .llseek = seq_lseek, + .release = single_release, +}; + +static int nvc_debugfs_init(const char *dir_name, + struct dentry **d_entry, struct dentry **f_entry, void *info) +{ + struct dentry *dp, *fp; + + dp = debugfs_create_dir(dir_name, NULL); + if (dp == NULL) { + pr_info("%s: debugfs create dir failed\n", __func__); + return -ENOMEM; + } + + fp = debugfs_create_file("d", S_IRUGO|S_IWUSR, + dp, info, &nvc_debugfs_fops); + if (!fp) { + pr_info("%s: debugfs create file failed\n", __func__); + debugfs_remove_recursive(dp); + return -ENOMEM; + } + + if (d_entry) + *d_entry = dp; + if (f_entry) + *f_entry = fp; + return 0; +} +#else +static int nvc_debugfs_init(const char *dir_name, + struct dentry **d_entry, struct dentry **f_entry, void *info) +{ + return 0; +} +#endif + + +static const struct i2c_device_id dw9718_id[] = { + { "dw9718", 0 }, + { }, +}; + + +MODULE_DEVICE_TABLE(i2c, dw9718_id); + +static struct i2c_driver dw9718_i2c_driver = { + .driver = { + .name = "dw9718", + .owner = THIS_MODULE, + }, + .id_table = dw9718_id, + .probe = dw9718_probe, + .remove = dw9718_remove, +}; + +static int __init dw9718_init(void) +{ + return i2c_add_driver(&dw9718_i2c_driver); +} + +static void __exit dw9718_exit(void) +{ + i2c_del_driver(&dw9718_i2c_driver); +} + +module_init(dw9718_init); +module_exit(dw9718_exit); +MODULE_LICENSE("GPL v2"); |