From 82b635d15f294b1384d632f4c7268078d547155d Mon Sep 17 00:00:00 2001 From: Xilin Wu Date: Thu, 6 Aug 2026 16:39:52 +0800 Subject: [PATCH 1/5] pwm: clk: support constant output levels with a GPIO Clock outputs do not guarantee a defined pin level when the clock is disabled, and some clock providers cannot represent exact 0% or 100% duty cycles. Add optional GPIO and pinctrl support. Use the GPIO to drive constant levels when the PWM is disabled or configured for 0% or 100% duty cycle, and select the clock function for normal PWM output. Link: https://github.com/radxa/kernel/commit/8975042b7c9aa5f5a3635404bb059e52a1aedf94 Signed-off-by: Xilin Wu Signed-off-by: Songjun Li --- drivers/pwm/pwm-clk.c | 85 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-clk.c b/drivers/pwm/pwm-clk.c index 9dd88b386907c..8fac5c50d7ae2 100644 --- a/drivers/pwm/pwm-clk.c +++ b/drivers/pwm/pwm-clk.c @@ -11,11 +11,20 @@ * - Due to the fact that exact behavior depends on the underlying * clock driver, various limitations are possible. * - Underlying clock may not be able to give 0% or 100% duty cycle - * (constant off or on), exact behavior will depend on the clock. + * (constant off or on), exact behavior will depend on the clock, + * unless a gpio pinctrl state is supplied. * - When the PWM is disabled, the clock will be disabled as well, - * line state will depend on the clock. + * line state will depend on the clock, unless a gpio pinctrl + * state is supplied. * - The clk API doesn't expose the necessary calls to implement * .get_state(). + * + * Optionally, a GPIO descriptor and pinctrl states ("default" and + * "gpio") can be provided. When a constant output level is needed + * (0% duty, 100% duty, or disabled), the driver switches the pin to + * GPIO mode and drives the appropriate level. For normal PWM output + * the pin is switched back to its clock function mux. If no GPIO is + * provided, the driver falls back to the original clock-only behavior. */ #include @@ -25,12 +34,18 @@ #include #include #include +#include +#include #include struct pwm_clk_chip { struct pwm_chip chip; struct clk *clk; bool clk_enabled; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_default; + struct pinctrl_state *pins_gpio; + struct gpio_desc *gpiod; }; #define to_pwm_clk_chip(_chip) container_of(_chip, struct pwm_clk_chip, chip) @@ -43,14 +58,38 @@ static int pwm_clk_apply(struct pwm_chip *chip, struct pwm_device *pwm, u32 rate; u64 period = state->period; u64 duty_cycle = state->duty_cycle; + bool constant_level = false; + int gpio_value = 0; if (!state->enabled) { - if (pwm->state.enabled) { + constant_level = true; + gpio_value = 0; + } else if (state->duty_cycle == 0) { + constant_level = true; + gpio_value = (state->polarity == PWM_POLARITY_INVERSED) ? 1 : 0; + } else if (state->duty_cycle >= state->period) { + constant_level = true; + gpio_value = (state->polarity == PWM_POLARITY_INVERSED) ? 0 : 1; + } + + if (constant_level) { + if (pcchip->gpiod) + gpiod_direction_output(pcchip->gpiod, gpio_value); + if (pcchip->clk_enabled) { clk_disable(pcchip->clk); pcchip->clk_enabled = false; } + if (pcchip->gpiod) { + pinctrl_select_state(pcchip->pinctrl, pcchip->pins_gpio); + gpiod_direction_output(pcchip->gpiod, gpio_value); + } return 0; - } else if (!pwm->state.enabled) { + } + + if (pcchip->gpiod) + pinctrl_select_state(pcchip->pinctrl, pcchip->pins_default); + + if (!pcchip->clk_enabled) { ret = clk_enable(pcchip->clk); if (ret) return ret; @@ -93,6 +132,44 @@ static int pwm_clk_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk), "Failed to get clock\n"); + pcchip->pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(pcchip->pinctrl)) { + ret = PTR_ERR(pcchip->pinctrl); + pcchip->pinctrl = NULL; + if (ret == -EPROBE_DEFER) + return ret; + } else { + pcchip->pins_default = pinctrl_lookup_state(pcchip->pinctrl, + PINCTRL_STATE_DEFAULT); + pcchip->pins_gpio = pinctrl_lookup_state(pcchip->pinctrl, "gpio"); + if (IS_ERR(pcchip->pins_default) || IS_ERR(pcchip->pins_gpio)) + pcchip->pinctrl = NULL; + } + + /* + * Switch to GPIO pinctrl state before requesting the GPIO. The driver + * core has already applied the default state, which muxes the pin to the + * clock function and claims it. Release that claim first so gpiolib can + * request the pin. + */ + if (pcchip->pinctrl) + pinctrl_select_state(pcchip->pinctrl, pcchip->pins_gpio); + + pcchip->gpiod = devm_gpiod_get_optional(&pdev->dev, NULL, GPIOD_ASIS); + if (IS_ERR(pcchip->gpiod)) + return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->gpiod), + "Failed to get gpio\n"); + + /* + * If pinctrl states were found but no GPIO was provided, the pin is stuck + * in GPIO mode from the switch above. Restore the default mux and fall + * back to clock-only operation. + */ + if (pcchip->pinctrl && !pcchip->gpiod) { + pinctrl_select_state(pcchip->pinctrl, pcchip->pins_default); + pcchip->pinctrl = NULL; + } + pcchip->chip.dev = &pdev->dev; pcchip->chip.ops = &pwm_clk_ops; pcchip->chip.npwm = 1; From ff93daa607a82e96cdc460e34f74a4c91ab11c1d Mon Sep 17 00:00:00 2001 From: Songjun Li Date: Thu, 6 Aug 2026 16:50:31 +0800 Subject: [PATCH 2/5] clk: qcom: gcc-sa8775p: support PWM on GP clocks The SA8775P GP clock frequency table only provides 100 MHz and 200 MHz rates, which cannot satisfy low-frequency PWM consumers. Add 1, 5, 10, 20 and 100 kHz configurations derived from the 19.2 MHz TCXO. Use clk_rcg2_ops for the GP clock sources so clk-pwm can configure the output duty cycle. The 10 kHz and 20 kHz rates are used by the VMARC-Q9075 fan and display backlight respectively. Signed-off-by: Songjun Li --- drivers/clk/qcom/gcc-sa8775p.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/clk/qcom/gcc-sa8775p.c b/drivers/clk/qcom/gcc-sa8775p.c index 11c72a10ba698..199bfdfacb2b3 100644 --- a/drivers/clk/qcom/gcc-sa8775p.c +++ b/drivers/clk/qcom/gcc-sa8775p.c @@ -721,6 +721,11 @@ static struct clk_rcg2 gcc_emac1_rgmii_clk_src = { }; static const struct freq_tbl ftbl_gcc_gp1_clk_src[] = { + F(1000, P_BI_TCXO, 16, 1, 1200), + F(5000, P_BI_TCXO, 16, 1, 240), + F(10000, P_BI_TCXO, 16, 1, 120), + F(20000, P_BI_TCXO, 16, 1, 60), + F(100000, P_BI_TCXO, 16, 1, 12), F(100000000, P_GCC_GPLL0_OUT_MAIN, 6, 0, 0), F(200000000, P_GCC_GPLL0_OUT_MAIN, 3, 0, 0), { } @@ -736,7 +741,7 @@ static struct clk_rcg2 gcc_gp1_clk_src = { .name = "gcc_gp1_clk_src", .parent_data = gcc_parent_data_2, .num_parents = ARRAY_SIZE(gcc_parent_data_2), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_ops, }, }; @@ -750,7 +755,7 @@ static struct clk_rcg2 gcc_gp2_clk_src = { .name = "gcc_gp2_clk_src", .parent_data = gcc_parent_data_2, .num_parents = ARRAY_SIZE(gcc_parent_data_2), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_ops, }, }; @@ -764,7 +769,7 @@ static struct clk_rcg2 gcc_gp3_clk_src = { .name = "gcc_gp3_clk_src", .parent_data = gcc_parent_data_2, .num_parents = ARRAY_SIZE(gcc_parent_data_2), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_ops, }, }; @@ -778,7 +783,7 @@ static struct clk_rcg2 gcc_gp4_clk_src = { .name = "gcc_gp4_clk_src", .parent_data = gcc_parent_data_2, .num_parents = ARRAY_SIZE(gcc_parent_data_2), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_ops, }, }; @@ -792,7 +797,7 @@ static struct clk_rcg2 gcc_gp5_clk_src = { .name = "gcc_gp5_clk_src", .parent_data = gcc_parent_data_2, .num_parents = ARRAY_SIZE(gcc_parent_data_2), - .ops = &clk_rcg2_shared_ops, + .ops = &clk_rcg2_ops, }, }; From 2aa878d587ec1436e5fdae0677ced244290a0e54 Mon Sep 17 00:00:00 2001 From: Songjun Li Date: Thu, 6 Aug 2026 16:51:11 +0800 Subject: [PATCH 3/5] arm64: defconfig: Enable CONFIG_PWM_CLK Signed-off-by: Songjun Li --- arch/arm64/configs/qcom_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/qcom_defconfig b/arch/arm64/configs/qcom_defconfig index bad36f3818f3e..a1539178e5a14 100644 --- a/arch/arm64/configs/qcom_defconfig +++ b/arch/arm64/configs/qcom_defconfig @@ -760,6 +760,7 @@ CONFIG_IIO=y CONFIG_QCOM_SPMI_VADC=m CONFIG_QCOM_SPMI_ADC5=y CONFIG_PWM=y +CONFIG_PWM_CLK=y CONFIG_PWM_GPIO=m CONFIG_PWM_PCA9632=m CONFIG_PWM_PCA9685=m From b7070e32ce196b616997797ebdaba7caccf7245f Mon Sep 17 00:00:00 2001 From: Songjun Li Date: Thu, 6 Aug 2026 17:22:10 +0800 Subject: [PATCH 4/5] net: stmmac: qcom-ethqos: reset adapter on 2.5G speed changes Switching between 1Gbps and 2.5Gbps can leave the QCOM ETHQOS MAC and PCS in a state where the PHY ID cannot be read and the link cannot be restored. Track the previously configured link speed and request an adapter reset when the speed changes to or from 2.5Gbps. Skip the reset for the initial link setup. Expose stmmac_request_reset() so the platform driver can schedule the existing stmmac service-task reset instead of resetting the device directly from the MAC link-up callback. This restores link operation across 1Gbps and 2.5Gbps transitions. Signed-off-by: Songjun Li --- .../stmicro/stmmac/dwmac-qcom-ethqos.c | 23 ++++++++++++++++++- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 + .../net/ethernet/stmicro/stmmac/stmmac_main.c | 6 +++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c index d95f39e5b3219..ea1999d0290b3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c @@ -147,6 +147,7 @@ struct qcom_ethqos { struct phy *serdes_phy; unsigned int speed; int serdes_speed; + bool link_speed_valid; phy_interface_t phy_mode; struct reset_control *serdes_reset; @@ -948,15 +949,27 @@ static void ethqos_xpcs_validate_link_post_reset(struct qcom_ethqos *ethqos) } } +static bool ethqos_needs_speed_change_reset(unsigned int old_speed, + unsigned int new_speed) +{ + return old_speed != new_speed && + (old_speed == SPEED_2500 || new_speed == SPEED_2500); +} + static void ethqos_fix_mac_speed(void *priv_n, unsigned int speed, unsigned int mode) { struct qcom_ethqos *ethqos = priv_n; struct net_device *dev = platform_get_drvdata(ethqos->pdev); struct stmmac_priv *priv = netdev_priv(dev); + unsigned int old_speed = ethqos->speed; + bool reset_needed; + + reset_needed = ethqos->link_speed_valid && + ethqos_needs_speed_change_reset(old_speed, speed); qcom_ethqos_set_sgmii_loopback(ethqos, false); - ethqos->speed = speed; ethqos_update_link_clk(ethqos, speed); + ethqos->speed = speed; ethqos_configure(ethqos); if (priv->hw->phylink_pcs) { @@ -965,6 +978,14 @@ static void ethqos_fix_mac_speed(void *priv_n, unsigned int speed, unsigned int DUPLEX_FULL); ethqos_xpcs_validate_link_post_reset(ethqos); } + + ethqos->link_speed_valid = true; + + if (reset_needed) { + netdev_info(dev, "reset adapter after %uMbps to %uMbps link speed change\n", + old_speed, speed); + stmmac_request_reset(priv); + } } static int qcom_ethqos_serdes_powerup(struct net_device *ndev, void *priv) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 7020d369801b7..2b8eec75e1688 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -387,6 +387,7 @@ int stmmac_reinit_queues(struct net_device *dev, u32 rx_cnt, u32 tx_cnt); int stmmac_reinit_ringparam(struct net_device *dev, u32 rx_size, u32 tx_size); int stmmac_bus_clks_config(struct stmmac_priv *priv, bool enabled); void stmmac_fpe_handshake(struct stmmac_priv *priv, bool enable); +void stmmac_request_reset(struct stmmac_priv *priv); static inline bool stmmac_xdp_is_enabled(struct stmmac_priv *priv) { diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 40fdb1a11ff08..bd7f3ad3c3825 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -287,6 +287,12 @@ static void stmmac_global_err(struct stmmac_priv *priv) stmmac_service_event_schedule(priv); } +void stmmac_request_reset(struct stmmac_priv *priv) +{ + stmmac_global_err(priv); +} +EXPORT_SYMBOL_GPL(stmmac_request_reset); + /** * stmmac_clk_csr_set - dynamically set the MDC clock * @priv: driver private structure From d6f2a0b52d047f3ff8c080db7d6bbec5094ff269 Mon Sep 17 00:00:00 2001 From: Songjun Li Date: Thu, 6 Aug 2026 17:34:32 +0800 Subject: [PATCH 5/5] serial: qcom-geni: honor RS-485 RTS idle polarity The RS-485 configuration callback forces RTS to a fixed level when RS-485 mode is enabled. This ignores SER_RS485_RTS_AFTER_SEND and can leave the transceiver in transmit mode while the bus is idle. Initialize RTS according to SER_RS485_RTS_AFTER_SEND when RS-485 mode is enabled. Reapply the configured idle level during startup so the state is restored after port setup or power cycles. Signed-off-by: Songjun Li --- drivers/tty/serial/qcom_geni_serial.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 765a3701d95a0..a533457e02196 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1243,6 +1243,11 @@ static int qcom_geni_serial_startup(struct uart_port *uport) if (ret) return ret; } + + if (uport->rs485.flags & SER_RS485_ENABLED) + qcom_geni_set_rts_pin(uport, + !!(uport->rs485.flags & SER_RS485_RTS_AFTER_SEND)); + enable_irq(uport->irq); return 0; @@ -1601,12 +1606,12 @@ static void qcom_geni_serial_pm(struct uart_port *uport, static int qcom_geni_rs485_config(struct uart_port *uport, struct ktermios *termios, struct serial_rs485 *rs485) { - /* When RS485 is enabled, keep the RTS pin in ACTIVE state - * and revert back to auto flow control mode, i.e. flow control - * managed by the QUP HW once RS485 is disabled. + /* Initialize RTS to its idle state when RS485 is enabled and revert + * to QUP-managed automatic flow control once RS485 is disabled. */ if (rs485->flags & SER_RS485_ENABLED) - qcom_geni_set_rts_pin(uport, true); + qcom_geni_set_rts_pin(uport, + !!(rs485->flags & SER_RS485_RTS_AFTER_SEND)); else writel(0, uport->membase + SE_UART_MANUAL_RFR);