blob: 565237796bb496d632ecbf69c052bb3404d4df0a [file] [log] [blame]
/*
* Support for dw9719 vcm driver.
*
* Copyright (c) 2012 Intel Corporation. All Rights Reserved.
*
* 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/delay.h>
#include "dw9719.h"
static struct dw9719_device dw9719_dev;
static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val)
{
struct i2c_msg msg[2];
u8 buf[2] = { reg };
msg[0].addr = DW9719_VCM_ADDR;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = buf;
msg[1].addr = DW9719_VCM_ADDR;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = &buf[1];
*val = 0;
if (i2c_transfer(client->adapter, msg, 2) != 2)
return -EIO;
*val = buf[1];
return 0;
}
static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val)
{
struct i2c_msg msg;
u8 buf[2] = { reg, val };
msg.addr = DW9719_VCM_ADDR;
msg.flags = 0;
msg.len = sizeof(buf);
msg.buf = buf;
if (i2c_transfer(client->adapter, &msg, 1) != 1)
return -EIO;
return 0;
}
static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val)
{
struct i2c_msg msg;
u8 buf[3] = { reg, (u8)(val >> 8), (u8)(val & 0xff)};
msg.addr = DW9719_VCM_ADDR;
msg.flags = 0;
msg.len = sizeof(buf);
msg.buf = buf;
if (i2c_transfer(client->adapter, &msg, 1) != 1)
return -EIO;
return 0;
}
int dw9719_vcm_power_up(struct v4l2_subdev *sd)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
int ret;
u8 value;
/* Enable power */
ret = dw9719_dev.platform_data->power_ctrl(sd, 1);
/* waiting time requested by DW9714A(vcm) */
if (ret)
return ret;
/* Wait for VBAT to stabilize */
udelay(1);
/*
* Jiggle SCL pin to wake up device.
*/
ret = dw9719_i2c_wr8(client, DW9719_CONTROL, 1);
/* Need 100us to transit from SHUTDOWN to STANDBY*/
usleep_range(100, 1000);
/* Enable the ringing compensation */
ret = dw9719_i2c_wr8(client, DW9719_CONTROL, DW9719_ENABLE_RINGING);
if (ret < 0)
goto fail_powerdown;
/* Use SAC3 mode */
ret = dw9719_i2c_wr8(client, DW9719_MODE, DW9719_MODE_SAC3);
if (ret < 0)
goto fail_powerdown;
/* Set the resonance frequency */
ret = dw9719_i2c_wr8(client, DW9719_VCM_FREQ, DW9719_DEFAULT_VCM_FREQ);
if (ret < 0)
goto fail_powerdown;
/* Detect device */
ret = dw9719_i2c_rd8(client, DW9719_INFO, &value);
if (ret < 0)
goto fail_powerdown;
if (value != DW9719_ID) {
ret = -ENXIO;
goto fail_powerdown;
}
dw9719_dev.focus = 0;
dw9719_dev.initialized = true;
return 0;
fail_powerdown:
dw9719_dev.platform_data->power_ctrl(sd, 0);
return ret;
}
int dw9719_vcm_power_down(struct v4l2_subdev *sd)
{
return dw9719_dev.platform_data->power_ctrl(sd, 0);
}
int dw9719_q_focus_status(struct v4l2_subdev *sd, s32 *value)
{
static const struct timespec move_time = {
.tv_sec = 0,
.tv_nsec = 60000000
};
struct timespec current_time, finish_time, delta_time;
getnstimeofday(&current_time);
finish_time = timespec_add(dw9719_dev.focus_time, move_time);
delta_time = timespec_sub(current_time, finish_time);
if (delta_time.tv_sec >= 0 && delta_time.tv_nsec >= 0) {
*value = ATOMISP_FOCUS_HP_COMPLETE |
ATOMISP_FOCUS_STATUS_ACCEPTS_NEW_MOVE;
} else {
*value = ATOMISP_FOCUS_STATUS_MOVING |
ATOMISP_FOCUS_HP_IN_PROGRESS;
}
return 0;
}
int dw9719_t_focus_abs(struct v4l2_subdev *sd, s32 value)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
int ret;
value = clamp(value, 0, DW9719_MAX_FOCUS_POS);
ret = dw9719_i2c_wr16(client, DW9719_VCM_CURRENT, value);
if (ret < 0)
return ret;
getnstimeofday(&dw9719_dev.focus_time);
dw9719_dev.focus = value;
return 0;
}
int dw9719_t_focus_rel(struct v4l2_subdev *sd, s32 value)
{
return dw9719_t_focus_abs(sd, dw9719_dev.focus + value);
}
int dw9719_q_focus_abs(struct v4l2_subdev *sd, s32 *value)
{
*value = dw9719_dev.focus;
return 0;
}
int dw9719_t_vcm_slew(struct v4l2_subdev *sd, s32 value)
{
return 0;
}
int dw9719_t_vcm_timing(struct v4l2_subdev *sd, s32 value)
{
return 0;
}