From: Saikiran <bjsaikiran@gmail.com>
To: linux-media@vger.kernel.org
Cc: linux-arm-msm@vger.kernel.org, bryan.odonoghue@linaro.org,
bod@kernel.org, rfoss@kernel.org, todor.too@gmail.com,
vladimir.zapolskiy@linaro.org, hansg@kernel.org,
sakari.ailus@linux.intel.com, mchehab@kernel.org,
Saikiran <bjsaikiran@gmail.com>,
stable@vger.kernel.org
Subject: [PATCH v2 1/1] media: i2c: ov02c10: Keep power on and use reset for power management
Date: Mon, 26 Jan 2026 11:45:28 +0530 [thread overview]
Message-ID: <20260126061528.63785-2-bjsaikiran@gmail.com> (raw)
In-Reply-To: <20260126061528.63785-1-bjsaikiran@gmail.com>
The OV02C10 sensor was experiencing brownout conditions during rapid
power cycles (e.g., browser WebRTC permission checks) on Qualcomm
platforms, causing the sensor to lock up and require a system reboot.
Root cause:
The Qualcomm RPMh regulator driver does not support active discharge,
requiring regulators to passively discharge via leakage current. This
takes 2+ seconds on X1E80100 platforms. Without complete voltage
discharge, the sensor's internal microcontroller does not fully reset,
leading to I2C timeouts and a locked state.
Solution:
Instead of power cycling the regulators, keep them continuously enabled
and use reset signals to control the sensor state:
- power_off(): Assert hardware reset GPIO (keep regulators/clock ON)
- power_on(): Release hardware reset + trigger software reset via
register 0x0103 (standard OmniVision software reset)
This approach:
- Eliminates the 2+ second discharge delay
- Enables instant camera reopening (~17ms vs 2.3s)
- Properly resets the sensor state machine via reset signals
- Maintains correct power sequencing on first initialization
- Follows OmniVision sensor conventions (0x0103 software reset)
The first power-on still performs full regulator and clock
initialization. Subsequent power cycles only toggle reset signals,
avoiding the discharge delay entirely.
Tested on Lenovo Yoga Slim 7x (X1E80100) with rapid camera open/close
cycles - no brownouts or lockups observed.
Fixes: 44f89010dae0 ("media: i2c: add OmniVision OV02C10 sensor driver")
Cc: stable@vger.kernel.org
Signed-off-by: Saikiran <bjsaikiran@gmail.com>
---
drivers/media/i2c/ov02c10.c | 119 +++++++++++++++++++++---------------
1 file changed, 69 insertions(+), 50 deletions(-)
diff --git a/drivers/media/i2c/ov02c10.c b/drivers/media/i2c/ov02c10.c
index 7e9454e8540c..08d268de60ec 100644
--- a/drivers/media/i2c/ov02c10.c
+++ b/drivers/media/i2c/ov02c10.c
@@ -22,6 +22,8 @@
#define OV02C10_CHIP_ID 0x5602
#define OV02C10_REG_STREAM_CONTROL CCI_REG8(0x0100)
+#define OV02C10_REG_SOFTWARE_RESET CCI_REG8(0x0103)
+#define OV02C10_SOFTWARE_RESET_TRIGGER 0x01
#define OV02C10_REG_HTS CCI_REG16(0x380c)
@@ -390,8 +392,8 @@ struct ov02c10 {
u32 link_freq_index;
u8 mipi_lanes;
- /* Power cycling rate limit */
- ktime_t last_power_off;
+ /* Power management: track if regulators are enabled */
+ bool powered;
};
static inline struct ov02c10 *to_ov02c10(struct v4l2_subdev *subdev)
@@ -680,25 +682,16 @@ 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);
-
/*
- * 4. Discharge Wait
- * Wait for regulators to fully discharge before returning.
- * This delay ensures clean power cycling.
+ * Keep regulators and clock ON to avoid discharge delay.
+ * Just assert hardware reset to put sensor in reset state.
+ * This allows instant power-on without waiting for regulator discharge.
*/
- usleep_range(50000, 55000);
+ if (ov02c10->reset)
+ gpiod_set_value_cansleep(ov02c10->reset, 1);
- ov02c10->last_power_off = ktime_get();
+ /* Keep clock running - sensor needs it for software reset */
+ /* Keep regulators enabled - avoids 2.3s discharge delay */
return 0;
}
@@ -708,50 +701,63 @@ 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;
/*
- * 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.
+ * On first power-on, do full initialization.
+ * On subsequent power-ons, regulators/clock are already on,
+ * so we just need to release reset and do software reset.
*/
- 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);
+ if (!ov02c10->powered) {
+ /* First time: enable everything */
+ if (ov02c10->reset) {
+ 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;
+ }
+
+ usleep_range(2000, 2200);
+
+ 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;
+ }
+
+ ov02c10->powered = true;
}
- /*
- * 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);
- return ret;
+ /* Release hardware reset */
+ if (ov02c10->reset) {
+ /* Ensure reset was asserted for at least 2ms */
+ usleep_range(2000, 2200);
+ gpiod_set_value_cansleep(ov02c10->reset, 0);
+ /*
+ * Wait for sensor microcontroller to stabilize after reset release.
+ * 50ms prevents black frames during rapid power cycling by ensuring
+ * the sensor's internal state machine is fully initialized before
+ * software reset and register configuration.
+ */
+ msleep(50);
}
- 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);
+ /* Perform software reset to ensure clean state */
+ ret = cci_write(ov02c10->regmap, OV02C10_REG_SOFTWARE_RESET,
+ OV02C10_SOFTWARE_RESET_TRIGGER, NULL);
+ if (ret) {
+ dev_err(dev, "failed to send software reset: %d", ret);
return ret;
}
- /* Wait for power/clock to stabilize */
+ /* Wait for software reset to complete */
usleep_range(5000, 5500);
- if (ov02c10->reset) {
- gpiod_set_value_cansleep(ov02c10->reset, 0);
- usleep_range(80000, 85000);
- }
-
return 0;
}
@@ -924,6 +930,19 @@ static void ov02c10_remove(struct i2c_client *client)
ov02c10_power_off(ov02c10->dev);
pm_runtime_set_suspended(ov02c10->dev);
}
+
+ /* Clean up regulators/clock if still enabled */
+ if (ov02c10->powered) {
+ /* Assert reset before disabling power for clean shutdown */
+ if (ov02c10->reset)
+ gpiod_set_value_cansleep(ov02c10->reset, 1);
+
+ clk_disable_unprepare(ov02c10->img_clk);
+ regulator_bulk_disable(ARRAY_SIZE(ov02c10_supply_names),
+ ov02c10->supplies);
+ ov02c10->powered = false;
+ }
+
v4l2_subdev_cleanup(sd);
media_entity_cleanup(&sd->entity);
v4l2_ctrl_handler_free(sd->ctrl_handler);
--
2.51.0
next prev parent reply other threads:[~2026-01-26 6:15 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-25 17:17 [PATCH v2 0/2] Fix OV02C10 camera pipeline lock issues Saikiran
2026-01-25 17:17 ` [PATCH v2 1/2] media: qcom: camss: Fix pipeline lock leak in stop_streaming Saikiran
2026-01-25 17:17 ` [PATCH v2 2/2] media: i2c: ov02c10: Check for errors in disable_streams Saikiran
2026-01-26 10:13 ` Konrad Dybcio
2026-01-26 10:21 ` Hans de Goede
2026-01-26 6:15 ` [PATCH v2 0/1] media: i2c: ov02c10: Keep power on and use reset for power management Saikiran
2026-01-26 6:15 ` Saikiran [this message]
2026-01-26 10:32 ` [PATCH v2 1/1] " Hans de Goede
2026-01-26 10:59 ` Saikiran B
2026-01-26 11:09 ` Bryan O'Donoghue
2026-01-26 11:23 ` Saikiran B
2026-01-26 11:41 ` Bryan O'Donoghue
2026-01-26 11:58 ` Saikiran B
2026-01-26 12:06 ` Bryan O'Donoghue
2026-01-26 12:24 ` Saikiran B
2026-01-26 12:41 ` Bryan O'Donoghue
2026-01-26 12:48 ` Saikiran B
2026-01-26 13:31 ` Hans de Goede
[not found] ` <I-1OPz69QKXF-LDqvufQARvv_3TIYaLyZIETdiGvSj_JSYhnJNeqiLERDUH2R0kclFyo6MqMRsaiZaS3RKmdZA==@protonmail.internalid>
[not found] ` <CAAFDt1ufYyM4_xTy+AZTdXBB0cGNk+nFQHD5+5U7tUMQqZ+o=g@mail.gmail.com>
2026-01-26 15:04 ` Bryan O'Donoghue
2026-01-26 15:40 ` Saikiran B
2026-01-26 16:00 ` Bryan O'Donoghue
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=20260126061528.63785-2-bjsaikiran@gmail.com \
--to=bjsaikiran@gmail.com \
--cc=bod@kernel.org \
--cc=bryan.odonoghue@linaro.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=stable@vger.kernel.org \
--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