// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2018 Intel Corporation
+/*
+ * DW9807 is a 10-bit DAC driver, capable of sinking up to 100mA.
+ *
+ * DW9817 is a bidirectional 10-bit driver, driving up to +/- 100mA.
+ * Operationally it is identical to DW9807, except that the idle position is
+ * the mid-point, not 0.
+ */
+
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#define MAX_RETRY 10
+struct dw9807_cfg {
+ unsigned int idle_pos;
+ unsigned int default_pos;
+};
+
struct dw9807_device {
struct v4l2_ctrl_handler ctrls_vcm;
struct v4l2_subdev sd;
u16 current_val;
+ u16 idle_pos;
};
static inline struct dw9807_device *sd_to_dw9807_vcm(
return 0;
}
+/*
+ * The lens position is gradually moved in units of DW9807_CTRL_STEPS,
+ * to make the movements smoothly. In all cases, even when "start" and
+ * "end" are the same, the lens will be set to the "end" position.
+ *
+ * (We don't use hardware slew rate control, because it differs widely
+ * between otherwise-compatible ICs, and may need lens-specific tuning.)
+ */
+static int dw9807_ramp(struct i2c_client *client, int start, int end)
+{
+ int step, val, ret;
+
+ if (start < end)
+ step = DW9807_CTRL_STEPS;
+ else
+ step = -DW9807_CTRL_STEPS;
+
+ val = start;
+ while (true) {
+ val += step;
+ if (step * (val - end) >= 0)
+ val = end;
+ ret = dw9807_set_dac(client, val);
+ if (ret)
+ dev_err_ratelimited(&client->dev, "%s I2C failure: %d",
+ __func__, ret);
+ if (val == end)
+ break;
+ usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
+ }
+
+ return ret;
+}
+
static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl)
{
struct dw9807_device *dev_vcm = container_of(ctrl->handler,
struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd);
dev_vcm->current_val = ctrl->val;
- return dw9807_set_dac(client, ctrl->val);
+ return dw9807_ramp(client, ctrl->val, ctrl->val);
}
return -EINVAL;
v4l2_ctrl_handler_init(hdl, 1);
v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE,
- 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0);
+ 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS,
+ dev_vcm->current_val);
dev_vcm->sd.ctrl_handler = hdl;
if (hdl->error) {
return 0;
}
+/* Compatible devices; in fact there are many similar chips.
+ * "data" holds the powered-off (zero current) lens position and a
+ * default/initial control value (which need not be the same as the powered-off
+ * value).
+ */
+static const struct dw9807_cfg dw9807_cfg = {
+ .idle_pos = 0,
+ .default_pos = 0
+};
+
+static const struct dw9807_cfg dw9817_cfg = {
+ .idle_pos = 512,
+ .default_pos = 480,
+};
+
+static const struct of_device_id dw9807_of_table[] = {
+ { .compatible = "dongwoon,dw9807-vcm", .data = &dw9807_cfg },
+ { .compatible = "dongwoon,dw9817-vcm", .data = &dw9817_cfg },
+ { /* sentinel */ }
+};
+
static int dw9807_probe(struct i2c_client *client)
{
struct dw9807_device *dw9807_dev;
+ const struct of_device_id *match;
+ const struct dw9807_cfg *cfg;
int rval;
dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev),
if (dw9807_dev == NULL)
return -ENOMEM;
+ match = i2c_of_match_device(dw9807_of_table, client);
+ if (match) {
+ cfg = (const struct dw9807_cfg *)match->data;
+ dw9807_dev->idle_pos = cfg->idle_pos;
+ dw9807_dev->current_val = cfg->default_pos;
+ }
+
v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops);
dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
dw9807_dev->sd.internal_ops = &dw9807_int_ops;
if (rval < 0)
goto err_cleanup;
- pm_runtime_set_active(&client->dev);
+ if (!dw9807_dev->vdd)
+ pm_runtime_set_active(&client->dev);
pm_runtime_enable(&client->dev);
pm_runtime_idle(&client->dev);
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);
const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 };
- int ret, val;
+ int ret;
- for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1);
- val >= 0; val -= DW9807_CTRL_STEPS) {
- ret = dw9807_set_dac(client, val);
- if (ret)
- dev_err_once(dev, "%s I2C failure: %d", __func__, ret);
- usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
- }
+ if (abs(dw9807_dev->current_val - dw9807_dev->idle_pos) > DW9807_CTRL_STEPS)
+ dw9807_ramp(client, dw9807_dev->current_val, dw9807_dev->idle_pos);
/* Power down */
ret = i2c_master_send(client, tx_data, sizeof(tx_data));
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);
const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 };
- int ret, val;
+ int ret;
/* Power on */
ret = i2c_master_send(client, tx_data, sizeof(tx_data));
return ret;
}
- for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS;
- val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1;
- val += DW9807_CTRL_STEPS) {
- ret = dw9807_set_dac(client, val);
- if (ret)
- dev_err_ratelimited(dev, "%s I2C failure: %d",
- __func__, ret);
- usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
- }
+ dw9807_ramp(client, dw9807_dev->idle_pos, dw9807_dev->current_val);
return 0;
}
-static const struct of_device_id dw9807_of_table[] = {
- { .compatible = "dongwoon,dw9807-vcm" },
- /* Compatibility for older firmware, NEVER USE THIS IN FIRMWARE! */
- { .compatible = "dongwoon,dw9807" },
- { /* sentinel */ }
-};
MODULE_DEVICE_TABLE(of, dw9807_of_table);
static const struct dev_pm_ops dw9807_pm_ops = {