summaryrefslogtreecommitdiff
path: root/drivers/media/i2c/ov08x40.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/i2c/ov08x40.c')
-rw-r--r--drivers/media/i2c/ov08x40.c181
1 files changed, 157 insertions, 24 deletions
diff --git a/drivers/media/i2c/ov08x40.c b/drivers/media/i2c/ov08x40.c
index 7ead3c720e0e..b9682264e2f5 100644
--- a/drivers/media/i2c/ov08x40.c
+++ b/drivers/media/i2c/ov08x40.c
@@ -3,10 +3,13 @@
#include <linux/unaligned.h>
#include <linux/acpi.h>
+#include <linux/clk.h>
#include <linux/i2c.h>
+#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
@@ -1215,7 +1218,7 @@ static const char * const ov08x40_test_pattern_menu[] = {
/* Configurations for supported link frequencies */
#define OV08X40_LINK_FREQ_400MHZ 400000000ULL
#define OV08X40_SCLK_96MHZ 96000000ULL
-#define OV08X40_EXT_CLK 19200000
+#define OV08X40_XVCLK 19200000
#define OV08X40_DATA_LANES 4
/*
@@ -1279,6 +1282,12 @@ static const struct ov08x40_mode supported_modes[] = {
},
};
+static const char * const ov08x40_supply_names[] = {
+ "dovdd", /* Digital I/O power */
+ "avdd", /* Analog power */
+ "dvdd", /* Digital core power */
+};
+
struct ov08x40 {
struct v4l2_subdev sd;
struct media_pad pad;
@@ -1291,6 +1300,10 @@ struct ov08x40 {
struct v4l2_ctrl *hblank;
struct v4l2_ctrl *exposure;
+ struct clk *xvclk;
+ struct gpio_desc *reset_gpio;
+ struct regulator_bulk_data supplies[ARRAY_SIZE(ov08x40_supply_names)];
+
/* Current mode */
const struct ov08x40_mode *cur_mode;
@@ -1303,6 +1316,61 @@ struct ov08x40 {
#define to_ov08x40(_sd) container_of(_sd, struct ov08x40, sd)
+static int ov08x40_power_on(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct ov08x40 *ov08x = to_ov08x40(sd);
+ int ret;
+
+ if (is_acpi_node(dev_fwnode(dev)))
+ return 0;
+
+ ret = clk_prepare_enable(ov08x->xvclk);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable xvclk\n");
+ return ret;
+ }
+
+ if (ov08x->reset_gpio) {
+ gpiod_set_value_cansleep(ov08x->reset_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ov08x40_supply_names),
+ ov08x->supplies);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable regulators\n");
+ goto disable_clk;
+ }
+
+ gpiod_set_value_cansleep(ov08x->reset_gpio, 0);
+ usleep_range(1500, 1800);
+
+ return 0;
+
+disable_clk:
+ gpiod_set_value_cansleep(ov08x->reset_gpio, 1);
+ clk_disable_unprepare(ov08x->xvclk);
+
+ return ret;
+}
+
+static int ov08x40_power_off(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct ov08x40 *ov08x = to_ov08x40(sd);
+
+ if (is_acpi_node(dev_fwnode(dev)))
+ return 0;
+
+ gpiod_set_value_cansleep(ov08x->reset_gpio, 1);
+ regulator_bulk_disable(ARRAY_SIZE(ov08x40_supply_names),
+ ov08x->supplies);
+ clk_disable_unprepare(ov08x->xvclk);
+
+ return 0;
+}
+
/* Read registers up to 4 at a time */
static int ov08x40_read_reg(struct ov08x40 *ov08x,
u16 reg, u32 len, u32 *val)
@@ -1339,15 +1407,13 @@ static int ov08x40_read_reg(struct ov08x40 *ov08x,
return 0;
}
-static int ov08x40_burst_fill_regs(struct ov08x40 *ov08x, u16 first_reg,
- u16 last_reg, u8 val)
+static int __ov08x40_burst_fill_regs(struct i2c_client *client, u16 first_reg,
+ u16 last_reg, size_t num_regs, u8 val)
{
- struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd);
struct i2c_msg msgs;
- size_t i, num_regs;
+ size_t i;
int ret;
- num_regs = last_reg - first_reg + 1;
msgs.addr = client->addr;
msgs.flags = 0;
msgs.len = 2 + num_regs;
@@ -1373,6 +1439,31 @@ static int ov08x40_burst_fill_regs(struct ov08x40 *ov08x, u16 first_reg,
return 0;
}
+static int ov08x40_burst_fill_regs(struct ov08x40 *ov08x, u16 first_reg,
+ u16 last_reg, u8 val)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd);
+ size_t num_regs, num_write_regs;
+ int ret;
+
+ num_regs = last_reg - first_reg + 1;
+ num_write_regs = num_regs;
+
+ if (client->adapter->quirks && client->adapter->quirks->max_write_len)
+ num_write_regs = client->adapter->quirks->max_write_len - 2;
+
+ while (first_reg < last_reg) {
+ ret = __ov08x40_burst_fill_regs(client, first_reg, last_reg,
+ num_write_regs, val);
+ if (ret)
+ return ret;
+
+ first_reg += num_write_regs;
+ }
+
+ return 0;
+}
+
/* Write registers up to 4 at a time */
static int ov08x40_write_reg(struct ov08x40 *ov08x,
u16 reg, u32 len, u32 __val)
@@ -2049,7 +2140,7 @@ static void ov08x40_free_controls(struct ov08x40 *ov08x)
mutex_destroy(&ov08x->mutex);
}
-static int ov08x40_check_hwcfg(struct device *dev)
+static int ov08x40_check_hwcfg(struct ov08x40 *ov08x, struct device *dev)
{
struct v4l2_fwnode_endpoint bus_cfg = {
.bus_type = V4L2_MBUS_CSI2_DPHY
@@ -2058,21 +2149,46 @@ static int ov08x40_check_hwcfg(struct device *dev)
struct fwnode_handle *fwnode = dev_fwnode(dev);
unsigned int i, j;
int ret;
- u32 ext_clk;
+ u32 xvclk_rate;
if (!fwnode)
return -ENXIO;
- ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency",
- &ext_clk);
- if (ret) {
- dev_err(dev, "can't get clock frequency");
- return ret;
+ if (!is_acpi_node(fwnode)) {
+ ov08x->xvclk = devm_clk_get(dev, NULL);
+ if (IS_ERR(ov08x->xvclk)) {
+ dev_err(dev, "could not get xvclk clock (%pe)\n",
+ ov08x->xvclk);
+ return PTR_ERR(ov08x->xvclk);
+ }
+
+ xvclk_rate = clk_get_rate(ov08x->xvclk);
+
+ ov08x->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ov08x->reset_gpio))
+ return PTR_ERR(ov08x->reset_gpio);
+
+ for (i = 0; i < ARRAY_SIZE(ov08x40_supply_names); i++)
+ ov08x->supplies[i].supply = ov08x40_supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev,
+ ARRAY_SIZE(ov08x40_supply_names),
+ ov08x->supplies);
+ if (ret)
+ return ret;
+ } else {
+ ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency",
+ &xvclk_rate);
+ if (ret) {
+ dev_err(dev, "can't get clock frequency");
+ return ret;
+ }
}
- if (ext_clk != OV08X40_EXT_CLK) {
+ if (xvclk_rate != OV08X40_XVCLK) {
dev_err(dev, "external clock %d is not supported",
- ext_clk);
+ xvclk_rate);
return -EINVAL;
}
@@ -2120,32 +2236,37 @@ out_err:
}
static int ov08x40_probe(struct i2c_client *client)
-{
- struct ov08x40 *ov08x;
+{ struct ov08x40 *ov08x;
int ret;
bool full_power;
+ ov08x = devm_kzalloc(&client->dev, sizeof(*ov08x), GFP_KERNEL);
+ if (!ov08x)
+ return -ENOMEM;
+
/* Check HW config */
- ret = ov08x40_check_hwcfg(&client->dev);
+ ret = ov08x40_check_hwcfg(ov08x, &client->dev);
if (ret) {
dev_err(&client->dev, "failed to check hwcfg: %d", ret);
return ret;
}
- ov08x = devm_kzalloc(&client->dev, sizeof(*ov08x), GFP_KERNEL);
- if (!ov08x)
- return -ENOMEM;
-
/* Initialize subdev */
v4l2_i2c_subdev_init(&ov08x->sd, client, &ov08x40_subdev_ops);
full_power = acpi_dev_state_d0(&client->dev);
if (full_power) {
+ ret = ov08x40_power_on(&client->dev);
+ if (ret) {
+ dev_err(&client->dev, "failed to power on\n");
+ return ret;
+ }
+
/* Check module identity */
ret = ov08x40_identify_module(ov08x);
if (ret) {
dev_err(&client->dev, "failed to find sensor: %d\n", ret);
- return ret;
+ goto probe_power_off;
}
}
@@ -2154,7 +2275,7 @@ static int ov08x40_probe(struct i2c_client *client)
ret = ov08x40_init_controls(ov08x);
if (ret)
- return ret;
+ goto probe_power_off;
/* Initialize subdev */
ov08x->sd.internal_ops = &ov08x40_internal_ops;
@@ -2187,6 +2308,9 @@ error_media_entity:
error_handler_free:
ov08x40_free_controls(ov08x);
+probe_power_off:
+ ov08x40_power_off(&client->dev);
+
return ret;
}
@@ -2201,6 +2325,8 @@ static void ov08x40_remove(struct i2c_client *client)
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
+
+ ov08x40_power_off(&client->dev);
}
#ifdef CONFIG_ACPI
@@ -2212,10 +2338,17 @@ static const struct acpi_device_id ov08x40_acpi_ids[] = {
MODULE_DEVICE_TABLE(acpi, ov08x40_acpi_ids);
#endif
+static const struct of_device_id ov08x40_of_match[] = {
+ { .compatible = "ovti,ov08x40" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ov08x40_of_match);
+
static struct i2c_driver ov08x40_i2c_driver = {
.driver = {
.name = "ov08x40",
.acpi_match_table = ACPI_PTR(ov08x40_acpi_ids),
+ .of_match_table = ov08x40_of_match,
},
.probe = ov08x40_probe,
.remove = ov08x40_remove,