diff --git a/Documentation/devicetree/bindings/clock/gpio-gate-clock.yaml b/Documentation/devicetree/bindings/clock/gpio-gate-clock.yaml index d09d0e3f0c6ecb..a5fa40048bed77 100644 --- a/Documentation/devicetree/bindings/clock/gpio-gate-clock.yaml +++ b/Documentation/devicetree/bindings/clock/gpio-gate-clock.yaml @@ -11,7 +11,15 @@ maintainers: properties: compatible: - const: gpio-gate-clock + enum: + - gpio-gate-clock + - gpio-gate-clock-releasing + description: | + Use "gpio-gate-clock" for normal operation where the GPIO is held for the + lifetime of the clock. Use "gpio-gate-clock-releasing" for power-sensitive + applications where the GPIO should be acquired only when the clock is + enabled and released when disabled, allowing shared regulators to be + powered down. clocks: maxItems: 1 @@ -40,3 +48,14 @@ examples: #clock-cells = <0>; enable-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>; }; + + - | + #include + + /* Power-sensitive clock that releases GPIO when disabled */ + clock { + compatible = "gpio-gate-clock-releasing"; + clocks = <&parentclk>; + #clock-cells = <0>; + enable-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>; + }; diff --git a/arch/arm/boot/dts/overlays/imx500-overlay.dts b/arch/arm/boot/dts/overlays/imx500-overlay.dts index b8d76feb259aab..3ed8c8cd660cf2 100644 --- a/arch/arm/boot/dts/overlays/imx500-overlay.dts +++ b/arch/arm/boot/dts/overlays/imx500-overlay.dts @@ -82,7 +82,7 @@ }; clk_aicam_gated: clk-aicam-gated1 { - compatible = "gpio-gate-clock"; + compatible = "gpio-gate-clock-releasing"; clocks = <&clk_aicam>; #clock-cells = <0>; enable-gpios = <&spi_bridge 21 GPIO_ACTIVE_HIGH>; diff --git a/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts b/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts index 8ad4f0cd1c7b50..a512d4b76ede59 100644 --- a/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts +++ b/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts @@ -85,7 +85,7 @@ }; clk_aicam_gated: clk-aicam-gated1 { - compatible = "gpio-gate-clock"; + compatible = "gpio-gate-clock-releasing"; clocks = <&clk_aicam>; #clock-cells = <0>; enable-gpios = <&spi_bridge 21 GPIO_ACTIVE_HIGH>; diff --git a/drivers/clk/clk-gpio.c b/drivers/clk/clk-gpio.c index 5b114043771ddb..44c25a5494db30 100644 --- a/drivers/clk/clk-gpio.c +++ b/drivers/clk/clk-gpio.c @@ -33,6 +33,7 @@ * * @hw: handle between common and hardware-specific interfaces * @gpiod: gpio descriptor + * @dev: device pointer for acquire/release operations * * Clock with a gpio control for enabling and disabling the parent clock * or switching between two parents by asserting or deasserting the gpio. @@ -44,9 +45,37 @@ struct clk_gpio { struct clk_hw hw; struct gpio_desc *gpiod; + struct device *dev; }; #define to_clk_gpio(_hw) container_of(_hw, struct clk_gpio, hw) +static int clk_gpio_gate_acquire(struct clk_hw *hw) +{ + struct clk_gpio *clk = to_clk_gpio(hw); + struct device *dev = clk->dev; + + clk->gpiod = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(clk->gpiod)) + return PTR_ERR(clk->gpiod); + + return 0; +} + +static bool clk_gpio_gate_is_acquired(struct clk_hw *hw) +{ + struct clk_gpio *clk = to_clk_gpio(hw); + + return !!clk->gpiod; +} + +static void clk_gpio_gate_release(struct clk_hw *hw) +{ + struct clk_gpio *clk = to_clk_gpio(hw); + struct device *dev = clk->dev; + + devm_gpiod_put(dev, clk->gpiod); + clk->gpiod = NULL; +} static int clk_gpio_gate_enable(struct clk_hw *hw) { @@ -77,6 +106,37 @@ static const struct clk_ops clk_gpio_gate_ops = { .is_enabled = clk_gpio_gate_is_enabled, }; +static int clk_gpio_gate_releasing_enable(struct clk_hw *hw) +{ + int ret; + + ret = clk_gpio_gate_acquire(hw); + if (ret) + return ret; + + return clk_gpio_gate_enable(hw); +} + +static void clk_gpio_gate_releasing_disable(struct clk_hw *hw) +{ + clk_gpio_gate_disable(hw); + clk_gpio_gate_release(hw); +} + +static int clk_gpio_gate_releasing_is_enabled(struct clk_hw *hw) +{ + if (!clk_gpio_gate_is_acquired(hw)) + return 0; + + return clk_gpio_gate_is_enabled(hw); +} + +static const struct clk_ops clk_gpio_gate_releasing_ops = { + .enable = clk_gpio_gate_releasing_enable, + .disable = clk_gpio_gate_releasing_disable, + .is_enabled = clk_gpio_gate_releasing_is_enabled, +}; + static int clk_sleeping_gpio_gate_prepare(struct clk_hw *hw) { struct clk_gpio *clk = to_clk_gpio(hw); @@ -106,6 +166,37 @@ static const struct clk_ops clk_sleeping_gpio_gate_ops = { .is_prepared = clk_sleeping_gpio_gate_is_prepared, }; +static int clk_sleeping_gpio_gate_releasing_prepare(struct clk_hw *hw) +{ + int ret; + + ret = clk_gpio_gate_acquire(hw); + if (ret) + return ret; + + return clk_sleeping_gpio_gate_prepare(hw); +} + +static void clk_sleeping_gpio_gate_releasing_unprepare(struct clk_hw *hw) +{ + clk_sleeping_gpio_gate_unprepare(hw); + clk_gpio_gate_release(hw); +} + +static int clk_sleeping_gpio_gate_releasing_is_prepared(struct clk_hw *hw) +{ + if (!clk_gpio_gate_is_acquired(hw)) + return 0; + + return clk_sleeping_gpio_gate_is_prepared(hw); +} + +static const struct clk_ops clk_sleeping_gpio_gate_releasing_ops = { + .prepare = clk_sleeping_gpio_gate_releasing_prepare, + .unprepare = clk_sleeping_gpio_gate_releasing_unprepare, + .is_prepared = clk_sleeping_gpio_gate_releasing_is_prepared, +}; + /** * DOC: basic clock multiplexer which can be controlled with a gpio output * Traits of this clock: @@ -160,6 +251,7 @@ static struct clk_hw *clk_register_gpio(struct device *dev, u8 num_parents, init.flags = CLK_SET_RATE_PARENT; clk_gpio->gpiod = gpiod; + clk_gpio->dev = dev; clk_gpio->hw.init = &init; hw = &clk_gpio->hw; @@ -172,14 +264,29 @@ static struct clk_hw *clk_register_gpio(struct device *dev, u8 num_parents, static struct clk_hw *clk_hw_register_gpio_gate(struct device *dev, int num_parents, - struct gpio_desc *gpiod) + struct gpio_desc *gpiod, + bool releasing) { const struct clk_ops *ops; - if (gpiod_cansleep(gpiod)) - ops = &clk_sleeping_gpio_gate_ops; - else - ops = &clk_gpio_gate_ops; + if (releasing) { + /* For releasing variant, confirm GPIO works then release it + * for acquire/release semantics + */ + if (gpiod_cansleep(gpiod)) + ops = &clk_sleeping_gpio_gate_releasing_ops; + else + ops = &clk_gpio_gate_releasing_ops; + + devm_gpiod_put(dev, gpiod); + gpiod = NULL; + } else { + /* Regular variant - keep GPIO and choose appropriate ops */ + if (gpiod_cansleep(gpiod)) + ops = &clk_sleeping_gpio_gate_ops; + else + ops = &clk_gpio_gate_ops; + } return clk_register_gpio(dev, num_parents, gpiod, ops); } @@ -199,9 +306,12 @@ static int gpio_clk_driver_probe(struct platform_device *pdev) struct gpio_desc *gpiod; struct clk_hw *hw; bool is_mux; + bool is_releasing; int ret; is_mux = of_device_is_compatible(node, "gpio-mux-clock"); + is_releasing = + of_device_is_compatible(node, "gpio-gate-clock-releasing"); num_parents = of_clk_get_parent_count(node); if (is_mux && num_parents != 2) { @@ -226,7 +336,9 @@ static int gpio_clk_driver_probe(struct platform_device *pdev) if (is_mux) hw = clk_hw_register_gpio_mux(dev, gpiod); else - hw = clk_hw_register_gpio_gate(dev, num_parents, gpiod); + hw = clk_hw_register_gpio_gate(dev, num_parents, gpiod, + is_releasing); + if (IS_ERR(hw)) return PTR_ERR(hw); @@ -236,6 +348,7 @@ static int gpio_clk_driver_probe(struct platform_device *pdev) static const struct of_device_id gpio_clk_match_table[] = { { .compatible = "gpio-mux-clock" }, { .compatible = "gpio-gate-clock" }, + { .compatible = "gpio-gate-clock-releasing" }, { } }; diff --git a/drivers/media/i2c/imx500.c b/drivers/media/i2c/imx500.c index a16b577f483b6d..3931214e9b0cc6 100644 --- a/drivers/media/i2c/imx500.c +++ b/drivers/media/i2c/imx500.c @@ -2360,9 +2360,11 @@ static int imx500_state_transition(struct imx500 *imx500, const u8 *fw, } /* Do SPI transfer */ - gpiod_set_value_cansleep(imx500->led_gpio, 1); + if (imx500->led_gpio) + gpiod_set_value_cansleep(imx500->led_gpio, 1); ret = imx500_spi_write(imx500, data, size); - gpiod_set_value_cansleep(imx500->led_gpio, 0); + if (imx500->led_gpio) + gpiod_set_value_cansleep(imx500->led_gpio, 0); imx500->fw_progress += size; @@ -2512,7 +2514,7 @@ static int imx500_start_streaming(struct imx500 *imx500) if (ret) { dev_err(&client->dev, "%s failed to set image mode\n", __func__); - return ret; + goto err_runtime_put; } /* Acquire loader and main firmware if needed */ @@ -2524,7 +2526,7 @@ static int imx500_start_streaming(struct imx500 *imx500) if (ret) { dev_err(&client->dev, "Unable to acquire firmware loader\n"); - return ret; + goto err_runtime_put; } } if (!imx500->fw_main) { @@ -2534,7 +2536,7 @@ static int imx500_start_streaming(struct imx500 *imx500) if (ret) { dev_err(&client->dev, "Unable to acquire main firmware\n"); - return ret; + goto err_runtime_put; } } } @@ -2546,7 +2548,7 @@ static int imx500_start_streaming(struct imx500 *imx500) if (ret) { dev_err(&client->dev, "%s failed to set common settings\n", __func__); - return ret; + goto err_runtime_put; } imx500->common_regs_written = true; @@ -2558,7 +2560,7 @@ static int imx500_start_streaming(struct imx500 *imx500) dev_err(&client->dev, "%s failed to transition from program empty state\n", __func__); - return ret; + goto err_runtime_put; } imx500->loader_and_main_written = true; } @@ -2569,7 +2571,7 @@ static int imx500_start_streaming(struct imx500 *imx500) dev_err(&client->dev, "%s failed to transition to network loaded\n", __func__); - return ret; + goto err_runtime_put; } imx500->network_written = true; } @@ -2580,7 +2582,7 @@ static int imx500_start_streaming(struct imx500 *imx500) if (ret) { dev_err(&client->dev, "%s failed to enable DNN\n", __func__); - return ret; + goto err_runtime_put; } } @@ -2590,7 +2592,7 @@ static int imx500_start_streaming(struct imx500 *imx500) reg_list->num_of_regs, NULL); if (ret) { dev_err(&client->dev, "%s failed to set mode\n", __func__); - return ret; + goto err_runtime_put; } /* Apply customized values from user */ @@ -2603,6 +2605,14 @@ static int imx500_start_streaming(struct imx500 *imx500) cci_write(imx500->regmap, IMX500_REG_MODE_SELECT, IMX500_MODE_STREAMING, &ret); + if (ret) + goto err_runtime_put; + + return 0; + +err_runtime_put: + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); return ret; } @@ -2675,11 +2685,28 @@ static int imx500_power_on(struct device *dev) struct imx500 *imx500 = to_imx500(sd); int ret; + /* Acquire GPIOs first to ensure reset is asserted before power is applied */ + imx500->led_gpio = devm_gpiod_get_optional(dev, "led", GPIOD_OUT_LOW); + if (IS_ERR(imx500->led_gpio)) { + ret = PTR_ERR(imx500->led_gpio); + dev_err(&client->dev, "%s: failed to get led gpio\n", __func__); + return ret; + } + + imx500->reset_gpio = + devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(imx500->reset_gpio)) { + ret = PTR_ERR(imx500->reset_gpio); + dev_err(&client->dev, "%s: failed to get reset gpio\n", + __func__); + goto gpio_led_put; + } + ret = regulator_bulk_enable(IMX500_NUM_SUPPLIES, imx500->supplies); if (ret) { dev_err(&client->dev, "%s: failed to enable regulators\n", __func__); - return ret; + goto gpio_reset_put; } /* T4 - 1us @@ -2699,7 +2726,8 @@ static int imx500_power_on(struct device *dev) * as 0ms but also "XCLR pin should be set to 'High' after INCK supplied.". * T4 and T5 are shown as overlapping. */ - gpiod_set_value_cansleep(imx500->reset_gpio, 1); + if (imx500->reset_gpio) + gpiod_set_value_cansleep(imx500->reset_gpio, 1); /* T7 - 9ms * "INCK start and CXLR rising till Send Streaming Command wait time" @@ -2710,6 +2738,16 @@ static int imx500_power_on(struct device *dev) reg_off: regulator_bulk_disable(IMX500_NUM_SUPPLIES, imx500->supplies); +gpio_reset_put: + if (imx500->reset_gpio) { + devm_gpiod_put(dev, imx500->reset_gpio); + imx500->reset_gpio = NULL; + } +gpio_led_put: + if (imx500->led_gpio) { + devm_gpiod_put(dev, imx500->led_gpio); + imx500->led_gpio = NULL; + } return ret; } @@ -2726,7 +2764,19 @@ static int imx500_power_off(struct device *dev) * Note, this is not the reverse order of power up. */ clk_disable_unprepare(imx500->xclk); - gpiod_set_value_cansleep(imx500->reset_gpio, 0); + if (imx500->reset_gpio) + gpiod_set_value_cansleep(imx500->reset_gpio, 0); + + /* Release GPIOs before disabling regulators */ + if (imx500->reset_gpio) { + devm_gpiod_put(&client->dev, imx500->reset_gpio); + imx500->reset_gpio = NULL; + } + if (imx500->led_gpio) { + devm_gpiod_put(&client->dev, imx500->led_gpio); + imx500->led_gpio = NULL; + } + regulator_bulk_disable(IMX500_NUM_SUPPLIES, imx500->supplies); /* Force reprogramming of the common registers when powered up again. */ @@ -3044,14 +3094,14 @@ static int imx500_probe(struct i2c_client *client) return ret; } - imx500->led_gpio = devm_gpiod_get_optional(dev, "led", GPIOD_OUT_LOW); - - imx500->reset_gpio = - devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + /* GPIOs are acquired in imx500_power_on() to avoid preventing + * regulator power down when shared with other drivers. + */ /* * The sensor must be powered for imx500_identify_module() - * to be able to read the CHIP_ID register + * to be able to read the CHIP_ID register. This also ensures + * GPIOs are available. */ ret = imx500_power_on(dev); if (ret)