diff options
author | Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org> | 2024-08-30 09:34:57 +0300 |
---|---|---|
committer | Hans Verkuil <hverkuil-cisco@xs4all.nl> | 2024-08-31 10:40:44 +0300 |
commit | a95ffde287835cc60df7dba2c15cfc07c6776e95 (patch) | |
tree | acf0b7d582e56a0fa006224793b9532afb5fd035 /drivers/media/i2c | |
parent | 1c004ef7ffc37e81a1c1fac7fffb4f37b4b2a30e (diff) | |
download | linux-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.c | 46 |
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); |