1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2012 Intel Corporation |
3 | |
4 | /* |
5 | * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: |
6 | * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/pm_runtime.h> |
12 | #include <linux/regulator/consumer.h> |
13 | #include <linux/types.h> |
14 | |
15 | #include <media/v4l2-cci.h> |
16 | #include <media/v4l2-common.h> |
17 | #include <media/v4l2-ctrls.h> |
18 | #include <media/v4l2-subdev.h> |
19 | |
20 | #define DW9719_MAX_FOCUS_POS 1023 |
21 | #define DW9719_CTRL_STEPS 16 |
22 | #define DW9719_CTRL_DELAY_US 1000 |
23 | |
24 | #define DW9719_INFO CCI_REG8(0) |
25 | #define DW9719_ID 0xF1 |
26 | |
27 | #define DW9719_CONTROL CCI_REG8(2) |
28 | #define DW9719_ENABLE_RINGING 0x02 |
29 | |
30 | #define DW9719_VCM_CURRENT CCI_REG16(3) |
31 | |
32 | #define DW9719_MODE CCI_REG8(6) |
33 | #define DW9719_MODE_SAC_SHIFT 4 |
34 | #define DW9719_MODE_SAC3 4 |
35 | |
36 | #define DW9719_VCM_FREQ CCI_REG8(7) |
37 | #define DW9719_DEFAULT_VCM_FREQ 0x60 |
38 | |
39 | #define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) |
40 | |
41 | struct dw9719_device { |
42 | struct v4l2_subdev sd; |
43 | struct device *dev; |
44 | struct regmap *regmap; |
45 | struct regulator *regulator; |
46 | u32 sac_mode; |
47 | u32 vcm_freq; |
48 | |
49 | struct dw9719_v4l2_ctrls { |
50 | struct v4l2_ctrl_handler handler; |
51 | struct v4l2_ctrl *focus; |
52 | } ctrls; |
53 | }; |
54 | |
55 | static int dw9719_detect(struct dw9719_device *dw9719) |
56 | { |
57 | int ret; |
58 | u64 val; |
59 | |
60 | ret = cci_read(map: dw9719->regmap, DW9719_INFO, val: &val, NULL); |
61 | if (ret < 0) |
62 | return ret; |
63 | |
64 | if (val != DW9719_ID) { |
65 | dev_err(dw9719->dev, "Failed to detect correct id\n" ); |
66 | return -ENXIO; |
67 | } |
68 | |
69 | return 0; |
70 | } |
71 | |
72 | static int dw9719_power_down(struct dw9719_device *dw9719) |
73 | { |
74 | return regulator_disable(regulator: dw9719->regulator); |
75 | } |
76 | |
77 | static int dw9719_power_up(struct dw9719_device *dw9719) |
78 | { |
79 | int ret; |
80 | |
81 | ret = regulator_enable(regulator: dw9719->regulator); |
82 | if (ret) |
83 | return ret; |
84 | |
85 | /* Jiggle SCL pin to wake up device */ |
86 | cci_write(map: dw9719->regmap, DW9719_CONTROL, val: 1, err: &ret); |
87 | |
88 | /* Need 100us to transit from SHUTDOWN to STANDBY */ |
89 | fsleep(usecs: 100); |
90 | |
91 | cci_write(map: dw9719->regmap, DW9719_CONTROL, DW9719_ENABLE_RINGING, err: &ret); |
92 | cci_write(map: dw9719->regmap, DW9719_MODE, |
93 | val: dw9719->sac_mode << DW9719_MODE_SAC_SHIFT, err: &ret); |
94 | cci_write(map: dw9719->regmap, DW9719_VCM_FREQ, val: dw9719->vcm_freq, err: &ret); |
95 | |
96 | if (ret) |
97 | dw9719_power_down(dw9719); |
98 | |
99 | return ret; |
100 | } |
101 | |
102 | static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) |
103 | { |
104 | return cci_write(map: dw9719->regmap, DW9719_VCM_CURRENT, val: value, NULL); |
105 | } |
106 | |
107 | static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) |
108 | { |
109 | struct dw9719_device *dw9719 = container_of(ctrl->handler, |
110 | struct dw9719_device, |
111 | ctrls.handler); |
112 | int ret; |
113 | |
114 | /* Only apply changes to the controls if the device is powered up */ |
115 | if (!pm_runtime_get_if_in_use(dev: dw9719->dev)) |
116 | return 0; |
117 | |
118 | switch (ctrl->id) { |
119 | case V4L2_CID_FOCUS_ABSOLUTE: |
120 | ret = dw9719_t_focus_abs(dw9719, value: ctrl->val); |
121 | break; |
122 | default: |
123 | ret = -EINVAL; |
124 | } |
125 | |
126 | pm_runtime_put(dev: dw9719->dev); |
127 | |
128 | return ret; |
129 | } |
130 | |
131 | static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { |
132 | .s_ctrl = dw9719_set_ctrl, |
133 | }; |
134 | |
135 | static int dw9719_suspend(struct device *dev) |
136 | { |
137 | struct v4l2_subdev *sd = dev_get_drvdata(dev); |
138 | struct dw9719_device *dw9719 = to_dw9719_device(sd); |
139 | int ret; |
140 | int val; |
141 | |
142 | for (val = dw9719->ctrls.focus->val; val >= 0; |
143 | val -= DW9719_CTRL_STEPS) { |
144 | ret = dw9719_t_focus_abs(dw9719, value: val); |
145 | if (ret) |
146 | return ret; |
147 | |
148 | usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); |
149 | } |
150 | |
151 | return dw9719_power_down(dw9719); |
152 | } |
153 | |
154 | static int dw9719_resume(struct device *dev) |
155 | { |
156 | struct v4l2_subdev *sd = dev_get_drvdata(dev); |
157 | struct dw9719_device *dw9719 = to_dw9719_device(sd); |
158 | int current_focus = dw9719->ctrls.focus->val; |
159 | int ret; |
160 | int val; |
161 | |
162 | ret = dw9719_power_up(dw9719); |
163 | if (ret) |
164 | return ret; |
165 | |
166 | for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; |
167 | val += DW9719_CTRL_STEPS) { |
168 | ret = dw9719_t_focus_abs(dw9719, value: val); |
169 | if (ret) |
170 | goto err_power_down; |
171 | |
172 | usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); |
173 | } |
174 | |
175 | return 0; |
176 | |
177 | err_power_down: |
178 | dw9719_power_down(dw9719); |
179 | return ret; |
180 | } |
181 | |
182 | static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) |
183 | { |
184 | return pm_runtime_resume_and_get(dev: sd->dev); |
185 | } |
186 | |
187 | static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) |
188 | { |
189 | pm_runtime_put(dev: sd->dev); |
190 | |
191 | return 0; |
192 | } |
193 | |
194 | static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { |
195 | .open = dw9719_open, |
196 | .close = dw9719_close, |
197 | }; |
198 | |
199 | static int dw9719_init_controls(struct dw9719_device *dw9719) |
200 | { |
201 | const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; |
202 | int ret; |
203 | |
204 | v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); |
205 | |
206 | dw9719->ctrls.focus = v4l2_ctrl_new_std(hdl: &dw9719->ctrls.handler, ops, |
207 | V4L2_CID_FOCUS_ABSOLUTE, min: 0, |
208 | DW9719_MAX_FOCUS_POS, step: 1, def: 0); |
209 | |
210 | if (dw9719->ctrls.handler.error) { |
211 | dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n" ); |
212 | ret = dw9719->ctrls.handler.error; |
213 | goto err_free_handler; |
214 | } |
215 | |
216 | dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; |
217 | return 0; |
218 | |
219 | err_free_handler: |
220 | v4l2_ctrl_handler_free(hdl: &dw9719->ctrls.handler); |
221 | return ret; |
222 | } |
223 | |
224 | static const struct v4l2_subdev_ops dw9719_ops = { }; |
225 | |
226 | static int dw9719_probe(struct i2c_client *client) |
227 | { |
228 | struct dw9719_device *dw9719; |
229 | int ret; |
230 | |
231 | dw9719 = devm_kzalloc(dev: &client->dev, size: sizeof(*dw9719), GFP_KERNEL); |
232 | if (!dw9719) |
233 | return -ENOMEM; |
234 | |
235 | dw9719->regmap = devm_cci_regmap_init_i2c(client, reg_addr_bits: 8); |
236 | if (IS_ERR(ptr: dw9719->regmap)) |
237 | return PTR_ERR(ptr: dw9719->regmap); |
238 | |
239 | dw9719->dev = &client->dev; |
240 | dw9719->sac_mode = DW9719_MODE_SAC3; |
241 | dw9719->vcm_freq = DW9719_DEFAULT_VCM_FREQ; |
242 | |
243 | /* Optional indication of SAC mode select */ |
244 | device_property_read_u32(dev: &client->dev, propname: "dongwoon,sac-mode" , |
245 | val: &dw9719->sac_mode); |
246 | |
247 | /* Optional indication of VCM frequency */ |
248 | device_property_read_u32(dev: &client->dev, propname: "dongwoon,vcm-freq" , |
249 | val: &dw9719->vcm_freq); |
250 | |
251 | dw9719->regulator = devm_regulator_get(dev: &client->dev, id: "vdd" ); |
252 | if (IS_ERR(ptr: dw9719->regulator)) |
253 | return dev_err_probe(dev: &client->dev, err: PTR_ERR(ptr: dw9719->regulator), |
254 | fmt: "getting regulator\n" ); |
255 | |
256 | v4l2_i2c_subdev_init(sd: &dw9719->sd, client, ops: &dw9719_ops); |
257 | dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; |
258 | dw9719->sd.internal_ops = &dw9719_internal_ops; |
259 | |
260 | ret = dw9719_init_controls(dw9719); |
261 | if (ret) |
262 | return ret; |
263 | |
264 | ret = media_entity_pads_init(entity: &dw9719->sd.entity, num_pads: 0, NULL); |
265 | if (ret < 0) |
266 | goto err_free_ctrl_handler; |
267 | |
268 | dw9719->sd.entity.function = MEDIA_ENT_F_LENS; |
269 | |
270 | /* |
271 | * We need the driver to work in the event that pm runtime is disable in |
272 | * the kernel, so power up and verify the chip now. In the event that |
273 | * runtime pm is disabled this will leave the chip on, so that the lens |
274 | * will work. |
275 | */ |
276 | |
277 | ret = dw9719_power_up(dw9719); |
278 | if (ret) |
279 | goto err_cleanup_media; |
280 | |
281 | ret = dw9719_detect(dw9719); |
282 | if (ret) |
283 | goto err_powerdown; |
284 | |
285 | pm_runtime_set_active(dev: &client->dev); |
286 | pm_runtime_get_noresume(dev: &client->dev); |
287 | pm_runtime_enable(dev: &client->dev); |
288 | |
289 | ret = v4l2_async_register_subdev(sd: &dw9719->sd); |
290 | if (ret < 0) |
291 | goto err_pm_runtime; |
292 | |
293 | pm_runtime_set_autosuspend_delay(dev: &client->dev, delay: 1000); |
294 | pm_runtime_use_autosuspend(dev: &client->dev); |
295 | pm_runtime_put_autosuspend(dev: &client->dev); |
296 | |
297 | return ret; |
298 | |
299 | err_pm_runtime: |
300 | pm_runtime_disable(dev: &client->dev); |
301 | pm_runtime_put_noidle(dev: &client->dev); |
302 | err_powerdown: |
303 | dw9719_power_down(dw9719); |
304 | err_cleanup_media: |
305 | media_entity_cleanup(entity: &dw9719->sd.entity); |
306 | err_free_ctrl_handler: |
307 | v4l2_ctrl_handler_free(hdl: &dw9719->ctrls.handler); |
308 | |
309 | return ret; |
310 | } |
311 | |
312 | static void dw9719_remove(struct i2c_client *client) |
313 | { |
314 | struct v4l2_subdev *sd = i2c_get_clientdata(client); |
315 | struct dw9719_device *dw9719 = |
316 | container_of(sd, struct dw9719_device, sd); |
317 | |
318 | v4l2_async_unregister_subdev(sd); |
319 | v4l2_ctrl_handler_free(hdl: &dw9719->ctrls.handler); |
320 | media_entity_cleanup(entity: &dw9719->sd.entity); |
321 | |
322 | pm_runtime_disable(dev: &client->dev); |
323 | if (!pm_runtime_status_suspended(dev: &client->dev)) |
324 | dw9719_power_down(dw9719); |
325 | pm_runtime_set_suspended(dev: &client->dev); |
326 | } |
327 | |
328 | static const struct i2c_device_id dw9719_id_table[] = { |
329 | { "dw9719" }, |
330 | { } |
331 | }; |
332 | MODULE_DEVICE_TABLE(i2c, dw9719_id_table); |
333 | |
334 | static DEFINE_RUNTIME_DEV_PM_OPS(dw9719_pm_ops, dw9719_suspend, dw9719_resume, |
335 | NULL); |
336 | |
337 | static struct i2c_driver dw9719_i2c_driver = { |
338 | .driver = { |
339 | .name = "dw9719" , |
340 | .pm = pm_sleep_ptr(&dw9719_pm_ops), |
341 | }, |
342 | .probe = dw9719_probe, |
343 | .remove = dw9719_remove, |
344 | .id_table = dw9719_id_table, |
345 | }; |
346 | module_i2c_driver(dw9719_i2c_driver); |
347 | |
348 | MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>" ); |
349 | MODULE_DESCRIPTION("DW9719 VCM Driver" ); |
350 | MODULE_LICENSE("GPL" ); |
351 | |