brintos

brintos / linux-shallow public Read only

0
0
Text · 8.1 KiB · 4148009 Raw
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