Skip to content

Commit 97ec6ae

Browse files
kbinghampelwell
authored andcommitted
media: i2c: Add ROHM BU64754 Camera Autofocus Actuator
Add support for the ROHM BU64754 Motor Driver for Camera Autofocus. A V4L2 Subdevice is registered and provides a single V4L2_CID_FOCUS_ABSOLUTE control. Signed-off-by: Kieran Bingham <[email protected]> Signed-off-by: Jacopo Mondi <[email protected]>
1 parent 87e3fca commit 97ec6ae

File tree

3 files changed

+329
-0
lines changed

3 files changed

+329
-0
lines changed

drivers/media/i2c/Kconfig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,19 @@ config VIDEO_AK7375
917917
capability. This is designed for linear control of
918918
voice coil motors, controlled via I2C serial interface.
919919

920+
config VIDEO_BU64754
921+
tristate "BU64754 Motor Driver for Camera Autofocus"
922+
depends on I2C && VIDEO_DEV
923+
select MEDIA_CONTROLLER
924+
select VIDEO_V4L2_SUBDEV_API
925+
select V4L2_ASYNC
926+
select V4L2_CCI_I2C
927+
help
928+
This is a driver for the BU64754 Motor Driver for Camera
929+
Autofocus. The BU64754GWZ is an actuator driver IC which
930+
can be controlled the actuator position precisely using
931+
with internal Hall Sensor.
932+
920933
config VIDEO_DW9714
921934
tristate "DW9714 lens voice coil support"
922935
depends on I2C && VIDEO_DEV

drivers/media/i2c/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_ARDUCAM_PIVARIETY) += arducam-pivariety.o
2626
obj-$(CONFIG_VIDEO_BT819) += bt819.o
2727
obj-$(CONFIG_VIDEO_BT856) += bt856.o
2828
obj-$(CONFIG_VIDEO_BT866) += bt866.o
29+
obj-$(CONFIG_VIDEO_BU64754) += bu64754.o
2930
obj-$(CONFIG_VIDEO_CCS) += ccs/
3031
obj-$(CONFIG_VIDEO_CCS_PLL) += ccs-pll.o
3132
obj-$(CONFIG_VIDEO_CS3308) += cs3308.o

