From: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
To: Saikiran <bjsaikiran@gmail.com>, linux-media@vger.kernel.org
Cc: linux-arm-msm@vger.kernel.org, rfoss@kernel.org,
todor.too@gmail.com, bod@kernel.org,
vladimir.zapolskiy@linaro.org, hansg@kernel.org,
sakari.ailus@linux.intel.com, mchehab@kernel.org
Subject: Re: [PATCH] media: i2c: ov02c10: Enforce cool-down period to prevent brownout
Date: Sun, 25 Jan 2026 13:21:28 +0000 [thread overview]
Message-ID: <52133fc4-9b4e-4ef4-8e04-a392d9c71da8@linaro.org> (raw)
In-Reply-To: <20260124071751.5885-4-bjsaikiran@gmail.com>
On 24/01/2026 07:17, Saikiran wrote:
> The OV02C10 sensor is susceptible to brownout/latch-up states when
> power-cycled rapidly (e.g., within 50-100ms). This often occurs during
> userspace interactions like browser WebRTC permissions checks, where
> the device is opened, closed, and reopened in quick succession.
>
> When this happens, the regulator discharge is incomplete, and the
> sensor fails to perform a clean Power-On Reset (POR). The internal
> microcontroller locks up, resulting in I2C timeouts ("failed to set
> mode") and necessitating a full system reboot to recover the camera.
>
> To prevent this, implement a mandatory cool-down period. The driver
> now tracks the timestamp of the last power-off. If a power-on attempt
> occurs within 3 seconds of the last power-off, the driver sleeps for
> the remaining duration to ensure physical power rails have fully
> discharged and the sensor has completely reset before voltage is
> re-applied.
3 seconds ????????????
This seems completely wrong.
I think we should look at - again - improving/fixing the power_on()
logic to ensure we
- Put the reset pin into a known state
- Carefully apply clock and power as per chip stipulations
- Try to capture the T timings either with documentation or
trial and error
That is to say getting the power-on function right should fix this.
Likely we are going through power_on() incorrectly for one of the
timings to correctly bring the chip into the right state - or not
respecting the gap between power-on and first CCI or first stream.
Since this code has mostly been developed used on x86/ACPI systems it is
entirely plausible that the OSPM agent on x86 does stuff around
reset/power-on that we don't have in !ACPI world.
Lets take a quick look.
Minimum XVCLK freq is 6 MHz.
t1: XSHUTDN min 5 unit milliseconds
t2: first CCI min 8192 XVCLK cycles
@ 6MHz this is 1365361 nanoseconds
1.37 milliseconds
t3: MIPI CLK start time max 8192 XVCLK cycles
t4: First data on MIPI bus - variable
t5: infinite nanoseconds
power_on() {
- XSHUTDOWN is assumed be be asserted
- XVCLK is assumed to be freerunning i.e. already started and
stable prior to the next step
- T5: DOVDD, AVDD, DVDD power on = potentially infinite
Hardware standby period
- T2: First CCI
}
stream_on() {
- T3: Time it take for MIPI MCP/MCN clock to start = 8192 XVCLKs
- T4: First time to data is variable but, this is irrelevant
}
T3: T3: the time from XSHUTOWN/VDD off to power_off unspecified.
power_off() {
- XSHUTDOWN
- T3: hardware standby period
- VDD shutdown
}
In the code, we don't assert reset in power_on() - so we are reliant on
power_off(); to have run and completed and that XSHUTDOWN is in the
logical state we expect.
Try something like:
diff --git a/drivers/media/i2c/ov02c10.c b/drivers/media/i2c/ov02c10.c
index cf93d36032e14..ab68fc3a971f8 100644
--- a/drivers/media/i2c/ov02c10.c
+++ b/drivers/media/i2c/ov02c10.c
@@ -661,6 +661,7 @@ static int ov02c10_power_off(struct device *dev)
struct ov02c10 *ov02c10 = to_ov02c10(sd);
gpiod_set_value_cansleep(ov02c10->reset, 1);
+ usleep_range(2000, 2200);
regulator_bulk_disable(ARRAY_SIZE(ov02c10_supply_names),
ov02c10->supplies);
@@ -676,12 +677,21 @@ static int ov02c10_power_on(struct device *dev)
struct ov02c10 *ov02c10 = to_ov02c10(sd);
int ret;
+ if (ov02c10->reset) {
+ /* Ensure reset is asserted before trying to power_on */
+ gpiod_set_value_cansleep(ov02c10->reset, 1);
+ usleep_range(2000, 2200);
+ }
+
ret = clk_prepare_enable(ov02c10->img_clk);
if (ret < 0) {
dev_err(dev, "failed to enable imaging clock: %d", ret);
return ret;
}
+ /* Let the clock stabilise */
+ usleep_range(2000, 2200);
+
ret = regulator_bulk_enable(ARRAY_SIZE(ov02c10_supply_names),
ov02c10->supplies);
if (ret < 0) {
@@ -694,6 +704,7 @@ static int ov02c10_power_on(struct device *dev)
/* Assert reset for at least 2ms on back to back off-on */
usleep_range(2000, 2200);
gpiod_set_value_cansleep(ov02c10->reset, 0);
+ /* This is where we need to capture power_on() T2 */
usleep_range(5000, 5100);
}
> Additionally, standard Power-On-Reset logic is refined:
> 1. Ensure MCLK is disabled BEFORE regulators during power-off to
> prevent phantom power injection.
> 2. Assert the reset line (hold low) throughout the regulator ramp-up
> phase to prevent indeterminate states.
>
> Testing Results (10 rapid cycles each):
> 1. 900ms minimum gap: Failed (brownout/timeout errors)
> 2. 1500ms minimum gap: Failed (intermittent failures)
> 3. 2000ms minimum gap: Reliable (0 failures in 50+ test cycles)
> 4. 3000ms minimum gap: Reliable (excessive, 2s is sufficient)
>
> The 3-second check window with 2-second minimum enforcement provides
> the optimal balance between reliability and responsiveness.
>
> Signed-off-by: Saikiran <bjsaikiran@gmail.com>
> ---
> drivers/media/i2c/ov02c10.c | 63 +++++++++++++++++++++++++++++++------
> 1 file changed, 54 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/media/i2c/ov02c10.c b/drivers/media/i2c/ov02c10.c
> index db191dccff75..7e9454e8540c 100644
> --- a/drivers/media/i2c/ov02c10.c
> +++ b/drivers/media/i2c/ov02c10.c
> @@ -389,6 +389,9 @@ struct ov02c10 {
> /* MIPI lane info */
> u32 link_freq_index;
> u8 mipi_lanes;
> +
> + /* Power cycling rate limit */
> + ktime_t last_power_off;
> };
>
> static inline struct ov02c10 *to_ov02c10(struct v4l2_subdev *subdev)
> @@ -616,6 +619,13 @@ static int ov02c10_enable_streams(struct v4l2_subdev *sd,
> if (ret)
> goto out;
>
> + /*
> + * Delay before streaming:
> + * Give the sensor time to process all the register writes and internal
> + * calibration before we assert the STREAM_ON bit.
> + */
> + usleep_range(2000, 2500);
> +
> ret = cci_write(ov02c10->regmap, OV02C10_REG_STREAM_CONTROL, 1, NULL);
> out:
> if (ret)
> @@ -670,12 +680,25 @@ static int ov02c10_power_off(struct device *dev)
> struct v4l2_subdev *sd = dev_get_drvdata(dev);
> struct ov02c10 *ov02c10 = to_ov02c10(sd);
>
> + /* 1. Assert Reset */
> gpiod_set_value_cansleep(ov02c10->reset, 1);
>
> + /* 2. Disable Clock (Stop sensor state machine) */
> + clk_disable_unprepare(ov02c10->img_clk);
> + usleep_range(1000, 1500);
> +
> + /* 3. Disable Power */
> regulator_bulk_disable(ARRAY_SIZE(ov02c10_supply_names),
> ov02c10->supplies);
>
> - clk_disable_unprepare(ov02c10->img_clk);
> + /*
> + * 4. Discharge Wait
> + * Wait for regulators to fully discharge before returning.
> + * This delay ensures clean power cycling.
> + */
> + usleep_range(50000, 55000);
> +
> + ov02c10->last_power_off = ktime_get();
>
> return 0;
> }
> @@ -685,26 +708,48 @@ static int ov02c10_power_on(struct device *dev)
> struct v4l2_subdev *sd = dev_get_drvdata(dev);
> struct ov02c10 *ov02c10 = to_ov02c10(sd);
> int ret;
> + s64 delta_us;
>
> - ret = clk_prepare_enable(ov02c10->img_clk);
> - if (ret < 0) {
> - dev_err(dev, "failed to enable imaging clock: %d", ret);
> - return ret;
> + /*
> + * Mandatory Cool-Down:
> + * If the camera was powered off within the last 3 seconds, ensure at least
> + * 2 seconds have elapsed to allow full regulator discharge and sensor reset.
> + * This prevents brownouts during rapid open/close/open sequences.
> + */
> + delta_us = ktime_us_delta(ktime_get(), ov02c10->last_power_off);
> + if (delta_us < 3000000) {
> + dev_dbg(dev, "Enforcing %lld us cool-down period\n", 2000000 - delta_us);
> + fsleep(2000000 - delta_us);
> }
>
> + /*
> + * Standard Power-Up Sequence:
> + * 1. Enable Regulators
> + * 2. Enable Clock
> + * 3. Release Reset (with ample boot time)
> + */
> +
> ret = regulator_bulk_enable(ARRAY_SIZE(ov02c10_supply_names),
> ov02c10->supplies);
> if (ret < 0) {
> dev_err(dev, "failed to enable regulators: %d", ret);
> - clk_disable_unprepare(ov02c10->img_clk);
> return ret;
> }
>
> + ret = clk_prepare_enable(ov02c10->img_clk);
> + if (ret < 0) {
> + dev_err(dev, "failed to enable imaging clock: %d", ret);
> + regulator_bulk_disable(ARRAY_SIZE(ov02c10_supply_names),
> + ov02c10->supplies);
> + return ret;
> + }
> +
> + /* Wait for power/clock to stabilize */
> + usleep_range(5000, 5500);
> +
> if (ov02c10->reset) {
> - /* Assert reset for at least 2ms on back to back off-on */
> - usleep_range(5000, 5500);
> gpiod_set_value_cansleep(ov02c10->reset, 0);
> - usleep_range(20000, 21000);
> + usleep_range(80000, 85000);
> }
>
> return 0;
prev parent reply other threads:[~2026-01-25 13:21 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-24 7:17 [PATCH 0/3] Fix OV02C10 camera stability on Snapdragon X Elite Saikiran
2026-01-24 7:17 ` [PATCH] media: qcom: camss: Fix pipeline lock leak in stop_streaming Saikiran
2026-01-25 12:23 ` Bryan O'Donoghue
2026-01-24 7:17 ` [PATCH] media: i2c: ov02c10: Check for errors in disable_streams Saikiran
2026-01-25 12:26 ` Bryan O'Donoghue
2026-01-26 10:18 ` Hans de Goede
2026-01-24 7:17 ` [PATCH] media: i2c: ov02c10: Enforce cool-down period to prevent brownout Saikiran
2026-01-25 13:21 ` Bryan O'Donoghue [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=52133fc4-9b4e-4ef4-8e04-a392d9c71da8@linaro.org \
--to=bryan.odonoghue@linaro.org \
--cc=bjsaikiran@gmail.com \
--cc=bod@kernel.org \
--cc=hansg@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=mchehab@kernel.org \
--cc=rfoss@kernel.org \
--cc=sakari.ailus@linux.intel.com \
--cc=todor.too@gmail.com \
--cc=vladimir.zapolskiy@linaro.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox