File Index
Files and directories
Sign in
FILE

dw9714.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/i2c/dw9714.c C
  1// SPDX-License-Identifier: GPL-2.0
  2// Copyright (c) 2015--2017 Intel Corporation.
  3
  4#include <linux/delay.h>
  5#include <linux/gpio/consumer.h>
  6#include <linux/i2c.h>
  7#include <linux/module.h>
  8#include <linux/pm_runtime.h>
  9#include <linux/regulator/consumer.h>
 10#include <media/v4l2-ctrls.h>
 11#include <media/v4l2-device.h>
 12#include <media/v4l2-event.h>
 13
 14#define DW9714_NAME		"dw9714"
 15#define DW9714_MAX_FOCUS_POS	1023
 16/*
 17 * This sets the minimum granularity for the focus positions.
 18 * A value of 1 gives maximum accuracy for a desired focus position
 19 */
 20#define DW9714_FOCUS_STEPS	1
 21/*
 22 * This acts as the minimum granularity of lens movement.
 23 * Keep this value power of 2, so the control steps can be
 24 * uniformly adjusted for gradual lens movement, with desired
 25 * number of control steps.
 26 */
 27#define DW9714_CTRL_STEPS	16
 28#define DW9714_CTRL_DELAY_US	1000
 29/*
 30 * S[3:2] = 0x00, codes per step for "Linear Slope Control"
 31 * S[1:0] = 0x00, step period
 32 */
 33#define DW9714_DEFAULT_S 0x0
 34#define DW9714_VAL(data, s) ((data) << 4 | (s))
 35
 36/* dw9714 device structure */
 37struct dw9714_device {
 38	struct v4l2_ctrl_handler ctrls_vcm;
 39	struct v4l2_subdev sd;
 40	u16 current_val;
 41	struct regulator *vcc;
 42	struct gpio_desc *powerdown_gpio;
 43};
 44
 45static inline struct dw9714_device *to_dw9714_vcm(struct v4l2_ctrl *ctrl)
 46{
 47	return container_of(ctrl->handler, struct dw9714_device, ctrls_vcm);
 48}
 49
 50static inline struct dw9714_device *sd_to_dw9714_vcm(struct v4l2_subdev *subdev)
 51{
 52	return container_of(subdev, struct dw9714_device, sd);
 53}
 54
 55static int dw9714_i2c_write(struct i2c_client *client, u16 data)
 56{
 57	int ret;
 58	__be16 val = cpu_to_be16(data);
 59
 60	ret = i2c_master_send(client, (const char *)&val, sizeof(val));
 61	if (ret != sizeof(val)) {
 62		dev_err(&client->dev, "I2C write fail\n");
 63		return -EIO;
 64	}
 65	return 0;
 66}
 67
 68static int dw9714_t_focus_vcm(struct dw9714_device *dw9714_dev, u16 val)
 69{
 70	struct i2c_client *client = v4l2_get_subdevdata(&dw9714_dev->sd);
 71
 72	dw9714_dev->current_val = val;
 73
 74	return dw9714_i2c_write(client, DW9714_VAL(val, DW9714_DEFAULT_S));
 75}
 76
 77static int dw9714_set_ctrl(struct v4l2_ctrl *ctrl)
 78{
 79	struct dw9714_device *dev_vcm = to_dw9714_vcm(ctrl);
 80
 81	if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE)
 82		return dw9714_t_focus_vcm(dev_vcm, ctrl->val);
 83
 84	return -EINVAL;
 85}
 86
 87static const struct v4l2_ctrl_ops dw9714_vcm_ctrl_ops = {
 88	.s_ctrl = dw9714_set_ctrl,
 89};
 90
 91static int dw9714_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
 92{
 93	return pm_runtime_resume_and_get(sd->dev);
 94}
 95
 96static int dw9714_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
 97{
 98	pm_runtime_put(sd->dev);
 99
100	return 0;
101}
102
103static const struct v4l2_subdev_internal_ops dw9714_int_ops = {
104	.open = dw9714_open,
105	.close = dw9714_close,
106};
107
108static const struct v4l2_subdev_core_ops dw9714_core_ops = {
109	.log_status = v4l2_ctrl_subdev_log_status,
110	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
111	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
112};
113
114static const struct v4l2_subdev_ops dw9714_ops = {
115	.core = &dw9714_core_ops,
116};
117
118static void dw9714_subdev_cleanup(struct dw9714_device *dw9714_dev)
119{
120	v4l2_async_unregister_subdev(&dw9714_dev->sd);
121	v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm);
122	media_entity_cleanup(&dw9714_dev->sd.entity);
123}
124
125static int dw9714_init_controls(struct dw9714_device *dev_vcm)
126{
127	struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm;
128	const struct v4l2_ctrl_ops *ops = &dw9714_vcm_ctrl_ops;
129
130	v4l2_ctrl_handler_init(hdl, 1);
131
132	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE,
133			  0, DW9714_MAX_FOCUS_POS, DW9714_FOCUS_STEPS, 0);
134
135	if (hdl->error)
136		dev_err(dev_vcm->sd.dev, "%s fail error: 0x%x\n",
137			__func__, hdl->error);
138	dev_vcm->sd.ctrl_handler = hdl;
139	return hdl->error;
140}
141
142static int dw9714_power_up(struct dw9714_device *dw9714_dev)
143{
144	int ret;
145
146	ret = regulator_enable(dw9714_dev->vcc);
147	if (ret)
148		return ret;
149
150	gpiod_set_value_cansleep(dw9714_dev->powerdown_gpio, 0);
151
152	usleep_range(12000, 14000);
153
154	return 0;
155}
156
157static int dw9714_power_down(struct dw9714_device *dw9714_dev)
158{
159	gpiod_set_value_cansleep(dw9714_dev->powerdown_gpio, 1);
160
161	return regulator_disable(dw9714_dev->vcc);
162}
163
164static int dw9714_probe(struct i2c_client *client)
165{
166	struct dw9714_device *dw9714_dev;
167	int rval;
168
169	dw9714_dev = devm_kzalloc(&client->dev, sizeof(*dw9714_dev),
170				  GFP_KERNEL);
171	if (!dw9714_dev)
172		return -ENOMEM;
173
174	dw9714_dev->vcc = devm_regulator_get(&client->dev, "vcc");
175	if (IS_ERR(dw9714_dev->vcc))
176		return PTR_ERR(dw9714_dev->vcc);
177
178	dw9714_dev->powerdown_gpio = devm_gpiod_get_optional(&client->dev,
179							     "powerdown",
180							     GPIOD_OUT_HIGH);
181	if (IS_ERR(dw9714_dev->powerdown_gpio))
182		return dev_err_probe(&client->dev,
183				     PTR_ERR(dw9714_dev->powerdown_gpio),
184				     "could not get powerdown gpio\n");
185
186	rval = dw9714_power_up(dw9714_dev);
187	if (rval)
188		return dev_err_probe(&client->dev, rval,
189				     "failed to power up: %d\n", rval);
190
191	v4l2_i2c_subdev_init(&dw9714_dev->sd, client, &dw9714_ops);
192	dw9714_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
193				V4L2_SUBDEV_FL_HAS_EVENTS;
194	dw9714_dev->sd.internal_ops = &dw9714_int_ops;
195
196	rval = dw9714_init_controls(dw9714_dev);
197	if (rval)
198		goto err_cleanup;
199
200	rval = media_entity_pads_init(&dw9714_dev->sd.entity, 0, NULL);
201	if (rval < 0)
202		goto err_cleanup;
203
204	dw9714_dev->sd.entity.function = MEDIA_ENT_F_LENS;
205
206	rval = v4l2_async_register_subdev(&dw9714_dev->sd);
207	if (rval < 0)
208		goto err_cleanup;
209
210	pm_runtime_set_active(&client->dev);
211	pm_runtime_enable(&client->dev);
212	pm_runtime_idle(&client->dev);
213
214	return 0;
215
216err_cleanup:
217	dw9714_power_down(dw9714_dev);
218	v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm);
219	media_entity_cleanup(&dw9714_dev->sd.entity);
220
221	return rval;
222}
223
224static void dw9714_remove(struct i2c_client *client)
225{
226	struct v4l2_subdev *sd = i2c_get_clientdata(client);
227	struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd);
228	int ret;
229
230	pm_runtime_disable(&client->dev);
231	if (!pm_runtime_status_suspended(&client->dev)) {
232		ret = dw9714_power_down(dw9714_dev);
233		if (ret) {
234			dev_err(&client->dev,
235				"Failed to power down: %d\n", ret);
236		}
237	}
238	pm_runtime_set_suspended(&client->dev);
239	dw9714_subdev_cleanup(dw9714_dev);
240}
241
242/*
243 * This function sets the vcm position, so it consumes least current
244 * The lens position is gradually moved in units of DW9714_CTRL_STEPS,
245 * to make the movements smoothly.
246 */
247static int __maybe_unused dw9714_vcm_suspend(struct device *dev)
248{
249	struct i2c_client *client = to_i2c_client(dev);
250	struct v4l2_subdev *sd = i2c_get_clientdata(client);
251	struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd);
252	int ret, val;
253
254	if (pm_runtime_suspended(&client->dev))
255		return 0;
256
257	for (val = dw9714_dev->current_val & ~(DW9714_CTRL_STEPS - 1);
258	     val >= 0; val -= DW9714_CTRL_STEPS) {
259		ret = dw9714_i2c_write(client,
260				       DW9714_VAL(val, DW9714_DEFAULT_S));
261		if (ret)
262			dev_err_once(dev, "%s I2C failure: %d", __func__, ret);
263		usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10);
264	}
265
266	ret = dw9714_power_down(dw9714_dev);
267	if (ret)
268		dev_err(dev, "Failed to power down: %d\n", ret);
269
270	return ret;
271}
272
273/*
274 * This function sets the vcm position to the value set by the user
275 * through v4l2_ctrl_ops s_ctrl handler
276 * The lens position is gradually moved in units of DW9714_CTRL_STEPS,
277 * to make the movements smoothly.
278 */
279static int __maybe_unused dw9714_vcm_resume(struct device *dev)
280{
281	struct i2c_client *client = to_i2c_client(dev);
282	struct v4l2_subdev *sd = i2c_get_clientdata(client);
283	struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd);
284	int ret, val;
285
286	if (pm_runtime_suspended(&client->dev))
287		return 0;
288
289	ret = dw9714_power_up(dw9714_dev);
290	if (ret) {
291		dev_err(dev, "Failed to power up: %d\n", ret);
292		return ret;
293	}
294
295	for (val = dw9714_dev->current_val % DW9714_CTRL_STEPS;
296	     val < dw9714_dev->current_val + DW9714_CTRL_STEPS - 1;
297	     val += DW9714_CTRL_STEPS) {
298		ret = dw9714_i2c_write(client,
299				       DW9714_VAL(val, DW9714_DEFAULT_S));
300		if (ret)
301			dev_err_ratelimited(dev, "%s I2C failure: %d",
302					    __func__, ret);
303		usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10);
304	}
305
306	return 0;
307}
308
309static const struct i2c_device_id dw9714_id_table[] = {
310	{ DW9714_NAME },
311	{ }
312};
313MODULE_DEVICE_TABLE(i2c, dw9714_id_table);
314
315static const struct of_device_id dw9714_of_table[] = {
316	{ .compatible = "dongwoon,dw9714" },
317	{ { 0 } }
318};
319MODULE_DEVICE_TABLE(of, dw9714_of_table);
320
321static const struct dev_pm_ops dw9714_pm_ops = {
322	SET_SYSTEM_SLEEP_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume)
323	SET_RUNTIME_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume, NULL)
324};
325
326static struct i2c_driver dw9714_i2c_driver = {
327	.driver = {
328		.name = DW9714_NAME,
329		.pm = &dw9714_pm_ops,
330		.of_match_table = dw9714_of_table,
331	},
332	.probe = dw9714_probe,
333	.remove = dw9714_remove,
334	.id_table = dw9714_id_table,
335};
336
337module_i2c_driver(dw9714_i2c_driver);
338
339MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
340MODULE_AUTHOR("Jian Xu Zheng");
341MODULE_AUTHOR("Yuning Pu");
342MODULE_AUTHOR("Jouni Ukkonen");
343MODULE_AUTHOR("Tommi Franttila");
344MODULE_DESCRIPTION("DW9714 VCM driver");
345MODULE_LICENSE("GPL v2");
~/ Read-only file preview.