diff mbox

[v3,7/8] V4L2 subdev patchset for Intel Moorestown Camera Imaging Subsystem

Message ID 33AB447FBD802F4E932063B962385B351E895734@shsmsx501.ccr.corp.intel.com (mailing list archive)
State RFC
Headers show

Commit Message

Xiaolin Zhang May 18, 2010, 9:23 a.m. UTC
None
diff mbox

Patch

diff --git a/drivers/media/video/s5k4e1_motor.c b/drivers/media/video/s5k4e1_motor.c
new file mode 100644
index 0000000..7ed1b77
--- /dev/null
+++ b/drivers/media/video/s5k4e1_motor.c
@@ -0,0 +1,413 @@ 
+/*
+ * Support for Moorestown Langwell Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2009 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.
+ *
+ *
+ * Xiaolin Zhang <xiaolin.zhang@intel.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <media/v4l2-device.h>
+
+#include "s5k4e1_motor.h"
+
+static int s5k4e1_motor_debug;
+module_param(s5k4e1_motor_debug, int, 0644);
+MODULE_PARM_DESC(s5k4e1_motor_debug, "Debug level (0-1)");
+
+#define dprintk(level, fmt, arg...) \
+    do {			\
+	if (s5k4e1_motor_debug >= level) \
+		printk(KERN_DEBUG "mrstisp@%s: " fmt "\n", __func__, ## arg); \
+    } while (0)
+
+#define eprintk(fmt, arg...)	\
+	printk(KERN_ERR "mrstisp@%s: line %d: " fmt "\n",	\
+	       __func__, __LINE__, ## arg);
+
+static inline struct s5k4e1_motor *to_motor_config(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct s5k4e1_motor, sd);
+}
+
+static int motor_read(struct i2c_client *c, u32 *reg)
+{
+	int ret;
+	struct i2c_msg msg;
+	u8 msgbuf[3];
+
+	msgbuf[0] = 0;
+	msgbuf[1] = 0;
+	msgbuf[2] = 0;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.addr = c->addr;
+	msg.buf = msgbuf;
+	msg.len = 3;
+	msg.flags = I2C_M_RD;
+
+	ret = i2c_transfer(c->adapter, &msg, 1);
+
+	*reg = (msgbuf[0] << 16 | msgbuf[1] << 8 | msgbuf[2]);
+
+	ret = (ret == 1) ? 0 : -1;
+	return ret;
+}
+
+static int motor_write(struct i2c_client *c, u32 reg)
+{
+	int ret;
+	struct i2c_msg msg;
+	u8 msgbuf[3];
+
+	memset(&msg, 0, sizeof(msg));
+	msgbuf[0] = (reg & 0x00FFFFFFFF) >> 16;
+	msgbuf[1] = (reg & 0x0000FFFF) >> 8 ;
+	msgbuf[2] = reg;
+
+	msg.addr = c->addr;
+	msg.flags = 0;
+	msg.buf = msgbuf;
+	msg.len = 3;
+
+	ret = i2c_transfer(c->adapter, &msg, 1);
+
+	ret = (ret == 1) ? 0 : -1;
+	return ret;
+}
+
+static int s5k4e1_motor_goto_position(struct i2c_client *c,
+				      unsigned short code,
+				      struct s5k4e1_motor *config,
+				      unsigned int step)
+{
+	int max_code, min_code;
+	int timeout = 25; /*TODO: check the timeout time */
+	u8 cmdh, cmdl, finished;
+	u32 cmd = 0, val = 0;
+
+	max_code = config->macro_code;
+	min_code = config->infin_code;
+
+	if (code > max_code)
+		code = max_code;
+	if (code < min_code)
+		code = min_code;
+
+	cmdh = MOTOR_DAC_CTRL_MODE_1 | (code >> 8); /* PS EN x x M W TD9 TD8*/
+	cmdl = code; /* TD7 ~ TD0 */
+	cmd |= (cmdh << 16) | (cmdl << 8);
+
+	dprintk(1, "cmdh: %x, cmdl: %x, cmd: %x", cmdh, cmdl, cmd);
+	dprintk(1, "DAC code: %x", code);
+
+	motor_write(c, cmd);
+	finished = 0;
+	while ((!finished) && timeout--) {
+		msleep(1);
+		motor_read(c, &val);
+		cmdh = val >> 16;
+		cmdl = val >> 8;
+
+		dprintk(1, "cmdh & MOTOR_F = %x", cmdh & MOTOR_F);
+		finished = cmdh & MOTOR_F;
+		finished = (finished) ? 0 : 1;
+	};
+
+	if (finished) {
+		dprintk(1, "Moving from code %x to code %x takes %d ms.",
+		       config->cur_code, code, 25-timeout);
+		return 0;
+	} else {
+		eprintk("Unable to move motor to step %d, TIMEOUT!!", step);
+		return -1;
+	}
+
+}
+
+static int s5k4e1_motor_wakeup(struct i2c_client *client)
+{
+	/* hardware wakeup: set PS = 1 */
+	return motor_write(client, 0xC00000);
+}
+
+static int s5k4e1_motor_init(struct i2c_client *client,
+						struct s5k4e1_motor *config)
+{
+
+	int ret;
+	int infin_cur, macro_cur;
+	int step_res, step_time;
+	int val;
+
+	infin_cur = max(MOTOR_INFIN_CUR, MOTOR_DAC_MIN_CUR);
+	macro_cur = min(MOTOR_MACRO_CUR, MOTOR_DAC_MAX_CUR);
+	step_res = 1 << MOTOR_STEP_SHIFT;
+	step_time = MOTOR_STEP_TIME;
+
+	config->infin_cur = infin_cur;
+	config->macro_cur = macro_cur;
+
+	config->infin_code = MOTOR_INFIN_CODE;
+	config->macro_code = MOTOR_MACRO_CODE;
+
+	config->max_step = ((config->macro_code - config->infin_code)
+			    >> MOTOR_STEP_SHIFT) + 1;
+	config->step_res = step_res;
+	config->step_time = step_time;
+
+	dprintk(1, "max_step: %d, step_res: %d, step_time: %d",
+		config->max_step, step_res, step_time);
+
+	val = (MOTOR_DAC_CTRL_MODE_0 << 16) | (step_res << 8) | step_time;
+	ret = motor_write(client, val);
+
+	ret |= s5k4e1_motor_goto_position(client, config->infin_code,
+					  config, 0);
+	if (!ret) {
+		config->cur_code = config->infin_code;
+		dprintk(1, "Motor initialization success!");
+	} else
+		eprintk("Error while initializing motor!!!");
+
+	return ret;
+}
+
+static int s5k4e1_motor_get_focus(struct i2c_client *c,
+			   unsigned int *step,
+			   struct s5k4e1_motor *config)
+{
+	int ret_step;
+
+	ret_step = ((config->cur_code - config->infin_code)
+		    >> MOTOR_STEP_SHIFT);
+
+	if (ret_step <= config->max_step)
+		*step = ret_step;
+	else
+		*step = config->max_step;
+
+	return 0;
+}
+
+static int s5k4e1_motor_set_focus(struct i2c_client *c,
+			   unsigned int step,
+			   struct s5k4e1_motor *config)
+{
+	int s_code, ret;
+	int max_step = config->max_step;
+	unsigned int val = step;
+
+	if (val > max_step)
+		val = max_step;
+
+	s_code = (val << MOTOR_STEP_SHIFT);
+	s_code += config->infin_code;
+
+	ret = s5k4e1_motor_goto_position(c, s_code, config, step);
+	if (!ret)
+		config->cur_code = s_code;
+
+	return ret;
+}
+
+static int s5k4e1_motor_g_ctrl(struct v4l2_subdev *sd,
+			       struct v4l2_control *ctrl)
+{
+	int ret = -EINVAL;
+	struct i2c_client *c = v4l2_get_subdevdata(sd);
+	struct s5k4e1_motor *config = to_motor_config(sd);
+	if (!sd || !ctrl)
+		return ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_FOCUS_ABSOLUTE:
+		ret = s5k4e1_motor_get_focus(c, &ctrl->value, config);
+		if (ret) {
+			eprintk("error call s5k4e1_motor_get_focue");
+			return ret;
+		}
+		break;
+	default:
+		dprintk(1, "not supported ctrl id");
+		break;
+	}
+	return ret;
+}
+
+static int s5k4e1_motor_s_ctrl(struct v4l2_subdev *sd,
+			       struct v4l2_control *ctrl)
+{
+	int ret = -EINVAL;
+	struct i2c_client *c = v4l2_get_subdevdata(sd);
+	struct s5k4e1_motor *config = to_motor_config(sd);
+	if (!sd || !ctrl)
+		return ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_FOCUS_ABSOLUTE:
+		ret = s5k4e1_motor_set_focus(c, ctrl->value, config);
+		if (ret) {
+			eprintk("error call s5k4e1_motor_set_focue");
+			return ret;
+		}
+		break;
+	default:
+		dprintk(1, "not supported ctrl id");
+		break;
+	}
+	return ret;
+}
+
+static int s5k4e1_motor_queryctrl(struct v4l2_subdev *sd,
+			    struct v4l2_queryctrl *qc)
+{
+	int ret = -EINVAL;
+	struct s5k4e1_motor *config;
+	if (!sd)
+		return -ENODEV;
+	if (!qc)
+		return ret;
+	config = to_motor_config(sd);
+
+	switch (qc->id) {
+	case V4L2_CID_FOCUS_ABSOLUTE:
+		ret = v4l2_ctrl_query_fill(qc, 0, config->max_step,
+				 config->max_step, config->macro_code);
+		break;
+	default:
+		dprintk(1, "not supported queryctrl id");
+		break;
+	}
+	return ret;
+}
+
+static const struct v4l2_subdev_core_ops s5k4e1_motor_core_ops = {
+	.g_ctrl = s5k4e1_motor_g_ctrl,
+	.s_ctrl = s5k4e1_motor_s_ctrl,
+	.queryctrl = s5k4e1_motor_queryctrl,
+};
+
+static const struct v4l2_subdev_ops s5k4e1_motor_ops = {
+	.core = &s5k4e1_motor_core_ops,
+};
+
+static int s5k4e1_motor_detect(struct i2c_client *client)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	int adap_id = i2c_adapter_id(adapter);
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
+		eprintk("error i2c check func");
+		return -ENODEV;
+	}
+
+	if (adap_id != 1) {
+		eprintk("adap_id != 1");
+		return -ENODEV;
+	}
+
+	if (s5k4e1_motor_wakeup(client))
+		eprintk("unable to wakeup s5k4e1 motor.");
+
+	return 0;
+}
+
+static int s5k4e1_motor_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct s5k4e1_motor *info;
+	struct v4l2_subdev *sd;
+	int ret = -1;
+
+	v4l_info(client, "chip found @ 0x%x (%s)\n",
+		 client->addr << 1, client->adapter->name);
+
+	info = kzalloc(sizeof(struct s5k4e1_motor), GFP_KERNEL);
+	if (!info) {
+		eprintk("fail to malloc for ci_motor");
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = s5k4e1_motor_detect(client);
+	if (ret) {
+		eprintk("error s5k4e1_motor_detect");
+		goto out_free;
+	}
+
+	sd = &info->sd;
+	v4l2_i2c_subdev_init(sd, client, &s5k4e1_motor_ops);
+
+	ret = s5k4e1_motor_init(client, info);
+	if (ret) {
+		eprintk("error calling s5k4e1_motor_init");
+		goto out_free;
+	}
+
+	ret = 0;
+	goto out;
+
+out_free:
+	kfree(info);
+out:
+	return ret;
+}
+
+static int s5k4e1_motor_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+
+	v4l2_device_unregister_subdev(sd);
+	kfree(to_motor_config(sd));
+	return 0;
+}
+
+static const struct i2c_device_id s5k4e1_motor_id[] = {
+	{"s5k4e1_motor", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, s5k4e1_motor_id);
+
+static struct i2c_driver s5k4e1_motor_i2c_driver = {
+	.driver = {
+		.name = "s5k4e1_motor",
+	},
+	.probe = s5k4e1_motor_probe,
+	.remove = s5k4e1_motor_remove,
+	.id_table = s5k4e1_motor_id,
+};
+
+static int __init s5k4e1_motor_drv_init(void)
+{
+	return i2c_add_driver(&s5k4e1_motor_i2c_driver);
+}
+
+static void __exit s5k4e1_motor_drv_cleanup(void)
+{
+	i2c_del_driver(&s5k4e1_motor_i2c_driver);
+}
+
+module_init(s5k4e1_motor_drv_init);
+module_exit(s5k4e1_motor_drv_cleanup);
+
+MODULE_AUTHOR("Xiaolin Zhang <xiaolin.zhang@intel.com>");
+MODULE_DESCRIPTION("A low-level driver for Samsung S5K4E1 sensor motor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/video/s5k4e1_motor.h b/drivers/media/video/s5k4e1_motor.h
new file mode 100644
index 0000000..b4a2aa1
--- /dev/null
+++ b/drivers/media/video/s5k4e1_motor.h
@@ -0,0 +1,90 @@ 
+/*
+ * Support for Moorestown Langwell Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2009 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.
+ *
+ *
+ * Xiaolin Zhang <xiaolin.zhang@intel.com>
+ */
+
+#include <media/v4l2-subdev.h>
+#include <linux/kernel.h>
+
+/* DAC output max current (mA) */
+#define MOTOR_DAC_MAX_CUR	125
+/* DAC output min current (mA) */
+#define MOTOR_DAC_MIN_CUR	1
+/* DAC max code (Hex) */
+#define MOTOR_DAC_CODE_MAX 	0x3FF
+/* DAC min code (Hex) */
+#define MOTOR_DAC_CODE_MIN	0x0
+
+/* VCM start code (Hex) */
+#define MOTOR_INFIN_CODE	0x120
+/* VCM stop code (Hex) */
+#define MOTOR_MACRO_CODE	0x205
+
+#define MOTOR_STEP_SHIFT	4	/* Step res = 2^4 = 10H */
+#define MOTOR_STEP_TIME		20	/* Step time = 50us x 20d = 1ms */
+
+/* VCM start current (mA) */
+#define MOTOR_INFIN_CUR		((MOTOR_DAC_MAX_CUR / MOTOR_DAC_CODE_MAX) \
+				 * MOTOR_INFIN_CODE + 1)
+/* VCM max current for Macro (mA) */
+#define MOTOR_MACRO_CUR		((MOTOR_DAC_MAX_CUR / MOTOR_DAC_CODE_MAX) \
+				 * MOTOR_MACRO_CODE + 1)
+
+
+#define MOTOR_DAC_BIT_RES	10
+#define MOTOR_DAC_MAX_CODE	((1 << MOTOR_DAC_BIT_RES) - 1)
+
+#define MOTOR_STEP_SHIFT	4
+
+
+/* DAC register related define */
+#define MOTOR_PS	(1 << 7) /* power save */
+#define MOTOR_EN	(1 << 6) /* out pin status*/
+#define MOTOR_M		(1 << 3) /* mode select */
+#define MOTOR_W		(1 << 2) /* register address */
+#define MOTOR_F		(1 << 4) /* finish flag */
+
+#define MOTOR_DAC_CODE_L(x)	(x & 0xff)
+#define MOTOR_DAC_CODE_H(x)	((x >> 8) & 0xf3)
+
+/* Step mode setting */
+#define MOTOR_DAC_CTRL_MODE_0	0xCC
+/* DAC code setting */
+#define MOTOR_DAC_CTRL_MODE_1	0xC8
+
+#define S5K4E1_MOTOR_ADDR	(0x18 >> 1)
+/*#define POWER_EN_PIN	7*/
+#define GPIO_AF_PD	95
+
+#define DEBUG	0
+
+struct s5k4e1_motor{
+	/*struct i2c_client *motor;*/
+	unsigned int infin_cur;
+	unsigned int infin_code;
+	unsigned int macro_cur;
+	unsigned int macro_code;
+	unsigned int max_step;
+	unsigned int cur_code;
+	unsigned int step_res;
+	unsigned int step_time;
+	struct v4l2_subdev sd;
+};