drivers/media/i2c/bu64754.c

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* The BU64754GWZ is an actuator driver IC which can control the
4+
* actuator position precisely using an internal Hall Sensor.
5+
*/
6+
7+
#include <linux/delay.h>
8+
#include <linux/i2c.h>
9+
#include <linux/module.h>
10+
#include <linux/pm_runtime.h>
11+
#include <linux/regulator/consumer.h>
12+
13+
#include <media/v4l2-cci.h>
14+
#include <media/v4l2-ctrls.h>
15+
#include <media/v4l2-device.h>
16+
17+
#define BU64754_REG_ACTIVE CCI_REG16(0x07)
18+
#define BU64754_ACTIVE_MODE 0x8080
19+
20+
#define BU64754_REG_SERVE CCI_REG16(0xd9)
21+
#define BU64754_SERVE_ON 0x0404
22+
23+
#define BU64754_REG_POSITION CCI_REG16(0x45)
24+
#define BU64753_POSITION_MAX 1023 /* 0x3ff */
25+
#define BU64753_POSITION_STEPS 1
26+
27+
#define BU64754_POWER_ON_DELAY 800 /* uS : t1, t3 */
28+
29+
struct bu64754 {
30+
struct device *dev;
31+
32+
struct v4l2_ctrl_handler ctrls_vcm;
33+
struct v4l2_subdev sd;
34+
struct regmap *cci;
35+
36+
u16 current_val;
37+
struct regulator *vdd;
38+
struct notifier_block notifier;
39+
};
40+
41+
static inline struct bu64754 *sd_to_bu64754(struct v4l2_subdev *subdev)
42+
{
43+
return container_of(subdev, struct bu64754, sd);
44+
}
45+
46+
static int bu64754_set(struct bu64754 *bu64754, u16 position)
47+
{
48+
int ret;
49+
50+
position &= 0x3ff; /* BU64753_POSITION_MAX */
51+
ret = cci_write(bu64754->cci, BU64754_REG_POSITION, position, NULL);
52+
if (ret) {
53+
dev_err(bu64754->dev, "Set position failed ret=%d\n", ret);
54+
return ret;
55+
}
56+
57+
return 0;
58+
}
59+
60+
static int bu64754_active(struct bu64754 *bu64754)
61+
{
62+
int ret;
63+
64+
/* Power on */
65+
ret = cci_write(bu64754->cci, BU64754_REG_ACTIVE, BU64754_ACTIVE_MODE, NULL);
66+
if (ret < 0) {
67+
dev_err(bu64754->dev, "Failed to set active mode ret = %d\n",
68+
ret);
69+
return ret;
70+
}
71+
72+
/* Serve on */
73+
ret = cci_write(bu64754->cci, BU64754_REG_SERVE, BU64754_SERVE_ON, NULL);
74+
if (ret < 0) {
75+
dev_err(bu64754->dev, "Failed to enable serve ret = %d\n",
76+
ret);
77+
return ret;
78+
}
79+
80+
return bu64754_set(bu64754, bu64754->current_val);
81+
}
82+
83+
static int bu64754_standby(struct bu64754 *bu64754)
84+
{
85+
int ret;
86+
87+
ret = cci_write(bu64754->cci, BU64754_REG_ACTIVE, 0, NULL);
88+
if (ret < 0)
89+
dev_err(bu64754->dev, "Failed to enter standby mode ret = %d\n",
90+
ret);
91+
92+
return ret;
93+
}
94+
95+
static int bu64754_regulator_event(struct notifier_block *nb,
96+
unsigned long action, void *data)
97+
{
98+
struct bu64754 *bu64754 = container_of(nb, struct bu64754, notifier);
99+
100+
if (action & REGULATOR_EVENT_ENABLE) {
101+
/*
102+
* Initialisation delay between VDD low->high and availability
103+
* i2c operation.
104+
*/
105+
usleep_range(BU64754_POWER_ON_DELAY,
106+
BU64754_POWER_ON_DELAY + 100);
107+
108+
bu64754_active(bu64754);
109+
} else if (action & REGULATOR_EVENT_PRE_DISABLE) {
110+
bu64754_standby(bu64754);
111+
}
112+
113+
return 0;
114+
}
115+
116+
static int bu64754_set_ctrl(struct v4l2_ctrl *ctrl)
117+
{
118+
struct bu64754 *bu64754 = container_of(ctrl->handler,
119+
struct bu64754, ctrls_vcm);
120+
121+
if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) {
122+
bu64754->current_val = ctrl->val;
123+
return bu64754_set(bu64754, ctrl->val);
124+
}
125+
126+
return -EINVAL;
127+
}
128+
129+
static const struct v4l2_ctrl_ops bu64754_vcm_ctrl_ops = {
130+
.s_ctrl = bu64754_set_ctrl,
131+
};
132+
133+
static int bu64754_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
134+
{
135+
return pm_runtime_resume_and_get(sd->dev);
136+
}
137+
138+
static int bu64754_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
139+
{
140+
pm_runtime_put(sd->dev);
141+
return 0;
142+
}
143+
144+
static const struct v4l2_subdev_internal_ops bu64754_int_ops = {
145+
.open = bu64754_open,
146+
.close = bu64754_close,
147+
};
148+
149+
static const struct v4l2_subdev_ops bu64754_ops = { };
150+
151+
static void bu64754_subdev_cleanup(struct bu64754 *bu64754)
152+
{
153+
v4l2_async_unregister_subdev(&bu64754->sd);
154+
v4l2_ctrl_handler_free(&bu64754->ctrls_vcm);
155+
media_entity_cleanup(&bu64754->sd.entity);
156+
}
157+
158+
static int bu64754_init_controls(struct bu64754 *bu64754)
159+
{
160+
struct v4l2_ctrl_handler *hdl = &bu64754->ctrls_vcm;
161+
const struct v4l2_ctrl_ops *ops = &bu64754_vcm_ctrl_ops;
162+
163+
v4l2_ctrl_handler_init(hdl, 1);
164+
165+
v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE,
166+
0, BU64753_POSITION_MAX, BU64753_POSITION_STEPS,
167+
0);
168+
169+
bu64754->current_val = 0;
170+
171+
bu64754->sd.ctrl_handler = hdl;
172+
if (hdl->error) {
173+
dev_err(bu64754->dev, "%s fail error: 0x%x\n",
174+
__func__, hdl->error);
175+
return hdl->error;
176+
}
177+
178+
return 0;
179+
}
180+
181+
static int bu64754_probe(struct i2c_client *client)
182+
{
183+
struct bu64754 *bu64754;
184+
int ret;
185+
186+
bu64754 = devm_kzalloc(&client->dev, sizeof(*bu64754), GFP_KERNEL);
187+
if (!bu64754)
188+
return -ENOMEM;
189+
190+
bu64754->dev = &client->dev;
191+
192+
bu64754->cci = devm_cci_regmap_init_i2c(client, 8);
193+
if (IS_ERR(bu64754->cci)) {
194+
dev_err(bu64754->dev, "Failed to initialize CCI\n");
195+
return PTR_ERR(bu64754->cci);
196+
}
197+
198+
bu64754->vdd = devm_regulator_get_optional(&client->dev, "vdd");
199+
if (IS_ERR(bu64754->vdd)) {
200+
if (PTR_ERR(bu64754->vdd) != -ENODEV)
201+
return PTR_ERR(bu64754->vdd);
202+
203+
bu64754->vdd = NULL;
204+
} else {
205+
bu64754->notifier.notifier_call = bu64754_regulator_event;
206+
207+
ret = regulator_register_notifier(bu64754->vdd,
208+
&bu64754->notifier);
209+
if (ret) {
210+
dev_err(bu64754->dev,
211+
"could not register regulator notifier\n");
212+
return ret;
213+
}
214+
}
215+
216+
v4l2_i2c_subdev_init(&bu64754->sd, client, &bu64754_ops);
217+
bu64754->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
218+
bu64754->sd.internal_ops = &bu64754_int_ops;
219+
bu64754->sd.entity.function = MEDIA_ENT_F_LENS;
220+
221+
ret = bu64754_init_controls(bu64754);
222+
if (ret)
223+
goto err_cleanup;
224+
225+
ret = media_entity_pads_init(&bu64754->sd.entity, 0, NULL);
226+
if (ret < 0)
227+
goto err_cleanup;
228+
229+
bu64754->sd.entity.function = MEDIA_ENT_F_LENS;
230+
231+
ret = v4l2_async_register_subdev(&bu64754->sd);
232+
if (ret < 0)
233+
goto err_cleanup;
234+
235+
if (!bu64754->vdd)
236+
pm_runtime_set_active(&client->dev);
237+
238+
pm_runtime_enable(&client->dev);
239+
pm_runtime_idle(&client->dev);
240+
241+
return 0;
242+
243+
err_cleanup:
244+
v4l2_ctrl_handler_free(&bu64754->ctrls_vcm);
245+
media_entity_cleanup(&bu64754->sd.entity);
246+
247+
return ret;
248+
}
249+
250+
static void bu64754_remove(struct i2c_client *client)
251+
{
252+
struct v4l2_subdev *sd = i2c_get_clientdata(client);
253+
struct bu64754 *bu64754 = sd_to_bu64754(sd);
254+
255+
if (bu64754->vdd)
256+
regulator_unregister_notifier(bu64754->vdd,
257+
&bu64754->notifier);
258+
259+
pm_runtime_disable(&client->dev);
260+
261+
bu64754_subdev_cleanup(bu64754);
262+
}
263+
264+
static int __maybe_unused bu64754_vcm_suspend(struct device *dev)
265+
{
266+
struct i2c_client *client = to_i2c_client(dev);
267+
struct v4l2_subdev *sd = i2c_get_clientdata(client);
268+
struct bu64754 *bu64754 = sd_to_bu64754(sd);
269+
270+
if (bu64754->vdd)
271+
return regulator_disable(bu64754->vdd);
272+
273+
return bu64754_standby(bu64754);
274+
}
275+
276+
static int __maybe_unused bu64754_vcm_resume(struct device *dev)
277+
{
278+
struct i2c_client *client = to_i2c_client(dev);
279+
struct v4l2_subdev *sd = i2c_get_clientdata(client);
280+
struct bu64754 *bu64754 = sd_to_bu64754(sd);
281+
282+
if (bu64754->vdd)
283+
return regulator_enable(bu64754->vdd);
284+
285+
return bu64754_active(bu64754);
286+
}
287+
288+
static const struct of_device_id bu64754_of_table[] = {
289+
{ .compatible = "rohm,bu64754", },
290+
{ /* sentinel */ }
291+
};
292+
293+
MODULE_DEVICE_TABLE(of, bu64754_of_table);
294+
295+
static const struct dev_pm_ops bu64754_pm_ops = {
296+
SET_SYSTEM_SLEEP_PM_OPS(bu64754_vcm_suspend, bu64754_vcm_resume)
297+
SET_RUNTIME_PM_OPS(bu64754_vcm_suspend, bu64754_vcm_resume, NULL)
298+
};
299+
300+
static struct i2c_driver bu64754_i2c_driver = {
301+
.driver = {
302+
.name = "bu64754",
303+
.pm = &bu64754_pm_ops,
304+
.of_match_table = bu64754_of_table,
305+
},
306+
.probe_new = bu64754_probe,
307+
.remove = bu64754_remove,
308+
};
309+
310+
module_i2c_driver(bu64754_i2c_driver);
311+
312+
MODULE_AUTHOR("Kieran Bingham");
313+
MODULE_DESCRIPTION("BU64754 VCM driver");
314+
MODULE_LICENSE("GPL");
315+

0 commit comments

Comments
 (0)