351 lines · c
1// SPDX-License-Identifier: GPL-2.02// Copyright (c) 2012 Intel Corporation3 4/*5 * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo:6 * https://github.com/ZenfoneArea/android_kernel_asus_zenfone57 */8 9#include <linux/delay.h>10#include <linux/i2c.h>11#include <linux/pm_runtime.h>12#include <linux/regulator/consumer.h>13#include <linux/types.h>14 15#include <media/v4l2-cci.h>16#include <media/v4l2-common.h>17#include <media/v4l2-ctrls.h>18#include <media/v4l2-subdev.h>19 20#define DW9719_MAX_FOCUS_POS 102321#define DW9719_CTRL_STEPS 1622#define DW9719_CTRL_DELAY_US 100023 24#define DW9719_INFO CCI_REG8(0)25#define DW9719_ID 0xF126 27#define DW9719_CONTROL CCI_REG8(2)28#define DW9719_ENABLE_RINGING 0x0229 30#define DW9719_VCM_CURRENT CCI_REG16(3)31 32#define DW9719_MODE CCI_REG8(6)33#define DW9719_MODE_SAC_SHIFT 434#define DW9719_MODE_SAC3 435 36#define DW9719_VCM_FREQ CCI_REG8(7)37#define DW9719_DEFAULT_VCM_FREQ 0x6038 39#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd)40 41struct dw9719_device {42 struct v4l2_subdev sd;43 struct device *dev;44 struct regmap *regmap;45 struct regulator *regulator;46 u32 sac_mode;47 u32 vcm_freq;48 49 struct dw9719_v4l2_ctrls {50 struct v4l2_ctrl_handler handler;51 struct v4l2_ctrl *focus;52 } ctrls;53};54 55static int dw9719_detect(struct dw9719_device *dw9719)56{57 int ret;58 u64 val;59 60 ret = cci_read(dw9719->regmap, DW9719_INFO, &val, NULL);61 if (ret < 0)62 return ret;63 64 if (val != DW9719_ID) {65 dev_err(dw9719->dev, "Failed to detect correct id\n");66 return -ENXIO;67 }68 69 return 0;70}71 72static int dw9719_power_down(struct dw9719_device *dw9719)73{74 return regulator_disable(dw9719->regulator);75}76 77static int dw9719_power_up(struct dw9719_device *dw9719)78{79 int ret;80 81 ret = regulator_enable(dw9719->regulator);82 if (ret)83 return ret;84 85 /* Jiggle SCL pin to wake up device */86 cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret);87 88 /* Need 100us to transit from SHUTDOWN to STANDBY */89 fsleep(100);90 91 cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_ENABLE_RINGING, &ret);92 cci_write(dw9719->regmap, DW9719_MODE,93 dw9719->sac_mode << DW9719_MODE_SAC_SHIFT, &ret);94 cci_write(dw9719->regmap, DW9719_VCM_FREQ, dw9719->vcm_freq, &ret);95 96 if (ret)97 dw9719_power_down(dw9719);98 99 return ret;100}101 102static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value)103{104 return cci_write(dw9719->regmap, DW9719_VCM_CURRENT, value, NULL);105}106 107static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl)108{109 struct dw9719_device *dw9719 = container_of(ctrl->handler,110 struct dw9719_device,111 ctrls.handler);112 int ret;113 114 /* Only apply changes to the controls if the device is powered up */115 if (!pm_runtime_get_if_in_use(dw9719->dev))116 return 0;117 118 switch (ctrl->id) {119 case V4L2_CID_FOCUS_ABSOLUTE:120 ret = dw9719_t_focus_abs(dw9719, ctrl->val);121 break;122 default:123 ret = -EINVAL;124 }125 126 pm_runtime_put(dw9719->dev);127 128 return ret;129}130 131static const struct v4l2_ctrl_ops dw9719_ctrl_ops = {132 .s_ctrl = dw9719_set_ctrl,133};134 135static int dw9719_suspend(struct device *dev)136{137 struct v4l2_subdev *sd = dev_get_drvdata(dev);138 struct dw9719_device *dw9719 = to_dw9719_device(sd);139 int ret;140 int val;141 142 for (val = dw9719->ctrls.focus->val; val >= 0;143 val -= DW9719_CTRL_STEPS) {144 ret = dw9719_t_focus_abs(dw9719, val);145 if (ret)146 return ret;147 148 usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10);149 }150 151 return dw9719_power_down(dw9719);152}153 154static int dw9719_resume(struct device *dev)155{156 struct v4l2_subdev *sd = dev_get_drvdata(dev);157 struct dw9719_device *dw9719 = to_dw9719_device(sd);158 int current_focus = dw9719->ctrls.focus->val;159 int ret;160 int val;161 162 ret = dw9719_power_up(dw9719);163 if (ret)164 return ret;165 166 for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus;167 val += DW9719_CTRL_STEPS) {168 ret = dw9719_t_focus_abs(dw9719, val);169 if (ret)170 goto err_power_down;171 172 usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10);173 }174 175 return 0;176 177err_power_down:178 dw9719_power_down(dw9719);179 return ret;180}181 182static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)183{184 return pm_runtime_resume_and_get(sd->dev);185}186 187static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)188{189 pm_runtime_put(sd->dev);190 191 return 0;192}193 194static const struct v4l2_subdev_internal_ops dw9719_internal_ops = {195 .open = dw9719_open,196 .close = dw9719_close,197};198 199static int dw9719_init_controls(struct dw9719_device *dw9719)200{201 const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops;202 int ret;203 204 v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1);205 206 dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops,207 V4L2_CID_FOCUS_ABSOLUTE, 0,208 DW9719_MAX_FOCUS_POS, 1, 0);209 210 if (dw9719->ctrls.handler.error) {211 dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n");212 ret = dw9719->ctrls.handler.error;213 goto err_free_handler;214 }215 216 dw9719->sd.ctrl_handler = &dw9719->ctrls.handler;217 return 0;218 219err_free_handler:220 v4l2_ctrl_handler_free(&dw9719->ctrls.handler);221 return ret;222}223 224static const struct v4l2_subdev_ops dw9719_ops = { };225 226static int dw9719_probe(struct i2c_client *client)227{228 struct dw9719_device *dw9719;229 int ret;230 231 dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL);232 if (!dw9719)233 return -ENOMEM;234 235 dw9719->regmap = devm_cci_regmap_init_i2c(client, 8);236 if (IS_ERR(dw9719->regmap))237 return PTR_ERR(dw9719->regmap);238 239 dw9719->dev = &client->dev;240 dw9719->sac_mode = DW9719_MODE_SAC3;241 dw9719->vcm_freq = DW9719_DEFAULT_VCM_FREQ;242 243 /* Optional indication of SAC mode select */244 device_property_read_u32(&client->dev, "dongwoon,sac-mode",245 &dw9719->sac_mode);246 247 /* Optional indication of VCM frequency */248 device_property_read_u32(&client->dev, "dongwoon,vcm-freq",249 &dw9719->vcm_freq);250 251 dw9719->regulator = devm_regulator_get(&client->dev, "vdd");252 if (IS_ERR(dw9719->regulator))253 return dev_err_probe(&client->dev, PTR_ERR(dw9719->regulator),254 "getting regulator\n");255 256 v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops);257 dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;258 dw9719->sd.internal_ops = &dw9719_internal_ops;259 260 ret = dw9719_init_controls(dw9719);261 if (ret)262 return ret;263 264 ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL);265 if (ret < 0)266 goto err_free_ctrl_handler;267 268 dw9719->sd.entity.function = MEDIA_ENT_F_LENS;269 270 /*271 * We need the driver to work in the event that pm runtime is disable in272 * the kernel, so power up and verify the chip now. In the event that273 * runtime pm is disabled this will leave the chip on, so that the lens274 * will work.275 */276 277 ret = dw9719_power_up(dw9719);278 if (ret)279 goto err_cleanup_media;280 281 ret = dw9719_detect(dw9719);282 if (ret)283 goto err_powerdown;284 285 pm_runtime_set_active(&client->dev);286 pm_runtime_get_noresume(&client->dev);287 pm_runtime_enable(&client->dev);288 289 ret = v4l2_async_register_subdev(&dw9719->sd);290 if (ret < 0)291 goto err_pm_runtime;292 293 pm_runtime_set_autosuspend_delay(&client->dev, 1000);294 pm_runtime_use_autosuspend(&client->dev);295 pm_runtime_put_autosuspend(&client->dev);296 297 return ret;298 299err_pm_runtime:300 pm_runtime_disable(&client->dev);301 pm_runtime_put_noidle(&client->dev);302err_powerdown:303 dw9719_power_down(dw9719);304err_cleanup_media:305 media_entity_cleanup(&dw9719->sd.entity);306err_free_ctrl_handler:307 v4l2_ctrl_handler_free(&dw9719->ctrls.handler);308 309 return ret;310}311 312static void dw9719_remove(struct i2c_client *client)313{314 struct v4l2_subdev *sd = i2c_get_clientdata(client);315 struct dw9719_device *dw9719 =316 container_of(sd, struct dw9719_device, sd);317 318 v4l2_async_unregister_subdev(sd);319 v4l2_ctrl_handler_free(&dw9719->ctrls.handler);320 media_entity_cleanup(&dw9719->sd.entity);321 322 pm_runtime_disable(&client->dev);323 if (!pm_runtime_status_suspended(&client->dev))324 dw9719_power_down(dw9719);325 pm_runtime_set_suspended(&client->dev);326}327 328static const struct i2c_device_id dw9719_id_table[] = {329 { "dw9719" },330 { }331};332MODULE_DEVICE_TABLE(i2c, dw9719_id_table);333 334static DEFINE_RUNTIME_DEV_PM_OPS(dw9719_pm_ops, dw9719_suspend, dw9719_resume,335 NULL);336 337static struct i2c_driver dw9719_i2c_driver = {338 .driver = {339 .name = "dw9719",340 .pm = pm_sleep_ptr(&dw9719_pm_ops),341 },342 .probe = dw9719_probe,343 .remove = dw9719_remove,344 .id_table = dw9719_id_table,345};346module_i2c_driver(dw9719_i2c_driver);347 348MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>");349MODULE_DESCRIPTION("DW9719 VCM Driver");350MODULE_LICENSE("GPL");351