cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

dw9807-vcm.c (8322B)


      1// SPDX-License-Identifier: GPL-2.0
      2// Copyright (C) 2018 Intel Corporation
      3
      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	1023
     14/*
     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	1
     19/*
     20 * This acts as the minimum granularity of lens movement.
     21 * Keep this value power of 2, so the control steps can be
     22 * uniformly adjusted for gradual lens movement, with desired
     23 * number of control steps.
     24 */
     25#define DW9807_CTRL_STEPS	16
     26#define DW9807_CTRL_DELAY_US	1000
     27
     28#define DW9807_CTL_ADDR		0x02
     29/*
     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		0x03
     34#define DW9807_LSB_ADDR		0x04
     35#define DW9807_STATUS_ADDR	0x05
     36#define DW9807_MODE_ADDR	0x06
     37#define DW9807_RESONANCE_ADDR	0x07
     38
     39#define MAX_RETRY		10
     40
     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 we
     85	 * write VCM position. This ensure that we really write the value
     86	 * into the register
     87	 */
     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 int 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	return 0;
    229}
    230
    231/*
    232 * This function sets the vcm position, so it consumes least current
    233 * The lens position is gradually moved in units of DW9807_CTRL_STEPS,
    234 * to make the movements smoothly.
    235 */
    236static int __maybe_unused dw9807_vcm_suspend(struct device *dev)
    237{
    238	struct i2c_client *client = to_i2c_client(dev);
    239	struct v4l2_subdev *sd = i2c_get_clientdata(client);
    240	struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);
    241	const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 };
    242	int ret, val;
    243
    244	for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1);
    245	     val >= 0; val -= DW9807_CTRL_STEPS) {
    246		ret = dw9807_set_dac(client, val);
    247		if (ret)
    248			dev_err_once(dev, "%s I2C failure: %d", __func__, ret);
    249		usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
    250	}
    251
    252	/* Power down */
    253	ret = i2c_master_send(client, tx_data, sizeof(tx_data));
    254	if (ret < 0) {
    255		dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret);
    256		return ret;
    257	}
    258
    259	return 0;
    260}
    261
    262/*
    263 * This function sets the vcm position to the value set by the user
    264 * through v4l2_ctrl_ops s_ctrl handler
    265 * The lens position is gradually moved in units of DW9807_CTRL_STEPS,
    266 * to make the movements smoothly.
    267 */
    268static int  __maybe_unused dw9807_vcm_resume(struct device *dev)
    269{
    270	struct i2c_client *client = to_i2c_client(dev);
    271	struct v4l2_subdev *sd = i2c_get_clientdata(client);
    272	struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);
    273	const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 };
    274	int ret, val;
    275
    276	/* Power on */
    277	ret = i2c_master_send(client, tx_data, sizeof(tx_data));
    278	if (ret < 0) {
    279		dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret);
    280		return ret;
    281	}
    282
    283	for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS;
    284	     val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1;
    285	     val += DW9807_CTRL_STEPS) {
    286		ret = dw9807_set_dac(client, val);
    287		if (ret)
    288			dev_err_ratelimited(dev, "%s I2C failure: %d",
    289						__func__, ret);
    290		usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
    291	}
    292
    293	return 0;
    294}
    295
    296static const struct of_device_id dw9807_of_table[] = {
    297	{ .compatible = "dongwoon,dw9807-vcm" },
    298	/* Compatibility for older firmware, NEVER USE THIS IN FIRMWARE! */
    299	{ .compatible = "dongwoon,dw9807" },
    300	{ /* sentinel */ }
    301};
    302MODULE_DEVICE_TABLE(of, dw9807_of_table);
    303
    304static const struct dev_pm_ops dw9807_pm_ops = {
    305	SET_SYSTEM_SLEEP_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume)
    306	SET_RUNTIME_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume, NULL)
    307};
    308
    309static struct i2c_driver dw9807_i2c_driver = {
    310	.driver = {
    311		.name = "dw9807",
    312		.pm = &dw9807_pm_ops,
    313		.of_match_table = dw9807_of_table,
    314	},
    315	.probe_new = dw9807_probe,
    316	.remove = dw9807_remove,
    317};
    318
    319module_i2c_driver(dw9807_i2c_driver);
    320
    321MODULE_AUTHOR("Chiang, Alan");
    322MODULE_DESCRIPTION("DW9807 VCM driver");
    323MODULE_LICENSE("GPL v2");