* [PATCH v7 0/3] pwm: add support for Andes platform
@ 2026-08-12 3:24 ` Ben Zong-You Xie
0 siblings, 0 replies; 9+ messages in thread
From: Ben Zong-You Xie via B4 Relay @ 2026-08-12 3:24 UTC (permalink / raw)
To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-pwm, devicetree, linux-kernel, Ben Zong-You Xie
The ATCPIT100 is a set of compact multi-function timers, which can be
a simple timer or PWM, or a combination of the timer and the PWM. This
IP block is a core component of the Andes AE350 platform, which serves
as a reference architecture for SoC designs. The QiLai SoC also
integrates this controller.
This device driver was previously part of the Linux Kernel, and the
compatible was its IP name "andestech,atcpit100". However, the driver
and its binding were removed due to the deprecation of the NDS32
architecture [1]. Although Andes now dedicates our effort on RISC-V,
ATCPIT100 is still one of our peripheral IPs, and that's why we are
re-introducing it now. Since only using the IP block name as the
compatible is forbidden now, change the compatible to
"andestech,ae350-pwm" and "andestech,qilai-pwm".
[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=aec499c75cf8e0b599be4d559e6922b613085f8f
Signed-off-by: Ben Zong-You Xie <ben717@andestech.com>
---
Changes in v7:
- Rebased on ukleinek/pwm/for-next
- (2/3)
- Add the missing Kconfig dependencies: select REGMAP_MMIO, and depend on
HAS_IOMEM && COMMON_CLK. (Uwe)
- Only reject a period shorter than one cycle. With a single cycle the duty
cycle can only be 0 or 1 cycle, and both are emitted by parking the
channel. (Uwe)
- Check the return value of regmap_assign_bits() in andes_pwm_park(). (Uwe)
- Select the phase carrying the duty cycle first and convert it to
nanoseconds once in .get_state(). (Uwe)
- Call devm_clk_rate_exclusive_get() for both clocks so their rates cannot
be changed behind the driver's back. (Uwe)
- Set chip->atomic. (Uwe)
- Set state->polarity before .get_state() returns early for an unavailable
clock source.
- Link to v6: https://patch.msgid.link/20260625-andes-pwm-v6-0-3aef11711017@andestech.com
Changes in v6:
- Rebased on ukleinek/pwm/for-next.
- (2/3)
- Round a too-large period or duty cycle down to the largest achievable
value, and emulate 0%/100% relative duty cycles by parking the channel,
instead of erroring out. (Uwe)
- Choose the period from the requested period alone, so it no longer
depends on the requested duty cycle. (Uwe)
- Convert the period to ticks and compute the high/low phases in the tick
domain to avoid rounding errors. (Uwe)
- Reconstruct the period in .get_state() with a single DIV_ROUND_UP_ULL()
over both phases, shared by both polarities, and read the control
register with FIELD_GET(). (Uwe)
- Set an out-of-range clock rate to 0 so that source cannot be selected,
and name both rates in the "no usable clock" error message. (Uwe)
- Rename to_andes_pwm() to andes_pwm_from_chip(). (Uwe)
- Capitalize error messages. (Uwe)
- Tidy up the in-function comment and the limitations list.
- Link to v5: https://patch.msgid.link/20260330-andes-pwm-v5-0-01c59a659d2c@andestech.com
Changes in v5:
- Rebased on ukleinek/pwm/for-next
- (1/3)
- Added Rob's reviewed-by tag.
- Link to v4: https://patch.msgid.link/20260204-andes-pwm-v4-0-67016bb13555@andestech.com
Changes in v4:
- Updated cover letter title and its commit message.
- (1/3)
- Updated the compatibles and the binding file name.
- Added the description to the binding.
- Corrected device register size.
- Dropped the changes to the MAINTAINERS file.
- (2/3)
- Updated the driver name.
- Made the driver support inversed polarity.
- Changed the clock selection algorithm.
- Made the configuration depend on ARCH_ANDES, instead of RISCV. (Krzysztof)
- Dropped the changes to the MAINTAINERS file.
- (3/3) (new)
- Collected all changes to the MAINTAINERS file into a single patch.
- Link to v3: https://patch.msgid.link/20250123193534.874256-1-ben717@andestech.com
Changes in v3:
- (1/2):
- modified the compatible string. (Krzysztof)
- (2/2):
- added a check for the clock rate to prevent the overflow warning.
(kernel test robot)
- removed the unnecessary check in .apply(). (kernel test robot)
- Link to v2: https://patch.msgid.link/20241202060147.1271264-1-ben717@andestech.com
Changes in v2:
- (1/2):
- changed "title" in the yaml file.
- removed vendor-specific property, and added clocks property.
- (2/2):
- added a description for hardware limitations. (Uwe)
- switched the clock parent depending on the requested setting
instead of statically configuring the clock source in DT. (Uwe)
- Link to v1: https://patch.msgid.link/20241028102721.1961289-1-ben717@andestech.com
---
Ben Zong-You Xie (3):
dt-bindings: pwm: add support for AE350 PWM controller
pwm: add Andes PWM driver support
MAINTAINERS: add an entry for Andes PWM driver
.../bindings/pwm/andestech,ae350-pwm.yaml | 61 ++++
MAINTAINERS | 6 +
drivers/pwm/Kconfig | 12 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-andes.c | 365 +++++++++++++++++++++
5 files changed, 445 insertions(+)
---
base-commit: b4e94b0418ad6dbe62b6ca53369159ed3d9f0d8c
change-id: 20260204-andes-pwm-10ea6611c3cf
Best regards,
--
Ben Zong-You Xie <ben717@andestech.com>
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v7 0/3] pwm: add support for Andes platform @ 2026-08-12 3:24 ` Ben Zong-You Xie 0 siblings, 0 replies; 9+ messages in thread From: Ben Zong-You Xie @ 2026-08-12 3:24 UTC (permalink / raw) To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-pwm, devicetree, linux-kernel, Ben Zong-You Xie The ATCPIT100 is a set of compact multi-function timers, which can be a simple timer or PWM, or a combination of the timer and the PWM. This IP block is a core component of the Andes AE350 platform, which serves as a reference architecture for SoC designs. The QiLai SoC also integrates this controller. This device driver was previously part of the Linux Kernel, and the compatible was its IP name "andestech,atcpit100". However, the driver and its binding were removed due to the deprecation of the NDS32 architecture [1]. Although Andes now dedicates our effort on RISC-V, ATCPIT100 is still one of our peripheral IPs, and that's why we are re-introducing it now. Since only using the IP block name as the compatible is forbidden now, change the compatible to "andestech,ae350-pwm" and "andestech,qilai-pwm". [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=aec499c75cf8e0b599be4d559e6922b613085f8f Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> --- Changes in v7: - Rebased on ukleinek/pwm/for-next - (2/3) - Add the missing Kconfig dependencies: select REGMAP_MMIO, and depend on HAS_IOMEM && COMMON_CLK. (Uwe) - Only reject a period shorter than one cycle. With a single cycle the duty cycle can only be 0 or 1 cycle, and both are emitted by parking the channel. (Uwe) - Check the return value of regmap_assign_bits() in andes_pwm_park(). (Uwe) - Select the phase carrying the duty cycle first and convert it to nanoseconds once in .get_state(). (Uwe) - Call devm_clk_rate_exclusive_get() for both clocks so their rates cannot be changed behind the driver's back. (Uwe) - Set chip->atomic. (Uwe) - Set state->polarity before .get_state() returns early for an unavailable clock source. - Link to v6: https://patch.msgid.link/20260625-andes-pwm-v6-0-3aef11711017@andestech.com Changes in v6: - Rebased on ukleinek/pwm/for-next. - (2/3) - Round a too-large period or duty cycle down to the largest achievable value, and emulate 0%/100% relative duty cycles by parking the channel, instead of erroring out. (Uwe) - Choose the period from the requested period alone, so it no longer depends on the requested duty cycle. (Uwe) - Convert the period to ticks and compute the high/low phases in the tick domain to avoid rounding errors. (Uwe) - Reconstruct the period in .get_state() with a single DIV_ROUND_UP_ULL() over both phases, shared by both polarities, and read the control register with FIELD_GET(). (Uwe) - Set an out-of-range clock rate to 0 so that source cannot be selected, and name both rates in the "no usable clock" error message. (Uwe) - Rename to_andes_pwm() to andes_pwm_from_chip(). (Uwe) - Capitalize error messages. (Uwe) - Tidy up the in-function comment and the limitations list. - Link to v5: https://patch.msgid.link/20260330-andes-pwm-v5-0-01c59a659d2c@andestech.com Changes in v5: - Rebased on ukleinek/pwm/for-next - (1/3) - Added Rob's reviewed-by tag. - Link to v4: https://patch.msgid.link/20260204-andes-pwm-v4-0-67016bb13555@andestech.com Changes in v4: - Updated cover letter title and its commit message. - (1/3) - Updated the compatibles and the binding file name. - Added the description to the binding. - Corrected device register size. - Dropped the changes to the MAINTAINERS file. - (2/3) - Updated the driver name. - Made the driver support inversed polarity. - Changed the clock selection algorithm. - Made the configuration depend on ARCH_ANDES, instead of RISCV. (Krzysztof) - Dropped the changes to the MAINTAINERS file. - (3/3) (new) - Collected all changes to the MAINTAINERS file into a single patch. - Link to v3: https://patch.msgid.link/20250123193534.874256-1-ben717@andestech.com Changes in v3: - (1/2): - modified the compatible string. (Krzysztof) - (2/2): - added a check for the clock rate to prevent the overflow warning. (kernel test robot) - removed the unnecessary check in .apply(). (kernel test robot) - Link to v2: https://patch.msgid.link/20241202060147.1271264-1-ben717@andestech.com Changes in v2: - (1/2): - changed "title" in the yaml file. - removed vendor-specific property, and added clocks property. - (2/2): - added a description for hardware limitations. (Uwe) - switched the clock parent depending on the requested setting instead of statically configuring the clock source in DT. (Uwe) - Link to v1: https://patch.msgid.link/20241028102721.1961289-1-ben717@andestech.com --- Ben Zong-You Xie (3): dt-bindings: pwm: add support for AE350 PWM controller pwm: add Andes PWM driver support MAINTAINERS: add an entry for Andes PWM driver .../bindings/pwm/andestech,ae350-pwm.yaml | 61 ++++ MAINTAINERS | 6 + drivers/pwm/Kconfig | 12 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-andes.c | 365 +++++++++++++++++++++ 5 files changed, 445 insertions(+) --- base-commit: b4e94b0418ad6dbe62b6ca53369159ed3d9f0d8c change-id: 20260204-andes-pwm-10ea6611c3cf Best regards, -- Ben Zong-You Xie <ben717@andestech.com> ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v7 1/3] dt-bindings: pwm: add support for AE350 PWM controller 2026-08-12 3:24 ` Ben Zong-You Xie @ 2026-08-12 3:24 ` Ben Zong-You Xie -1 siblings, 0 replies; 9+ messages in thread From: Ben Zong-You Xie via B4 Relay @ 2026-08-12 3:24 UTC (permalink / raw) To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-pwm, devicetree, linux-kernel, Ben Zong-You Xie From: Ben Zong-You Xie <ben717@andestech.com> The ATCPIT100 is a set of compact multi-function timers, which can be used as pulse width modulators (PWM) as well as simple timers. ATCPIT100 supports up to 4 PIT channels, and each PIT channel may be configured as a simple timer or PWM, or a combination of the timer and the PWM. This IP block is a core component of the Andes AE350 platform, which serves as a reference architecture for SoC designs. The QiLai SoC also integrates this controller. The binding introduces the following compatible strings: - "andestech,qilai-pwm": For the implementation integrated into the Andes QiLai SoC. - "andestech,ae350-pwm": As a fallback compatible string representing the base IP design used across the AE350 platform architecture. Reviewed-by: Rob Herring (Arm) <robh@kernel.org> Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> --- .../bindings/pwm/andestech,ae350-pwm.yaml | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/andestech,ae350-pwm.yaml b/Documentation/devicetree/bindings/pwm/andestech,ae350-pwm.yaml new file mode 100644 index 000000000000..287f3c62965f --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/andestech,ae350-pwm.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/andestech,ae350-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Andes PWM controller on AE350 platform + +description: + This controller has 4 channels and two clock sources. Each channel can + switch the clock source by programming the corresponding register. + +maintainers: + - Ben Zong-You Xie <ben717@andestech.com> + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + oneOf: + - items: + - enum: + - andestech,qilai-pwm + - const: andestech,ae350-pwm + - const: andestech,ae350-pwm + + reg: + maxItems: 1 + + "#pwm-cells": + const: 3 + + clocks: + items: + - description: APB bus clock + - description: External clock + + clock-names: + items: + - const: pclk + - const: extclk + +required: + - compatible + - reg + - "#pwm-cells" + - clocks + - clock-names + +unevaluatedProperties: false + +examples: + - | + pwm@f0400000 { + compatible = "andestech,ae350-pwm"; + reg = <0xf0400000 0x100000>; + #pwm-cells = <3>; + clocks = <&pclk>, <&extclk>; + clock-names = "pclk", "extclk"; + }; -- 2.34.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v7 1/3] dt-bindings: pwm: add support for AE350 PWM controller @ 2026-08-12 3:24 ` Ben Zong-You Xie 0 siblings, 0 replies; 9+ messages in thread From: Ben Zong-You Xie @ 2026-08-12 3:24 UTC (permalink / raw) To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-pwm, devicetree, linux-kernel, Ben Zong-You Xie The ATCPIT100 is a set of compact multi-function timers, which can be used as pulse width modulators (PWM) as well as simple timers. ATCPIT100 supports up to 4 PIT channels, and each PIT channel may be configured as a simple timer or PWM, or a combination of the timer and the PWM. This IP block is a core component of the Andes AE350 platform, which serves as a reference architecture for SoC designs. The QiLai SoC also integrates this controller. The binding introduces the following compatible strings: - "andestech,qilai-pwm": For the implementation integrated into the Andes QiLai SoC. - "andestech,ae350-pwm": As a fallback compatible string representing the base IP design used across the AE350 platform architecture. Reviewed-by: Rob Herring (Arm) <robh@kernel.org> Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> --- .../bindings/pwm/andestech,ae350-pwm.yaml | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/andestech,ae350-pwm.yaml b/Documentation/devicetree/bindings/pwm/andestech,ae350-pwm.yaml new file mode 100644 index 000000000000..287f3c62965f --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/andestech,ae350-pwm.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/andestech,ae350-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Andes PWM controller on AE350 platform + +description: + This controller has 4 channels and two clock sources. Each channel can + switch the clock source by programming the corresponding register. + +maintainers: + - Ben Zong-You Xie <ben717@andestech.com> + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + oneOf: + - items: + - enum: + - andestech,qilai-pwm + - const: andestech,ae350-pwm + - const: andestech,ae350-pwm + + reg: + maxItems: 1 + + "#pwm-cells": + const: 3 + + clocks: + items: + - description: APB bus clock + - description: External clock + + clock-names: + items: + - const: pclk + - const: extclk + +required: + - compatible + - reg + - "#pwm-cells" + - clocks + - clock-names + +unevaluatedProperties: false + +examples: + - | + pwm@f0400000 { + compatible = "andestech,ae350-pwm"; + reg = <0xf0400000 0x100000>; + #pwm-cells = <3>; + clocks = <&pclk>, <&extclk>; + clock-names = "pclk", "extclk"; + }; -- 2.34.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v7 2/3] pwm: add Andes PWM driver support 2026-08-12 3:24 ` Ben Zong-You Xie @ 2026-08-12 3:24 ` Ben Zong-You Xie -1 siblings, 0 replies; 9+ messages in thread From: Ben Zong-You Xie via B4 Relay @ 2026-08-12 3:24 UTC (permalink / raw) To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-pwm, devicetree, linux-kernel, Ben Zong-You Xie From: Ben Zong-You Xie <ben717@andestech.com> Add a driver for the PWM controller found in Andes AE350 platforms and QiLai SoCs. The Andes PWM controller features: - 4 independent channels. - Dual clock source support (APB clock and external clock) to provide a flexible range of frequencies. - Support for normal and inversed polarity. The driver implements the .apply() and .get_state() callbacks. Since the clock source of each channel can be selected by programming the register, clock selection logic is implemented to prioritize the external clock to maximize the supported period range, falling back to the APB clock for higher frequency requirements. Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> --- drivers/pwm/Kconfig | 12 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-andes.c | 365 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 378 insertions(+) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 729776086879..4b5d7d037284 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -73,6 +73,18 @@ config PWM_AIROHA To compile this driver as a module, choose M here: the module will be called pwm-airoha. +config PWM_ANDES + tristate "Andes PWM support" + depends on ARCH_ANDES || COMPILE_TEST + depends on HAS_IOMEM && COMMON_CLK + select REGMAP_MMIO + help + Generic PWM framework driver for Andes platform, such as QiLai SoC + and AE350 platform. + + To compile this driver as a module, choose M here: the module + will be called pwm-andes. + config PWM_APPLE tristate "Apple SoC PWM support" depends on ARCH_APPLE || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 5630a521a7cf..c92369ee251d 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o obj-$(CONFIG_PWM_AIROHA) += pwm-airoha.o +obj-$(CONFIG_PWM_ANDES) += pwm-andes.o obj-$(CONFIG_PWM_APPLE) += pwm-apple.o obj-$(CONFIG_PWM_ARGON_FAN_HAT) += pwm-argon-fan-hat.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o diff --git a/drivers/pwm/pwm-andes.c b/drivers/pwm/pwm-andes.c new file mode 100644 index 000000000000..8dafce7c467c --- /dev/null +++ b/drivers/pwm/pwm-andes.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Andes PWM, used in Andes AE350 platform and QiLai SoC + * + * Copyright (C) 2026 Andes Technology Corporation. + * + * Limitations: + * - When disabling a channel, the current period is not completed and the + * output is driven to the PARK level (low when ANDES_PWM_CH_CTRL_PARK is + * clear, high when it is set). + * - A change of clock source takes effect immediately, modifying the current + * output. Otherwise there is no glitch as the currently running period is + * completed before the new settings take effect. + * - The hardware cannot run a 0% or 100% relative duty cycle; the driver + * emulates these by disabling the channel and parking the output at the + * constant level. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/math64.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/time.h> +#include <linux/types.h> + +#define ANDES_PWM_CH_ENABLE 0x1C +#define ANDES_PWM_CH_ENABLE_PWM(ch) BIT(3 + (4 * (ch))) + +#define ANDES_PWM_CH_CTRL(ch) (0x20 + (0x10 * (ch))) +#define ANDES_PWM_CH_CTRL_MODE_PWM BIT(2) +#define ANDES_PWM_CH_CTRL_CLK BIT(3) +#define ANDES_PWM_CH_CTRL_PARK BIT(4) +#define ANDES_PWM_CH_CTRL_MASK GENMASK(4, 0) + +#define ANDES_PWM_CH_RELOAD(ch) (0x24 + (0x10 * (ch))) +#define ANDES_PWM_CH_RELOAD_HIGH GENMASK(31, 16) +#define ANDES_PWM_CH_RELOAD_LOW GENMASK(15, 0) + +#define ANDES_PWM_CH_COUNTER(ch) (0x28 + (0x10 * (ch))) + +#define ANDES_PWM_CH_MAX 4 +#define ANDES_PWM_CYCLE_MIN 1 +#define ANDES_PWM_CYCLE_MAX 0x10000 + +struct andes_pwm { + struct regmap *regmap; + struct clk *pclk; + struct clk *extclk; + unsigned int pclk_rate; + unsigned int extclk_rate; +}; + +static const struct regmap_config andes_pwm_regmap_config = { + .name = "andes_pwm", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .pad_bits = 0, + .max_register = ANDES_PWM_CH_COUNTER(ANDES_PWM_CH_MAX - 1), + .cache_type = REGCACHE_NONE, +}; + +static inline struct andes_pwm *andes_pwm_from_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static int andes_pwm_enable(struct pwm_chip *chip, unsigned int channel, + bool enable) +{ + struct andes_pwm *ap = andes_pwm_from_chip(chip); + + return regmap_assign_bits(ap->regmap, ANDES_PWM_CH_ENABLE, + ANDES_PWM_CH_ENABLE_PWM(channel), enable); +} + +/* + * Hold the output at a constant level by parking the disabled channel. A + * disabled channel drives its output to the PARK level (low when @high is + * false, high when @high is true), which is used to emulate a 0% or 100% + * relative duty cycle. + */ +static int andes_pwm_park(struct pwm_chip *chip, unsigned int channel, + bool high) +{ + struct andes_pwm *ap = andes_pwm_from_chip(chip); + int ret; + + ret = regmap_assign_bits(ap->regmap, ANDES_PWM_CH_CTRL(channel), + ANDES_PWM_CH_CTRL_PARK, high); + if (ret) + return ret; + + return andes_pwm_enable(chip, channel, false); +} + +static int andes_pwm_config(struct pwm_chip *chip, unsigned int channel, + const struct pwm_state *state) +{ + struct andes_pwm *ap = andes_pwm_from_chip(chip); + unsigned int clk_rate = ap->extclk_rate; + unsigned int ctrl = ANDES_PWM_CH_CTRL_MODE_PWM; + bool use_pclk = false; + u64 high_cycles; + u64 low_cycles; + u64 period_cycles; + u64 duty_cycles; + u32 reload; + + /* + * Reload register for PWM mode: + * + * 31 : 16 15 : 0 + * PWM16_Hi | PWM16_Lo + * + * The high duration is (PWM16_Hi + 1) cycles and the low duration is + * (PWM16_Lo + 1) cycles, so each phase spans ANDES_PWM_CYCLE_MIN to + * ANDES_PWM_CYCLE_MAX cycles. The hardware period (their sum) can reach + * 2 * ANDES_PWM_CYCLE_MAX cycles, but the PWM core requires the period + * to be chosen from the requested period alone, independent of the duty + * cycle. That holds only while both phases stay within + * ANDES_PWM_CYCLE_MAX for every duty split, so the usable period is + * capped at ANDES_PWM_CYCLE_MAX + ANDES_PWM_CYCLE_MIN cycles. + * + * The controller has two clock sources, the APB clock and an external + * clock. Since the external clock frequency must be slower than the APB + * clock, it is tried first for its wider period range; the APB clock is + * used only when the external clock resolves fewer than two cycles (or + * is absent) and is itself usable. + */ + period_cycles = mul_u64_u64_div_u64(clk_rate, state->period, + NSEC_PER_SEC); + if (period_cycles < 2 * ANDES_PWM_CYCLE_MIN && ap->pclk_rate) { + use_pclk = true; + clk_rate = ap->pclk_rate; + period_cycles = mul_u64_u64_div_u64(clk_rate, state->period, + NSEC_PER_SEC); + } + + /* + * A single cycle is still usable: the duty cycle can then only be zero + * or one cycle, and both are emitted by parking the channel below. + */ + if (!period_cycles) + return -EINVAL; + + /* + * Round the period down to the largest value representable for every + * duty cycle, so the chosen period depends on the requested period + * alone. With both phases capped at ANDES_PWM_CYCLE_MAX, that bound is + * ANDES_PWM_CYCLE_MAX + ANDES_PWM_CYCLE_MIN cycles. + */ + period_cycles = min_t(u64, period_cycles, + ANDES_PWM_CYCLE_MAX + ANDES_PWM_CYCLE_MIN); + + /* The duty cycle cannot exceed the (possibly clamped) period. */ + duty_cycles = mul_u64_u64_div_u64(clk_rate, state->duty_cycle, + NSEC_PER_SEC); + duty_cycles = min_t(u64, duty_cycles, period_cycles); + + if (state->polarity == PWM_POLARITY_INVERSED) { + low_cycles = duty_cycles; + high_cycles = period_cycles - low_cycles; + } else { + high_cycles = duty_cycles; + low_cycles = period_cycles - high_cycles; + } + + /* + * A zero-length phase means a 0% or 100% relative duty cycle, which the + * hardware cannot run. Emit the matching constant level by parking the + * channel: high_cycles == 0 stays low, low_cycles == 0 stays high. + */ + if (!high_cycles) + return andes_pwm_park(chip, channel, false); + if (!low_cycles) + return andes_pwm_park(chip, channel, true); + + /* + * A change of clock source takes effect immediately, modifying the + * current output. Otherwise there is no glitch as the currently running + * period is completed before the new settings take effect. + */ + ctrl |= use_pclk ? ANDES_PWM_CH_CTRL_CLK : 0; + ctrl |= (state->polarity == PWM_POLARITY_INVERSED) ? + ANDES_PWM_CH_CTRL_PARK : 0; + regmap_update_bits(ap->regmap, ANDES_PWM_CH_CTRL(channel), + ANDES_PWM_CH_CTRL_MASK, ctrl); + reload = FIELD_PREP(ANDES_PWM_CH_RELOAD_HIGH, high_cycles - 1) | + FIELD_PREP(ANDES_PWM_CH_RELOAD_LOW, low_cycles - 1); + regmap_write(ap->regmap, ANDES_PWM_CH_RELOAD(channel), reload); + + return andes_pwm_enable(chip, channel, true); +} + +static int andes_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + unsigned int channel = pwm->hwpwm; + + if (!state->enabled) { + if (pwm->state.enabled) + andes_pwm_enable(chip, channel, false); + + return 0; + } + + return andes_pwm_config(chip, channel, state); +} + +static int andes_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct andes_pwm *ap = andes_pwm_from_chip(chip); + unsigned int channel = pwm->hwpwm; + unsigned int ctrl; + unsigned int clk_rate; + unsigned int reload; + u64 high_cycles; + u64 low_cycles; + u64 duty_cycles; + + regmap_read(ap->regmap, ANDES_PWM_CH_CTRL(channel), &ctrl); + state->polarity = FIELD_GET(ANDES_PWM_CH_CTRL_PARK, ctrl) ? + PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; + + clk_rate = FIELD_GET(ANDES_PWM_CH_CTRL_CLK, ctrl) ? ap->pclk_rate + : ap->extclk_rate; + if (!clk_rate) { + /* + * The selected clock source is unavailable, so the channel + * cannot be running; report it as disabled and avoid the + * division by zero below. + */ + state->enabled = false; + state->period = 0; + state->duty_cycle = 0; + + return 0; + } + + state->enabled = regmap_test_bits(ap->regmap, ANDES_PWM_CH_ENABLE, + ANDES_PWM_CH_ENABLE_PWM(channel)) > 0; + regmap_read(ap->regmap, ANDES_PWM_CH_RELOAD(channel), &reload); + high_cycles = FIELD_GET(ANDES_PWM_CH_RELOAD_HIGH, reload) + 1; + low_cycles = FIELD_GET(ANDES_PWM_CH_RELOAD_LOW, reload) + 1; + + if (state->polarity == PWM_POLARITY_INVERSED) + duty_cycles = low_cycles; + else + duty_cycles = high_cycles; + + /* + * high_cycles and low_cycles are each at most ANDES_PWM_CYCLE_MAX + * (0x10000, 17 bits) and NSEC_PER_SEC is below 2^30, so the products + * below are safe from 64-bit overflow. + */ + state->duty_cycle = DIV_ROUND_UP_ULL(duty_cycles * NSEC_PER_SEC, + clk_rate); + state->period = DIV_ROUND_UP_ULL((high_cycles + low_cycles) * + NSEC_PER_SEC, clk_rate); + + return 0; +} + +static const struct pwm_ops andes_pwm_ops = { + .apply = andes_pwm_apply, + .get_state = andes_pwm_get_state, +}; + +static int andes_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwm_chip *chip; + struct andes_pwm *ap; + void __iomem *reg_base; + unsigned long pclk_rate; + unsigned long extclk_rate; + int ret; + + chip = devm_pwmchip_alloc(dev, ANDES_PWM_CH_MAX, sizeof(*ap)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + ap = andes_pwm_from_chip(chip); + reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(reg_base)) + return dev_err_probe(dev, PTR_ERR(reg_base), + "Failed to map I/O space\n"); + + ap->pclk = devm_clk_get_enabled(dev, "pclk"); + if (IS_ERR(ap->pclk)) + return dev_err_probe(dev, PTR_ERR(ap->pclk), + "Failed to get APB clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, ap->pclk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to lock APB clock rate\n"); + + ap->extclk = devm_clk_get_optional_enabled(dev, "extclk"); + if (IS_ERR(ap->extclk)) + return dev_err_probe(dev, PTR_ERR(ap->extclk), + "Failed to get external clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, ap->extclk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to lock external clock rate\n"); + + /* + * If the clock rate is greater than 10^9, there may be an overflow when + * calculating the cycles in andes_pwm_config() + */ + pclk_rate = clk_get_rate(ap->pclk); + extclk_rate = clk_get_rate(ap->extclk); + + ap->pclk_rate = pclk_rate > NSEC_PER_SEC ? 0 : pclk_rate; + ap->extclk_rate = extclk_rate > NSEC_PER_SEC ? 0 : extclk_rate; + + if (!ap->pclk_rate && !ap->extclk_rate) + return dev_err_probe(dev, -EINVAL, + "No usable clock: pclk %lu Hz, extclk %lu Hz\n", + pclk_rate, extclk_rate); + + ap->regmap = devm_regmap_init_mmio(dev, reg_base, + &andes_pwm_regmap_config); + if (IS_ERR(ap->regmap)) + return dev_err_probe(dev, PTR_ERR(ap->regmap), + "Failed to initialize regmap\n"); + + chip->ops = &andes_pwm_ops; + chip->atomic = true; + + 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 andes_pwm_of_match[] = { + { .compatible = "andestech,ae350-pwm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, andes_pwm_of_match); + +static struct platform_driver andes_pwm_driver = { + .driver = { + .name = "andes_pwm", + .of_match_table = andes_pwm_of_match, + }, + .probe = andes_pwm_probe, +}; +module_platform_driver(andes_pwm_driver); + +MODULE_AUTHOR("Ben Zong-You Xie <ben717@andestech.com>"); +MODULE_DESCRIPTION("Andes PWM driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v7 2/3] pwm: add Andes PWM driver support @ 2026-08-12 3:24 ` Ben Zong-You Xie 0 siblings, 0 replies; 9+ messages in thread From: Ben Zong-You Xie @ 2026-08-12 3:24 UTC (permalink / raw) To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-pwm, devicetree, linux-kernel, Ben Zong-You Xie Add a driver for the PWM controller found in Andes AE350 platforms and QiLai SoCs. The Andes PWM controller features: - 4 independent channels. - Dual clock source support (APB clock and external clock) to provide a flexible range of frequencies. - Support for normal and inversed polarity. The driver implements the .apply() and .get_state() callbacks. Since the clock source of each channel can be selected by programming the register, clock selection logic is implemented to prioritize the external clock to maximize the supported period range, falling back to the APB clock for higher frequency requirements. Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> --- drivers/pwm/Kconfig | 12 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-andes.c | 365 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 378 insertions(+) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 729776086879..4b5d7d037284 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -73,6 +73,18 @@ config PWM_AIROHA To compile this driver as a module, choose M here: the module will be called pwm-airoha. +config PWM_ANDES + tristate "Andes PWM support" + depends on ARCH_ANDES || COMPILE_TEST + depends on HAS_IOMEM && COMMON_CLK + select REGMAP_MMIO + help + Generic PWM framework driver for Andes platform, such as QiLai SoC + and AE350 platform. + + To compile this driver as a module, choose M here: the module + will be called pwm-andes. + config PWM_APPLE tristate "Apple SoC PWM support" depends on ARCH_APPLE || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 5630a521a7cf..c92369ee251d 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o obj-$(CONFIG_PWM_AIROHA) += pwm-airoha.o +obj-$(CONFIG_PWM_ANDES) += pwm-andes.o obj-$(CONFIG_PWM_APPLE) += pwm-apple.o obj-$(CONFIG_PWM_ARGON_FAN_HAT) += pwm-argon-fan-hat.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o diff --git a/drivers/pwm/pwm-andes.c b/drivers/pwm/pwm-andes.c new file mode 100644 index 000000000000..8dafce7c467c --- /dev/null +++ b/drivers/pwm/pwm-andes.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Andes PWM, used in Andes AE350 platform and QiLai SoC + * + * Copyright (C) 2026 Andes Technology Corporation. + * + * Limitations: + * - When disabling a channel, the current period is not completed and the + * output is driven to the PARK level (low when ANDES_PWM_CH_CTRL_PARK is + * clear, high when it is set). + * - A change of clock source takes effect immediately, modifying the current + * output. Otherwise there is no glitch as the currently running period is + * completed before the new settings take effect. + * - The hardware cannot run a 0% or 100% relative duty cycle; the driver + * emulates these by disabling the channel and parking the output at the + * constant level. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/math64.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/time.h> +#include <linux/types.h> + +#define ANDES_PWM_CH_ENABLE 0x1C +#define ANDES_PWM_CH_ENABLE_PWM(ch) BIT(3 + (4 * (ch))) + +#define ANDES_PWM_CH_CTRL(ch) (0x20 + (0x10 * (ch))) +#define ANDES_PWM_CH_CTRL_MODE_PWM BIT(2) +#define ANDES_PWM_CH_CTRL_CLK BIT(3) +#define ANDES_PWM_CH_CTRL_PARK BIT(4) +#define ANDES_PWM_CH_CTRL_MASK GENMASK(4, 0) + +#define ANDES_PWM_CH_RELOAD(ch) (0x24 + (0x10 * (ch))) +#define ANDES_PWM_CH_RELOAD_HIGH GENMASK(31, 16) +#define ANDES_PWM_CH_RELOAD_LOW GENMASK(15, 0) + +#define ANDES_PWM_CH_COUNTER(ch) (0x28 + (0x10 * (ch))) + +#define ANDES_PWM_CH_MAX 4 +#define ANDES_PWM_CYCLE_MIN 1 +#define ANDES_PWM_CYCLE_MAX 0x10000 + +struct andes_pwm { + struct regmap *regmap; + struct clk *pclk; + struct clk *extclk; + unsigned int pclk_rate; + unsigned int extclk_rate; +}; + +static const struct regmap_config andes_pwm_regmap_config = { + .name = "andes_pwm", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .pad_bits = 0, + .max_register = ANDES_PWM_CH_COUNTER(ANDES_PWM_CH_MAX - 1), + .cache_type = REGCACHE_NONE, +}; + +static inline struct andes_pwm *andes_pwm_from_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static int andes_pwm_enable(struct pwm_chip *chip, unsigned int channel, + bool enable) +{ + struct andes_pwm *ap = andes_pwm_from_chip(chip); + + return regmap_assign_bits(ap->regmap, ANDES_PWM_CH_ENABLE, + ANDES_PWM_CH_ENABLE_PWM(channel), enable); +} + +/* + * Hold the output at a constant level by parking the disabled channel. A + * disabled channel drives its output to the PARK level (low when @high is + * false, high when @high is true), which is used to emulate a 0% or 100% + * relative duty cycle. + */ +static int andes_pwm_park(struct pwm_chip *chip, unsigned int channel, + bool high) +{ + struct andes_pwm *ap = andes_pwm_from_chip(chip); + int ret; + + ret = regmap_assign_bits(ap->regmap, ANDES_PWM_CH_CTRL(channel), + ANDES_PWM_CH_CTRL_PARK, high); + if (ret) + return ret; + + return andes_pwm_enable(chip, channel, false); +} + +static int andes_pwm_config(struct pwm_chip *chip, unsigned int channel, + const struct pwm_state *state) +{ + struct andes_pwm *ap = andes_pwm_from_chip(chip); + unsigned int clk_rate = ap->extclk_rate; + unsigned int ctrl = ANDES_PWM_CH_CTRL_MODE_PWM; + bool use_pclk = false; + u64 high_cycles; + u64 low_cycles; + u64 period_cycles; + u64 duty_cycles; + u32 reload; + + /* + * Reload register for PWM mode: + * + * 31 : 16 15 : 0 + * PWM16_Hi | PWM16_Lo + * + * The high duration is (PWM16_Hi + 1) cycles and the low duration is + * (PWM16_Lo + 1) cycles, so each phase spans ANDES_PWM_CYCLE_MIN to + * ANDES_PWM_CYCLE_MAX cycles. The hardware period (their sum) can reach + * 2 * ANDES_PWM_CYCLE_MAX cycles, but the PWM core requires the period + * to be chosen from the requested period alone, independent of the duty + * cycle. That holds only while both phases stay within + * ANDES_PWM_CYCLE_MAX for every duty split, so the usable period is + * capped at ANDES_PWM_CYCLE_MAX + ANDES_PWM_CYCLE_MIN cycles. + * + * The controller has two clock sources, the APB clock and an external + * clock. Since the external clock frequency must be slower than the APB + * clock, it is tried first for its wider period range; the APB clock is + * used only when the external clock resolves fewer than two cycles (or + * is absent) and is itself usable. + */ + period_cycles = mul_u64_u64_div_u64(clk_rate, state->period, + NSEC_PER_SEC); + if (period_cycles < 2 * ANDES_PWM_CYCLE_MIN && ap->pclk_rate) { + use_pclk = true; + clk_rate = ap->pclk_rate; + period_cycles = mul_u64_u64_div_u64(clk_rate, state->period, + NSEC_PER_SEC); + } + + /* + * A single cycle is still usable: the duty cycle can then only be zero + * or one cycle, and both are emitted by parking the channel below. + */ + if (!period_cycles) + return -EINVAL; + + /* + * Round the period down to the largest value representable for every + * duty cycle, so the chosen period depends on the requested period + * alone. With both phases capped at ANDES_PWM_CYCLE_MAX, that bound is + * ANDES_PWM_CYCLE_MAX + ANDES_PWM_CYCLE_MIN cycles. + */ + period_cycles = min_t(u64, period_cycles, + ANDES_PWM_CYCLE_MAX + ANDES_PWM_CYCLE_MIN); + + /* The duty cycle cannot exceed the (possibly clamped) period. */ + duty_cycles = mul_u64_u64_div_u64(clk_rate, state->duty_cycle, + NSEC_PER_SEC); + duty_cycles = min_t(u64, duty_cycles, period_cycles); + + if (state->polarity == PWM_POLARITY_INVERSED) { + low_cycles = duty_cycles; + high_cycles = period_cycles - low_cycles; + } else { + high_cycles = duty_cycles; + low_cycles = period_cycles - high_cycles; + } + + /* + * A zero-length phase means a 0% or 100% relative duty cycle, which the + * hardware cannot run. Emit the matching constant level by parking the + * channel: high_cycles == 0 stays low, low_cycles == 0 stays high. + */ + if (!high_cycles) + return andes_pwm_park(chip, channel, false); + if (!low_cycles) + return andes_pwm_park(chip, channel, true); + + /* + * A change of clock source takes effect immediately, modifying the + * current output. Otherwise there is no glitch as the currently running + * period is completed before the new settings take effect. + */ + ctrl |= use_pclk ? ANDES_PWM_CH_CTRL_CLK : 0; + ctrl |= (state->polarity == PWM_POLARITY_INVERSED) ? + ANDES_PWM_CH_CTRL_PARK : 0; + regmap_update_bits(ap->regmap, ANDES_PWM_CH_CTRL(channel), + ANDES_PWM_CH_CTRL_MASK, ctrl); + reload = FIELD_PREP(ANDES_PWM_CH_RELOAD_HIGH, high_cycles - 1) | + FIELD_PREP(ANDES_PWM_CH_RELOAD_LOW, low_cycles - 1); + regmap_write(ap->regmap, ANDES_PWM_CH_RELOAD(channel), reload); + + return andes_pwm_enable(chip, channel, true); +} + +static int andes_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + unsigned int channel = pwm->hwpwm; + + if (!state->enabled) { + if (pwm->state.enabled) + andes_pwm_enable(chip, channel, false); + + return 0; + } + + return andes_pwm_config(chip, channel, state); +} + +static int andes_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct andes_pwm *ap = andes_pwm_from_chip(chip); + unsigned int channel = pwm->hwpwm; + unsigned int ctrl; + unsigned int clk_rate; + unsigned int reload; + u64 high_cycles; + u64 low_cycles; + u64 duty_cycles; + + regmap_read(ap->regmap, ANDES_PWM_CH_CTRL(channel), &ctrl); + state->polarity = FIELD_GET(ANDES_PWM_CH_CTRL_PARK, ctrl) ? + PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; + + clk_rate = FIELD_GET(ANDES_PWM_CH_CTRL_CLK, ctrl) ? ap->pclk_rate + : ap->extclk_rate; + if (!clk_rate) { + /* + * The selected clock source is unavailable, so the channel + * cannot be running; report it as disabled and avoid the + * division by zero below. + */ + state->enabled = false; + state->period = 0; + state->duty_cycle = 0; + + return 0; + } + + state->enabled = regmap_test_bits(ap->regmap, ANDES_PWM_CH_ENABLE, + ANDES_PWM_CH_ENABLE_PWM(channel)) > 0; + regmap_read(ap->regmap, ANDES_PWM_CH_RELOAD(channel), &reload); + high_cycles = FIELD_GET(ANDES_PWM_CH_RELOAD_HIGH, reload) + 1; + low_cycles = FIELD_GET(ANDES_PWM_CH_RELOAD_LOW, reload) + 1; + + if (state->polarity == PWM_POLARITY_INVERSED) + duty_cycles = low_cycles; + else + duty_cycles = high_cycles; + + /* + * high_cycles and low_cycles are each at most ANDES_PWM_CYCLE_MAX + * (0x10000, 17 bits) and NSEC_PER_SEC is below 2^30, so the products + * below are safe from 64-bit overflow. + */ + state->duty_cycle = DIV_ROUND_UP_ULL(duty_cycles * NSEC_PER_SEC, + clk_rate); + state->period = DIV_ROUND_UP_ULL((high_cycles + low_cycles) * + NSEC_PER_SEC, clk_rate); + + return 0; +} + +static const struct pwm_ops andes_pwm_ops = { + .apply = andes_pwm_apply, + .get_state = andes_pwm_get_state, +}; + +static int andes_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwm_chip *chip; + struct andes_pwm *ap; + void __iomem *reg_base; + unsigned long pclk_rate; + unsigned long extclk_rate; + int ret; + + chip = devm_pwmchip_alloc(dev, ANDES_PWM_CH_MAX, sizeof(*ap)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + ap = andes_pwm_from_chip(chip); + reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(reg_base)) + return dev_err_probe(dev, PTR_ERR(reg_base), + "Failed to map I/O space\n"); + + ap->pclk = devm_clk_get_enabled(dev, "pclk"); + if (IS_ERR(ap->pclk)) + return dev_err_probe(dev, PTR_ERR(ap->pclk), + "Failed to get APB clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, ap->pclk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to lock APB clock rate\n"); + + ap->extclk = devm_clk_get_optional_enabled(dev, "extclk"); + if (IS_ERR(ap->extclk)) + return dev_err_probe(dev, PTR_ERR(ap->extclk), + "Failed to get external clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, ap->extclk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to lock external clock rate\n"); + + /* + * If the clock rate is greater than 10^9, there may be an overflow when + * calculating the cycles in andes_pwm_config() + */ + pclk_rate = clk_get_rate(ap->pclk); + extclk_rate = clk_get_rate(ap->extclk); + + ap->pclk_rate = pclk_rate > NSEC_PER_SEC ? 0 : pclk_rate; + ap->extclk_rate = extclk_rate > NSEC_PER_SEC ? 0 : extclk_rate; + + if (!ap->pclk_rate && !ap->extclk_rate) + return dev_err_probe(dev, -EINVAL, + "No usable clock: pclk %lu Hz, extclk %lu Hz\n", + pclk_rate, extclk_rate); + + ap->regmap = devm_regmap_init_mmio(dev, reg_base, + &andes_pwm_regmap_config); + if (IS_ERR(ap->regmap)) + return dev_err_probe(dev, PTR_ERR(ap->regmap), + "Failed to initialize regmap\n"); + + chip->ops = &andes_pwm_ops; + chip->atomic = true; + + 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 andes_pwm_of_match[] = { + { .compatible = "andestech,ae350-pwm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, andes_pwm_of_match); + +static struct platform_driver andes_pwm_driver = { + .driver = { + .name = "andes_pwm", + .of_match_table = andes_pwm_of_match, + }, + .probe = andes_pwm_probe, +}; +module_platform_driver(andes_pwm_driver); + +MODULE_AUTHOR("Ben Zong-You Xie <ben717@andestech.com>"); +MODULE_DESCRIPTION("Andes PWM driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v7 2/3] pwm: add Andes PWM driver support 2026-08-12 3:24 ` Ben Zong-You Xie (?) @ 2026-08-12 3:34 ` sashiko-bot -1 siblings, 0 replies; 9+ messages in thread From: sashiko-bot @ 2026-08-12 3:34 UTC (permalink / raw) To: Ben Zong-You Xie; +Cc: robh, devicetree, conor+dt Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] The driver fails to update the inactive output state (PARK bit) when disabling the PWM channel, ignoring polarity changes and leaving the output at the active level if previously at 100% duty cycle. - [High] Emulating 0% or 100% duty cycles leaves RELOAD registers stale and breaks `pwm_get_state` reporting by corrupting polarity, enabled status, and duty cycle calculations. -- --- Patch [2]: [PATCH v7 2/3] pwm: add Andes PWM driver support --- commit 76e1cbe61be3ddd82abebdaef695376f3fc6a571 Author: Ben Zong-You Xie <ben717@andestech.com> pwm: add Andes PWM driver support Add a driver for the PWM controller found in Andes AE350 platforms and QiLai SoCs. > diff --git a/drivers/pwm/pwm-andes.c b/drivers/pwm/pwm-andes.c > new file mode 100644 > index 0000000000000..8dafce7c467c1 > --- /dev/null > +++ b/drivers/pwm/pwm-andes.c [ ... ] > +static int andes_pwm_config(struct pwm_chip *chip, unsigned int channel, > + const struct pwm_state *state) > +{ [ ... ] > + /* > + * A zero-length phase means a 0% or 100% relative duty cycle, which the > + * hardware cannot run. Emit the matching constant level by parking the > + * channel: high_cycles == 0 stays low, low_cycles == 0 stays high. > + */ > + if (!high_cycles) > + return andes_pwm_park(chip, channel, false); > + if (!low_cycles) > + return andes_pwm_park(chip, channel, true); [Severity: High] Does using the PARK bit to emulate 100% and 0% duty cycles cause incorrect states to be reported by andes_pwm_get_state() later? If a channel is emulating a 100% duty cycle, andes_pwm_park() modifies the PARK bit and disables the channel without updating the RELOAD registers. When andes_pwm_get_state() is later called, it uses the PARK bit to deduce polarity, which incorrectly evaluates to PWM_POLARITY_INVERSED. Additionally, the enabled status will be evaluated as false since the channel was physically disabled, and the unupdated RELOAD register will cause stale period and duty cycle values to be returned. [ ... ] > +static int andes_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, > + const struct pwm_state *state) > +{ > + unsigned int channel = pwm->hwpwm; > + > + if (!state->enabled) { > + if (pwm->state.enabled) > + andes_pwm_enable(chip, channel, false); > + > + return 0; > + } [Severity: High] When disabling the PWM channel, does the driver need to update the PARK bit to ensure the output goes to the correct inactive state based on polarity? If a channel is configured to a 100% duty cycle with NORMAL polarity, andes_pwm_config() will have set the PARK bit to 1. If the PWM is subsequently disabled, andes_pwm_apply() clears the PWM enable bit but does not reset the PARK bit. Since a disabled channel continues to drive the PARK level, might this leave the disabled channel driving the active state (HIGH) instead of the expected inactive state (LOW)? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260812-andes-pwm-v7-0-a806da8185d1@andestech.com?part=2 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v7 3/3] MAINTAINERS: add an entry for Andes PWM driver 2026-08-12 3:24 ` Ben Zong-You Xie @ 2026-08-12 3:24 ` Ben Zong-You Xie -1 siblings, 0 replies; 9+ messages in thread From: Ben Zong-You Xie via B4 Relay @ 2026-08-12 3:24 UTC (permalink / raw) To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-pwm, devicetree, linux-kernel, Ben Zong-You Xie From: Ben Zong-You Xie <ben717@andestech.com> Add an entry for the Andes PWM driver to the MAINTAINERS file. Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..dcf16c40a99e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1938,6 +1938,12 @@ S: Supported F: Documentation/devicetree/bindings/watchdog/andestech,ae350-wdt.yaml F: drivers/watchdog/atcwdt200_wdt.c +ANDES PWM DRIVER +M: Ben Zong-You Xie <ben717@andestech.com> +S: Supported +F: Documentation/devicetree/bindings/pwm/andestech,ae350-pwm.yaml +F: drivers/pwm/pwm-andes.c + ANDROID DRIVERS M: Greg Kroah-Hartman <gregkh@linuxfoundation.org> M: Arve Hjønnevåg <arve@android.com> -- 2.34.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v7 3/3] MAINTAINERS: add an entry for Andes PWM driver @ 2026-08-12 3:24 ` Ben Zong-You Xie 0 siblings, 0 replies; 9+ messages in thread From: Ben Zong-You Xie @ 2026-08-12 3:24 UTC (permalink / raw) To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-pwm, devicetree, linux-kernel, Ben Zong-You Xie Add an entry for the Andes PWM driver to the MAINTAINERS file. Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..dcf16c40a99e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1938,6 +1938,12 @@ S: Supported F: Documentation/devicetree/bindings/watchdog/andestech,ae350-wdt.yaml F: drivers/watchdog/atcwdt200_wdt.c +ANDES PWM DRIVER +M: Ben Zong-You Xie <ben717@andestech.com> +S: Supported +F: Documentation/devicetree/bindings/pwm/andestech,ae350-pwm.yaml +F: drivers/pwm/pwm-andes.c + ANDROID DRIVERS M: Greg Kroah-Hartman <gregkh@linuxfoundation.org> M: Arve Hjønnevåg <arve@android.com> -- 2.34.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-08-12 3:34 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-12 3:24 [PATCH v7 0/3] pwm: add support for Andes platform Ben Zong-You Xie via B4 Relay 2026-08-12 3:24 ` Ben Zong-You Xie 2026-08-12 3:24 ` [PATCH v7 1/3] dt-bindings: pwm: add support for AE350 PWM controller Ben Zong-You Xie via B4 Relay 2026-08-12 3:24 ` Ben Zong-You Xie 2026-08-12 3:24 ` [PATCH v7 2/3] pwm: add Andes PWM driver support Ben Zong-You Xie via B4 Relay 2026-08-12 3:24 ` Ben Zong-You Xie 2026-08-12 3:34 ` sashiko-bot 2026-08-12 3:24 ` [PATCH v7 3/3] MAINTAINERS: add an entry for Andes PWM driver Ben Zong-You Xie via B4 Relay 2026-08-12 3:24 ` Ben Zong-You Xie
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.