blob: 3863dfeb829346ff5cdd0ce2f20e9cad5d37830d [file] [log] [blame]
Rajmohan Mani136fe992018-02-20 19:54:05 -05001// SPDX-License-Identifier: GPL-2.0
2// Copyright (c) 2015--2017 Intel Corporation.
Rajmohan Manicc95d342017-06-03 05:11:40 -03003
Rajmohan Manicc95d342017-06-03 05:11:40 -03004#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>
Ricardo Ribalda98442bd2021-10-07 00:26:23 +020010#include <media/v4l2-event.h>
Rajmohan Manicc95d342017-06-03 05:11:40 -030011
12#define DW9714_NAME "dw9714"
13#define DW9714_MAX_FOCUS_POS 1023
14/*
Rajmohan Mani1a58fbf2017-08-30 14:48:52 -030015 * This sets the minimum granularity for the focus positions.
16 * A value of 1 gives maximum accuracy for a desired focus position
17 */
18#define DW9714_FOCUS_STEPS 1
19/*
Rajmohan Manicc95d342017-06-03 05:11:40 -030020 * This acts as the minimum granularity of lens movement.
21 * Keep this value power of 2, so the control steps can be
22 * uniformly adjusted for gradual lens movement, with desired
23 * number of control steps.
24 */
25#define DW9714_CTRL_STEPS 16
26#define DW9714_CTRL_DELAY_US 1000
27/*
28 * S[3:2] = 0x00, codes per step for "Linear Slope Control"
29 * S[1:0] = 0x00, step period
30 */
31#define DW9714_DEFAULT_S 0x0
32#define DW9714_VAL(data, s) ((data) << 4 | (s))
33
34/* dw9714 device structure */
35struct dw9714_device {
Rajmohan Manicc95d342017-06-03 05:11:40 -030036 struct v4l2_ctrl_handler ctrls_vcm;
37 struct v4l2_subdev sd;
38 u16 current_val;
39};
40
41static inline struct dw9714_device *to_dw9714_vcm(struct v4l2_ctrl *ctrl)
42{
43 return container_of(ctrl->handler, struct dw9714_device, ctrls_vcm);
44}
45
46static inline struct dw9714_device *sd_to_dw9714_vcm(struct v4l2_subdev *subdev)
47{
48 return container_of(subdev, struct dw9714_device, sd);
49}
50
51static int dw9714_i2c_write(struct i2c_client *client, u16 data)
52{
53 int ret;
Mauro Carvalho Chehaba4a02b62018-01-23 07:46:07 -050054 __be16 val = cpu_to_be16(data);
Rajmohan Manicc95d342017-06-03 05:11:40 -030055
56 ret = i2c_master_send(client, (const char *)&val, sizeof(val));
57 if (ret != sizeof(val)) {
58 dev_err(&client->dev, "I2C write fail\n");
59 return -EIO;
60 }
61 return 0;
62}
63
64static int dw9714_t_focus_vcm(struct dw9714_device *dw9714_dev, u16 val)
65{
Sakari Ailusa69e9972018-01-02 05:55:17 -050066 struct i2c_client *client = v4l2_get_subdevdata(&dw9714_dev->sd);
Rajmohan Manicc95d342017-06-03 05:11:40 -030067
68 dw9714_dev->current_val = val;
69
70 return dw9714_i2c_write(client, DW9714_VAL(val, DW9714_DEFAULT_S));
71}
72
73static int dw9714_set_ctrl(struct v4l2_ctrl *ctrl)
74{
75 struct dw9714_device *dev_vcm = to_dw9714_vcm(ctrl);
76
77 if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE)
78 return dw9714_t_focus_vcm(dev_vcm, ctrl->val);
79
80 return -EINVAL;
81}
82
83static const struct v4l2_ctrl_ops dw9714_vcm_ctrl_ops = {
84 .s_ctrl = dw9714_set_ctrl,
85};
86
87static int dw9714_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
88{
Mauro Carvalho Chehab7917f272021-04-23 17:19:11 +020089 return pm_runtime_resume_and_get(sd->dev);
Rajmohan Manicc95d342017-06-03 05:11:40 -030090}
91
92static int dw9714_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
93{
Sakari Ailusa69e9972018-01-02 05:55:17 -050094 pm_runtime_put(sd->dev);
Rajmohan Manicc95d342017-06-03 05:11:40 -030095
96 return 0;
97}
98
99static const struct v4l2_subdev_internal_ops dw9714_int_ops = {
100 .open = dw9714_open,
101 .close = dw9714_close,
102};
103
Ricardo Ribalda98442bd2021-10-07 00:26:23 +0200104static const struct v4l2_subdev_core_ops dw9714_core_ops = {
105 .log_status = v4l2_ctrl_subdev_log_status,
106 .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
107 .unsubscribe_event = v4l2_event_subdev_unsubscribe,
108};
109
110static const struct v4l2_subdev_ops dw9714_ops = {
111 .core = &dw9714_core_ops,
112};
Rajmohan Manicc95d342017-06-03 05:11:40 -0300113
114static void dw9714_subdev_cleanup(struct dw9714_device *dw9714_dev)
115{
116 v4l2_async_unregister_subdev(&dw9714_dev->sd);
117 v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm);
118 media_entity_cleanup(&dw9714_dev->sd.entity);
119}
120
121static int dw9714_init_controls(struct dw9714_device *dev_vcm)
122{
123 struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm;
124 const struct v4l2_ctrl_ops *ops = &dw9714_vcm_ctrl_ops;
Rajmohan Manicc95d342017-06-03 05:11:40 -0300125
126 v4l2_ctrl_handler_init(hdl, 1);
127
128 v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE,
Rajmohan Mani1a58fbf2017-08-30 14:48:52 -0300129 0, DW9714_MAX_FOCUS_POS, DW9714_FOCUS_STEPS, 0);
Rajmohan Manicc95d342017-06-03 05:11:40 -0300130
131 if (hdl->error)
Sakari Ailusa69e9972018-01-02 05:55:17 -0500132 dev_err(dev_vcm->sd.dev, "%s fail error: 0x%x\n",
Rajmohan Manicc95d342017-06-03 05:11:40 -0300133 __func__, hdl->error);
134 dev_vcm->sd.ctrl_handler = hdl;
135 return hdl->error;
136}
137
Sakari Ailusf758eb22017-08-15 05:08:52 -0400138static int dw9714_probe(struct i2c_client *client)
Rajmohan Manicc95d342017-06-03 05:11:40 -0300139{
140 struct dw9714_device *dw9714_dev;
141 int rval;
142
143 dw9714_dev = devm_kzalloc(&client->dev, sizeof(*dw9714_dev),
144 GFP_KERNEL);
145 if (dw9714_dev == NULL)
146 return -ENOMEM;
147
Rajmohan Manicc95d342017-06-03 05:11:40 -0300148 v4l2_i2c_subdev_init(&dw9714_dev->sd, client, &dw9714_ops);
Ricardo Ribalda98442bd2021-10-07 00:26:23 +0200149 dw9714_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
150 V4L2_SUBDEV_FL_HAS_EVENTS;
Rajmohan Manicc95d342017-06-03 05:11:40 -0300151 dw9714_dev->sd.internal_ops = &dw9714_int_ops;
152
153 rval = dw9714_init_controls(dw9714_dev);
154 if (rval)
155 goto err_cleanup;
156
157 rval = media_entity_pads_init(&dw9714_dev->sd.entity, 0, NULL);
158 if (rval < 0)
159 goto err_cleanup;
160
161 dw9714_dev->sd.entity.function = MEDIA_ENT_F_LENS;
162
163 rval = v4l2_async_register_subdev(&dw9714_dev->sd);
164 if (rval < 0)
165 goto err_cleanup;
166
167 pm_runtime_set_active(&client->dev);
168 pm_runtime_enable(&client->dev);
Sakari Ailusfa429332018-01-02 05:51:57 -0500169 pm_runtime_idle(&client->dev);
Rajmohan Manicc95d342017-06-03 05:11:40 -0300170
171 return 0;
172
173err_cleanup:
Rajmohan Manif9a0b142018-10-05 12:22:17 -0400174 v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm);
175 media_entity_cleanup(&dw9714_dev->sd.entity);
Sakari Ailus1c55eca2018-10-05 17:19:38 -0400176
Rajmohan Manicc95d342017-06-03 05:11:40 -0300177 return rval;
178}
179
180static int dw9714_remove(struct i2c_client *client)
181{
182 struct v4l2_subdev *sd = i2c_get_clientdata(client);
183 struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd);
184
185 pm_runtime_disable(&client->dev);
186 dw9714_subdev_cleanup(dw9714_dev);
187
188 return 0;
189}
190
191/*
192 * This function sets the vcm position, so it consumes least current
193 * The lens position is gradually moved in units of DW9714_CTRL_STEPS,
194 * to make the movements smoothly.
195 */
196static int __maybe_unused dw9714_vcm_suspend(struct device *dev)
197{
198 struct i2c_client *client = to_i2c_client(dev);
199 struct v4l2_subdev *sd = i2c_get_clientdata(client);
200 struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd);
201 int ret, val;
202
203 for (val = dw9714_dev->current_val & ~(DW9714_CTRL_STEPS - 1);
204 val >= 0; val -= DW9714_CTRL_STEPS) {
205 ret = dw9714_i2c_write(client,
206 DW9714_VAL(val, DW9714_DEFAULT_S));
207 if (ret)
208 dev_err_once(dev, "%s I2C failure: %d", __func__, ret);
209 usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10);
210 }
211 return 0;
212}
213
214/*
215 * This function sets the vcm position to the value set by the user
216 * through v4l2_ctrl_ops s_ctrl handler
217 * The lens position is gradually moved in units of DW9714_CTRL_STEPS,
218 * to make the movements smoothly.
219 */
220static int __maybe_unused dw9714_vcm_resume(struct device *dev)
221{
222 struct i2c_client *client = to_i2c_client(dev);
223 struct v4l2_subdev *sd = i2c_get_clientdata(client);
224 struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd);
225 int ret, val;
226
227 for (val = dw9714_dev->current_val % DW9714_CTRL_STEPS;
228 val < dw9714_dev->current_val + DW9714_CTRL_STEPS - 1;
229 val += DW9714_CTRL_STEPS) {
230 ret = dw9714_i2c_write(client,
231 DW9714_VAL(val, DW9714_DEFAULT_S));
232 if (ret)
233 dev_err_ratelimited(dev, "%s I2C failure: %d",
234 __func__, ret);
235 usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10);
236 }
237
238 return 0;
239}
240
Rajmohan Manicc95d342017-06-03 05:11:40 -0300241static const struct i2c_device_id dw9714_id_table[] = {
Sakari Ailusf758eb22017-08-15 05:08:52 -0400242 { DW9714_NAME, 0 },
243 { { 0 } }
Rajmohan Manicc95d342017-06-03 05:11:40 -0300244};
Rajmohan Manicc95d342017-06-03 05:11:40 -0300245MODULE_DEVICE_TABLE(i2c, dw9714_id_table);
246
Sakari Ailusc2bc8b02017-08-15 05:06:59 -0400247static const struct of_device_id dw9714_of_table[] = {
248 { .compatible = "dongwoon,dw9714" },
249 { { 0 } }
250};
251MODULE_DEVICE_TABLE(of, dw9714_of_table);
252
Rajmohan Manicc95d342017-06-03 05:11:40 -0300253static const struct dev_pm_ops dw9714_pm_ops = {
254 SET_SYSTEM_SLEEP_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume)
255 SET_RUNTIME_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume, NULL)
256};
257
258static struct i2c_driver dw9714_i2c_driver = {
259 .driver = {
260 .name = DW9714_NAME,
261 .pm = &dw9714_pm_ops,
Sakari Ailusc2bc8b02017-08-15 05:06:59 -0400262 .of_match_table = dw9714_of_table,
Rajmohan Manicc95d342017-06-03 05:11:40 -0300263 },
Sakari Ailusf758eb22017-08-15 05:08:52 -0400264 .probe_new = dw9714_probe,
Rajmohan Manicc95d342017-06-03 05:11:40 -0300265 .remove = dw9714_remove,
266 .id_table = dw9714_id_table,
267};
268
269module_i2c_driver(dw9714_i2c_driver);
270
271MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
Sakari Ailus8f577632019-01-07 06:07:05 -0500272MODULE_AUTHOR("Jian Xu Zheng");
Rajmohan Manicc95d342017-06-03 05:11:40 -0300273MODULE_AUTHOR("Yuning Pu <yuning.pu@intel.com>");
274MODULE_AUTHOR("Jouni Ukkonen <jouni.ukkonen@intel.com>");
275MODULE_AUTHOR("Tommi Franttila <tommi.franttila@intel.com>");
276MODULE_DESCRIPTION("DW9714 VCM driver");
277MODULE_LICENSE("GPL v2");