Linux ARM-MSM sub-architecture
 help / color / mirror / Atom feed
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;


      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