Commit | Line | Data |
---|---|---|
136fe992 RM |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2015--2017 Intel Corporation. | |
cc95d342 | 3 | |
cc95d342 RM |
4 | #include <linux/delay.h> |
5 | #include <linux/i2c.h> | |
6 | #include <linux/module.h> | |
7 | #include <linux/pm_runtime.h> | |
8 | #include <media/v4l2-ctrls.h> | |
9 | #include <media/v4l2-device.h> | |
10 | ||
11 | #define DW9714_NAME "dw9714" | |
12 | #define DW9714_MAX_FOCUS_POS 1023 | |
1a58fbf5 RM |
13 | /* |
14 | * This sets the minimum granularity for the focus positions. | |
15 | * A value of 1 gives maximum accuracy for a desired focus position | |
16 | */ | |
17 | #define DW9714_FOCUS_STEPS 1 | |
cc95d342 RM |
18 | /* |
19 | * This acts as the minimum granularity of lens movement. | |
20 | * Keep this value power of 2, so the control steps can be | |
21 | * uniformly adjusted for gradual lens movement, with desired | |
22 | * number of control steps. | |
23 | */ | |
24 | #define DW9714_CTRL_STEPS 16 | |
25 | #define DW9714_CTRL_DELAY_US 1000 | |
26 | /* | |
27 | * S[3:2] = 0x00, codes per step for "Linear Slope Control" | |
28 | * S[1:0] = 0x00, step period | |
29 | */ | |
30 | #define DW9714_DEFAULT_S 0x0 | |
31 | #define DW9714_VAL(data, s) ((data) << 4 | (s)) | |
32 | ||
33 | /* dw9714 device structure */ | |
34 | struct dw9714_device { | |
cc95d342 RM |
35 | struct v4l2_ctrl_handler ctrls_vcm; |
36 | struct v4l2_subdev sd; | |
37 | u16 current_val; | |
38 | }; | |
39 | ||
40 | static inline struct dw9714_device *to_dw9714_vcm(struct v4l2_ctrl *ctrl) | |
41 | { | |
42 | return container_of(ctrl->handler, struct dw9714_device, ctrls_vcm); | |
43 | } | |
44 | ||
45 | static inline struct dw9714_device *sd_to_dw9714_vcm(struct v4l2_subdev *subdev) | |
46 | { | |
47 | return container_of(subdev, struct dw9714_device, sd); | |
48 | } | |
49 | ||
50 | static int dw9714_i2c_write(struct i2c_client *client, u16 data) | |
51 | { | |
52 | int ret; | |
a4a02b6e | 53 | __be16 val = cpu_to_be16(data); |
cc95d342 RM |
54 | |
55 | ret = i2c_master_send(client, (const char *)&val, sizeof(val)); | |
56 | if (ret != sizeof(val)) { | |
57 | dev_err(&client->dev, "I2C write fail\n"); | |
58 | return -EIO; | |
59 | } | |
60 | return 0; | |
61 | } | |
62 | ||
63 | static int dw9714_t_focus_vcm(struct dw9714_device *dw9714_dev, u16 val) | |
64 | { | |
a69e9972 | 65 | struct i2c_client *client = v4l2_get_subdevdata(&dw9714_dev->sd); |
cc95d342 RM |
66 | |
67 | dw9714_dev->current_val = val; | |
68 | ||
69 | return dw9714_i2c_write(client, DW9714_VAL(val, DW9714_DEFAULT_S)); | |
70 | } | |
71 | ||
72 | static int dw9714_set_ctrl(struct v4l2_ctrl *ctrl) | |
73 | { | |
74 | struct dw9714_device *dev_vcm = to_dw9714_vcm(ctrl); | |
75 | ||
76 | if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) | |
77 | return dw9714_t_focus_vcm(dev_vcm, ctrl->val); | |
78 | ||
79 | return -EINVAL; | |
80 | } | |
81 | ||
82 | static const struct v4l2_ctrl_ops dw9714_vcm_ctrl_ops = { | |
83 | .s_ctrl = dw9714_set_ctrl, | |
84 | }; | |
85 | ||
86 | static int dw9714_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) | |
87 | { | |
cc95d342 RM |
88 | int rval; |
89 | ||
a69e9972 | 90 | rval = pm_runtime_get_sync(sd->dev); |
cc95d342 | 91 | if (rval < 0) { |
a69e9972 | 92 | pm_runtime_put_noidle(sd->dev); |
cc95d342 RM |
93 | return rval; |
94 | } | |
95 | ||
96 | return 0; | |
97 | } | |
98 | ||
99 | static int dw9714_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) | |
100 | { | |
a69e9972 | 101 | pm_runtime_put(sd->dev); |
cc95d342 RM |
102 | |
103 | return 0; | |
104 | } | |
105 | ||
106 | static const struct v4l2_subdev_internal_ops dw9714_int_ops = { | |
107 | .open = dw9714_open, | |
108 | .close = dw9714_close, | |
109 | }; | |
110 | ||
111 | static const struct v4l2_subdev_ops dw9714_ops = { }; | |
112 | ||
113 | static void dw9714_subdev_cleanup(struct dw9714_device *dw9714_dev) | |
114 | { | |
115 | v4l2_async_unregister_subdev(&dw9714_dev->sd); | |
116 | v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm); | |
117 | media_entity_cleanup(&dw9714_dev->sd.entity); | |
118 | } | |
119 | ||
120 | static int dw9714_init_controls(struct dw9714_device *dev_vcm) | |
121 | { | |
122 | struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; | |
123 | const struct v4l2_ctrl_ops *ops = &dw9714_vcm_ctrl_ops; | |
cc95d342 RM |
124 | |
125 | v4l2_ctrl_handler_init(hdl, 1); | |
126 | ||
127 | v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, | |
1a58fbf5 | 128 | 0, DW9714_MAX_FOCUS_POS, DW9714_FOCUS_STEPS, 0); |
cc95d342 RM |
129 | |
130 | if (hdl->error) | |
a69e9972 | 131 | dev_err(dev_vcm->sd.dev, "%s fail error: 0x%x\n", |
cc95d342 RM |
132 | __func__, hdl->error); |
133 | dev_vcm->sd.ctrl_handler = hdl; | |
134 | return hdl->error; | |
135 | } | |
136 | ||
f758eb23 | 137 | static int dw9714_probe(struct i2c_client *client) |
cc95d342 RM |
138 | { |
139 | struct dw9714_device *dw9714_dev; | |
140 | int rval; | |
141 | ||
142 | dw9714_dev = devm_kzalloc(&client->dev, sizeof(*dw9714_dev), | |
143 | GFP_KERNEL); | |
144 | if (dw9714_dev == NULL) | |
145 | return -ENOMEM; | |
146 | ||
cc95d342 RM |
147 | v4l2_i2c_subdev_init(&dw9714_dev->sd, client, &dw9714_ops); |
148 | dw9714_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; | |
149 | dw9714_dev->sd.internal_ops = &dw9714_int_ops; | |
150 | ||
151 | rval = dw9714_init_controls(dw9714_dev); | |
152 | if (rval) | |
153 | goto err_cleanup; | |
154 | ||
155 | rval = media_entity_pads_init(&dw9714_dev->sd.entity, 0, NULL); | |
156 | if (rval < 0) | |
157 | goto err_cleanup; | |
158 | ||
159 | dw9714_dev->sd.entity.function = MEDIA_ENT_F_LENS; | |
160 | ||
161 | rval = v4l2_async_register_subdev(&dw9714_dev->sd); | |
162 | if (rval < 0) | |
163 | goto err_cleanup; | |
164 | ||
165 | pm_runtime_set_active(&client->dev); | |
166 | pm_runtime_enable(&client->dev); | |
fa429334 | 167 | pm_runtime_idle(&client->dev); |
cc95d342 RM |
168 | |
169 | return 0; | |
170 | ||
171 | err_cleanup: | |
f9a0b142 RM |
172 | v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm); |
173 | media_entity_cleanup(&dw9714_dev->sd.entity); | |
1c55ecab | 174 | |
cc95d342 RM |
175 | return rval; |
176 | } | |
177 | ||
178 | static int dw9714_remove(struct i2c_client *client) | |
179 | { | |
180 | struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
181 | struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); | |
182 | ||
183 | pm_runtime_disable(&client->dev); | |
184 | dw9714_subdev_cleanup(dw9714_dev); | |
185 | ||
186 | return 0; | |
187 | } | |
188 | ||
189 | /* | |
190 | * This function sets the vcm position, so it consumes least current | |
191 | * The lens position is gradually moved in units of DW9714_CTRL_STEPS, | |
192 | * to make the movements smoothly. | |
193 | */ | |
194 | static int __maybe_unused dw9714_vcm_suspend(struct device *dev) | |
195 | { | |
196 | struct i2c_client *client = to_i2c_client(dev); | |
197 | struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
198 | struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); | |
199 | int ret, val; | |
200 | ||
201 | for (val = dw9714_dev->current_val & ~(DW9714_CTRL_STEPS - 1); | |
202 | val >= 0; val -= DW9714_CTRL_STEPS) { | |
203 | ret = dw9714_i2c_write(client, | |
204 | DW9714_VAL(val, DW9714_DEFAULT_S)); | |
205 | if (ret) | |
206 | dev_err_once(dev, "%s I2C failure: %d", __func__, ret); | |
207 | usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); | |
208 | } | |
209 | return 0; | |
210 | } | |
211 | ||
212 | /* | |
213 | * This function sets the vcm position to the value set by the user | |
214 | * through v4l2_ctrl_ops s_ctrl handler | |
215 | * The lens position is gradually moved in units of DW9714_CTRL_STEPS, | |
216 | * to make the movements smoothly. | |
217 | */ | |
218 | static int __maybe_unused dw9714_vcm_resume(struct device *dev) | |
219 | { | |
220 | struct i2c_client *client = to_i2c_client(dev); | |
221 | struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
222 | struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); | |
223 | int ret, val; | |
224 | ||
225 | for (val = dw9714_dev->current_val % DW9714_CTRL_STEPS; | |
226 | val < dw9714_dev->current_val + DW9714_CTRL_STEPS - 1; | |
227 | val += DW9714_CTRL_STEPS) { | |
228 | ret = dw9714_i2c_write(client, | |
229 | DW9714_VAL(val, DW9714_DEFAULT_S)); | |
230 | if (ret) | |
231 | dev_err_ratelimited(dev, "%s I2C failure: %d", | |
232 | __func__, ret); | |
233 | usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); | |
234 | } | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
cc95d342 | 239 | static const struct i2c_device_id dw9714_id_table[] = { |
f758eb23 SA |
240 | { DW9714_NAME, 0 }, |
241 | { { 0 } } | |
cc95d342 | 242 | }; |
cc95d342 RM |
243 | MODULE_DEVICE_TABLE(i2c, dw9714_id_table); |
244 | ||
c2bc8b06 SA |
245 | static const struct of_device_id dw9714_of_table[] = { |
246 | { .compatible = "dongwoon,dw9714" }, | |
247 | { { 0 } } | |
248 | }; | |
249 | MODULE_DEVICE_TABLE(of, dw9714_of_table); | |
250 | ||
cc95d342 RM |
251 | static const struct dev_pm_ops dw9714_pm_ops = { |
252 | SET_SYSTEM_SLEEP_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume) | |
253 | SET_RUNTIME_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume, NULL) | |
254 | }; | |
255 | ||
256 | static struct i2c_driver dw9714_i2c_driver = { | |
257 | .driver = { | |
258 | .name = DW9714_NAME, | |
259 | .pm = &dw9714_pm_ops, | |
c2bc8b06 | 260 | .of_match_table = dw9714_of_table, |
cc95d342 | 261 | }, |
f758eb23 | 262 | .probe_new = dw9714_probe, |
cc95d342 RM |
263 | .remove = dw9714_remove, |
264 | .id_table = dw9714_id_table, | |
265 | }; | |
266 | ||
267 | module_i2c_driver(dw9714_i2c_driver); | |
268 | ||
269 | MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); | |
8f57763e | 270 | MODULE_AUTHOR("Jian Xu Zheng"); |
cc95d342 RM |
271 | MODULE_AUTHOR("Yuning Pu <yuning.pu@intel.com>"); |
272 | MODULE_AUTHOR("Jouni Ukkonen <jouni.ukkonen@intel.com>"); | |
273 | MODULE_AUTHOR("Tommi Franttila <tommi.franttila@intel.com>"); | |
274 | MODULE_DESCRIPTION("DW9714 VCM driver"); | |
275 | MODULE_LICENSE("GPL v2"); |