summaryrefslogtreecommitdiff
path: root/drivers/media/i2c
diff options
context:
space:
mode:
authorVladimir Zapolskiy <vladimir.zapolskiy@linaro.org>2024-08-30 09:34:57 +0300
committerHans Verkuil <hverkuil-cisco@xs4all.nl>2024-08-31 10:40:44 +0300
commita95ffde287835cc60df7dba2c15cfc07c6776e95 (patch)
treeacf0b7d582e56a0fa006224793b9532afb5fd035 /drivers/media/i2c
parent1c004ef7ffc37e81a1c1fac7fffb4f37b4b2a30e (diff)
downloadlinux-a95ffde287835cc60df7dba2c15cfc07c6776e95.tar.xz
media: i2c: og01a1b: Add support of xvclk supply clock in power management
The OmniVision OG01A1B camera sensor has an xvclk supply clock, which could be described and then explicitly controlled on OF platforms. Signed-off-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org> [Sakari Ailus: Use UL specifier for power-up delay cycle value.] Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Diffstat (limited to 'drivers/media/i2c')
-rw-r--r--drivers/media/i2c/og01a1b.c46
1 files changed, 40 insertions, 6 deletions
diff --git a/drivers/media/i2c/og01a1b.c b/drivers/media/i2c/og01a1b.c
index d993ef4bad46..ac719611b194 100644
--- a/drivers/media/i2c/og01a1b.c
+++ b/drivers/media/i2c/og01a1b.c
@@ -3,6 +3,7 @@
#include <asm/unaligned.h>
#include <linux/acpi.h>
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
@@ -418,6 +419,8 @@ static const struct og01a1b_mode supported_modes[] = {
};
struct og01a1b {
+ struct clk *xvclk;
+
struct v4l2_subdev sd;
struct media_pad pad;
struct v4l2_ctrl_handler ctrl_handler;
@@ -898,8 +901,10 @@ static int og01a1b_identify_module(struct og01a1b *og01a1b)
return 0;
}
-static int og01a1b_check_hwcfg(struct device *dev)
+static int og01a1b_check_hwcfg(struct og01a1b *og01a1b)
{
+ struct i2c_client *client = v4l2_get_subdevdata(&og01a1b->sd);
+ struct device *dev = &client->dev;
struct fwnode_handle *ep;
struct fwnode_handle *fwnode = dev_fwnode(dev);
struct v4l2_fwnode_endpoint bus_cfg = {
@@ -913,10 +918,13 @@ static int og01a1b_check_hwcfg(struct device *dev)
return -ENXIO;
ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk);
-
if (ret) {
- dev_err(dev, "can't get clock frequency");
- return ret;
+ if (!og01a1b->xvclk) {
+ dev_err(dev, "can't get clock frequency");
+ return ret;
+ }
+
+ mclk = clk_get_rate(og01a1b->xvclk);
}
if (mclk != OG01A1B_MCLK) {
@@ -970,13 +978,32 @@ check_hwcfg_error:
/* Power/clock management functions */
static int og01a1b_power_on(struct device *dev)
{
- /* Device is already turned on by i2c-core with ACPI domain PM. */
+ unsigned long delay = DIV_ROUND_UP(8192UL * USEC_PER_SEC, OG01A1B_MCLK);
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct og01a1b *og01a1b = to_og01a1b(sd);
+ int ret;
+
+ ret = clk_prepare_enable(og01a1b->xvclk);
+ if (ret)
+ return ret;
+
+ if (og01a1b->xvclk)
+ usleep_range(delay, 2 * delay);
return 0;
}
static int og01a1b_power_off(struct device *dev)
{
+ unsigned long delay = DIV_ROUND_UP(512 * USEC_PER_SEC, OG01A1B_MCLK);
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct og01a1b *og01a1b = to_og01a1b(sd);
+
+ if (og01a1b->xvclk)
+ usleep_range(delay, 2 * delay);
+
+ clk_disable_unprepare(og01a1b->xvclk);
+
return 0;
}
@@ -1003,7 +1030,14 @@ static int og01a1b_probe(struct i2c_client *client)
v4l2_i2c_subdev_init(&og01a1b->sd, client, &og01a1b_subdev_ops);
- ret = og01a1b_check_hwcfg(&client->dev);
+ og01a1b->xvclk = devm_clk_get_optional(&client->dev, NULL);
+ if (IS_ERR(og01a1b->xvclk)) {
+ ret = PTR_ERR(og01a1b->xvclk);
+ dev_err(&client->dev, "failed to get xvclk clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = og01a1b_check_hwcfg(og01a1b);
if (ret) {
dev_err(&client->dev, "failed to check HW configuration: %d",
ret);