322 lines · c
1// SPDX-License-Identifier: GPL-2.02// Copyright (C) 2018 Intel Corporation3 4#include <linux/acpi.h>5#include <linux/delay.h>6#include <linux/i2c.h>7#include <linux/iopoll.h>8#include <linux/module.h>9#include <linux/pm_runtime.h>10#include <media/v4l2-ctrls.h>11#include <media/v4l2-device.h>12 13#define DW9807_MAX_FOCUS_POS 102314/*15 * 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 DW9807_FOCUS_STEPS 119/*20 * This acts as the minimum granularity of lens movement.21 * Keep this value power of 2, so the control steps can be22 * uniformly adjusted for gradual lens movement, with desired23 * number of control steps.24 */25#define DW9807_CTRL_STEPS 1626#define DW9807_CTRL_DELAY_US 100027 28#define DW9807_CTL_ADDR 0x0229/*30 * DW9807 separates two registers to control the VCM position.31 * One for MSB value, another is LSB value.32 */33#define DW9807_MSB_ADDR 0x0334#define DW9807_LSB_ADDR 0x0435#define DW9807_STATUS_ADDR 0x0536#define DW9807_MODE_ADDR 0x0637#define DW9807_RESONANCE_ADDR 0x0738 39#define MAX_RETRY 1040 41struct dw9807_device {42 struct v4l2_ctrl_handler ctrls_vcm;43 struct v4l2_subdev sd;44 u16 current_val;45};46 47static inline struct dw9807_device *sd_to_dw9807_vcm(48 struct v4l2_subdev *subdev)49{50 return container_of(subdev, struct dw9807_device, sd);51}52 53static int dw9807_i2c_check(struct i2c_client *client)54{55 const char status_addr = DW9807_STATUS_ADDR;56 char status_result;57 int ret;58 59 ret = i2c_master_send(client, &status_addr, sizeof(status_addr));60 if (ret < 0) {61 dev_err(&client->dev, "I2C write STATUS address fail ret = %d\n",62 ret);63 return ret;64 }65 66 ret = i2c_master_recv(client, &status_result, sizeof(status_result));67 if (ret < 0) {68 dev_err(&client->dev, "I2C read STATUS value fail ret = %d\n",69 ret);70 return ret;71 }72 73 return status_result;74}75 76static int dw9807_set_dac(struct i2c_client *client, u16 data)77{78 const char tx_data[3] = {79 DW9807_MSB_ADDR, ((data >> 8) & 0x03), (data & 0xff)80 };81 int val, ret;82 83 /*84 * According to the datasheet, need to check the bus status before we85 * write VCM position. This ensure that we really write the value86 * into the register87 */88 ret = readx_poll_timeout(dw9807_i2c_check, client, val, val <= 0,89 DW9807_CTRL_DELAY_US, MAX_RETRY * DW9807_CTRL_DELAY_US);90 91 if (ret || val < 0) {92 if (ret) {93 dev_warn(&client->dev,94 "Cannot do the write operation because VCM is busy\n");95 }96 97 return ret ? -EBUSY : val;98 }99 100 /* Write VCM position to registers */101 ret = i2c_master_send(client, tx_data, sizeof(tx_data));102 if (ret < 0) {103 dev_err(&client->dev,104 "I2C write MSB fail ret=%d\n", ret);105 106 return ret;107 }108 109 return 0;110}111 112static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl)113{114 struct dw9807_device *dev_vcm = container_of(ctrl->handler,115 struct dw9807_device, ctrls_vcm);116 117 if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) {118 struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd);119 120 dev_vcm->current_val = ctrl->val;121 return dw9807_set_dac(client, ctrl->val);122 }123 124 return -EINVAL;125}126 127static const struct v4l2_ctrl_ops dw9807_vcm_ctrl_ops = {128 .s_ctrl = dw9807_set_ctrl,129};130 131static int dw9807_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)132{133 return pm_runtime_resume_and_get(sd->dev);134}135 136static int dw9807_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)137{138 pm_runtime_put(sd->dev);139 140 return 0;141}142 143static const struct v4l2_subdev_internal_ops dw9807_int_ops = {144 .open = dw9807_open,145 .close = dw9807_close,146};147 148static const struct v4l2_subdev_ops dw9807_ops = { };149 150static void dw9807_subdev_cleanup(struct dw9807_device *dw9807_dev)151{152 v4l2_async_unregister_subdev(&dw9807_dev->sd);153 v4l2_ctrl_handler_free(&dw9807_dev->ctrls_vcm);154 media_entity_cleanup(&dw9807_dev->sd.entity);155}156 157static int dw9807_init_controls(struct dw9807_device *dev_vcm)158{159 struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm;160 const struct v4l2_ctrl_ops *ops = &dw9807_vcm_ctrl_ops;161 struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd);162 163 v4l2_ctrl_handler_init(hdl, 1);164 165 v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE,166 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0);167 168 dev_vcm->sd.ctrl_handler = hdl;169 if (hdl->error) {170 dev_err(&client->dev, "%s fail error: 0x%x\n",171 __func__, hdl->error);172 return hdl->error;173 }174 175 return 0;176}177 178static int dw9807_probe(struct i2c_client *client)179{180 struct dw9807_device *dw9807_dev;181 int rval;182 183 dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev),184 GFP_KERNEL);185 if (dw9807_dev == NULL)186 return -ENOMEM;187 188 v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops);189 dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;190 dw9807_dev->sd.internal_ops = &dw9807_int_ops;191 192 rval = dw9807_init_controls(dw9807_dev);193 if (rval)194 goto err_cleanup;195 196 rval = media_entity_pads_init(&dw9807_dev->sd.entity, 0, NULL);197 if (rval < 0)198 goto err_cleanup;199 200 dw9807_dev->sd.entity.function = MEDIA_ENT_F_LENS;201 202 rval = v4l2_async_register_subdev(&dw9807_dev->sd);203 if (rval < 0)204 goto err_cleanup;205 206 pm_runtime_set_active(&client->dev);207 pm_runtime_enable(&client->dev);208 pm_runtime_idle(&client->dev);209 210 return 0;211 212err_cleanup:213 v4l2_ctrl_handler_free(&dw9807_dev->ctrls_vcm);214 media_entity_cleanup(&dw9807_dev->sd.entity);215 216 return rval;217}218 219static void dw9807_remove(struct i2c_client *client)220{221 struct v4l2_subdev *sd = i2c_get_clientdata(client);222 struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);223 224 pm_runtime_disable(&client->dev);225 226 dw9807_subdev_cleanup(dw9807_dev);227}228 229/*230 * This function sets the vcm position, so it consumes least current231 * The lens position is gradually moved in units of DW9807_CTRL_STEPS,232 * to make the movements smoothly.233 */234static int __maybe_unused dw9807_vcm_suspend(struct device *dev)235{236 struct i2c_client *client = to_i2c_client(dev);237 struct v4l2_subdev *sd = i2c_get_clientdata(client);238 struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);239 const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 };240 int ret, val;241 242 for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1);243 val >= 0; val -= DW9807_CTRL_STEPS) {244 ret = dw9807_set_dac(client, val);245 if (ret)246 dev_err_once(dev, "%s I2C failure: %d", __func__, ret);247 usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);248 }249 250 /* Power down */251 ret = i2c_master_send(client, tx_data, sizeof(tx_data));252 if (ret < 0) {253 dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret);254 return ret;255 }256 257 return 0;258}259 260/*261 * This function sets the vcm position to the value set by the user262 * through v4l2_ctrl_ops s_ctrl handler263 * The lens position is gradually moved in units of DW9807_CTRL_STEPS,264 * to make the movements smoothly.265 */266static int __maybe_unused dw9807_vcm_resume(struct device *dev)267{268 struct i2c_client *client = to_i2c_client(dev);269 struct v4l2_subdev *sd = i2c_get_clientdata(client);270 struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);271 const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 };272 int ret, val;273 274 /* Power on */275 ret = i2c_master_send(client, tx_data, sizeof(tx_data));276 if (ret < 0) {277 dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret);278 return ret;279 }280 281 for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS;282 val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1;283 val += DW9807_CTRL_STEPS) {284 ret = dw9807_set_dac(client, val);285 if (ret)286 dev_err_ratelimited(dev, "%s I2C failure: %d",287 __func__, ret);288 usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);289 }290 291 return 0;292}293 294static const struct of_device_id dw9807_of_table[] = {295 { .compatible = "dongwoon,dw9807-vcm" },296 /* Compatibility for older firmware, NEVER USE THIS IN FIRMWARE! */297 { .compatible = "dongwoon,dw9807" },298 { /* sentinel */ }299};300MODULE_DEVICE_TABLE(of, dw9807_of_table);301 302static const struct dev_pm_ops dw9807_pm_ops = {303 SET_SYSTEM_SLEEP_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume)304 SET_RUNTIME_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume, NULL)305};306 307static struct i2c_driver dw9807_i2c_driver = {308 .driver = {309 .name = "dw9807",310 .pm = &dw9807_pm_ops,311 .of_match_table = dw9807_of_table,312 },313 .probe = dw9807_probe,314 .remove = dw9807_remove,315};316 317module_i2c_driver(dw9807_i2c_driver);318 319MODULE_AUTHOR("Chiang, Alan");320MODULE_DESCRIPTION("DW9807 VCM driver");321MODULE_LICENSE("GPL v2");322