|
| 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