From aac7d517d04ad3ea95de1f1968803bfe412bb1dd Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 1 Apr 2025 10:01:30 +0100 Subject: [PATCH 01/39] soc: renesas: Kconfig: Enable SoCs by default when ARCH_RENESAS is set Enable various Renesas SoCs by default when ARCH_RENESAS is selected. Adding default y if ARCH_RENESAS to the relevant configurations removes the need to manually enable individual SoCs in defconfig files. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/20250401090133.68146-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/Kconfig | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig index 49648cf28bd2..4990b85d7df7 100644 --- a/drivers/soc/renesas/Kconfig +++ b/drivers/soc/renesas/Kconfig @@ -65,17 +65,20 @@ if ARM && ARCH_RENESAS config ARCH_EMEV2 bool "ARM32 Platform support for Emma Mobile EV2" + default ARCH_RENESAS select HAVE_ARM_SCU if SMP select SYS_SUPPORTS_EM_STI config ARCH_R8A7794 bool "ARM32 Platform support for R-Car E2" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_814220 select SYSC_R8A7794 config ARCH_R8A7779 bool "ARM32 Platform support for R-Car H1" + default ARCH_RENESAS select ARCH_RCAR_GEN1 select ARM_ERRATA_754322 select ARM_GLOBAL_TIMER @@ -85,6 +88,7 @@ config ARCH_R8A7779 config ARCH_R8A7790 bool "ARM32 Platform support for R-Car H2" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_798181 if SMP select ARM_ERRATA_814220 @@ -93,11 +97,13 @@ config ARCH_R8A7790 config ARCH_R8A7778 bool "ARM32 Platform support for R-Car M1A" + default ARCH_RENESAS select ARCH_RCAR_GEN1 select ARM_ERRATA_754322 config ARCH_R8A7793 bool "ARM32 Platform support for R-Car M2-N" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_798181 if SMP select I2C @@ -105,6 +111,7 @@ config ARCH_R8A7793 config ARCH_R8A7791 bool "ARM32 Platform support for R-Car M2-W" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_798181 if SMP select I2C @@ -112,18 +119,21 @@ config ARCH_R8A7791 config ARCH_R8A7792 bool "ARM32 Platform support for R-Car V2H" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_798181 if SMP select SYSC_R8A7792 config ARCH_R8A7740 bool "ARM32 Platform support for R-Mobile A1" + default ARCH_RENESAS select ARCH_RMOBILE select ARM_ERRATA_754322 select RENESAS_INTC_IRQPIN config ARCH_R8A73A4 bool "ARM32 Platform support for R-Mobile APE6" + default ARCH_RENESAS select ARCH_RMOBILE select ARM_ERRATA_798181 if SMP select ARM_ERRATA_814220 @@ -132,6 +142,7 @@ config ARCH_R8A73A4 config ARCH_R7S72100 bool "ARM32 Platform support for RZ/A1H" + default ARCH_RENESAS select ARM_ERRATA_754322 select PM select PM_GENERIC_DOMAINS @@ -141,6 +152,7 @@ config ARCH_R7S72100 config ARCH_R7S9210 bool "ARM32 Platform support for RZ/A2" + default ARCH_RENESAS select PM select PM_GENERIC_DOMAINS select RENESAS_OSTM @@ -148,18 +160,21 @@ config ARCH_R7S9210 config ARCH_R8A77470 bool "ARM32 Platform support for RZ/G1C" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_814220 select SYSC_R8A77470 config ARCH_R8A7745 bool "ARM32 Platform support for RZ/G1E" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_814220 select SYSC_R8A7745 config ARCH_R8A7742 bool "ARM32 Platform support for RZ/G1H" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_798181 if SMP select ARM_ERRATA_814220 @@ -167,23 +182,27 @@ config ARCH_R8A7742 config ARCH_R8A7743 bool "ARM32 Platform support for RZ/G1M" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_798181 if SMP select SYSC_R8A7743 config ARCH_R8A7744 bool "ARM32 Platform support for RZ/G1N" + default ARCH_RENESAS select ARCH_RCAR_GEN2 select ARM_ERRATA_798181 if SMP select SYSC_R8A7743 config ARCH_R9A06G032 bool "ARM32 Platform support for RZ/N1D" + default ARCH_RENESAS select ARCH_RZN1 select ARM_ERRATA_814220 config ARCH_SH73A0 bool "ARM32 Platform support for SH-Mobile AG5" + default ARCH_RENESAS select ARCH_RMOBILE select ARM_ERRATA_754322 select ARM_GLOBAL_TIMER @@ -197,6 +216,7 @@ if ARM64 config ARCH_R8A77995 bool "ARM64 Platform support for R-Car D3" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A77995 help @@ -205,6 +225,7 @@ config ARCH_R8A77995 config ARCH_R8A77990 bool "ARM64 Platform support for R-Car E3" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A77990 help @@ -213,6 +234,7 @@ config ARCH_R8A77990 config ARCH_R8A77951 bool "ARM64 Platform support for R-Car H3 ES2.0+" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A7795 help @@ -222,6 +244,7 @@ config ARCH_R8A77951 config ARCH_R8A77965 bool "ARM64 Platform support for R-Car M3-N" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A77965 help @@ -230,6 +253,7 @@ config ARCH_R8A77965 config ARCH_R8A77960 bool "ARM64 Platform support for R-Car M3-W" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A77960 help @@ -237,6 +261,7 @@ config ARCH_R8A77960 config ARCH_R8A77961 bool "ARM64 Platform support for R-Car M3-W+" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A77961 help @@ -245,6 +270,7 @@ config ARCH_R8A77961 config ARCH_R8A779F0 bool "ARM64 Platform support for R-Car S4-8" + default y if ARCH_RENESAS select ARCH_RCAR_GEN4 select SYSC_R8A779F0 help @@ -252,6 +278,7 @@ config ARCH_R8A779F0 config ARCH_R8A77980 bool "ARM64 Platform support for R-Car V3H" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A77980 help @@ -259,6 +286,7 @@ config ARCH_R8A77980 config ARCH_R8A77970 bool "ARM64 Platform support for R-Car V3M" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A77970 help @@ -266,6 +294,7 @@ config ARCH_R8A77970 config ARCH_R8A779A0 bool "ARM64 Platform support for R-Car V3U" + default y if ARCH_RENESAS select ARCH_RCAR_GEN4 select SYSC_R8A779A0 help @@ -273,6 +302,7 @@ config ARCH_R8A779A0 config ARCH_R8A779G0 bool "ARM64 Platform support for R-Car V4H" + default y if ARCH_RENESAS select ARCH_RCAR_GEN4 select SYSC_R8A779G0 help @@ -280,6 +310,7 @@ config ARCH_R8A779G0 config ARCH_R8A779H0 bool "ARM64 Platform support for R-Car V4M" + default y if ARCH_RENESAS select ARCH_RCAR_GEN4 select SYSC_R8A779H0 help @@ -287,6 +318,7 @@ config ARCH_R8A779H0 config ARCH_R8A774C0 bool "ARM64 Platform support for RZ/G2E" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A774C0 help @@ -294,6 +326,7 @@ config ARCH_R8A774C0 config ARCH_R8A774E1 bool "ARM64 Platform support for RZ/G2H" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A774E1 help @@ -301,6 +334,7 @@ config ARCH_R8A774E1 config ARCH_R8A774A1 bool "ARM64 Platform support for RZ/G2M" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A774A1 help @@ -308,6 +342,7 @@ config ARCH_R8A774A1 config ARCH_R8A774B1 bool "ARM64 Platform support for RZ/G2N" + default y if ARCH_RENESAS select ARCH_RCAR_GEN3 select SYSC_R8A774B1 help @@ -315,24 +350,28 @@ config ARCH_R8A774B1 config ARCH_R9A07G043 bool "ARM64 Platform support for RZ/G2UL" + default y if ARCH_RENESAS select ARCH_RZG2L help This enables support for the Renesas RZ/G2UL SoC variants. config ARCH_R9A07G044 bool "ARM64 Platform support for RZ/G2L" + default y if ARCH_RENESAS select ARCH_RZG2L help This enables support for the Renesas RZ/G2L SoC variants. config ARCH_R9A07G054 bool "ARM64 Platform support for RZ/V2L" + default y if ARCH_RENESAS select ARCH_RZG2L help This enables support for the Renesas RZ/V2L SoC variants. config ARCH_R9A08G045 bool "ARM64 Platform support for RZ/G3S" + default y if ARCH_RENESAS select ARCH_RZG2L select SYSC_R9A08G045 help @@ -340,6 +379,7 @@ config ARCH_R9A08G045 config ARCH_R9A09G011 bool "ARM64 Platform support for RZ/V2M" + default y if ARCH_RENESAS select PM select PM_GENERIC_DOMAINS select PWC_RZV2M @@ -348,12 +388,14 @@ config ARCH_R9A09G011 config ARCH_R9A09G047 bool "ARM64 Platform support for RZ/G3E" + default y if ARCH_RENESAS select SYS_R9A09G047 help This enables support for the Renesas RZ/G3E SoC variants. config ARCH_R9A09G057 bool "ARM64 Platform support for RZ/V2H(P)" + default y if ARCH_RENESAS select RENESAS_RZV2H_ICU select SYS_R9A09G057 help From ae7ace712d5064c13ece545902d3421a2aa40f71 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 1 Apr 2025 10:01:31 +0100 Subject: [PATCH 02/39] arm64: defconfig: Remove individual Renesas SoC entries Remove explicit entries for individual Renesas SoCs from the defconfig file. Since all Renesas SoCs are enabled by default when ARCH_RENESAS is selected, and ARCH_RENESAS is already enabled in the defconfig, listing them separately is redundant. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/20250401090133.68146-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/configs/defconfig | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 5bb8f09422a2..5494b8a2380a 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1474,29 +1474,6 @@ CONFIG_QCOM_WCNSS_CTRL=m CONFIG_QCOM_APR=m CONFIG_QCOM_ICC_BWMON=m CONFIG_QCOM_PBS=m -CONFIG_ARCH_R8A77995=y -CONFIG_ARCH_R8A77990=y -CONFIG_ARCH_R8A77951=y -CONFIG_ARCH_R8A77965=y -CONFIG_ARCH_R8A77960=y -CONFIG_ARCH_R8A77961=y -CONFIG_ARCH_R8A779F0=y -CONFIG_ARCH_R8A77980=y -CONFIG_ARCH_R8A77970=y -CONFIG_ARCH_R8A779A0=y -CONFIG_ARCH_R8A779G0=y -CONFIG_ARCH_R8A779H0=y -CONFIG_ARCH_R8A774C0=y -CONFIG_ARCH_R8A774E1=y -CONFIG_ARCH_R8A774A1=y -CONFIG_ARCH_R8A774B1=y -CONFIG_ARCH_R9A07G043=y -CONFIG_ARCH_R9A07G044=y -CONFIG_ARCH_R9A07G054=y -CONFIG_ARCH_R9A08G045=y -CONFIG_ARCH_R9A09G011=y -CONFIG_ARCH_R9A09G047=y -CONFIG_ARCH_R9A09G057=y CONFIG_ROCKCHIP_IODOMAIN=y CONFIG_ARCH_TEGRA_132_SOC=y CONFIG_ARCH_TEGRA_210_SOC=y From 38509467f64ebc5414099c6d9e00797801dfc3a0 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 1 Apr 2025 10:01:32 +0100 Subject: [PATCH 03/39] arm: shmobile_defconfig: Drop individual Renesas SoC entries ARCH_RENESAS is already enabled in shmobile_defconfig, which ensures that all ARM32 Renesas SoCs are enabled by default. As a result, explicitly listing individual Renesas SoC entries is redundant. Remove these entries to simplify the configuration. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/20250401090133.68146-4-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm/configs/shmobile_defconfig | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/arch/arm/configs/shmobile_defconfig b/arch/arm/configs/shmobile_defconfig index 8c30ed14e52c..357cb144150b 100644 --- a/arch/arm/configs/shmobile_defconfig +++ b/arch/arm/configs/shmobile_defconfig @@ -195,25 +195,6 @@ CONFIG_RCAR_DMAC=y CONFIG_RENESAS_USB_DMAC=y CONFIG_RZ_DMAC=y # CONFIG_IOMMU_SUPPORT is not set -CONFIG_ARCH_EMEV2=y -CONFIG_ARCH_R8A7794=y -CONFIG_ARCH_R8A7779=y -CONFIG_ARCH_R8A7790=y -CONFIG_ARCH_R8A7778=y -CONFIG_ARCH_R8A7793=y -CONFIG_ARCH_R8A7791=y -CONFIG_ARCH_R8A7792=y -CONFIG_ARCH_R8A7740=y -CONFIG_ARCH_R8A73A4=y -CONFIG_ARCH_R7S72100=y -CONFIG_ARCH_R7S9210=y -CONFIG_ARCH_R8A77470=y -CONFIG_ARCH_R8A7745=y -CONFIG_ARCH_R8A7742=y -CONFIG_ARCH_R8A7743=y -CONFIG_ARCH_R8A7744=y -CONFIG_ARCH_R9A06G032=y -CONFIG_ARCH_SH73A0=y CONFIG_IIO=y CONFIG_AK8975=y CONFIG_PWM=y From 33d5bf70fff43fbc612450164bd0bab6b9ada261 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 1 Apr 2025 10:01:33 +0100 Subject: [PATCH 04/39] arm: multi_v7_defconfig: Drop individual Renesas SoC entries ARCH_RENESAS is already enabled in multi_v7_defconfig, which ensures that all ARM32 Renesas SoCs are enabled by default. As a result, explicitly listing individual Renesas SoC entries is redundant. Remove these entries to simplify the configuration. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/20250401090133.68146-5-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm/configs/multi_v7_defconfig | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig index ad037c175fdb..28a6ca750861 100644 --- a/arch/arm/configs/multi_v7_defconfig +++ b/arch/arm/configs/multi_v7_defconfig @@ -1121,25 +1121,6 @@ CONFIG_QCOM_SMSM=y CONFIG_QCOM_SOCINFO=m CONFIG_QCOM_STATS=m CONFIG_QCOM_WCNSS_CTRL=m -CONFIG_ARCH_EMEV2=y -CONFIG_ARCH_R8A7794=y -CONFIG_ARCH_R8A7779=y -CONFIG_ARCH_R8A7790=y -CONFIG_ARCH_R8A7778=y -CONFIG_ARCH_R8A7793=y -CONFIG_ARCH_R8A7791=y -CONFIG_ARCH_R8A7792=y -CONFIG_ARCH_R8A7740=y -CONFIG_ARCH_R8A73A4=y -CONFIG_ARCH_R7S72100=y -CONFIG_ARCH_R7S9210=y -CONFIG_ARCH_R8A77470=y -CONFIG_ARCH_R8A7745=y -CONFIG_ARCH_R8A7742=y -CONFIG_ARCH_R8A7743=y -CONFIG_ARCH_R8A7744=y -CONFIG_ARCH_R9A06G032=y -CONFIG_ARCH_SH73A0=y CONFIG_ROCKCHIP_IODOMAIN=y CONFIG_ARCH_TEGRA_2x_SOC=y CONFIG_ARCH_TEGRA_3x_SOC=y From 8c22e890402a990d712538d6d46245df129cf302 Mon Sep 17 00:00:00 2001 From: George Stark Date: Tue, 19 Nov 2024 15:53:15 +0300 Subject: [PATCH 05/39] pwm: meson: Simplify get_state() callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In .get_state() callback meson_pwm_channel struct are used to store lo and hi reg values but they are never reused after that so for clearness use local variable instead. Signed-off-by: George Stark Link: https://lore.kernel.org/r/20241119125318.3492261-2-gnstark@salutedevices.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-meson.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 98e6c1533312..c4ee019ce577 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -309,21 +309,20 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, { struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel_data *channel_data; - struct meson_pwm_channel *channel; + unsigned int hi, lo; u32 value; - channel = &meson->channels[pwm->hwpwm]; channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; value = readl(meson->base + REG_MISC_AB); state->enabled = value & channel_data->pwm_en_mask; value = readl(meson->base + channel_data->reg_offset); - channel->lo = FIELD_GET(PWM_LOW_MASK, value); - channel->hi = FIELD_GET(PWM_HIGH_MASK, value); + lo = FIELD_GET(PWM_LOW_MASK, value); + hi = FIELD_GET(PWM_HIGH_MASK, value); - state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->lo + channel->hi); - state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); + state->period = meson_pwm_cnt_to_ns(chip, pwm, lo + hi); + state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, hi); state->polarity = PWM_POLARITY_NORMAL; From dd4d280ac5589fb3e1f333e6dbac4432abff4985 Mon Sep 17 00:00:00 2001 From: George Stark Date: Tue, 19 Nov 2024 15:53:16 +0300 Subject: [PATCH 06/39] pwm: meson: Support constant and polarity bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newer meson PWM IPs support constant and polarity bits. Support them to correctly implement constant and inverted output levels. Using constant bit allows to have truly stable low or high output level. Since hi and low regs internally increment its values by 1 just writing zero to any of them gives 1 clock count impulse. If constant bit is set zero value in hi and low regs is not incremented. Using polarity bit instead of swapping hi and low reg values allows to correctly identify inversion in .get_state(). Signed-off-by: George Stark Link: https://lore.kernel.org/r/20241119125318.3492261-3-gnstark@salutedevices.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-meson.c | 61 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index c4ee019ce577..d7335efa3db7 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -6,7 +6,7 @@ * PWM output is achieved by calculating a clock that permits calculating * two periods (low and high). The counter then has to be set to switch after * N cycles for the first half period. - * The hardware has no "polarity" setting. This driver reverses the period + * Partly the hardware has no "polarity" setting. This driver reverses the period * cycles (the low length is inverted with the high length) for * PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity * from the hardware. @@ -56,6 +56,10 @@ #define MISC_B_CLK_SEL_SHIFT 6 #define MISC_A_CLK_SEL_SHIFT 4 #define MISC_CLK_SEL_MASK 0x3 +#define MISC_B_CONSTANT_EN BIT(29) +#define MISC_A_CONSTANT_EN BIT(28) +#define MISC_B_INVERT_EN BIT(27) +#define MISC_A_INVERT_EN BIT(26) #define MISC_B_EN BIT(1) #define MISC_A_EN BIT(0) @@ -68,6 +72,8 @@ static struct meson_pwm_channel_data { u8 clk_div_shift; u8 clk_en_shift; u32 pwm_en_mask; + u32 const_en_mask; + u32 inv_en_mask; } meson_pwm_per_channel_data[MESON_NUM_PWMS] = { { .reg_offset = REG_PWM_A, @@ -75,6 +81,8 @@ static struct meson_pwm_channel_data { .clk_div_shift = MISC_A_CLK_DIV_SHIFT, .clk_en_shift = MISC_A_CLK_EN_SHIFT, .pwm_en_mask = MISC_A_EN, + .const_en_mask = MISC_A_CONSTANT_EN, + .inv_en_mask = MISC_A_INVERT_EN, }, { .reg_offset = REG_PWM_B, @@ -82,6 +90,8 @@ static struct meson_pwm_channel_data { .clk_div_shift = MISC_B_CLK_DIV_SHIFT, .clk_en_shift = MISC_B_CLK_EN_SHIFT, .pwm_en_mask = MISC_B_EN, + .const_en_mask = MISC_B_CONSTANT_EN, + .inv_en_mask = MISC_B_INVERT_EN, } }; @@ -89,6 +99,8 @@ struct meson_pwm_channel { unsigned long rate; unsigned int hi; unsigned int lo; + bool constant; + bool inverted; struct clk_mux mux; struct clk_divider div; @@ -99,6 +111,8 @@ struct meson_pwm_channel { struct meson_pwm_data { const char *const parent_names[MESON_NUM_MUX_PARENTS]; int (*channels_init)(struct pwm_chip *chip); + bool has_constant; + bool has_polarity; }; struct meson_pwm { @@ -160,7 +174,7 @@ static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm, * Fixing this needs some care however as some machines might rely on * this. */ - if (state->polarity == PWM_POLARITY_INVERSED) + if (state->polarity == PWM_POLARITY_INVERSED && !meson->data->has_polarity) duty = period - duty; freq = div64_u64(NSEC_PER_SEC * 0xffffULL, period); @@ -187,9 +201,11 @@ static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm, if (duty == period) { channel->hi = cnt; channel->lo = 0; + channel->constant = true; } else if (duty == 0) { channel->hi = 0; channel->lo = cnt; + channel->constant = true; } else { duty_cnt = mul_u64_u64_div_u64(fin_freq, duty, NSEC_PER_SEC); @@ -197,6 +213,7 @@ static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm, channel->hi = duty_cnt; channel->lo = cnt - duty_cnt; + channel->constant = false; } channel->rate = fin_freq; @@ -227,6 +244,19 @@ static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) value = readl(meson->base + REG_MISC_AB); value |= channel_data->pwm_en_mask; + + if (meson->data->has_constant) { + value &= ~channel_data->const_en_mask; + if (channel->constant) + value |= channel_data->const_en_mask; + } + + if (meson->data->has_polarity) { + value &= ~channel_data->inv_en_mask; + if (channel->inverted) + value |= channel_data->inv_en_mask; + } + writel(value, meson->base + REG_MISC_AB); spin_unlock_irqrestore(&meson->lock, flags); @@ -235,13 +265,24 @@ static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) static void meson_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; + struct meson_pwm_channel_data *channel_data; unsigned long flags; u32 value; + channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; + spin_lock_irqsave(&meson->lock, flags); value = readl(meson->base + REG_MISC_AB); - value &= ~meson_pwm_per_channel_data[pwm->hwpwm].pwm_en_mask; + value &= ~channel_data->pwm_en_mask; + + if (meson->data->has_polarity) { + value &= ~channel_data->inv_en_mask; + if (channel->inverted) + value |= channel_data->inv_en_mask; + } + writel(value, meson->base + REG_MISC_AB); spin_unlock_irqrestore(&meson->lock, flags); @@ -254,10 +295,12 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm]; int err = 0; + channel->inverted = (state->polarity == PWM_POLARITY_INVERSED); + if (!state->enabled) { - if (state->polarity == PWM_POLARITY_INVERSED) { + if (channel->inverted && !meson->data->has_polarity) { /* - * This IP block revision doesn't have an "always high" + * Some of IP block revisions don't have an "always high" * setting which we can use for "inverted disabled". * Instead we achieve this by setting mux parent with * highest rate and minimum divider value, resulting @@ -271,6 +314,7 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, channel->rate = ULONG_MAX; channel->hi = ~0; channel->lo = 0; + channel->constant = true; meson_pwm_enable(chip, pwm); } else { @@ -317,6 +361,11 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, value = readl(meson->base + REG_MISC_AB); state->enabled = value & channel_data->pwm_en_mask; + if (meson->data->has_polarity && (value & channel_data->inv_en_mask)) + state->polarity = PWM_POLARITY_INVERSED; + else + state->polarity = PWM_POLARITY_NORMAL; + value = readl(meson->base + channel_data->reg_offset); lo = FIELD_GET(PWM_LOW_MASK, value); hi = FIELD_GET(PWM_HIGH_MASK, value); @@ -324,8 +373,6 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, state->period = meson_pwm_cnt_to_ns(chip, pwm, lo + hi); state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, hi); - state->polarity = PWM_POLARITY_NORMAL; - return 0; } From 3a44aacf156055574dbfddd615ac5194aadacaad Mon Sep 17 00:00:00 2001 From: George Stark Date: Tue, 19 Nov 2024 15:53:17 +0300 Subject: [PATCH 07/39] pwm: meson: Use separate device id data for axg and g12 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add separate devce id data for compatibles: amlogic,meson-g12a-ee-pwm, amlogic,meson-axg-pwm-v2, amlogic,meson-g12-pwm-v2 due to those PWM modules have different set of features than meson8. Signed-off-by: George Stark Reviewed-by: Neil Armstrong Link: https://lore.kernel.org/r/20241119125318.3492261-4-gnstark@salutedevices.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-meson.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index d7335efa3db7..3cc48c3dde5e 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -561,6 +561,11 @@ static const struct meson_pwm_data pwm_axg_ao_data = { .channels_init = meson_pwm_init_channels_meson8b_legacy, }; +static const struct meson_pwm_data pwm_g12a_ee_data = { + .parent_names = { "xtal", NULL, "fclk_div4", "fclk_div3" }, + .channels_init = meson_pwm_init_channels_meson8b_legacy, +}; + static const struct meson_pwm_data pwm_g12a_ao_ab_data = { .parent_names = { "xtal", "g12a_ao_clk81", "fclk_div4", "fclk_div5" }, .channels_init = meson_pwm_init_channels_meson8b_legacy, @@ -575,6 +580,10 @@ static const struct meson_pwm_data pwm_meson8_v2_data = { .channels_init = meson_pwm_init_channels_meson8b_v2, }; +static const struct meson_pwm_data pwm_meson_axg_v2_data = { + .channels_init = meson_pwm_init_channels_meson8b_v2, +}; + static const struct meson_pwm_data pwm_s4_data = { .channels_init = meson_pwm_init_channels_s4, }; @@ -584,6 +593,14 @@ static const struct of_device_id meson_pwm_matches[] = { .compatible = "amlogic,meson8-pwm-v2", .data = &pwm_meson8_v2_data }, + { + .compatible = "amlogic,meson-axg-pwm-v2", + .data = &pwm_meson_axg_v2_data + }, + { + .compatible = "amlogic,meson-g12-pwm-v2", + .data = &pwm_meson_axg_v2_data + }, /* The following compatibles are obsolete */ { .compatible = "amlogic,meson8b-pwm", @@ -607,7 +624,7 @@ static const struct of_device_id meson_pwm_matches[] = { }, { .compatible = "amlogic,meson-g12a-ee-pwm", - .data = &pwm_meson8b_data + .data = &pwm_g12a_ee_data }, { .compatible = "amlogic,meson-g12a-ao-pwm-ab", From 5dca8a93b015c3362d5168bbe0bf109ac1b9bfa5 Mon Sep 17 00:00:00 2001 From: George Stark Date: Tue, 19 Nov 2024 15:53:18 +0300 Subject: [PATCH 08/39] pwm: meson: Enable constant and polarity features for g12, axg, s4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit g12, axg and s4 SoC families support constant and polarity bits so enable those features in corresponding chip data structs. Signed-off-by: George Stark Reviewed-by: Neil Armstrong Link: https://lore.kernel.org/r/20241119125318.3492261-5-gnstark@salutedevices.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-meson.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 3cc48c3dde5e..806e06c2b92e 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -554,26 +554,36 @@ static const struct meson_pwm_data pwm_gxbb_ao_data = { static const struct meson_pwm_data pwm_axg_ee_data = { .parent_names = { "xtal", "fclk_div5", "fclk_div4", "fclk_div3" }, .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, }; static const struct meson_pwm_data pwm_axg_ao_data = { .parent_names = { "xtal", "axg_ao_clk81", "fclk_div4", "fclk_div5" }, .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, }; static const struct meson_pwm_data pwm_g12a_ee_data = { .parent_names = { "xtal", NULL, "fclk_div4", "fclk_div3" }, .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, }; static const struct meson_pwm_data pwm_g12a_ao_ab_data = { .parent_names = { "xtal", "g12a_ao_clk81", "fclk_div4", "fclk_div5" }, .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, }; static const struct meson_pwm_data pwm_g12a_ao_cd_data = { .parent_names = { "xtal", "g12a_ao_clk81", NULL, NULL }, .channels_init = meson_pwm_init_channels_meson8b_legacy, + .has_constant = true, + .has_polarity = true, }; static const struct meson_pwm_data pwm_meson8_v2_data = { @@ -582,10 +592,14 @@ static const struct meson_pwm_data pwm_meson8_v2_data = { static const struct meson_pwm_data pwm_meson_axg_v2_data = { .channels_init = meson_pwm_init_channels_meson8b_v2, + .has_constant = true, + .has_polarity = true, }; static const struct meson_pwm_data pwm_s4_data = { .channels_init = meson_pwm_init_channels_s4, + .has_constant = true, + .has_polarity = true, }; static const struct of_device_id meson_pwm_matches[] = { From 08d8c9f593c79ec3fac8bc47ce01be83ecde850f Mon Sep 17 00:00:00 2001 From: George Stark Date: Wed, 25 Dec 2024 13:56:38 +0300 Subject: [PATCH 09/39] pwm: meson: Simplify meson_pwm_cnt_to_ns() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meson_pwm_cnt_to_ns() uses clock rate got from clk_get_rate(). clk object is getting from driver's private data thru several steps. Since meson_pwm_cnt_to_ns() is called several times from a single scope it's easier to get clock rate once and pass it as parameter. Signed-off-by: George Stark Link: https://lore.kernel.org/r/20241225105639.1787237-2-gnstark@salutedevices.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-meson.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 806e06c2b92e..8c6bf3d49753 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -331,21 +331,9 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } -static u64 meson_pwm_cnt_to_ns(struct pwm_chip *chip, struct pwm_device *pwm, - u32 cnt) +static u64 meson_pwm_cnt_to_ns(unsigned long fin_freq, u32 cnt) { - struct meson_pwm *meson = to_meson_pwm(chip); - struct meson_pwm_channel *channel; - unsigned long fin_freq; - - /* to_meson_pwm() can only be used after .get_state() is called */ - channel = &meson->channels[pwm->hwpwm]; - - fin_freq = clk_get_rate(channel->clk); - if (fin_freq == 0) - return 0; - - return div64_ul(NSEC_PER_SEC * (u64)cnt, fin_freq); + return fin_freq ? div64_ul(NSEC_PER_SEC * (u64)cnt, fin_freq) : 0; } static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, @@ -353,10 +341,12 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, { struct meson_pwm *meson = to_meson_pwm(chip); struct meson_pwm_channel_data *channel_data; + unsigned long fin_freq; unsigned int hi, lo; u32 value; channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; + fin_freq = clk_get_rate(meson->channels[pwm->hwpwm].clk); value = readl(meson->base + REG_MISC_AB); state->enabled = value & channel_data->pwm_en_mask; @@ -370,8 +360,8 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, lo = FIELD_GET(PWM_LOW_MASK, value); hi = FIELD_GET(PWM_HIGH_MASK, value); - state->period = meson_pwm_cnt_to_ns(chip, pwm, lo + hi); - state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, hi); + state->period = meson_pwm_cnt_to_ns(fin_freq, lo + hi); + state->duty_cycle = meson_pwm_cnt_to_ns(fin_freq, hi); return 0; } From 90cd430f04d0f2874f9c0fc75b9084f5162299c9 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Mon, 31 Mar 2025 14:53:49 +0800 Subject: [PATCH 10/39] dt-bindings: pwm: Add Loongson PWM controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Loongson PWM controller binding with DT schema format using json-schema. Reviewed-by: Krzysztof Kozlowski Acked-by: Huacai Chen Signed-off-by: Binbin Zhou Link: https://lore.kernel.org/r/57e0cbd4b7ce37da94094205e28a2ec2256c7175.1743403075.git.zhoubinbin@loongson.cn Signed-off-by: Uwe Kleine-König --- .../bindings/pwm/loongson,ls7a-pwm.yaml | 67 +++++++++++++++++++ MAINTAINERS | 6 ++ 2 files changed, 73 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/loongson,ls7a-pwm.yaml diff --git a/Documentation/devicetree/bindings/pwm/loongson,ls7a-pwm.yaml b/Documentation/devicetree/bindings/pwm/loongson,ls7a-pwm.yaml new file mode 100644 index 000000000000..5d64fb40a0d6 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/loongson,ls7a-pwm.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/loongson,ls7a-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Loongson PWM Controller + +maintainers: + - Binbin Zhou + +description: + The Loongson PWM has one pulse width output signal and one pulse input + signal to be measured. + It can be found on Loongson-2K series cpus and Loongson LS7A bridge chips. + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + oneOf: + - const: loongson,ls7a-pwm + - items: + - enum: + - loongson,ls2k0500-pwm + - loongson,ls2k1000-pwm + - loongson,ls2k2000-pwm + - const: loongson,ls7a-pwm + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + '#pwm-cells': + description: + The first cell must have a value of 0, which specifies the PWM output signal; + The second cell is the period in nanoseconds; + The third cell flag supported by this binding is PWM_POLARITY_INVERTED. + const: 3 + +required: + - compatible + - reg + - interrupts + - clocks + +additionalProperties: false + +examples: + - | + #include + #include + + pwm@1fe22000 { + compatible = "loongson,ls2k1000-pwm", "loongson,ls7a-pwm"; + reg = <0x1fe22000 0x10>; + interrupt-parent = <&liointc0>; + interrupts = <24 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk LOONGSON2_APB_CLK>; + #pwm-cells = <3>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index c59316109e3f..57053ad4dee5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13912,6 +13912,12 @@ S: Maintained F: Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml F: drivers/i2c/busses/i2c-ls2x.c +LOONGSON PWM DRIVER +M: Binbin Zhou +L: linux-pwm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/pwm/loongson,ls7a-pwm.yaml + LOONGSON-2 SOC SERIES CLOCK DRIVER M: Yinbo Zhu L: linux-clk@vger.kernel.org From 2b62c89448dd41ebf16d860c52fe9a8aba3dd8a3 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Mon, 31 Mar 2025 14:53:50 +0800 Subject: [PATCH 11/39] pwm: Add Loongson PWM controller support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a generic PWM framework driver for the PWM controller found on Loongson family chips. Acked-by: Huacai Chen Co-developed-by: Juxin Gao Signed-off-by: Juxin Gao Signed-off-by: Binbin Zhou Link: https://lore.kernel.org/r/76050a903a8015422fb9261ad88c7d9cc2edbbd8.1743403075.git.zhoubinbin@loongson.cn Signed-off-by: Uwe Kleine-König --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 12 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-loongson.c | 290 +++++++++++++++++++++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 drivers/pwm/pwm-loongson.c diff --git a/MAINTAINERS b/MAINTAINERS index 57053ad4dee5..571f56ba33b0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13917,6 +13917,7 @@ M: Binbin Zhou L: linux-pwm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/pwm/loongson,ls7a-pwm.yaml +F: drivers/pwm/pwm-loongson.c LOONGSON-2 SOC SERIES CLOCK DRIVER M: Yinbo Zhu diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 4731d5b90d7e..ead63f9d0c73 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -351,6 +351,18 @@ config PWM_KEEMBAY To compile this driver as a module, choose M here: the module will be called pwm-keembay. +config PWM_LOONGSON + tristate "Loongson PWM support" + depends on MACH_LOONGSON64 || COMPILE_TEST + depends on COMMON_CLK + help + Generic PWM framework driver for Loongson family. + It can be found on Loongson-2K series cpus and Loongson LS7A + bridge chips. + + To compile this driver as a module, choose M here: the module + will be called pwm-loongson. + config PWM_LP3943 tristate "TI/National Semiconductor LP3943 PWM support" depends on MFD_LP3943 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 539e0def3f82..e52d0940b247 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_PWM_INTEL_LGM) += pwm-intel-lgm.o obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o +obj-$(CONFIG_PWM_LOONGSON) += pwm-loongson.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o diff --git a/drivers/pwm/pwm-loongson.c b/drivers/pwm/pwm-loongson.c new file mode 100644 index 000000000000..412c67739ef9 --- /dev/null +++ b/drivers/pwm/pwm-loongson.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017-2025 Loongson Technology Corporation Limited. + * + * Loongson PWM driver + * + * For Loongson's PWM IP block documentation please refer Chapter 11 of + * Reference Manual: https://loongson.github.io/LoongArch-Documentation/Loongson-7A1000-usermanual-EN.pdf + * + * Author: Juxin Gao + * Further cleanup and restructuring by: + * Binbin Zhou + * + * Limitations: + * - If both DUTY and PERIOD are set to 0, the output is a constant low signal. + * - When disabled the output is driven to 0 independent of the configured + * polarity. + * - If the register is reconfigured while PWM is running, it does not complete + * the currently running period. + * - Disabling the PWM stops the output immediately (without waiting for current + * period to complete first). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Loongson PWM registers */ +#define LOONGSON_PWM_REG_DUTY 0x4 /* Low Pulse Buffer Register */ +#define LOONGSON_PWM_REG_PERIOD 0x8 /* Pulse Period Buffer Register */ +#define LOONGSON_PWM_REG_CTRL 0xc /* Control Register */ + +/* Control register bits */ +#define LOONGSON_PWM_CTRL_REG_EN BIT(0) /* Counter Enable Bit */ +#define LOONGSON_PWM_CTRL_REG_OE BIT(3) /* Pulse Output Enable Control Bit, Valid Low */ +#define LOONGSON_PWM_CTRL_REG_SINGLE BIT(4) /* Single Pulse Control Bit */ +#define LOONGSON_PWM_CTRL_REG_INTE BIT(5) /* Interrupt Enable Bit */ +#define LOONGSON_PWM_CTRL_REG_INT BIT(6) /* Interrupt Bit */ +#define LOONGSON_PWM_CTRL_REG_RST BIT(7) /* Counter Reset Bit */ +#define LOONGSON_PWM_CTRL_REG_CAPTE BIT(8) /* Measurement Pulse Enable Bit */ +#define LOONGSON_PWM_CTRL_REG_INVERT BIT(9) /* Output flip-flop Enable Bit */ +#define LOONGSON_PWM_CTRL_REG_DZONE BIT(10) /* Anti-dead Zone Enable Bit */ + +/* default input clk frequency for the ACPI case */ +#define LOONGSON_PWM_FREQ_DEFAULT 50000 /* Hz */ + +struct pwm_loongson_ddata { + struct clk *clk; + void __iomem *base; + u64 clk_rate; +}; + +static inline __pure struct pwm_loongson_ddata *to_pwm_loongson_ddata(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata, u32 offset) +{ + return readl(ddata->base + offset); +} + +static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata, + u32 val, u32 offset) +{ + writel(val, ddata->base + offset); +} + +static int pwm_loongson_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + u16 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); + + if (polarity == PWM_POLARITY_INVERSED) + /* Duty cycle defines LOW period of PWM */ + val |= LOONGSON_PWM_CTRL_REG_INVERT; + else + /* Duty cycle defines HIGH period of PWM */ + val &= ~LOONGSON_PWM_CTRL_REG_INVERT; + + pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); + + return 0; +} + +static void pwm_loongson_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); + val &= ~LOONGSON_PWM_CTRL_REG_EN; + pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); +} + +static int pwm_loongson_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + val = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); + val |= LOONGSON_PWM_CTRL_REG_EN; + pwm_loongson_writel(ddata, val, LOONGSON_PWM_REG_CTRL); + + return 0; +} + +static int pwm_loongson_config(struct pwm_chip *chip, struct pwm_device *pwm, + u64 duty_ns, u64 period_ns) +{ + u32 duty, period; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + /* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */ + duty = mul_u64_u64_div_u64(duty_ns, ddata->clk_rate, NSEC_PER_SEC); + if (duty > U32_MAX) + duty = U32_MAX; + + /* period = period_ns * ddata->clk_rate / NSEC_PER_SEC */ + period = mul_u64_u64_div_u64(period_ns, ddata->clk_rate, NSEC_PER_SEC); + if (period > U32_MAX) + period = U32_MAX; + + pwm_loongson_writel(ddata, duty, LOONGSON_PWM_REG_DUTY); + pwm_loongson_writel(ddata, period, LOONGSON_PWM_REG_PERIOD); + + return 0; +} + +static int pwm_loongson_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + int ret; + bool enabled = pwm->state.enabled; + + if (!state->enabled) { + if (enabled) + pwm_loongson_disable(chip, pwm); + return 0; + } + + ret = pwm_loongson_set_polarity(chip, pwm, state->polarity); + if (ret) + return ret; + + ret = pwm_loongson_config(chip, pwm, state->duty_cycle, state->period); + if (ret) + return ret; + + if (!enabled && state->enabled) + ret = pwm_loongson_enable(chip, pwm); + + return ret; +} + +static int pwm_loongson_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + u32 duty, period, ctrl; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + duty = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_DUTY); + period = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_PERIOD); + ctrl = pwm_loongson_readl(ddata, LOONGSON_PWM_REG_CTRL); + + /* duty & period have a max of 2^32, so we can't overflow */ + state->duty_cycle = DIV64_U64_ROUND_UP((u64)duty * NSEC_PER_SEC, ddata->clk_rate); + state->period = DIV64_U64_ROUND_UP((u64)period * NSEC_PER_SEC, ddata->clk_rate); + state->polarity = (ctrl & LOONGSON_PWM_CTRL_REG_INVERT) ? PWM_POLARITY_INVERSED : + PWM_POLARITY_NORMAL; + state->enabled = (ctrl & LOONGSON_PWM_CTRL_REG_EN) ? true : false; + + return 0; +} + +static const struct pwm_ops pwm_loongson_ops = { + .apply = pwm_loongson_apply, + .get_state = pwm_loongson_get_state, +}; + +static int pwm_loongson_probe(struct platform_device *pdev) +{ + int ret; + struct pwm_chip *chip; + struct pwm_loongson_ddata *ddata; + struct device *dev = &pdev->dev; + + chip = devm_pwmchip_alloc(dev, 1, sizeof(*ddata)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + ddata = to_pwm_loongson_ddata(chip); + + ddata->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ddata->base)) + return PTR_ERR(ddata->base); + + ddata->clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(ddata->clk)) + return dev_err_probe(dev, PTR_ERR(ddata->clk), + "Failed to get pwm clock\n"); + if (ddata->clk) { + ret = devm_clk_rate_exclusive_get(dev, ddata->clk); + if (ret) + return dev_err_probe(dev, PTR_ERR(ddata->clk), + "Failed to get exclusive rate\n"); + + ddata->clk_rate = clk_get_rate(ddata->clk); + if (!ddata->clk_rate) + return dev_err_probe(dev, -EINVAL, + "Failed to get frequency\n"); + } else { + ddata->clk_rate = LOONGSON_PWM_FREQ_DEFAULT; + } + + /* This check is done to prevent an overflow in .apply */ + if (ddata->clk_rate > NSEC_PER_SEC) + return dev_err_probe(dev, -EINVAL, "PWM clock out of range\n"); + + chip->ops = &pwm_loongson_ops; + chip->atomic = true; + dev_set_drvdata(dev, chip); + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + + return 0; +} + +static int pwm_loongson_suspend(struct device *dev) +{ + struct pwm_chip *chip = dev_get_drvdata(dev); + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + struct pwm_device *pwm = &chip->pwms[0]; + + if (pwm->state.enabled) + return -EBUSY; + + clk_disable_unprepare(ddata->clk); + + return 0; +} + +static int pwm_loongson_resume(struct device *dev) +{ + struct pwm_chip *chip = dev_get_drvdata(dev); + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + return clk_prepare_enable(ddata->clk); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(pwm_loongson_pm_ops, pwm_loongson_suspend, + pwm_loongson_resume); + +static const struct of_device_id pwm_loongson_of_ids[] = { + { .compatible = "loongson,ls7a-pwm" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, pwm_loongson_of_ids); + +static const struct acpi_device_id pwm_loongson_acpi_ids[] = { + { "LOON0006" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, pwm_loongson_acpi_ids); + +static struct platform_driver pwm_loongson_driver = { + .probe = pwm_loongson_probe, + .driver = { + .name = "loongson-pwm", + .pm = pm_ptr(&pwm_loongson_pm_ops), + .of_match_table = pwm_loongson_of_ids, + .acpi_match_table = pwm_loongson_acpi_ids, + }, +}; +module_platform_driver(pwm_loongson_driver); + +MODULE_DESCRIPTION("Loongson PWM driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited."); +MODULE_LICENSE("GPL"); From df08fff8add2fbca9ab4513c169b39802d59907f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Thu, 13 Mar 2025 08:28:55 +0100 Subject: [PATCH 12/39] pwm: pxa: Improve using dev_err_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a message to the error path of devm_clk_get() and simplify the error path of devm_pwmchip_add() while improving the error message en passant. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20250313072855.3360076-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-pxa.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index 430bd6a709e9..8a4a3d2df30d 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -160,24 +160,24 @@ static int pwm_probe(struct platform_device *pdev) const struct platform_device_id *id = platform_get_device_id(pdev); struct pwm_chip *chip; struct pxa_pwm_chip *pc; + struct device *dev = &pdev->dev; int ret = 0; if (IS_ENABLED(CONFIG_OF) && id == NULL) - id = of_device_get_match_data(&pdev->dev); + id = of_device_get_match_data(dev); if (id == NULL) return -EINVAL; - chip = devm_pwmchip_alloc(&pdev->dev, - (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1, + chip = devm_pwmchip_alloc(dev, (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1, sizeof(*pc)); if (IS_ERR(chip)) return PTR_ERR(chip); pc = to_pxa_pwm_chip(chip); - pc->clk = devm_clk_get(&pdev->dev, NULL); + pc->clk = devm_clk_get(dev, NULL); if (IS_ERR(pc->clk)) - return PTR_ERR(pc->clk); + return dev_err_probe(dev, PTR_ERR(pc->clk), "Failed to get clock\n"); chip->ops = &pxa_pwm_ops; @@ -188,11 +188,9 @@ static int pwm_probe(struct platform_device *pdev) if (IS_ERR(pc->mmio_base)) return PTR_ERR(pc->mmio_base); - ret = devm_pwmchip_add(&pdev->dev, chip); - if (ret < 0) { - dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); - return ret; - } + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "pwmchip_add() failed\n"); return 0; } From 7cfe1e208b86c7fd4b35a8a502a8b15604eec1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Thu, 3 Apr 2025 17:11:32 +0200 Subject: [PATCH 13/39] pwm: Make chip parameter to pwmchip_get_drvdata() a const pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev_get_drvdata()'s parameter is a const pointer, so the chip passed to pwmchip_get_drvdata() can be const, too. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20250403151134.266388-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- include/linux/pwm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 9ece4e5d3815..bf0469b2201d 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -369,7 +369,7 @@ static inline struct device *pwmchip_parent(const struct pwm_chip *chip) return chip->dev.parent; } -static inline void *pwmchip_get_drvdata(struct pwm_chip *chip) +static inline void *pwmchip_get_drvdata(const struct pwm_chip *chip) { return dev_get_drvdata(&chip->dev); } From 461d68d43d69a3d96750448ea5c3e0a68fc8d4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 4 Apr 2025 12:48:42 +0200 Subject: [PATCH 14/39] pwm: Add actual hardware state to pwm debugfs file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Traditionally /sys/kernel/debug/pwm only contained info from pwm->state. Most of the time this data represents the last requested setting which might differ considerably from the actually configured in hardware setting. Expand the information in the debugfs file with the actual values. Signed-off-by: Uwe Kleine-König Reviewed-by: Trevor Gamblin Link: https://lore.kernel.org/r/20250404104844.543479-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 0387bd838487..02d98e4c75a5 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -2221,25 +2221,28 @@ static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s) for (i = 0; i < chip->npwm; i++) { struct pwm_device *pwm = &chip->pwms[i]; - struct pwm_state state; + struct pwm_state state, hwstate; pwm_get_state(pwm, &state); + pwm_get_state_hw(pwm, &hwstate); seq_printf(s, " pwm-%-3d (%-20.20s):", i, pwm->label); if (test_bit(PWMF_REQUESTED, &pwm->flags)) seq_puts(s, " requested"); - if (state.enabled) - seq_puts(s, " enabled"); + seq_puts(s, "\n"); - seq_printf(s, " period: %llu ns", state.period); - seq_printf(s, " duty: %llu ns", state.duty_cycle); - seq_printf(s, " polarity: %s", + seq_printf(s, " requested configuration: %3sabled, %llu/%llu ns, %s polarity", + state.enabled ? "en" : "dis", state.duty_cycle, state.period, state.polarity ? "inverse" : "normal"); - if (state.usage_power) - seq_puts(s, " usage_power"); + seq_puts(s, ", usage_power"); + seq_puts(s, "\n"); + + seq_printf(s, " actual configuration: %3sabled, %llu/%llu ns, %s polarity", + hwstate.enabled ? "en" : "dis", hwstate.duty_cycle, hwstate.period, + hwstate.polarity ? "inverse" : "normal"); seq_puts(s, "\n"); } From 4cbeffc40798efb84eb2ed9144e0e36dda603dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sat, 5 Apr 2025 11:27:14 +0200 Subject: [PATCH 15/39] pwm: stm32: Don't open-code TIM_CCER_CCxE() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of manually calculating the offset of the channels CCxE bit, make use of the TIM_CCER_CCxE macro. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/7803f63b1310ddbd706f51f2f42d30b6dd786b03.1743844730.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-stm32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index ec2c05c9ee7a..c6625f51a199 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -88,7 +88,7 @@ static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip, rate = clk_get_rate(priv->clk); - if (active_channels(priv) & ~(1 << ch * 4)) { + if (active_channels(priv) & ~TIM_CCER_CCxE(ch + 1)) { u64 arr; /* From fa829c1f40b7818a6c0b06719b6f057b3b0424dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sat, 5 Apr 2025 11:27:15 +0200 Subject: [PATCH 16/39] pwm: stm32: Emit debug output also for corner cases of the rounding callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When you're interested in the actual register settings the driver chooses or interprets you want to see them also for calls that hit corner cases. Make sure that all calls to stm32_pwm_round_waveform_tohw() and stm32_pwm_round_waveform_fromhw() emit the debug message about the register settings. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/fe154e79319da5ff4159cdc71201a9d3b395e491.1743844730.git.u.kleine-koenig@baylibre.com [ukleinek: widen scope of rate in stm32_pwm_round_waveform_fromhw() to fix FTBFS] Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-stm32.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index c6625f51a199..4b148f0afeb9 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -180,11 +180,11 @@ static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip, wfhw->ccr = min_t(u64, ccr, wfhw->arr + 1); +out: dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x\n", pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, rate, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr); -out: clk_disable(priv->clk); return ret; @@ -213,10 +213,10 @@ static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip, { const struct stm32_pwm_waveform *wfhw = _wfhw; struct stm32_pwm *priv = to_stm32_pwm_dev(chip); + unsigned long rate = clk_get_rate(priv->clk); unsigned int ch = pwm->hwpwm; if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) { - unsigned long rate = clk_get_rate(priv->clk); u64 ccr_ns; /* The result doesn't overflow for rate >= 15259 */ @@ -236,17 +236,16 @@ static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip, wf->duty_length_ns = ccr_ns; wf->duty_offset_ns = 0; } - - dev_dbg(&chip->dev, "pwm#%u: CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x @%lu -> %lld/%lld [+%lld]\n", - pwm->hwpwm, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr, rate, - wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns); - } else { *wf = (struct pwm_waveform){ .period_length_ns = 0, }; } + dev_dbg(&chip->dev, "pwm#%u: CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x @%lu -> %lld/%lld [+%lld]\n", + pwm->hwpwm, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr, rate, + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns); + return 0; } From 96d20cfd16e779923153f7347b0bef6b3c7606ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sat, 5 Apr 2025 11:27:17 +0200 Subject: [PATCH 17/39] pwm: Do stricter return value checking for .round_waveform_tohw() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .round_waveform_tohw() is supposed to return 0 if the request could be rounded down to match the hardware capabilities and return 1 if rounding down wasn't possible. Expand the PWM_DEBUG check to not only assert proper downrounding if 0 was returned but also check that it was actually rounded up when the callback signalled uprounding. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/dfb824ae37f99df068c752d48cbd163c044a74fb.1743844730.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 02d98e4c75a5..59cc8792e312 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -270,10 +270,10 @@ int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform * wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw); if (IS_ENABLED(CONFIG_PWM_DEBUG) && - ret_tohw == 0 && !pwm_check_rounding(&wf_req, wf)) - dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", + (ret_tohw == 0) != pwm_check_rounding(&wf_req, wf)) + dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n", wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, - wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns); + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, ret_tohw); return ret_tohw; } @@ -341,10 +341,10 @@ static int __pwm_set_waveform(struct pwm_device *pwm, if (err) return err; - if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw == 0 && !pwm_check_rounding(wf, &wf_rounded)) - dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", + if (IS_ENABLED(CONFIG_PWM_DEBUG) && (ret_tohw == 0) != pwm_check_rounding(wf, &wf_rounded)) + dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n", wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, - wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns); + wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns, ret_tohw); if (exact && pwmwfcmp(wf, &wf_rounded)) { dev_dbg(&chip->dev, "Requested no rounding, but %llu/%llu [+%llu] -> %llu/%llu [+%llu]\n", From 29f1d5cac294dbc0e9ebb9d1fea4e2c1c1d2e5f4 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Apr 2025 09:31:46 +0200 Subject: [PATCH 18/39] pwm: pca9685: Use new GPIO line value setter callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250408-gpiochip-set-rv-pwm-v1-1-61e5c3358a74@linaro.org Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-pca9685.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 5162f3991644..eb03ccd5b688 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -263,12 +263,14 @@ static int pca9685_pwm_gpio_get(struct gpio_chip *gpio, unsigned int offset) return pca9685_pwm_get_duty(chip, offset) != 0; } -static void pca9685_pwm_gpio_set(struct gpio_chip *gpio, unsigned int offset, - int value) +static int pca9685_pwm_gpio_set(struct gpio_chip *gpio, unsigned int offset, + int value) { struct pwm_chip *chip = gpiochip_get_data(gpio); pca9685_pwm_set_duty(chip, offset, value ? PCA9685_COUNTER_RANGE : 0); + + return 0; } static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset) @@ -321,7 +323,7 @@ static int pca9685_pwm_gpio_probe(struct pwm_chip *chip) pca->gpio.direction_input = pca9685_pwm_gpio_direction_input; pca->gpio.direction_output = pca9685_pwm_gpio_direction_output; pca->gpio.get = pca9685_pwm_gpio_get; - pca->gpio.set = pca9685_pwm_gpio_set; + pca->gpio.set_rv = pca9685_pwm_gpio_set; pca->gpio.base = -1; pca->gpio.ngpio = PCA9685_MAXCHAN; pca->gpio.can_sleep = true; From dcb882bd436e2124e37640671cfa773dfaed485c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sat, 12 Apr 2025 14:21:24 +0200 Subject: [PATCH 19/39] pwm: loongson: Fix u32 overflow in waveform calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mul_u64_u64_div_u64() returns an u64 that might be bigger than U32_MAX. To properly handle this case it must not be directly assigned to an u32 value. Use a wider type for duty and period to make the idiom: duty = mul_u64_u64_div_u64(...) if (duty > U32_MAX) duty = U32_MAX; actually work as intended. Reported-by: Dan Carpenter Link: https://lore.kernel.org/r/44f3c764-8b65-49a9-b3ad-797e9fbb96f5@stanley.mountain Fixes: 2b62c89448dd ("pwm: Add Loongson PWM controller support") Signed-off-by: Uwe Kleine-König Reviewed-by: Binbin Zhou Link: https://lore.kernel.org/r/20250412122124.1636152-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-loongson.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-loongson.c b/drivers/pwm/pwm-loongson.c index 412c67739ef9..6392c4e34136 100644 --- a/drivers/pwm/pwm-loongson.c +++ b/drivers/pwm/pwm-loongson.c @@ -118,7 +118,7 @@ static int pwm_loongson_enable(struct pwm_chip *chip, struct pwm_device *pwm) static int pwm_loongson_config(struct pwm_chip *chip, struct pwm_device *pwm, u64 duty_ns, u64 period_ns) { - u32 duty, period; + u64 duty, period; struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); /* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */ From bd897149e40cb5c95f3d8546108035d1ea167db5 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 9 Apr 2025 14:00:20 +0300 Subject: [PATCH 20/39] pwm: loongson: Fix an error code in probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a copy and paste bug so we accidentally returned PTR_ERR(ddata->clk) instead of "ret". Fixes: 2b62c89448dd ("pwm: Add Loongson PWM controller support") Signed-off-by: Dan Carpenter Reviewed-by: Binbin Zhou Link: https://lore.kernel.org/r/6965a480-745c-426f-b17b-e96af532578f@stanley.mountain Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-loongson.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-loongson.c b/drivers/pwm/pwm-loongson.c index 6392c4e34136..1ba16168cbb4 100644 --- a/drivers/pwm/pwm-loongson.c +++ b/drivers/pwm/pwm-loongson.c @@ -211,7 +211,7 @@ static int pwm_loongson_probe(struct platform_device *pdev) if (ddata->clk) { ret = devm_clk_rate_exclusive_get(dev, ddata->clk); if (ret) - return dev_err_probe(dev, PTR_ERR(ddata->clk), + return dev_err_probe(dev, ret, "Failed to get exclusive rate\n"); ddata->clk_rate = clk_get_rate(ddata->clk); From e463b05d10da12b13d03f41a407e2ad043af158f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 8 Apr 2025 16:23:54 +0200 Subject: [PATCH 21/39] pwm: Better document return value of pwm_round_waveform_might_sleep() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Better explain how pwm_round_waveform_might_sleep() (and so the respective lowlevel driver callback) is supposed to round and the meaning of the return value. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/db84abf1e82e4498fc0e7c318d2673771d0039fe.1744120697.git.ukleinek@kernel.org [ukleinek: Fix a rst formatting issue reported by Stephen Rothwell] Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 59cc8792e312..079964961bd8 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -229,8 +229,12 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c * these two calls and the waveform determined by * pwm_round_waveform_might_sleep() cannot be implemented any more. * - * Returns 0 on success, 1 if there is no valid hardware configuration matching - * the input waveform under the PWM rounding rules or a negative errno. + * Usually all values passed in @wf are rounded down to the nearest possible + * value (in the order period_length_ns, duty_length_ns and then + * duty_offset_ns). Only if this isn't possible, a value might grow. + * + * Returns 0 on success, 1 if at least one value had to be rounded up or a + * negative errno. */ int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf) { From 9549d22684f19919f23576977830eca3d7b0cd90 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 26 Feb 2025 14:45:20 +0000 Subject: [PATCH 22/39] dt-bindings: pwm: Add RZ/G2L GPT binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add device tree bindings for the General PWM Timer (GPT). Reviewed-by: Rob Herring Signed-off-by: Biju Das Link: https://lore.kernel.org/r/20250226144531.176819-2-biju.das.jz@bp.renesas.com Signed-off-by: Uwe Kleine-König --- .../bindings/pwm/renesas,rzg2l-gpt.yaml | 378 ++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml diff --git a/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml b/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml new file mode 100644 index 000000000000..13b807765a30 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml @@ -0,0 +1,378 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/renesas,rzg2l-gpt.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/G2L General PWM Timer (GPT) + +maintainers: + - Biju Das + +description: | + RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit timer + (GPT32E). It supports the following functions + * 32 bits x 8 channels. + * Up-counting or down-counting (saw waves) or up/down-counting + (triangle waves) for each counter. + * Clock sources independently selectable for each channel. + * Two I/O pins per channel. + * Two output compare/input capture registers per channel. + * For the two output compare/input capture registers of each channel, + four registers are provided as buffer registers and are capable of + operating as comparison registers when buffering is not in use. + * In output compare operation, buffer switching can be at crests or + troughs, enabling the generation of laterally asymmetric PWM waveforms. + * Registers for setting up frame cycles in each channel (with capability + for generating interrupts at overflow or underflow) + * Generation of dead times in PWM operation. + * Synchronous starting, stopping and clearing counters for arbitrary + channels. + * Starting, stopping, clearing and up/down counters in response to input + level comparison. + * Starting, clearing, stopping and up/down counters in response to a + maximum of four external triggers. + * Output pin disable function by dead time error and detected + short-circuits between output pins. + * A/D converter start triggers can be generated (GPT32E0 to GPT32E3) + * Enables the noise filter for input capture and external trigger + operation. + + The below pwm channels are supported. + pwm0 - GPT32E0.GTIOC0A channel + pwm1 - GPT32E0.GTIOC0B channel + pwm2 - GPT32E1.GTIOC1A channel + pwm3 - GPT32E1.GTIOC1B channel + pwm4 - GPT32E2.GTIOC2A channel + pwm5 - GPT32E2.GTIOC2B channel + pwm6 - GPT32E3.GTIOC3A channel + pwm7 - GPT32E3.GTIOC3B channel + pwm8 - GPT32E4.GTIOC4A channel + pwm9 - GPT32E4.GTIOC4B channel + pwm10 - GPT32E5.GTIOC5A channel + pwm11 - GPT32E5.GTIOC5B channel + pwm12 - GPT32E6.GTIOC6A channel + pwm13 - GPT32E6.GTIOC6B channel + pwm14 - GPT32E7.GTIOC7A channel + pwm15 - GPT32E7.GTIOC7B channel + +properties: + compatible: + items: + - enum: + - renesas,r9a07g044-gpt # RZ/G2{L,LC} + - renesas,r9a07g054-gpt # RZ/V2L + - const: renesas,rzg2l-gpt + + reg: + maxItems: 1 + + '#pwm-cells': + const: 3 + + interrupts: + items: + - description: GPT32E0.GTCCRA input capture/compare match + - description: GPT32E0.GTCCRB input capture/compare + - description: GPT32E0.GTCCRC compare match + - description: GPT32E0.GTCCRD compare match + - description: GPT32E0.GTCCRE compare match + - description: GPT32E0.GTCCRF compare match + - description: GPT32E0.GTADTRA compare match + - description: GPT32E0.GTADTRB compare match + - description: GPT32E0.GTCNT overflow/GTPR compare match + - description: GPT32E0.GTCNT underflow + - description: GPT32E1.GTCCRA input capture/compare match + - description: GPT32E1.GTCCRB input capture/compare + - description: GPT32E1.GTCCRC compare match + - description: GPT32E1.GTCCRD compare match + - description: GPT32E1.GTCCRE compare match + - description: GPT32E1.GTCCRF compare match + - description: GPT32E1.GTADTRA compare match + - description: GPT32E1.GTADTRB compare match + - description: GPT32E1.GTCNT overflow/GTPR compare match + - description: GPT32E1.GTCNT underflow + - description: GPT32E2.GTCCRA input capture/compare match + - description: GPT32E2.GTCCRB input capture/compare + - description: GPT32E2.GTCCRC compare match + - description: GPT32E2.GTCCRD compare match + - description: GPT32E2.GTCCRE compare match + - description: GPT32E2.GTCCRF compare match + - description: GPT32E2.GTADTRA compare match + - description: GPT32E2.GTADTRB compare match + - description: GPT32E2.GTCNT overflow/GTPR compare match + - description: GPT32E2.GTCNT underflow + - description: GPT32E3.GTCCRA input capture/compare match + - description: GPT32E3.GTCCRB input capture/compare + - description: GPT32E3.GTCCRC compare match + - description: GPT32E3.GTCCRD compare match + - description: GPT32E3.GTCCRE compare match + - description: GPT32E3.GTCCRF compare match + - description: GPT32E3.GTADTRA compare match + - description: GPT32E3.GTADTRB compare match + - description: GPT32E3.GTCNT overflow/GTPR compare match + - description: GPT32E3.GTCNT underflow + - description: GPT32E4.GTCCRA input capture/compare match + - description: GPT32E4.GTCCRB input capture/compare + - description: GPT32E4.GTCCRC compare match + - description: GPT32E4.GTCCRD compare match + - description: GPT32E4.GTCCRE compare match + - description: GPT32E4.GTCCRF compare match + - description: GPT32E4.GTADTRA compare match + - description: GPT32E4.GTADTRB compare match + - description: GPT32E4.GTCNT overflow/GTPR compare match + - description: GPT32E4.GTCNT underflow + - description: GPT32E5.GTCCRA input capture/compare match + - description: GPT32E5.GTCCRB input capture/compare + - description: GPT32E5.GTCCRC compare match + - description: GPT32E5.GTCCRD compare match + - description: GPT32E5.GTCCRE compare match + - description: GPT32E5.GTCCRF compare match + - description: GPT32E5.GTADTRA compare match + - description: GPT32E5.GTADTRB compare match + - description: GPT32E5.GTCNT overflow/GTPR compare match + - description: GPT32E5.GTCNT underflow + - description: GPT32E6.GTCCRA input capture/compare match + - description: GPT32E6.GTCCRB input capture/compare + - description: GPT32E6.GTCCRC compare match + - description: GPT32E6.GTCCRD compare match + - description: GPT32E6.GTCCRE compare match + - description: GPT32E6.GTCCRF compare match + - description: GPT32E6.GTADTRA compare match + - description: GPT32E6.GTADTRB compare match + - description: GPT32E6.GTCNT overflow/GTPR compare match + - description: GPT32E6.GTCNT underflow + - description: GPT32E7.GTCCRA input capture/compare match + - description: GPT32E7.GTCCRB input capture/compare + - description: GPT32E7.GTCCRC compare match + - description: GPT32E7.GTCCRD compare match + - description: GPT32E7.GTCCRE compare match + - description: GPT32E7.GTCCRF compare match + - description: GPT32E7.GTADTRA compare match + - description: GPT32E7.GTADTRB compare match + - description: GPT32E7.GTCNT overflow/GTPR compare match + - description: GPT32E7.GTCNT underflow + + interrupt-names: + items: + - const: ccmpa0 + - const: ccmpb0 + - const: cmpc0 + - const: cmpd0 + - const: cmpe0 + - const: cmpf0 + - const: adtrga0 + - const: adtrgb0 + - const: ovf0 + - const: unf0 + - const: ccmpa1 + - const: ccmpb1 + - const: cmpc1 + - const: cmpd1 + - const: cmpe1 + - const: cmpf1 + - const: adtrga1 + - const: adtrgb1 + - const: ovf1 + - const: unf1 + - const: ccmpa2 + - const: ccmpb2 + - const: cmpc2 + - const: cmpd2 + - const: cmpe2 + - const: cmpf2 + - const: adtrga2 + - const: adtrgb2 + - const: ovf2 + - const: unf2 + - const: ccmpa3 + - const: ccmpb3 + - const: cmpc3 + - const: cmpd3 + - const: cmpe3 + - const: cmpf3 + - const: adtrga3 + - const: adtrgb3 + - const: ovf3 + - const: unf3 + - const: ccmpa4 + - const: ccmpb4 + - const: cmpc4 + - const: cmpd4 + - const: cmpe4 + - const: cmpf4 + - const: adtrga4 + - const: adtrgb4 + - const: ovf4 + - const: unf4 + - const: ccmpa5 + - const: ccmpb5 + - const: cmpc5 + - const: cmpd5 + - const: cmpe5 + - const: cmpf5 + - const: adtrga5 + - const: adtrgb5 + - const: ovf5 + - const: unf5 + - const: ccmpa6 + - const: ccmpb6 + - const: cmpc6 + - const: cmpd6 + - const: cmpe6 + - const: cmpf6 + - const: adtrga6 + - const: adtrgb6 + - const: ovf6 + - const: unf6 + - const: ccmpa7 + - const: ccmpb7 + - const: cmpc7 + - const: cmpd7 + - const: cmpe7 + - const: cmpf7 + - const: adtrga7 + - const: adtrgb7 + - const: ovf7 + - const: unf7 + + clocks: + maxItems: 1 + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - power-domains + - resets + +allOf: + - $ref: pwm.yaml# + +additionalProperties: false + +examples: + - | + #include + #include + + gpt: pwm@10048000 { + compatible = "renesas,r9a07g044-gpt", "renesas,rzg2l-gpt"; + reg = <0x10048000 0x800>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = "ccmpa0", "ccmpb0", "cmpc0", "cmpd0", + "cmpe0", "cmpf0", "adtrga0", "adtrgb0", + "ovf0", "unf0", + "ccmpa1", "ccmpb1", "cmpc1", "cmpd1", + "cmpe1", "cmpf1", "adtrga1", "adtrgb1", + "ovf1", "unf1", + "ccmpa2", "ccmpb2", "cmpc2", "cmpd2", + "cmpe2", "cmpf2", "adtrga2", "adtrgb2", + "ovf2", "unf2", + "ccmpa3", "ccmpb3", "cmpc3", "cmpd3", + "cmpe3", "cmpf3", "adtrga3", "adtrgb3", + "ovf3", "unf3", + "ccmpa4", "ccmpb4", "cmpc4", "cmpd4", + "cmpe4", "cmpf4", "adtrga4", "adtrgb4", + "ovf4", "unf4", + "ccmpa5", "ccmpb5", "cmpc5", "cmpd5", + "cmpe5", "cmpf5", "adtrga5", "adtrgb5", + "ovf5", "unf5", + "ccmpa6", "ccmpb6", "cmpc6", "cmpd6", + "cmpe6", "cmpf6", "adtrga6", "adtrgb6", + "ovf6", "unf6", + "ccmpa7", "ccmpb7", "cmpc7", "cmpd7", + "cmpe7", "cmpf7", "adtrga7", "adtrgb7", + "ovf7", "unf7"; + clocks = <&cpg CPG_MOD R9A07G044_GPT_PCLK>; + power-domains = <&cpg>; + resets = <&cpg R9A07G044_GPT_RST_C>; + #pwm-cells = <3>; + }; From 061f087f5d0bcae9f43ae0101121fcaa999d2809 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 26 Feb 2025 14:45:22 +0000 Subject: [PATCH 23/39] pwm: Add support for RZ/G2L GPT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit timer (GPT32E). It supports the following functions * 32 bits x 8 channels * Up-counting or down-counting (saw waves) or up/down-counting (triangle waves) for each counter. * Clock sources independently selectable for each channel * Two I/O pins per channel * Two output compare/input capture registers per channel * For the two output compare/input capture registers of each channel, four registers are provided as buffer registers and are capable of operating as comparison registers when buffering is not in use. * In output compare operation, buffer switching can be at crests or troughs, enabling the generation of laterally asymmetric PWM waveforms. * Registers for setting up frame cycles in each channel (with capability for generating interrupts at overflow or underflow) * Generation of dead times in PWM operation * Synchronous starting, stopping and clearing counters for arbitrary channels * Starting, stopping, clearing and up/down counters in response to input level comparison * Starting, clearing, stopping and up/down counters in response to a maximum of four external triggers * Output pin disable function by dead time error and detected short-circuits between output pins * A/D converter start triggers can be generated (GPT32E0 to GPT32E3) * Enables the noise filter for input capture and external trigger operation Add basic pwm support for RZ/G2L GPT driver by creating separate logical channels for each IOs. Signed-off-by: Biju Das Link: https://lore.kernel.org/r/20250226144531.176819-4-biju.das.jz@bp.renesas.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/Kconfig | 11 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-rzg2l-gpt.c | 443 ++++++++++++++++++++++++++++++++++++ 3 files changed, 455 insertions(+) create mode 100644 drivers/pwm/pwm-rzg2l-gpt.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index ead63f9d0c73..a888ac0b15fe 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -552,6 +552,17 @@ config PWM_ROCKCHIP Generic PWM framework driver for the PWM controller found on Rockchip SoCs. +config PWM_RZG2L_GPT + tristate "Renesas RZ/G2L General PWM Timer support" + depends on ARCH_RZG2L || COMPILE_TEST + depends on HAS_IOMEM + help + This driver exposes the General PWM Timer controller found in Renesas + RZ/G2L like chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-rzg2l-gpt. + config PWM_RZ_MTU3 tristate "Renesas RZ/G2L MTU3a PWM Timer support" depends on RZ_MTU3 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index e52d0940b247..adffe7010f74 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o +obj-$(CONFIG_PWM_RZG2L_GPT) += pwm-rzg2l-gpt.o obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c new file mode 100644 index 000000000000..2ddbb13f50aa --- /dev/null +++ b/drivers/pwm/pwm-rzg2l-gpt.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G2L General PWM Timer (GPT) driver + * + * Copyright (C) 2025 Renesas Electronics Corporation + * + * Hardware manual for this IP can be found here + * https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-users-manual-hardware-0?language=en + * + * Limitations: + * - Counter must be stopped before modifying Mode and Prescaler. + * - When PWM is disabled, the output is driven to inactive. + * - While the hardware supports both polarities, the driver (for now) + * only handles normal polarity. + * - General PWM Timer (GPT) has 8 HW channels for PWM operations and + * each HW channel have 2 IOs. + * - Each IO is modelled as an independent PWM channel. + * - When both channels are used, disabling the channel on one stops the + * other. + * - When both channels are used, the period of both IOs in the HW channel + * must be same (for now). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RZG2L_GET_CH(hwpwm) ((hwpwm) / 2) +#define RZG2L_GET_CH_OFFS(ch) (0x100 * (ch)) + +#define RZG2L_GTCR(ch) (0x2c + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTUDDTYC(ch) (0x30 + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTIOR(ch) (0x34 + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTBER(ch) (0x40 + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTCNT(ch) (0x48 + RZG2L_GET_CH_OFFS(ch)) +#define RZG2L_GTCCR(ch, sub_ch) (0x4c + RZG2L_GET_CH_OFFS(ch) + 4 * (sub_ch)) +#define RZG2L_GTPR(ch) (0x64 + RZG2L_GET_CH_OFFS(ch)) + +#define RZG2L_GTCR_CST BIT(0) +#define RZG2L_GTCR_MD GENMASK(18, 16) +#define RZG2L_GTCR_TPCS GENMASK(26, 24) + +#define RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE FIELD_PREP(RZG2L_GTCR_MD, 0) + +#define RZG2L_GTUDDTYC_UP BIT(0) +#define RZG2L_GTUDDTYC_UDF BIT(1) +#define RZG2L_GTUDDTYC_UP_COUNTING (RZG2L_GTUDDTYC_UP | RZG2L_GTUDDTYC_UDF) + +#define RZG2L_GTIOR_GTIOA GENMASK(4, 0) +#define RZG2L_GTIOR_GTIOB GENMASK(20, 16) +#define RZG2L_GTIOR_GTIOx(sub_ch) ((sub_ch) ? RZG2L_GTIOR_GTIOB : RZG2L_GTIOR_GTIOA) +#define RZG2L_GTIOR_OAE BIT(8) +#define RZG2L_GTIOR_OBE BIT(24) +#define RZG2L_GTIOR_OxE(sub_ch) ((sub_ch) ? RZG2L_GTIOR_OBE : RZG2L_GTIOR_OAE) + +#define RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE 0x1b +#define RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH \ + (RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE | RZG2L_GTIOR_OAE) +#define RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH \ + (FIELD_PREP(RZG2L_GTIOR_GTIOB, RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE) | RZG2L_GTIOR_OBE) + +#define RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(sub_ch) \ + ((sub_ch) ? RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH : \ + RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH) + +#define RZG2L_MAX_HW_CHANNELS 8 +#define RZG2L_CHANNELS_PER_IO 2 +#define RZG2L_MAX_PWM_CHANNELS (RZG2L_MAX_HW_CHANNELS * RZG2L_CHANNELS_PER_IO) +#define RZG2L_MAX_SCALE_FACTOR 1024 +#define RZG2L_MAX_TICKS ((u64)U32_MAX * RZG2L_MAX_SCALE_FACTOR) + +struct rzg2l_gpt_chip { + void __iomem *mmio; + struct mutex lock; /* lock to protect shared channel resources */ + unsigned long rate_khz; + u32 period_ticks[RZG2L_MAX_HW_CHANNELS]; + u32 channel_request_count[RZG2L_MAX_HW_CHANNELS]; + u32 channel_enable_count[RZG2L_MAX_HW_CHANNELS]; +}; + +static inline struct rzg2l_gpt_chip *to_rzg2l_gpt_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static inline unsigned int rzg2l_gpt_subchannel(unsigned int hwpwm) +{ + return hwpwm & 0x1; +} + +static void rzg2l_gpt_write(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 data) +{ + writel(data, rzg2l_gpt->mmio + reg); +} + +static u32 rzg2l_gpt_read(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg) +{ + return readl(rzg2l_gpt->mmio + reg); +} + +static void rzg2l_gpt_modify(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 clr, + u32 set) +{ + rzg2l_gpt_write(rzg2l_gpt, reg, + (rzg2l_gpt_read(rzg2l_gpt, reg) & ~clr) | set); +} + +static u8 rzg2l_gpt_calculate_prescale(struct rzg2l_gpt_chip *rzg2l_gpt, + u64 period_ticks) +{ + u32 prescaled_period_ticks; + u8 prescale; + + prescaled_period_ticks = period_ticks >> 32; + if (prescaled_period_ticks >= 256) + prescale = 5; + else + prescale = (fls(prescaled_period_ticks) + 1) / 2; + + return prescale; +} + +static int rzg2l_gpt_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + u32 ch = RZG2L_GET_CH(pwm->hwpwm); + + guard(mutex)(&rzg2l_gpt->lock); + rzg2l_gpt->channel_request_count[ch]++; + + return 0; +} + +static void rzg2l_gpt_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + u32 ch = RZG2L_GET_CH(pwm->hwpwm); + + guard(mutex)(&rzg2l_gpt->lock); + rzg2l_gpt->channel_request_count[ch]--; +} + +static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm) +{ + u8 ch = RZG2L_GET_CH(hwpwm); + u32 val; + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCR(ch)); + if (!(val & RZG2L_GTCR_CST)) + return false; + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTIOR(ch)); + + return val & RZG2L_GTIOR_OxE(rzg2l_gpt_subchannel(hwpwm)); +} + +/* Caller holds the lock while calling rzg2l_gpt_enable() */ +static void rzg2l_gpt_enable(struct rzg2l_gpt_chip *rzg2l_gpt, + struct pwm_device *pwm) +{ + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm); + u32 val = RZG2L_GTIOR_GTIOx(sub_ch) | RZG2L_GTIOR_OxE(sub_ch); + u8 ch = RZG2L_GET_CH(pwm->hwpwm); + + /* Enable pin output */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTIOR(ch), val, + RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(sub_ch)); + + if (!rzg2l_gpt->channel_enable_count[ch]) + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), 0, RZG2L_GTCR_CST); + + rzg2l_gpt->channel_enable_count[ch]++; +} + +/* Caller holds the lock while calling rzg2l_gpt_disable() */ +static void rzg2l_gpt_disable(struct rzg2l_gpt_chip *rzg2l_gpt, + struct pwm_device *pwm) +{ + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm); + u8 ch = RZG2L_GET_CH(pwm->hwpwm); + + /* Stop count, Output low on GTIOCx pin when counting stops */ + rzg2l_gpt->channel_enable_count[ch]--; + + if (!rzg2l_gpt->channel_enable_count[ch]) + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_CST, 0); + + /* Disable pin output */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTIOR(ch), RZG2L_GTIOR_OxE(sub_ch), 0); +} + +static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt, + u32 val, u8 prescale) +{ + u64 tmp; + + /* + * The calculation doesn't overflow an u64 because prescale ≤ 5 and so + * tmp = val << (2 * prescale) * USEC_PER_SEC + * < 2^32 * 2^10 * 10^6 + * < 2^32 * 2^10 * 2^20 + * = 2^62 + */ + tmp = (u64)val << (2 * prescale); + tmp *= USEC_PER_SEC; + + return DIV64_U64_ROUND_UP(tmp, rzg2l_gpt->rate_khz); +} + +static int rzg2l_gpt_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + + state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm); + if (state->enabled) { + u32 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm); + u32 ch = RZG2L_GET_CH(pwm->hwpwm); + u8 prescale; + u32 val; + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCR(ch)); + prescale = FIELD_GET(RZG2L_GTCR_TPCS, val); + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch)); + state->period = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale); + + val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch)); + state->duty_cycle = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale); + if (state->duty_cycle > state->period) + state->duty_cycle = state->period; + } + + state->polarity = PWM_POLARITY_NORMAL; + + return 0; +} + +static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale) +{ + return min_t(u64, DIV_ROUND_DOWN_ULL(period_or_duty_cycle, 1 << (2 * prescale)), + U32_MAX); +} + +/* Caller holds the lock while calling rzg2l_gpt_config() */ +static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm); + u8 ch = RZG2L_GET_CH(pwm->hwpwm); + u64 period_ticks, duty_ticks; + unsigned long pv, dc; + u8 prescale; + + /* Limit period/duty cycle to max value supported by the HW */ + period_ticks = mul_u64_u64_div_u64(state->period, rzg2l_gpt->rate_khz, USEC_PER_SEC); + if (period_ticks > RZG2L_MAX_TICKS) + period_ticks = RZG2L_MAX_TICKS; + /* + * GPT counter is shared by the two IOs of a single channel, so + * prescale and period can NOT be modified when there are multiple IOs + * in use with different settings. + */ + if (rzg2l_gpt->channel_request_count[ch] > 1 && period_ticks != rzg2l_gpt->period_ticks[ch]) + return -EBUSY; + + prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_ticks); + pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale); + + duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC); + if (duty_ticks > RZG2L_MAX_TICKS) + duty_ticks = RZG2L_MAX_TICKS; + dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale); + + /* + * GPT counter is shared by multiple channels, we cache the period ticks + * from the first enabled channel and use the same value for both + * channels. + */ + rzg2l_gpt->period_ticks[ch] = period_ticks; + + /* + * Counter must be stopped before modifying mode, prescaler, timer + * counter and buffer enable registers. These registers are shared + * between both channels. So allow updating these registers only for the + * first enabled channel. + */ + if (rzg2l_gpt->channel_enable_count[ch] <= 1) { + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_CST, 0); + + /* GPT set operating mode (saw-wave up-counting) */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_MD, + RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE); + + /* Set count direction */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTUDDTYC(ch), RZG2L_GTUDDTYC_UP_COUNTING); + + /* Select count clock */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS, + FIELD_PREP(RZG2L_GTCR_TPCS, prescale)); + + /* Set period */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTPR(ch), pv); + } + + /* Set duty cycle */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch), dc); + + if (rzg2l_gpt->channel_enable_count[ch] <= 1) { + /* Set initial value for counter */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCNT(ch), 0); + + /* Set no buffer operation */ + rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTBER(ch), 0); + + /* Restart the counter after updating the registers */ + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), + RZG2L_GTCR_CST, RZG2L_GTCR_CST); + } + + return 0; +} + +static int rzg2l_gpt_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip); + bool enabled = pwm->state.enabled; + int ret; + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + guard(mutex)(&rzg2l_gpt->lock); + if (!state->enabled) { + if (enabled) + rzg2l_gpt_disable(rzg2l_gpt, pwm); + + return 0; + } + + ret = rzg2l_gpt_config(chip, pwm, state); + if (!ret && !enabled) + rzg2l_gpt_enable(rzg2l_gpt, pwm); + + return ret; +} + +static const struct pwm_ops rzg2l_gpt_ops = { + .request = rzg2l_gpt_request, + .free = rzg2l_gpt_free, + .get_state = rzg2l_gpt_get_state, + .apply = rzg2l_gpt_apply, +}; + +static int rzg2l_gpt_probe(struct platform_device *pdev) +{ + struct rzg2l_gpt_chip *rzg2l_gpt; + struct device *dev = &pdev->dev; + struct reset_control *rstc; + struct pwm_chip *chip; + unsigned long rate; + struct clk *clk; + int ret; + + chip = devm_pwmchip_alloc(dev, RZG2L_MAX_PWM_CHANNELS, sizeof(*rzg2l_gpt)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + rzg2l_gpt = to_rzg2l_gpt_chip(chip); + + rzg2l_gpt->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rzg2l_gpt->mmio)) + return PTR_ERR(rzg2l_gpt->mmio); + + rstc = devm_reset_control_get_exclusive_deasserted(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), "Cannot deassert reset control\n"); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Cannot get clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, clk); + if (ret) + return ret; + + rate = clk_get_rate(clk); + if (!rate) + return dev_err_probe(dev, -EINVAL, "The gpt clk rate is 0"); + + /* + * Refuse clk rates > 1 GHz to prevent overflow later for computing + * period and duty cycle. + */ + if (rate > NSEC_PER_SEC) + return dev_err_probe(dev, -EINVAL, "The gpt clk rate is > 1GHz"); + + /* + * Rate is in MHz and is always integer for peripheral clk + * 2^32 * 2^10 (prescalar) * 10^6 (rate_khz) < 2^64 + * So make sure rate is multiple of 1000. + */ + rzg2l_gpt->rate_khz = rate / KILO; + if (rzg2l_gpt->rate_khz * KILO != rate) + return dev_err_probe(dev, -EINVAL, "Rate is not multiple of 1000"); + + mutex_init(&rzg2l_gpt->lock); + + chip->ops = &rzg2l_gpt_ops; + ret = devm_pwmchip_add(dev, chip); + if (ret) + return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + + return 0; +} + +static const struct of_device_id rzg2l_gpt_of_table[] = { + { .compatible = "renesas,rzg2l-gpt", }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rzg2l_gpt_of_table); + +static struct platform_driver rzg2l_gpt_driver = { + .driver = { + .name = "pwm-rzg2l-gpt", + .of_match_table = rzg2l_gpt_of_table, + }, + .probe = rzg2l_gpt_probe, +}; +module_platform_driver(rzg2l_gpt_driver); + +MODULE_AUTHOR("Biju Das "); +MODULE_DESCRIPTION("Renesas RZ/G2L General PWM Timer (GPT) Driver"); +MODULE_LICENSE("GPL"); From 7f8ce4d88b42fcbd3350370ec4d02e00979fc5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Thu, 17 Apr 2025 20:16:11 +0200 Subject: [PATCH 24/39] pwm: Fix various formatting issues in kernel-doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Return and (where interesting) Context sections, fix some formatting and drop documenting the internal function __pwm_apply(). Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20250417181611.2693599-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 42 +++++++++++++++++++++++++++--------------- include/linux/pwm.h | 8 +++++--- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 079964961bd8..e0a90c4cd723 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -216,14 +216,14 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c * * Typically a given waveform cannot be implemented exactly by hardware, e.g. * because hardware only supports coarse period resolution or no duty_offset. - * This function returns the actually implemented waveform if you pass wf to - * pwm_set_waveform_might_sleep now. + * This function returns the actually implemented waveform if you pass @wf to + * pwm_set_waveform_might_sleep() now. * * Note however that the world doesn't stop turning when you call it, so when - * doing + * doing:: * - * pwm_round_waveform_might_sleep(mypwm, &wf); - * pwm_set_waveform_might_sleep(mypwm, &wf, true); + * pwm_round_waveform_might_sleep(mypwm, &wf); + * pwm_set_waveform_might_sleep(mypwm, &wf, true); * * the latter might fail, e.g. because an input clock changed its rate between * these two calls and the waveform determined by @@ -233,8 +233,9 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c * value (in the order period_length_ns, duty_length_ns and then * duty_offset_ns). Only if this isn't possible, a value might grow. * - * Returns 0 on success, 1 if at least one value had to be rounded up or a + * Returns: 0 on success, 1 if at least one value had to be rounded up or a * negative errno. + * Context: May sleep. */ int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf) { @@ -291,6 +292,9 @@ EXPORT_SYMBOL_GPL(pwm_round_waveform_might_sleep); * * Stores the current configuration of the PWM in @wf. Note this is the * equivalent of pwm_get_state_hw() (and not pwm_get_state()) for pwm_waveform. + * + * Returns: 0 on success or a negative errno + * Context: May sleep. */ int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf) { @@ -399,13 +403,17 @@ static int __pwm_set_waveform(struct pwm_device *pwm, * * Typically a requested waveform cannot be implemented exactly, e.g. because * you requested .period_length_ns = 100 ns, but the hardware can only set - * periods that are a multiple of 8.5 ns. With that hardware passing exact = + * periods that are a multiple of 8.5 ns. With that hardware passing @exact = * true results in pwm_set_waveform_might_sleep() failing and returning 1. If - * exact = false you get a period of 93.5 ns (i.e. the biggest period not bigger + * @exact = false you get a period of 93.5 ns (i.e. the biggest period not bigger * than the requested value). - * Note that even with exact = true, some rounding by less than 1 is + * Note that even with @exact = true, some rounding by less than 1 ns is * possible/needed. In the above example requesting .period_length_ns = 94 and - * exact = true, you get the hardware configured with period = 93.5 ns. + * @exact = true, you get the hardware configured with period = 93.5 ns. + * + * Returns: 0 on success, 1 if was rounded up (if !@exact) or no perfect match was + * possible (if @exact), or a negative errno + * Context: May sleep. */ int pwm_set_waveform_might_sleep(struct pwm_device *pwm, const struct pwm_waveform *wf, bool exact) @@ -565,11 +573,6 @@ static bool pwm_state_valid(const struct pwm_state *state) return true; } -/** - * __pwm_apply() - atomically apply a new state to a PWM device - * @pwm: PWM device - * @state: new state to apply - */ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) { struct pwm_chip *chip; @@ -678,6 +681,9 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) * Cannot be used in atomic context. * @pwm: PWM device * @state: new state to apply + * + * Returns: 0 on success, or a negative errno + * Context: May sleep. */ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state) { @@ -719,6 +725,9 @@ EXPORT_SYMBOL_GPL(pwm_apply_might_sleep); * Not all PWM devices support this function, check with pwm_might_sleep(). * @pwm: PWM device * @state: new state to apply + * + * Returns: 0 on success, or a negative errno + * Context: Any */ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state) { @@ -792,6 +801,9 @@ EXPORT_SYMBOL_GPL(pwm_get_state_hw); * This function will adjust the PWM config to the PWM arguments provided * by the DT or PWM lookup table. This is particularly useful to adapt * the bootloader config to the Linux one. + * + * Returns: 0 on success or a negative error code on failure. + * Context: May sleep. */ int pwm_adjust_config(struct pwm_device *pwm) { diff --git a/include/linux/pwm.h b/include/linux/pwm.h index bf0469b2201d..63a17d2b4ec8 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -218,6 +218,8 @@ static inline void pwm_init_state(const struct pwm_device *pwm, * * pwm_get_state(pwm, &state); * duty = pwm_get_relative_duty_cycle(&state, 100); + * + * Returns: rounded relative duty cycle multiplied by @scale */ static inline unsigned int pwm_get_relative_duty_cycle(const struct pwm_state *state, unsigned int scale) @@ -244,8 +246,8 @@ pwm_get_relative_duty_cycle(const struct pwm_state *state, unsigned int scale) * pwm_set_relative_duty_cycle(&state, 50, 100); * pwm_apply_might_sleep(pwm, &state); * - * This functions returns -EINVAL if @duty_cycle and/or @scale are - * inconsistent (@scale == 0 or @duty_cycle > @scale). + * Returns: 0 on success or ``-EINVAL`` if @duty_cycle and/or @scale are + * inconsistent (@scale == 0 or @duty_cycle > @scale) */ static inline int pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle, @@ -351,7 +353,7 @@ struct pwm_chip { * pwmchip_supports_waveform() - checks if the given chip supports waveform callbacks * @chip: The pwm_chip to test * - * Returns true iff the pwm chip support the waveform functions like + * Returns: true iff the pwm chip support the waveform functions like * pwm_set_waveform_might_sleep() and pwm_round_waveform_might_sleep() */ static inline bool pwmchip_supports_waveform(struct pwm_chip *chip) From 769aa27801d2086c814c9e2b4ecba340b66f70bc Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 16 Apr 2025 14:02:53 +0200 Subject: [PATCH 25/39] dt-bindings: pwm: mediatek,pwm-disp: Add compatible for MT6893 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a compatible string for the Display Controller PWM IP found in the MediaTek Dimensity 1200 (MT6893) SoC, which is compatible with the one found in MT8183. Signed-off-by: AngeloGioacchino Del Regno Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250416120253.147977-1-angelogioacchino.delregno@collabora.com Signed-off-by: Uwe Kleine-König --- Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml b/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml index 195e4371196b..68ef30414325 100644 --- a/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml +++ b/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml @@ -27,6 +27,7 @@ properties: - const: mediatek,mt8173-disp-pwm - items: - enum: + - mediatek,mt6893-disp-pwm - mediatek,mt8186-disp-pwm - mediatek,mt8188-disp-pwm - mediatek,mt8192-disp-pwm From b6b5683e9692b7ea2d4ca875802ec00a2b78dd66 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Fri, 18 Apr 2025 18:55:07 +0400 Subject: [PATCH 26/39] dt-bindings: pwm: vt8500-pwm: Convert to YAML MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite the textual description for the WonderMedia PWM controller as YAML schema, and switch the filename to follow the compatible string. Signed-off-by: Alexey Charkov Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250418-via_pwm_binding-v2-1-17545f4d719e@gmail.com Signed-off-by: Uwe Kleine-König --- .../bindings/pwm/via,vt8500-pwm.yaml | 43 +++++++++++++++++++ .../devicetree/bindings/pwm/vt8500-pwm.txt | 18 -------- MAINTAINERS | 1 + 3 files changed, 44 insertions(+), 18 deletions(-) create mode 100644 Documentation/devicetree/bindings/pwm/via,vt8500-pwm.yaml delete mode 100644 Documentation/devicetree/bindings/pwm/vt8500-pwm.txt diff --git a/Documentation/devicetree/bindings/pwm/via,vt8500-pwm.yaml b/Documentation/devicetree/bindings/pwm/via,vt8500-pwm.yaml new file mode 100644 index 000000000000..d9146ad715ba --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/via,vt8500-pwm.yaml @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/via,vt8500-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: VIA/Wondermedia VT8500/WM8xxx series SoC PWM controller + +maintainers: + - Alexey Charkov + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + items: + - const: via,vt8500-pwm + + reg: + maxItems: 1 + + '#pwm-cells': + const: 3 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - clocks + +additionalProperties: false + +examples: + - | + pwm1: pwm@d8220000 { + compatible = "via,vt8500-pwm"; + reg = <0xd8220000 0x1000>; + #pwm-cells = <3>; + clocks = <&clkpwm>; + }; diff --git a/Documentation/devicetree/bindings/pwm/vt8500-pwm.txt b/Documentation/devicetree/bindings/pwm/vt8500-pwm.txt deleted file mode 100644 index 4fba93ce1985..000000000000 --- a/Documentation/devicetree/bindings/pwm/vt8500-pwm.txt +++ /dev/null @@ -1,18 +0,0 @@ -VIA/Wondermedia VT8500/WM8xxx series SoC PWM controller - -Required properties: -- compatible: should be "via,vt8500-pwm" -- reg: physical base address and length of the controller's registers -- #pwm-cells: should be 3. See pwm.yaml in this directory for a description of - the cells format. The only third cell flag supported by this binding is - PWM_POLARITY_INVERTED. -- clocks: phandle to the PWM source clock - -Example: - -pwm1: pwm@d8220000 { - #pwm-cells = <3>; - compatible = "via,vt8500-pwm"; - reg = <0xd8220000 0x1000>; - clocks = <&clkpwm>; -}; diff --git a/MAINTAINERS b/MAINTAINERS index 571f56ba33b0..26ef29a0c9bf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3428,6 +3428,7 @@ M: Krzysztof Kozlowski L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Odd Fixes F: Documentation/devicetree/bindings/i2c/i2c-wmt.txt +F: Documentation/devicetree/bindings/pwm/via,vt8500-pwm.yaml F: arch/arm/boot/dts/vt8500/ F: arch/arm/mach-vt8500/ F: drivers/clocksource/timer-vt8500.c From e373991eb9ff0a9617634017c7f19fd36ec4f208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 23 Apr 2025 11:57:15 +0200 Subject: [PATCH 27/39] pwm: rzg2l-gpt: Accept requests for too high period length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The period setting is shared for each pair of PWM channels. So if the twin channel is in use, the period must not be changed. According to the usual practise to pick the next smaller possible period, accept a request for a period that is bigger than the unchangable value. Signed-off-by: Uwe Kleine-König Tested-by: Biju Das Link: https://lore.kernel.org/r/20250423095715.2952692-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-rzg2l-gpt.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c index 2ddbb13f50aa..360c8bf3b190 100644 --- a/drivers/pwm/pwm-rzg2l-gpt.c +++ b/drivers/pwm/pwm-rzg2l-gpt.c @@ -270,15 +270,19 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm, * prescale and period can NOT be modified when there are multiple IOs * in use with different settings. */ - if (rzg2l_gpt->channel_request_count[ch] > 1 && period_ticks != rzg2l_gpt->period_ticks[ch]) - return -EBUSY; + if (rzg2l_gpt->channel_request_count[ch] > 1) { + if (period_ticks < rzg2l_gpt->period_ticks[ch]) + return -EBUSY; + else + period_ticks = rzg2l_gpt->period_ticks[ch]; + } prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_ticks); pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale); duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC); - if (duty_ticks > RZG2L_MAX_TICKS) - duty_ticks = RZG2L_MAX_TICKS; + if (duty_ticks > period_ticks) + duty_ticks = period_ticks; dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale); /* From 8b872a912aa10327a88fdea46cc7b30cae0c9ed4 Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Mon, 7 Apr 2025 13:21:51 +0200 Subject: [PATCH 28/39] dt-bindings: pwm: add support for MC33XS2410 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding documentation for NXPs MC33XS2410 high side switch. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Dimitri Fedrau Link: https://lore.kernel.org/r/20250407-mc33xs2410-v9-1-57adcb56a6e4@liebherr.com Signed-off-by: Uwe Kleine-König --- .../bindings/pwm/nxp,mc33xs2410.yaml | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/nxp,mc33xs2410.yaml diff --git a/Documentation/devicetree/bindings/pwm/nxp,mc33xs2410.yaml b/Documentation/devicetree/bindings/pwm/nxp,mc33xs2410.yaml new file mode 100644 index 000000000000..1729fe5c3dfb --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/nxp,mc33xs2410.yaml @@ -0,0 +1,118 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/nxp,mc33xs2410.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: High-side switch MC33XS2410 + +maintainers: + - Dimitri Fedrau + +allOf: + - $ref: pwm.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + const: nxp,mc33xs2410 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 10000000 + + spi-cpha: true + + spi-cs-setup-delay-ns: + minimum: 100 + default: 100 + + spi-cs-hold-delay-ns: + minimum: 10 + default: 10 + + spi-cs-inactive-delay-ns: + minimum: 300 + default: 300 + + reset-gpios: + description: + GPIO connected to the active low reset pin. + maxItems: 1 + + "#pwm-cells": + const: 3 + + pwm-names: + items: + - const: di0 + - const: di1 + - const: di2 + - const: di3 + + pwms: + description: + Direct inputs(di0-3) are used to directly turn-on or turn-off the + outputs. + maxItems: 4 + + interrupts: + maxItems: 1 + + clocks: + description: + The external clock can be used if the internal clock doesn't meet + timing requirements over temperature and voltage operating range. + maxItems: 1 + + vdd-supply: + description: + Logic supply voltage + + vspi-supply: + description: + Supply voltage for SPI + + vpwr-supply: + description: + Power switch supply + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + #include + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + + pwm@0 { + compatible = "nxp,mc33xs2410"; + reg = <0x0>; + spi-max-frequency = <4000000>; + spi-cpha; + spi-cs-setup-delay-ns = <100>; + spi-cs-hold-delay-ns = <10>; + spi-cs-inactive-delay-ns = <300>; + reset-gpios = <&gpio3 22 GPIO_ACTIVE_LOW>; + #pwm-cells = <3>; + pwm-names = "di0", "di1", "di2", "di3"; + pwms = <&pwm0 0 1000000>, + <&pwm1 0 1000000>, + <&pwm2 0 1000000>, + <&pwm3 0 1000000>; + interrupt-parent = <&gpio0>; + interrupts = <31 IRQ_TYPE_LEVEL_LOW>; + clocks = <&clk_ext_fixed>; + vdd-supply = <®_3v3>; + vspi-supply = <®_3v3>; + vpwr-supply = <®_24v0>; + }; + }; From 2006016ec6b3a18f2659704cb2f70ad3387f4a62 Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Mon, 7 Apr 2025 13:21:52 +0200 Subject: [PATCH 29/39] pwm: add support for NXPs high-side switch MC33XS2410 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MC33XS2410 is a four channel high-side switch. Featuring advanced monitoring and control function, the device is operational from 3.0 V to 60 V. The device is controlled by SPI port for configuration. Signed-off-by: Dimitri Fedrau Link: https://lore.kernel.org/r/20250407-mc33xs2410-v9-2-57adcb56a6e4@liebherr.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/Kconfig | 12 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-mc33xs2410.c | 391 +++++++++++++++++++++++++++++++++++ 3 files changed, 404 insertions(+) create mode 100644 drivers/pwm/pwm-mc33xs2410.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a888ac0b15fe..6faa8b2ec0a4 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -423,6 +423,18 @@ config PWM_LPSS_PLATFORM To compile this driver as a module, choose M here: the module will be called pwm-lpss-platform. +config PWM_MC33XS2410 + tristate "MC33XS2410 PWM support" + depends on OF + depends on SPI + help + NXP MC33XS2410 high-side switch driver. The MC33XS2410 is a four + channel high-side switch. The device is operational from 3.0 V + to 60 V. The device is controlled by SPI port for configuration. + + To compile this driver as a module, choose M here: the module + will be called pwm-mc33xs2410. + config PWM_MESON tristate "Amlogic Meson PWM driver" depends on ARCH_MESON || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index adffe7010f74..9742fc196f07 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o +obj-$(CONFIG_PWM_MC33XS2410) += pwm-mc33xs2410.o obj-$(CONFIG_PWM_MESON) += pwm-meson.o obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o diff --git a/drivers/pwm/pwm-mc33xs2410.c b/drivers/pwm/pwm-mc33xs2410.c new file mode 100644 index 000000000000..a1ac3445ccdb --- /dev/null +++ b/drivers/pwm/pwm-mc33xs2410.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH + * + * Reference Manual : https://www.nxp.com/docs/en/data-sheet/MC33XS2410.pdf + * + * Limitations: + * - Supports frequencies between 0.5Hz and 2048Hz with following steps: + * - 0.5 Hz steps from 0.5 Hz to 32 Hz + * - 2 Hz steps from 2 Hz to 128 Hz + * - 8 Hz steps from 8 Hz to 512 Hz + * - 32 Hz steps from 32 Hz to 2048 Hz + * - Cannot generate a 0 % duty cycle. + * - Always produces low output if disabled. + * - Configuration isn't atomic. When changing polarity, duty cycle or period + * the data is taken immediately, counters not being affected, resulting in a + * behavior of the output pin that is neither the old nor the new state, + * rather something in between. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MC33XS2410_GLB_CTRL 0x00 +#define MC33XS2410_GLB_CTRL_MODE GENMASK(7, 6) +#define MC33XS2410_GLB_CTRL_MODE_NORMAL FIELD_PREP(MC33XS2410_GLB_CTRL_MODE, 1) + +#define MC33XS2410_PWM_CTRL1 0x05 +/* chan in { 1 ... 4 } */ +#define MC33XS2410_PWM_CTRL1_POL_INV(chan) BIT((chan) + 1) + +#define MC33XS2410_PWM_CTRL3 0x07 +/* chan in { 1 ... 4 } */ +#define MC33XS2410_PWM_CTRL3_EN(chan) BIT(4 + (chan) - 1) + +/* chan in { 1 ... 4 } */ +#define MC33XS2410_PWM_FREQ(chan) (0x08 + (chan) - 1) +#define MC33XS2410_PWM_FREQ_STEP GENMASK(7, 6) +#define MC33XS2410_PWM_FREQ_COUNT GENMASK(5, 0) + +/* chan in { 1 ... 4 } */ +#define MC33XS2410_PWM_DC(chan) (0x0c + (chan) - 1) + +#define MC33XS2410_WDT 0x14 + +#define MC33XS2410_PWM_MIN_PERIOD 488282 +/* step in { 0 ... 3 } */ +#define MC33XS2410_PWM_MAX_PERIOD(step) (2000000000 >> (2 * (step))) + +#define MC33XS2410_FRAME_IN_ADDR GENMASK(15, 8) +#define MC33XS2410_FRAME_IN_DATA GENMASK(7, 0) +#define MC33XS2410_FRAME_IN_ADDR_WR BIT(7) +#define MC33XS2410_FRAME_IN_DATA_RD BIT(7) +#define MC33XS2410_FRAME_OUT_DATA GENMASK(13, 0) + +#define MC33XS2410_MAX_TRANSFERS 5 + +static int mc33xs2410_write_regs(struct spi_device *spi, u8 *reg, u8 *val, + unsigned int len) +{ + u16 tx[MC33XS2410_MAX_TRANSFERS]; + int i; + + if (len > MC33XS2410_MAX_TRANSFERS) + return -EINVAL; + + for (i = 0; i < len; i++) + tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, val[i]) | + FIELD_PREP(MC33XS2410_FRAME_IN_ADDR, + MC33XS2410_FRAME_IN_ADDR_WR | reg[i]); + + return spi_write(spi, tx, len * 2); +} + +static int mc33xs2410_read_regs(struct spi_device *spi, u8 *reg, u8 flag, + u16 *val, unsigned int len) +{ + u16 tx[MC33XS2410_MAX_TRANSFERS]; + u16 rx[MC33XS2410_MAX_TRANSFERS]; + struct spi_transfer t = { + .tx_buf = tx, + .rx_buf = rx, + }; + int i, ret; + + len++; + if (len > MC33XS2410_MAX_TRANSFERS) + return -EINVAL; + + t.len = len * 2; + for (i = 0; i < len - 1; i++) + tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, flag) | + FIELD_PREP(MC33XS2410_FRAME_IN_ADDR, reg[i]); + + ret = spi_sync_transfer(spi, &t, 1); + if (ret < 0) + return ret; + + for (i = 1; i < len; i++) + val[i - 1] = FIELD_GET(MC33XS2410_FRAME_OUT_DATA, rx[i]); + + return 0; +} + +static int mc33xs2410_write_reg(struct spi_device *spi, u8 reg, u8 val) +{ + return mc33xs2410_write_regs(spi, ®, &val, 1); +} + +static int mc33xs2410_read_reg(struct spi_device *spi, u8 reg, u16 *val, u8 flag) +{ + return mc33xs2410_read_regs(spi, ®, flag, val, 1); +} + +static int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val) +{ + return mc33xs2410_read_reg(spi, reg, val, MC33XS2410_FRAME_IN_DATA_RD); +} + +static int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val) +{ + u16 tmp; + int ret; + + ret = mc33xs2410_read_reg_ctrl(spi, reg, &tmp); + if (ret < 0) + return ret; + + tmp &= ~mask; + tmp |= val & mask; + + return mc33xs2410_write_reg(spi, reg, tmp); +} + +static u8 mc33xs2410_pwm_get_freq(u64 period) +{ + u8 step, count; + + /* + * Check which step [0 .. 3] is appropriate for the given period. The + * period ranges for the different step values overlap. Prefer big step + * values as these allow more finegrained period and duty cycle + * selection. + */ + + switch (period) { + case MC33XS2410_PWM_MIN_PERIOD ... MC33XS2410_PWM_MAX_PERIOD(3): + step = 3; + break; + case MC33XS2410_PWM_MAX_PERIOD(3) + 1 ... MC33XS2410_PWM_MAX_PERIOD(2): + step = 2; + break; + case MC33XS2410_PWM_MAX_PERIOD(2) + 1 ... MC33XS2410_PWM_MAX_PERIOD(1): + step = 1; + break; + case MC33XS2410_PWM_MAX_PERIOD(1) + 1 ... MC33XS2410_PWM_MAX_PERIOD(0): + step = 0; + break; + } + + /* + * Round up here because a higher count results in a higher frequency + * and so a smaller period. + */ + count = DIV_ROUND_UP((u32)MC33XS2410_PWM_MAX_PERIOD(step), (u32)period); + return FIELD_PREP(MC33XS2410_PWM_FREQ_STEP, step) | + FIELD_PREP(MC33XS2410_PWM_FREQ_COUNT, count - 1); +} + +static u64 mc33xs2410_pwm_get_period(u8 reg) +{ + u32 doubled_freq, code, doubled_steps; + + /* + * steps: + * - 0 = 0.5Hz + * - 1 = 2Hz + * - 2 = 8Hz + * - 3 = 32Hz + * frequency = (code + 1) x steps. + * + * To avoid losing precision in case steps value is zero, scale the + * steps value for now by two and keep it in mind when calculating the + * period that the frequency had been doubled. + */ + doubled_steps = 1 << (FIELD_GET(MC33XS2410_PWM_FREQ_STEP, reg) * 2); + code = FIELD_GET(MC33XS2410_PWM_FREQ_COUNT, reg); + doubled_freq = (code + 1) * doubled_steps; + + /* Convert frequency to period, considering the doubled frequency. */ + return DIV_ROUND_UP(2 * NSEC_PER_SEC, doubled_freq); +} + +/* + * The hardware cannot generate a 0% relative duty cycle for normal and inversed + * polarity. For normal polarity, the channel must be disabled, the device then + * emits a constant low signal. + * For inverted polarity, the channel must be enabled, the polarity must be set + * to normal and the relative duty cylce must be set to 100%. The device then + * emits a constant high signal. + */ +static int mc33xs2410_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct spi_device *spi = pwmchip_get_drvdata(chip); + u8 reg[4] = { + MC33XS2410_PWM_FREQ(pwm->hwpwm + 1), + MC33XS2410_PWM_DC(pwm->hwpwm + 1), + MC33XS2410_PWM_CTRL1, + MC33XS2410_PWM_CTRL3 + }; + u64 period, duty_cycle; + int ret, rel_dc; + u16 rd_val[2]; + u8 wr_val[4]; + u8 mask; + + period = min(state->period, MC33XS2410_PWM_MAX_PERIOD(0)); + if (period < MC33XS2410_PWM_MIN_PERIOD) + return -EINVAL; + + ret = mc33xs2410_read_regs(spi, ®[2], MC33XS2410_FRAME_IN_DATA_RD, rd_val, 2); + if (ret < 0) + return ret; + + /* Frequency */ + wr_val[0] = mc33xs2410_pwm_get_freq(period); + /* Continue calculations with the possibly truncated period */ + period = mc33xs2410_pwm_get_period(wr_val[0]); + + /* Duty cycle */ + duty_cycle = min(period, state->duty_cycle); + rel_dc = div64_u64(duty_cycle * 256, period) - 1; + if (rel_dc >= 0) + wr_val[1] = rel_dc; + else if (state->polarity == PWM_POLARITY_NORMAL) + wr_val[1] = 0; + else + wr_val[1] = 255; + + /* Polarity */ + mask = MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1); + if (state->polarity == PWM_POLARITY_INVERSED && rel_dc >= 0) + wr_val[2] = rd_val[0] | mask; + else + wr_val[2] = rd_val[0] & ~mask; + + /* Enable */ + mask = MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1); + if (state->enabled && + !(state->polarity == PWM_POLARITY_NORMAL && rel_dc < 0)) + wr_val[3] = rd_val[1] | mask; + else + wr_val[3] = rd_val[1] & ~mask; + + return mc33xs2410_write_regs(spi, reg, wr_val, 4); +} + +static int mc33xs2410_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct spi_device *spi = pwmchip_get_drvdata(chip); + u8 reg[4] = { + MC33XS2410_PWM_FREQ(pwm->hwpwm + 1), + MC33XS2410_PWM_DC(pwm->hwpwm + 1), + MC33XS2410_PWM_CTRL1, + MC33XS2410_PWM_CTRL3, + }; + u16 val[4]; + int ret; + + ret = mc33xs2410_read_regs(spi, reg, MC33XS2410_FRAME_IN_DATA_RD, val, + ARRAY_SIZE(reg)); + if (ret < 0) + return ret; + + state->period = mc33xs2410_pwm_get_period(val[0]); + state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1)) ? + PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; + state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1)); + state->duty_cycle = DIV_ROUND_UP_ULL((val[1] + 1) * state->period, 256); + + return 0; +} + +static const struct pwm_ops mc33xs2410_pwm_ops = { + .apply = mc33xs2410_pwm_apply, + .get_state = mc33xs2410_pwm_get_state, +}; + +static int mc33xs2410_reset(struct device *dev) +{ + struct gpio_desc *reset_gpio; + + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR_OR_NULL(reset_gpio)) + return PTR_ERR_OR_ZERO(reset_gpio); + + /* Wake-up time */ + fsleep(10000); + + return 0; +} + +static int mc33xs2410_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct pwm_chip *chip; + int ret; + + chip = devm_pwmchip_alloc(dev, 4, 0); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + spi->bits_per_word = 16; + spi->mode |= SPI_CS_WORD; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + pwmchip_set_drvdata(chip, spi); + chip->ops = &mc33xs2410_pwm_ops; + + /* + * Deasserts the reset of the device. Shouldn't change the output signal + * if the device was setup prior to probing. + */ + ret = mc33xs2410_reset(dev); + if (ret) + return ret; + + /* + * Disable watchdog and keep in mind that the watchdog won't trigger a + * reset of the machine when running into an timeout, instead the + * control over the outputs is handed over to the INx input logic + * signals of the device. Disabling it here just deactivates this + * feature until a proper solution is found. + */ + ret = mc33xs2410_write_reg(spi, MC33XS2410_WDT, 0x0); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to disable watchdog\n"); + + /* Transition to normal mode */ + ret = mc33xs2410_modify_reg(spi, MC33XS2410_GLB_CTRL, + MC33XS2410_GLB_CTRL_MODE, + MC33XS2410_GLB_CTRL_MODE_NORMAL); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to transition to normal mode\n"); + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add pwm chip\n"); + + return 0; +} + +static const struct spi_device_id mc33xs2410_spi_id[] = { + { "mc33xs2410" }, + { } +}; +MODULE_DEVICE_TABLE(spi, mc33xs2410_spi_id); + +static const struct of_device_id mc33xs2410_of_match[] = { + { .compatible = "nxp,mc33xs2410" }, + { } +}; +MODULE_DEVICE_TABLE(of, mc33xs2410_of_match); + +static struct spi_driver mc33xs2410_driver = { + .driver = { + .name = "mc33xs2410-pwm", + .of_match_table = mc33xs2410_of_match, + }, + .probe = mc33xs2410_probe, + .id_table = mc33xs2410_spi_id, +}; +module_spi_driver(mc33xs2410_driver); + +MODULE_DESCRIPTION("NXP MC33XS2410 high-side switch driver"); +MODULE_AUTHOR("Dimitri Fedrau "); +MODULE_LICENSE("GPL"); From 5ad7de623853570bd981be069ed5ce55540bedb8 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 24 Apr 2025 06:40:47 +0100 Subject: [PATCH 30/39] arm64: defconfig: Enable Renesas RZ/G2L GPT config Enable PWM config for Renesas RZ/G2L GPT as it is populated on the RZ/G2L and RZ/V2L SMARC EVKs. Signed-off-by: Biju Das Acked-by: Arnd Bergmann Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/20250424054050.28310-5-biju.das.jz@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/configs/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 5494b8a2380a..404d2d51a926 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1530,6 +1530,7 @@ CONFIG_PWM_MEDIATEK=m CONFIG_PWM_RCAR=m CONFIG_PWM_RENESAS_TPU=m CONFIG_PWM_ROCKCHIP=y +CONFIG_PWM_RZG2L_GPT=m CONFIG_PWM_RZ_MTU3=m CONFIG_PWM_SAMSUNG=y CONFIG_PWM_SL28CPLD=m From 754a11c7bbe4edae9f04c940d5cb160e74266689 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 17 Apr 2025 23:23:41 +0000 Subject: [PATCH 31/39] arm64: defconfig: Add Renesas MSIOF sound support Renesas V4H Sparrow Hawk board needs MSIOF Sound driver. Support it. Signed-off-by: Kuninori Morimoto Acked-by: Krzysztof Kozlowski Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/87o6wu2wzm.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/configs/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 404d2d51a926..2dd75c799848 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1010,6 +1010,7 @@ CONFIG_SND_SOC_ROCKCHIP_RT5645=m CONFIG_SND_SOC_RK3399_GRU_SOUND=m CONFIG_SND_SOC_SAMSUNG=y CONFIG_SND_SOC_RCAR=m +CONFIG_SND_SOC_MSIOF=m CONFIG_SND_SOC_RZ=m CONFIG_SND_SOC_SOF_TOPLEVEL=y CONFIG_SND_SOC_SOF_OF=y From 976c4626c5f57d9a0eea0f4618ce58e68102bee3 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 28 Apr 2025 14:16:13 +0200 Subject: [PATCH 32/39] ARM: shmobile: defconfig: Enable more support for RZN1D-DB/EB Enable more support for the Renesas RZN1D-DB and RZN1D-EB development and expansion boards: - Polled GPIO buttons (also used on the Marzen development board), - Synopsys DesignWare I2C adapters, - National Semiconductor LM75 sensors and compatibles (which requires not disabling Hardware Monitoring support), - Arasan SDHCI controllers. Signed-off-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Link: https://lore.kernel.org/a40aa69832ef292497b9170e2ad607bd9dfd7e21.1745842538.git.geert+renesas@glider.be --- arch/arm/configs/shmobile_defconfig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/arm/configs/shmobile_defconfig b/arch/arm/configs/shmobile_defconfig index 357cb144150b..0ea34d5d797c 100644 --- a/arch/arm/configs/shmobile_defconfig +++ b/arch/arm/configs/shmobile_defconfig @@ -63,6 +63,7 @@ CONFIG_SMSC_PHY=y CONFIG_CAN_RCAR=y CONFIG_INPUT_EVDEV=y CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_GPIO_POLLED=y # CONFIG_INPUT_MOUSE is not set CONFIG_INPUT_TOUCHSCREEN=y CONFIG_TOUCHSCREEN_EDT_FT5X06=y @@ -84,6 +85,7 @@ CONFIG_SERIAL_8250_EM=y CONFIG_SERIAL_SH_SCI=y CONFIG_I2C_CHARDEV=y CONFIG_I2C_DEMUX_PINCTRL=y +CONFIG_I2C_DESIGNWARE_CORE=y CONFIG_I2C_EMEV2=y CONFIG_I2C_GPIO=y CONFIG_I2C_RIIC=y @@ -104,7 +106,7 @@ CONFIG_GPIO_PCF857X=y CONFIG_POWER_RESET=y CONFIG_POWER_RESET_RMOBILE=y CONFIG_POWER_SUPPLY=y -# CONFIG_HWMON is not set +CONFIG_SENSORS_LM75=y CONFIG_THERMAL=y CONFIG_CPU_THERMAL=y CONFIG_RCAR_THERMAL=y @@ -174,6 +176,9 @@ CONFIG_USB_RENESAS_USBHS_UDC=y CONFIG_USB_RENESAS_USBF=y CONFIG_USB_ETH=y CONFIG_MMC=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_OF_ARASAN=y CONFIG_MMC_SDHI=y CONFIG_MMC_SH_MMCIF=y CONFIG_NEW_LEDS=y From e866834c8baabc33b431902beeeb0c94dfbc1024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 30 Apr 2025 13:55:58 +0200 Subject: [PATCH 33/39] pwm: Let pwm_set_waveform_might_sleep() fail for exact but impossible requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Up to now pwm_set_waveform_might_sleep() returned 1 for exact requests that couldn't be served exactly. In contrast to pwm_round_waveform_might_sleep() and pwm_set_waveform_might_sleep() with exact = false this is an error condition. So simplify handling for callers of pwm_set_waveform_might_sleep() by returning -EDOM instead of 1 in this case. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20538a46719584dafd8a1395c886780a97dcdf79.1746010245.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index e0a90c4cd723..28cb6ab0f62d 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -404,15 +404,16 @@ static int __pwm_set_waveform(struct pwm_device *pwm, * Typically a requested waveform cannot be implemented exactly, e.g. because * you requested .period_length_ns = 100 ns, but the hardware can only set * periods that are a multiple of 8.5 ns. With that hardware passing @exact = - * true results in pwm_set_waveform_might_sleep() failing and returning 1. If - * @exact = false you get a period of 93.5 ns (i.e. the biggest period not bigger - * than the requested value). + * true results in pwm_set_waveform_might_sleep() failing and returning -EDOM. + * If @exact = false you get a period of 93.5 ns (i.e. the biggest period not + * bigger than the requested value). * Note that even with @exact = true, some rounding by less than 1 ns is * possible/needed. In the above example requesting .period_length_ns = 94 and * @exact = true, you get the hardware configured with period = 93.5 ns. * - * Returns: 0 on success, 1 if was rounded up (if !@exact) or no perfect match was - * possible (if @exact), or a negative errno + * Returns: 0 on success, 1 if was rounded up (if !@exact), -EDOM if setting + * failed due to the exact waveform not being possible (if @exact), or a + * different negative errno on failure. * Context: May sleep. */ int pwm_set_waveform_might_sleep(struct pwm_device *pwm, @@ -440,6 +441,16 @@ int pwm_set_waveform_might_sleep(struct pwm_device *pwm, err = __pwm_set_waveform(pwm, wf, exact); } + /* + * map err == 1 to -EDOM for exact requests. Also make sure that -EDOM is + * only returned in exactly that case. Note that __pwm_set_waveform() + * should never return -EDOM which justifies the unlikely(). + */ + if (unlikely(err == -EDOM)) + err = -EINVAL; + else if (exact && err == 1) + err = -EDOM; + return err; } EXPORT_SYMBOL_GPL(pwm_set_waveform_might_sleep); From 164c4ac754abaf9643815d09001cc7d81042d624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 30 Apr 2025 13:55:59 +0200 Subject: [PATCH 34/39] pwm: Let pwm_set_waveform_might_sleep() return 0 instead of 1 after rounding up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While telling the caller of pwm_set_waveform_might_sleep() if the request was completed by rounding down only or (some) rounding up gives additional information, it makes usage this function needlessly hard and the additional information is not used. A prove for that is that currently both users of this function just pass the returned value up to their caller even though a positive value isn't intended there. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/528cc3bbd9e35dea8646b1bcc0fbfe6c498bb4ed.1746010245.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 28cb6ab0f62d..5cf64b3a4cdf 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -411,9 +411,8 @@ static int __pwm_set_waveform(struct pwm_device *pwm, * possible/needed. In the above example requesting .period_length_ns = 94 and * @exact = true, you get the hardware configured with period = 93.5 ns. * - * Returns: 0 on success, 1 if was rounded up (if !@exact), -EDOM if setting - * failed due to the exact waveform not being possible (if @exact), or a - * different negative errno on failure. + * Returns: 0 on success, -EDOM if setting failed due to the exact waveform not + * being possible (if @exact), or a different negative errno on failure. * Context: May sleep. */ int pwm_set_waveform_might_sleep(struct pwm_device *pwm, @@ -442,14 +441,17 @@ int pwm_set_waveform_might_sleep(struct pwm_device *pwm, } /* - * map err == 1 to -EDOM for exact requests. Also make sure that -EDOM is - * only returned in exactly that case. Note that __pwm_set_waveform() - * should never return -EDOM which justifies the unlikely(). + * map err == 1 to -EDOM for exact requests and 0 for !exact ones. Also + * make sure that -EDOM is only returned in exactly that case. Note that + * __pwm_set_waveform() should never return -EDOM which justifies the + * unlikely(). */ if (unlikely(err == -EDOM)) err = -EINVAL; else if (exact && err == 1) err = -EDOM; + else if (err == 1) + err = 0; return err; } From d041b76ac9fb9e60e7cdb0265ed9d8b6058a88bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 30 Apr 2025 13:56:00 +0200 Subject: [PATCH 35/39] pwm: Formally describe the procedure used to pick a hardware waveform setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This serves as specification for both, PWM consumers and the respective callback for lowlevel drivers. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/d2916bfa70274961ded26b07ab6998c36b90e69a.1746010245.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 5cf64b3a4cdf..4d842c692194 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -231,7 +231,9 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c * * Usually all values passed in @wf are rounded down to the nearest possible * value (in the order period_length_ns, duty_length_ns and then - * duty_offset_ns). Only if this isn't possible, a value might grow. + * duty_offset_ns). Only if this isn't possible, a value might grow. See the + * documentation for pwm_set_waveform_might_sleep() for a more formal + * description. * * Returns: 0 on success, 1 if at least one value had to be rounded up or a * negative errno. @@ -411,6 +413,26 @@ static int __pwm_set_waveform(struct pwm_device *pwm, * possible/needed. In the above example requesting .period_length_ns = 94 and * @exact = true, you get the hardware configured with period = 93.5 ns. * + * Let C be the set of possible hardware configurations for a given PWM device, + * consisting of tuples (p, d, o) where p is the period length, d is the duty + * length and o the duty offset. + * + * The following algorithm is implemented to pick the hardware setting + * (p, d, o) ∈ C for a given request (p', d', o') with @exact = false:: + * + * p = max( { ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C ∧ ṗ ≤ p' } ∪ { min({ ṗ | ∃ ḋ, ȯ : (ṗ, ḋ, ȯ) ∈ C }) }) + * d = max( { ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C ∧ ḋ ≤ d' } ∪ { min({ ḋ | ∃ ȯ : (p, ḋ, ȯ) ∈ C }) }) + * o = max( { ȯ | (p, d, ȯ) ∈ C ∧ ȯ ≤ o' } ∪ { min({ ȯ | (p, d, ȯ) ∈ C }) }) + * + * In words: The chosen period length is the maximal possible period length not + * bigger than the requested period length and if that doesn't exist, the + * minimal period length. The chosen duty length is the maximal possible duty + * length that is compatible with the chosen period length and isn't bigger than + * the requested duty length. Again if such a value doesn't exist, the minimal + * duty length compatible with the chosen period is picked. After that the duty + * offset compatible with the chosen period and duty length is chosen in the + * same way. + * * Returns: 0 on success, -EDOM if setting failed due to the exact waveform not * being possible (if @exact), or a different negative errno on failure. * Context: May sleep. From 84e351d8a5755c60e14b7e42d70c2415541928d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Thu, 8 May 2025 10:17:06 +0200 Subject: [PATCH 36/39] pwm: Restore alphabetic ordering in Kconfig and Makefile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The drivers are nearly ordered alphabetically by the symbol name. Fix the few outliers. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20250508081706.751209-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/Kconfig | 60 ++++++++++++++++++++++---------------------- drivers/pwm/Makefile | 4 +-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6faa8b2ec0a4..c866ed388da9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -114,6 +114,16 @@ config PWM_AXI_PWMGEN To compile this driver as a module, choose M here: the module will be called pwm-axi-pwmgen. +config PWM_BCM2835 + tristate "BCM2835 PWM support" + depends on ARCH_BCM2835 || ARCH_BRCMSTB || COMPILE_TEST + depends on HAS_IOMEM + help + PWM framework driver for BCM2835 controller (Raspberry Pi) + + To compile this driver as a module, choose M here: the module + will be called pwm-bcm2835. + config PWM_BCM_IPROC tristate "iProc PWM support" depends on ARCH_BCM_IPROC || COMPILE_TEST @@ -137,16 +147,6 @@ config PWM_BCM_KONA To compile this driver as a module, choose M here: the module will be called pwm-bcm-kona. -config PWM_BCM2835 - tristate "BCM2835 PWM support" - depends on ARCH_BCM2835 || ARCH_BRCMSTB || COMPILE_TEST - depends on HAS_IOMEM - help - PWM framework driver for BCM2835 controller (Raspberry Pi) - - To compile this driver as a module, choose M here: the module - will be called pwm-bcm2835. - config PWM_BERLIN tristate "Marvell Berlin PWM support" depends on ARCH_BERLIN || COMPILE_TEST @@ -435,6 +435,16 @@ config PWM_MC33XS2410 To compile this driver as a module, choose M here: the module will be called pwm-mc33xs2410. +config PWM_MEDIATEK + tristate "MediaTek PWM support" + depends on ARCH_MEDIATEK || RALINK || COMPILE_TEST + depends on HAS_IOMEM + help + Generic PWM framework driver for Mediatek ARM SoC. + + To compile this driver as a module, choose M here: the module + will be called pwm-mediatek. + config PWM_MESON tristate "Amlogic Meson PWM driver" depends on ARCH_MESON || COMPILE_TEST @@ -445,6 +455,16 @@ config PWM_MESON To compile this driver as a module, choose M here: the module will be called pwm-meson. +config PWM_MICROCHIP_CORE + tristate "Microchip corePWM PWM support" + depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on HAS_IOMEM && OF + help + PWM driver for Microchip FPGA soft IP core. + + To compile this driver as a module, choose M here: the module + will be called pwm-microchip-core. + config PWM_MTK_DISP tristate "MediaTek display PWM driver" depends on ARCH_MEDIATEK || COMPILE_TEST @@ -456,26 +476,6 @@ config PWM_MTK_DISP To compile this driver as a module, choose M here: the module will be called pwm-mtk-disp. -config PWM_MEDIATEK - tristate "MediaTek PWM support" - depends on ARCH_MEDIATEK || RALINK || COMPILE_TEST - depends on HAS_IOMEM - help - Generic PWM framework driver for Mediatek ARM SoC. - - To compile this driver as a module, choose M here: the module - will be called pwm-mediatek. - -config PWM_MICROCHIP_CORE - tristate "Microchip corePWM PWM support" - depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST - depends on HAS_IOMEM && OF - help - PWM driver for Microchip FPGA soft IP core. - - To compile this driver as a module, choose M here: the module - will be called pwm-microchip-core. - config PWM_MXS tristate "Freescale MXS PWM support" depends on ARCH_MXS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 9742fc196f07..5c782af8f49b 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -7,9 +7,9 @@ obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o obj-$(CONFIG_PWM_AXI_PWMGEN) += pwm-axi-pwmgen.o +obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o obj-$(CONFIG_PWM_BCM_IPROC) += pwm-bcm-iproc.o obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o -obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o obj-$(CONFIG_PWM_CLK) += pwm-clk.o @@ -38,8 +38,8 @@ obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o obj-$(CONFIG_PWM_MC33XS2410) += pwm-mc33xs2410.o -obj-$(CONFIG_PWM_MESON) += pwm-meson.o obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o +obj-$(CONFIG_PWM_MESON) += pwm-meson.o obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o From 25ac4834cae97f7787ba5562ce1511d7524955e0 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 12 May 2025 06:33:12 +0000 Subject: [PATCH 37/39] pwm: Tidyup PWM menu for Renesas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because current PWM Kconfig is sorting by symbol name, it looks strange ordering in menuconfig. => [ ] Renesas R-Car PWM support => [ ] Renesas TPU PWM support [ ] Rockchip PWM support => [ ] Renesas RZ/G2L General PWM Timer support => [ ] Renesas RZ/G2L MTU3a PWM Timer support Let's use common CONFIG_PWM_RENESAS_xxx symbol name for Renesas, and sort it. Signed-off-by: Kuninori Morimoto Reviewed-by: Biju Das Link: https://lore.kernel.org/r/877c2mxrrr.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Uwe Kleine-König --- arch/arm/configs/multi_v7_defconfig | 2 +- arch/arm/configs/shmobile_defconfig | 2 +- arch/arm64/configs/defconfig | 6 ++-- drivers/pwm/Kconfig | 46 ++++++++++++++--------------- drivers/pwm/Makefile | 6 ++-- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig index 28a6ca750861..957e6fde0280 100644 --- a/arch/arm/configs/multi_v7_defconfig +++ b/arch/arm/configs/multi_v7_defconfig @@ -1184,7 +1184,7 @@ CONFIG_PWM_BCM2835=y CONFIG_PWM_BRCMSTB=m CONFIG_PWM_FSL_FTM=m CONFIG_PWM_MESON=m -CONFIG_PWM_RCAR=m +CONFIG_PWM_RENESAS_RCAR=m CONFIG_PWM_RENESAS_TPU=y CONFIG_PWM_ROCKCHIP=m CONFIG_PWM_SAMSUNG=m diff --git a/arch/arm/configs/shmobile_defconfig b/arch/arm/configs/shmobile_defconfig index 0ea34d5d797c..7c3d6a8f0038 100644 --- a/arch/arm/configs/shmobile_defconfig +++ b/arch/arm/configs/shmobile_defconfig @@ -203,7 +203,7 @@ CONFIG_RZ_DMAC=y CONFIG_IIO=y CONFIG_AK8975=y CONFIG_PWM=y -CONFIG_PWM_RCAR=y +CONFIG_PWM_RENESAS_RCAR=y CONFIG_PWM_RENESAS_TPU=y CONFIG_PHY_RCAR_GEN2=y CONFIG_PHY_RCAR_GEN3_USB2=y diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 2dd75c799848..304a0285d61e 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1528,11 +1528,11 @@ CONFIG_PWM_IMX27=m CONFIG_PWM_MESON=m CONFIG_PWM_MTK_DISP=m CONFIG_PWM_MEDIATEK=m -CONFIG_PWM_RCAR=m +CONFIG_PWM_RENESAS_RCAR=m +CONFIG_PWM_RENESAS_RZG2L_GPT=m +CONFIG_PWM_RENESAS_RZ_MTU3=m CONFIG_PWM_RENESAS_TPU=m CONFIG_PWM_ROCKCHIP=y -CONFIG_PWM_RZG2L_GPT=m -CONFIG_PWM_RZ_MTU3=m CONFIG_PWM_SAMSUNG=y CONFIG_PWM_SL28CPLD=m CONFIG_PWM_SUN4I=m diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c866ed388da9..d9bcd1e8413e 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -534,7 +534,7 @@ config PWM_RASPBERRYPI_POE Enable Raspberry Pi firmware controller PWM bus used to control the official RPI PoE hat -config PWM_RCAR +config PWM_RENESAS_RCAR tristate "Renesas R-Car PWM support" depends on ARCH_RENESAS || COMPILE_TEST depends on HAS_IOMEM @@ -545,6 +545,28 @@ config PWM_RCAR To compile this driver as a module, choose M here: the module will be called pwm-rcar. +config PWM_RENESAS_RZG2L_GPT + tristate "Renesas RZ/G2L General PWM Timer support" + depends on ARCH_RZG2L || COMPILE_TEST + depends on HAS_IOMEM + help + This driver exposes the General PWM Timer controller found in Renesas + RZ/G2L like chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-rzg2l-gpt. + +config PWM_RENESAS_RZ_MTU3 + tristate "Renesas RZ/G2L MTU3a PWM Timer support" + depends on RZ_MTU3 + depends on HAS_IOMEM + help + This driver exposes the MTU3a PWM Timer controller found in Renesas + RZ/G2L like chips through the PWM API. + + To compile this driver as a module, choose M here: the module + will be called pwm-rz-mtu3. + config PWM_RENESAS_TPU tristate "Renesas TPU PWM support" depends on ARCH_RENESAS || COMPILE_TEST @@ -564,28 +586,6 @@ config PWM_ROCKCHIP Generic PWM framework driver for the PWM controller found on Rockchip SoCs. -config PWM_RZG2L_GPT - tristate "Renesas RZ/G2L General PWM Timer support" - depends on ARCH_RZG2L || COMPILE_TEST - depends on HAS_IOMEM - help - This driver exposes the General PWM Timer controller found in Renesas - RZ/G2L like chips through the PWM API. - - To compile this driver as a module, choose M here: the module - will be called pwm-rzg2l-gpt. - -config PWM_RZ_MTU3 - tristate "Renesas RZ/G2L MTU3a PWM Timer support" - depends on RZ_MTU3 - depends on HAS_IOMEM - help - This driver exposes the MTU3a PWM Timer controller found in Renesas - RZ/G2L like chips through the PWM API. - - To compile this driver as a module, choose M here: the module - will be called pwm-rz-mtu3. - config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 5c782af8f49b..96160f4257fc 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -48,11 +48,11 @@ obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o -obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o +obj-$(CONFIG_PWM_RENESAS_RCAR) += pwm-rcar.o +obj-$(CONFIG_PWM_RENESAS_RZG2L_GPT) += pwm-rzg2l-gpt.o +obj-$(CONFIG_PWM_RENESAS_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o -obj-$(CONFIG_PWM_RZG2L_GPT) += pwm-rzg2l-gpt.o -obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o From 9c5e285f602f3f9a8e095094737c176234cde5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Mon, 12 May 2025 13:39:14 +0100 Subject: [PATCH 38/39] pwm: adp5585: make sure to include mod_devicetable.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Explicitly include mod_devicetable.h for struct platform_device_id. Reviewed-by: Laurent Pinchart Signed-off-by: Nuno Sá Link: https://lore.kernel.org/r/20250512-dev-adp5589-fw-v3-22-092b14b79a88@analog.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-adp5585.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c index 40472ac5db64..d79106d12181 100644 --- a/drivers/pwm/pwm-adp5585.c +++ b/drivers/pwm/pwm-adp5585.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include From c0c980f237e822fd9cc6c0ab5b60ce8efe76464e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 10 Apr 2025 01:10:48 +0000 Subject: [PATCH 39/39] dt-bindings: timer: renesas,tpu: remove binding documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 1c4b5ecb7ea1 ("remove the h8300 architecture") removes Renesas TPU timer driver. Let's remove its binding documentation. Signed-off-by: Kuninori Morimoto Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/87semglt2g.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Uwe Kleine-König --- .../bindings/pwm/renesas,tpu-pwm.yaml | 9 --- .../bindings/timer/renesas,tpu.yaml | 56 ------------------- 2 files changed, 65 deletions(-) delete mode 100644 Documentation/devicetree/bindings/timer/renesas,tpu.yaml diff --git a/Documentation/devicetree/bindings/pwm/renesas,tpu-pwm.yaml b/Documentation/devicetree/bindings/pwm/renesas,tpu-pwm.yaml index a4dfa09344dd..f85ee5d20ccb 100644 --- a/Documentation/devicetree/bindings/pwm/renesas,tpu-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/renesas,tpu-pwm.yaml @@ -9,15 +9,6 @@ title: Renesas R-Car Timer Pulse Unit PWM Controller maintainers: - Laurent Pinchart -select: - properties: - compatible: - contains: - const: renesas,tpu - required: - - compatible - - '#pwm-cells' - properties: compatible: items: diff --git a/Documentation/devicetree/bindings/timer/renesas,tpu.yaml b/Documentation/devicetree/bindings/timer/renesas,tpu.yaml deleted file mode 100644 index 7a473b302775..000000000000 --- a/Documentation/devicetree/bindings/timer/renesas,tpu.yaml +++ /dev/null @@ -1,56 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/timer/renesas,tpu.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Renesas H8/300 Timer Pulse Unit - -maintainers: - - Yoshinori Sato - -description: - The TPU is a 16bit timer/counter with configurable clock inputs and - programmable compare match. - This implementation supports only cascade mode. - -select: - properties: - compatible: - contains: - const: renesas,tpu - '#pwm-cells': false - required: - - compatible - -properties: - compatible: - const: renesas,tpu - - reg: - items: - - description: First channel - - description: Second channel - - clocks: - maxItems: 1 - - clock-names: - const: fck - -required: - - compatible - - reg - - clocks - - clock-names - -additionalProperties: false - -examples: - - | - tpu: tpu@ffffe0 { - compatible = "renesas,tpu"; - reg = <0xffffe0 16>, <0xfffff0 12>; - clocks = <&pclk>; - clock-names = "fck"; - };