1 From 2977dc08b02dd7097791a4d3c94796f33a165abc Mon Sep 17 00:00:00 2001
2 From: Dave Stevenson <dave.stevenson@raspberrypi.com>
3 Date: Tue, 3 Jan 2023 15:38:08 +0000
4 Subject: [PATCH] media: dw9807-vcm: Add support for DW9817
5 bidirectional VCM driver
7 The DW9817 is effectively the same as DW9807 from a programming
8 interface, however it drives +/-100mA instead of 0-100mA. This means
9 that the power on ramp needs to take the lens from the midpoint, and
10 power off return it there. It also changes the default position for
13 Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
15 drivers/media/i2c/dw9807-vcm.c | 115 +++++++++++++++++++++++++--------
16 1 file changed, 88 insertions(+), 27 deletions(-)
18 --- a/drivers/media/i2c/dw9807-vcm.c
19 +++ b/drivers/media/i2c/dw9807-vcm.c
21 // SPDX-License-Identifier: GPL-2.0
22 // Copyright (C) 2018 Intel Corporation
25 + * DW9807 is a 10-bit DAC driver, capable of sinking up to 100mA.
27 + * DW9817 is a bidirectional 10-bit driver, driving up to +/- 100mA.
28 + * Operationally it is identical to DW9807, except that the idle position is
29 + * the mid-point, not 0.
32 #include <linux/acpi.h>
33 #include <linux/delay.h>
34 #include <linux/i2c.h>
40 + unsigned int idle_pos;
41 + unsigned int default_pos;
44 struct dw9807_device {
45 struct v4l2_ctrl_handler ctrls_vcm;
46 struct v4l2_subdev sd;
51 static inline struct dw9807_device *sd_to_dw9807_vcm(
52 @@ -109,6 +123,40 @@ static int dw9807_set_dac(struct i2c_cli
57 + * The lens position is gradually moved in units of DW9807_CTRL_STEPS,
58 + * to make the movements smoothly. In all cases, even when "start" and
59 + * "end" are the same, the lens will be set to the "end" position.
61 + * (We don't use hardware slew rate control, because it differs widely
62 + * between otherwise-compatible ICs, and may need lens-specific tuning.)
64 +static int dw9807_ramp(struct i2c_client *client, int start, int end)
69 + step = DW9807_CTRL_STEPS;
71 + step = -DW9807_CTRL_STEPS;
76 + if (step * (val - end) >= 0)
78 + ret = dw9807_set_dac(client, val);
80 + dev_err_ratelimited(&client->dev, "%s I2C failure: %d",
84 + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
90 static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl)
92 struct dw9807_device *dev_vcm = container_of(ctrl->handler,
93 @@ -118,7 +166,7 @@ static int dw9807_set_ctrl(struct v4l2_c
94 struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd);
96 dev_vcm->current_val = ctrl->val;
97 - return dw9807_set_dac(client, ctrl->val);
98 + return dw9807_ramp(client, ctrl->val, ctrl->val);
102 @@ -163,7 +211,8 @@ static int dw9807_init_controls(struct d
103 v4l2_ctrl_handler_init(hdl, 1);
105 v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE,
106 - 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0);
107 + 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS,
108 + dev_vcm->current_val);
110 dev_vcm->sd.ctrl_handler = hdl;
112 @@ -175,9 +224,32 @@ static int dw9807_init_controls(struct d
116 +/* Compatible devices; in fact there are many similar chips.
117 + * "data" holds the powered-off (zero current) lens position and a
118 + * default/initial control value (which need not be the same as the powered-off
121 +static const struct dw9807_cfg dw9807_cfg = {
126 +static const struct dw9807_cfg dw9817_cfg = {
128 + .default_pos = 480,
131 +static const struct of_device_id dw9807_of_table[] = {
132 + { .compatible = "dongwoon,dw9807-vcm", .data = &dw9807_cfg },
133 + { .compatible = "dongwoon,dw9817-vcm", .data = &dw9817_cfg },
137 static int dw9807_probe(struct i2c_client *client)
139 struct dw9807_device *dw9807_dev;
140 + const struct of_device_id *match;
141 + const struct dw9807_cfg *cfg;
144 dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev),
145 @@ -185,6 +257,13 @@ static int dw9807_probe(struct i2c_clien
146 if (dw9807_dev == NULL)
149 + match = i2c_of_match_device(dw9807_of_table, client);
151 + cfg = (const struct dw9807_cfg *)match->data;
152 + dw9807_dev->idle_pos = cfg->idle_pos;
153 + dw9807_dev->current_val = cfg->default_pos;
156 v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops);
157 dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
158 dw9807_dev->sd.internal_ops = &dw9807_int_ops;
159 @@ -203,7 +282,8 @@ static int dw9807_probe(struct i2c_clien
163 - pm_runtime_set_active(&client->dev);
164 + if (!dw9807_dev->vdd)
165 + pm_runtime_set_active(&client->dev);
166 pm_runtime_enable(&client->dev);
167 pm_runtime_idle(&client->dev);
169 @@ -237,15 +317,10 @@ static int __maybe_unused dw9807_vcm_sus
170 struct v4l2_subdev *sd = i2c_get_clientdata(client);
171 struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);
172 const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 };
176 - for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1);
177 - val >= 0; val -= DW9807_CTRL_STEPS) {
178 - ret = dw9807_set_dac(client, val);
180 - dev_err_once(dev, "%s I2C failure: %d", __func__, ret);
181 - usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
183 + if (abs(dw9807_dev->current_val - dw9807_dev->idle_pos) > DW9807_CTRL_STEPS)
184 + dw9807_ramp(client, dw9807_dev->current_val, dw9807_dev->idle_pos);
187 ret = i2c_master_send(client, tx_data, sizeof(tx_data));
188 @@ -269,7 +344,7 @@ static int __maybe_unused dw9807_vcm_re
189 struct v4l2_subdev *sd = i2c_get_clientdata(client);
190 struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);
191 const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 };
196 ret = i2c_master_send(client, tx_data, sizeof(tx_data));
197 @@ -278,25 +353,11 @@ static int __maybe_unused dw9807_vcm_re
201 - for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS;
202 - val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1;
203 - val += DW9807_CTRL_STEPS) {
204 - ret = dw9807_set_dac(client, val);
206 - dev_err_ratelimited(dev, "%s I2C failure: %d",
208 - usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
210 + dw9807_ramp(client, dw9807_dev->idle_pos, dw9807_dev->current_val);
215 -static const struct of_device_id dw9807_of_table[] = {
216 - { .compatible = "dongwoon,dw9807-vcm" },
217 - /* Compatibility for older firmware, NEVER USE THIS IN FIRMWARE! */
218 - { .compatible = "dongwoon,dw9807" },
221 MODULE_DEVICE_TABLE(of, dw9807_of_table);
223 static const struct dev_pm_ops dw9807_pm_ops = {