* [PATCH v4 0/2] pwm: add Axiado AX3000 PWM support
@ 2026-09-08 8:28 Petar Stepanovic
2026-09-08 8:28 ` [PATCH v4 1/2] dt-bindings: pwm: add Axiado AX3000 PWM Petar Stepanovic
2026-09-08 8:28 ` [PATCH v4 2/2] pwm: add Axiado AX3000 PWM driver Petar Stepanovic
0 siblings, 2 replies; 5+ messages in thread
From: Petar Stepanovic @ 2026-09-08 8:28 UTC (permalink / raw)
To: Akhila Kavi, Prasad Bolisetty, Uwe Kleine-König, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Harshit Shah
Cc: linux-pwm, devicetree, linux-arm-kernel, linux-kernel, openbmc,
Petar Stepanovic
This series adds support for the PWM controller found on Axiado
AX3000 and AX3005 SoCs.
A new driver is needed because this PWM controller is a SoC-specific
hardware block used on Axiado SoCs. It has its own register layout,
enable control, period configuration, duty-cycle configuration, and
does not match any existing upstream PWM driver.
The controller provides configurable PWM output signals. The driver
exposes the controller through the Linux PWM framework and supports
period and duty-cycle configuration.
The driver converts the requested period and duty cycle from nanoseconds
to hardware clock cycles based on the input clock rate.
The datasheet is not publicly available. Public high-level product
information is available at:
https://axiado.com/products/#AX3080
The register definitions and programming sequence used by this driver
are based on Axiado internal SoC documentation.
Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
---
Changes in v4:
- Corrected the AX3005 compatible ordering to list
axiado,ax3005-pwm before its axiado,ax3000-pwm fallback.
- Changed the disabled-state handling to program a constant-low
waveform,
because disabling the hardware controller drives the output high.
- Added software state tracking so readback reports this constant-low
hardware representation as a logically disabled PWM.
- Link to v3: https://patch.msgid.link/20260811-axiado-ax3000-pwm-v3-0-6ceaccd812cf@axiado.com
Changes in v3:
- Express AX3005 compatibility with AX3000 using a fallback compatible.
- Update the Devicetree binding example accordingly.
- Add 0% duty-cycle support using the hardware constant-low encoding.
- Cache the rounded period for 0% duty-cycle waveform readback.
- Allow sub-cycle duty requests to round down to 0% instead of rounding
up to one clock cycle.
- Normalize high-time values greater than the period to 100% duty.
- Drop the redundant AX3005 OF match entry and use the AX3000 fallback.
- Clarify comments and PWM hardware limitations.
- Link to v2: https://lore.kernel.org/r/20260723-axiado-ax3000-pwm-v2-0-22002f0b281d@axiado.com
Changes in v2:
- Migrated the driver from the legacy apply/get_state API to the new
waveform ops API (round_waveform_tohw/fromhw, read/write_waveform).
- Cache the clock rate at probe and lock it with
devm_clk_rate_exclusive_get() instead of reading it on every op.
- Improved rounding: clamp period/duty to hardware limits, reject
non-zero duty offset, preserve exact 100% duty, round sub-cycle duty
up to the minimum.
- Added a "Limitations" comment block documenting hardware constraints.
- Renamed macros from AX_PWM_* to AXIADO_PWM_* and dropped unused ones.
- DT binding: dropped clock-names, made #pwm-cells required, and
updated the description and compatible list to cover both the AX3000
and AX3005 SoCs.
- Added axiado,ax3005-pwm to the driver's OF device match table.
- Link to v1: https://lore.kernel.org/r/20260618-axiado-ax3000-pwm-v1-0-c9797a909414@axiado.com
To: Petar Stepanovic <pstepanovic@axiado.com>
To: Akhila Kavi <akavi@axiado.com>
To: Prasad Bolisetty <pbolisetty@axiado.com>
To: Uwe Kleine-König <ukleinek@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Harshit Shah <hshah@axiado.com>
Cc: linux-pwm@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
---
Petar Stepanovic (2):
dt-bindings: pwm: add Axiado AX3000 PWM
pwm: add Axiado AX3000 PWM driver
.../devicetree/bindings/pwm/axiado,ax3000-pwm.yaml | 53 ++++
MAINTAINERS | 9 +
drivers/pwm/Kconfig | 11 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-axiado.c | 332 +++++++++++++++++++++
5 files changed, 406 insertions(+)
---
base-commit: 368d34807e6d04bb2089918383b58abf40e720a0
change-id: 20260518-axiado-ax3000-pwm-cd7c346849f1
Best regards,
--
Petar Stepanovic <pstepanovic@axiado.com>
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v4 1/2] dt-bindings: pwm: add Axiado AX3000 PWM 2026-09-08 8:28 [PATCH v4 0/2] pwm: add Axiado AX3000 PWM support Petar Stepanovic @ 2026-09-08 8:28 ` Petar Stepanovic 2026-09-08 17:56 ` Conor Dooley 2026-09-08 8:28 ` [PATCH v4 2/2] pwm: add Axiado AX3000 PWM driver Petar Stepanovic 1 sibling, 1 reply; 5+ messages in thread From: Petar Stepanovic @ 2026-09-08 8:28 UTC (permalink / raw) To: Akhila Kavi, Prasad Bolisetty, Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah Cc: linux-pwm, devicetree, linux-arm-kernel, linux-kernel, openbmc, Petar Stepanovic The Axiado AX3000 and AX3005 SoCs include PWM controllers that can be used to generate configurable PWM output signals. Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com> --- .../devicetree/bindings/pwm/axiado,ax3000-pwm.yaml | 53 ++++++++++++++++++++++ MAINTAINERS | 8 ++++ 2 files changed, 61 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/axiado,ax3000-pwm.yaml b/Documentation/devicetree/bindings/pwm/axiado,ax3000-pwm.yaml new file mode 100644 index 000000000000..3b73b233da86 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/axiado,ax3000-pwm.yaml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/axiado,ax3000-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Axiado AX3000 PWM controller + +maintainers: + - Petar Stepanovic <pstepanovic@axiado.com> + - Akhila Kavi <akavi@axiado.com> + - Prasad Bolisetty <pbolisetty@axiado.com> + +description: + The Axiado PWM controller found on the AX3000 and AX3005 SoCs. + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + oneOf: + - const: axiado,ax3000-pwm + - items: + - const: axiado,ax3005-pwm + - const: axiado,ax3000-pwm + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + "#pwm-cells": + const: 2 + +required: + - compatible + - reg + - clocks + - "#pwm-cells" + +additionalProperties: false + +examples: + - | + pwm@80801c00 { + compatible = "axiado,ax3000-pwm"; + reg = <0x80801c00 0x1000>; + #pwm-cells = <2>; + clocks = <&clk>; + }; + diff --git a/MAINTAINERS b/MAINTAINERS index b2040011a386..9999480376cc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4312,6 +4312,14 @@ S: Orphan F: Documentation/devicetree/bindings/sound/axentia,* F: sound/soc/atmel/tse850-pcm5142.c +AXIADO AX3000 PWM DRIVER +M: Petar Stepanovic <pstepanovic@axiado.com> +M: Akhila Kavi <akavi@axiado.com> +M: Prasad Bolisetty <pbolisetty@axiado.com> +L: linux-pwm@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/pwm/axiado,ax3000-pwm.yaml + AXIS ARTPEC ARM64 SoC SUPPORT M: Jesper Nilsson <jesper.nilsson@axis.com> M: Lars Persson <lars.persson@axis.com> -- 2.34.1 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v4 1/2] dt-bindings: pwm: add Axiado AX3000 PWM 2026-09-08 8:28 ` [PATCH v4 1/2] dt-bindings: pwm: add Axiado AX3000 PWM Petar Stepanovic @ 2026-09-08 17:56 ` Conor Dooley 0 siblings, 0 replies; 5+ messages in thread From: Conor Dooley @ 2026-09-08 17:56 UTC (permalink / raw) To: Petar Stepanovic Cc: Akhila Kavi, Prasad Bolisetty, Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah, linux-pwm, devicetree, linux-arm-kernel, linux-kernel, openbmc [-- Attachment #1: Type: text/plain, Size: 75 bytes --] Acked-by: Conor Dooley <conor.dooley@microchip.com> pw-bot: not-applicable [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v4 2/2] pwm: add Axiado AX3000 PWM driver 2026-09-08 8:28 [PATCH v4 0/2] pwm: add Axiado AX3000 PWM support Petar Stepanovic 2026-09-08 8:28 ` [PATCH v4 1/2] dt-bindings: pwm: add Axiado AX3000 PWM Petar Stepanovic @ 2026-09-08 8:28 ` Petar Stepanovic 2026-09-09 10:25 ` Uwe Kleine-König 1 sibling, 1 reply; 5+ messages in thread From: Petar Stepanovic @ 2026-09-08 8:28 UTC (permalink / raw) To: Akhila Kavi, Prasad Bolisetty, Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah Cc: linux-pwm, devicetree, linux-arm-kernel, linux-kernel, openbmc, Petar Stepanovic The Axiado AX3000 and AX3005 SoCs include a single-channel PWM controller. Add a driver supporting period, duty-cycle, and enable-state configuration through the Linux PWM framework. Disabling the hardware controller drives its output high. Implement a logically disabled PWM using the controller's constant-low encoding instead. The same encoding is used for an enabled PWM with a 0% duty cycle, so cache the logical state and requested period for readback. AX3005 uses the same programming model as AX3000 and is supported through the axiado,ax3000-pwm fallback compatible. Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com> --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 11 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-axiado.c | 332 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 345 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 9999480376cc..14eea3bc4d4d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4319,6 +4319,7 @@ M: Prasad Bolisetty <pbolisetty@axiado.com> L: linux-pwm@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/pwm/axiado,ax3000-pwm.yaml +F: drivers/pwm/pwm-axiado.c AXIS ARTPEC ARM64 SoC SUPPORT M: Jesper Nilsson <jesper.nilsson@axis.com> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6f3147518376..76f6c04b0e23 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -129,6 +129,17 @@ config PWM_ATMEL_TCB To compile this driver as a module, choose M here: the module will be called pwm-atmel-tcb. +config PWM_AXIADO + tristate "Axiado PWM support" + depends on ARCH_AXIADO || COMPILE_TEST + depends on HAS_IOMEM + help + PWM framework driver for the PWM controller found on Axiado + AX3000 and AX3005 SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-axiado. + config PWM_AXI_PWMGEN tristate "Analog Devices AXI PWM generator" depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 0dc0d2b69025..4466a29e780a 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_PWM_ARGON_FAN_HAT) += pwm-argon-fan-hat.o 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_AXIADO) += pwm-axiado.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 diff --git a/drivers/pwm/pwm-axiado.c b/drivers/pwm/pwm-axiado.c new file mode 100644 index 000000000000..e1d408809694 --- /dev/null +++ b/drivers/pwm/pwm-axiado.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021-2026 Axiado Corporation. + */ + +/* + * - Supports normal polarity. Configuration changes take effect immediately + * without waiting for the current period to complete. + * - The hardware output remains high when the controller is disabled. + * Therefore, a logically disabled PWM is implemented using the hardware + * constant-low representation. + * - Supported period range: 2 through 0xfffffffe PWM input clock cycles; + * 0xffffffff is reserved by the hardware for a constant-low output. + * Longer periods are clamped to the maximum. + * - 0% duty cycle: Programmed as a constant-low period because the + * hardware interprets a zero high time as a constant-high output. + * Consequently, the requested period cannot be read back while the + * duty cycle is 0%. + * - 100% duty cycle: Fully supported and produces a constant-high output. + */ + +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +/* Register offsets */ +#define AXIADO_PWM_CTRL_REG 0x0000 +#define AXIADO_PWM_PERIOD_REG 0x0004 +#define AXIADO_PWM_HIGH_REG 0x0008 + +/* Period and duty cycle limits */ +#define AXIADO_PWM_PERIOD_MIN 2 +#define AXIADO_PWM_PERIOD_MAX 0xfffffffe +#define AXIADO_PWM_PERIOD_CONST_LOW 0xffffffff +#define AXIADO_PWM_DUTY_MIN 1 + +/* Control register bits */ +#define AXIADO_PWM_CTRL_ENABLE BIT(0) + +struct axiado_pwm_chip { + void __iomem *base; + unsigned long rate; + u32 cached_period; + bool logically_disabled; +}; + +struct axiado_pwm_waveform { + u32 period; + u32 duty; + bool enabled; +}; + +static int +axiado_pwm_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct axiado_pwm_chip *axpwm = pwmchip_get_drvdata(chip); + struct axiado_pwm_waveform *wfhw = _wfhw; + u64 period; + u64 duty; + int ret = 0; + + /* Encode a disabled request as a zeroed hardware waveform. */ + if (!wf->period_length_ns) { + *wfhw = (struct axiado_pwm_waveform) {}; + + return 0; + } + + /* Only an edge-aligned waveform starting at offset zero is supported. */ + if (wf->duty_offset_ns) + return -EINVAL; + + if (wf->duty_length_ns > wf->period_length_ns) + return -EINVAL; + + period = mul_u64_u64_div_u64(wf->period_length_ns, axpwm->rate, + NSEC_PER_SEC); + + if (period < AXIADO_PWM_PERIOD_MIN) { + period = AXIADO_PWM_PERIOD_MIN; + ret = 1; + } else if (period > AXIADO_PWM_PERIOD_MAX) { + period = AXIADO_PWM_PERIOD_MAX; + } + + /* + * Keep the rounded period for a 0% duty cycle. .write_waveform() + * translates it to the hardware constant-low representation. + */ + if (!wf->duty_length_ns) { + *wfhw = (struct axiado_pwm_waveform) { + .period = period, + .duty = 0, + .enabled = true, + }; + + return ret; + } + + duty = mul_u64_u64_div_u64(wf->duty_length_ns, axpwm->rate, + NSEC_PER_SEC); + + /* + * Preserve an exact 100% duty request when the hardware period has + * been clamped. + */ + if (wf->duty_length_ns == wf->period_length_ns) + duty = period; + + /* + * Period clamping can leave the converted duty greater than the + * final hardware period. In that case, clamp it to 100% duty. + */ + if (duty > period) + duty = period; + + *wfhw = (struct axiado_pwm_waveform) { + .period = period, + .duty = duty, + .enabled = true, + }; + + return ret; +} + +static int +axiado_pwm_round_waveform_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + struct axiado_pwm_chip *axpwm = pwmchip_get_drvdata(chip); + const struct axiado_pwm_waveform *wfhw = _wfhw; + + if (!wfhw->enabled) { + *wf = (struct pwm_waveform) { + .period_length_ns = 0, + .duty_length_ns = 0, + .duty_offset_ns = 0, + }; + + return 0; + } + + *wf = (struct pwm_waveform) { + .period_length_ns = + mul_u64_u64_div_u64_roundup(wfhw->period, NSEC_PER_SEC, + axpwm->rate), + .duty_length_ns = + mul_u64_u64_div_u64_roundup(wfhw->duty, NSEC_PER_SEC, + axpwm->rate), + .duty_offset_ns = 0, + }; + + return 0; +} + +static int axiado_pwm_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) +{ + struct axiado_pwm_chip *axpwm = pwmchip_get_drvdata(chip); + struct axiado_pwm_waveform *wfhw = _wfhw; + u32 period; + u32 duty; + u32 ctrl; + + /* + * A logically disabled request is implemented as a constant-low + * hardware waveform because disabling the controller drives the + * output high. + */ + if (axpwm->logically_disabled) { + *wfhw = (struct axiado_pwm_waveform) {}; + return 0; + } + + ctrl = readl(axpwm->base + AXIADO_PWM_CTRL_REG); + period = readl(axpwm->base + AXIADO_PWM_PERIOD_REG); + duty = readl(axpwm->base + AXIADO_PWM_HIGH_REG); + + /* The constant-low encoding doesn't hold the period, so restore it. */ + if (period == AXIADO_PWM_PERIOD_CONST_LOW) { + period = axpwm->cached_period; + duty = 0; + } else if (duty > period) { + duty = period; + } + + *wfhw = (struct axiado_pwm_waveform) { + .period = period, + .duty = duty, + .enabled = !!(ctrl & AXIADO_PWM_CTRL_ENABLE), + }; + + return 0; +} + +static int axiado_pwm_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + struct axiado_pwm_chip *axpwm = pwmchip_get_drvdata(chip); + const struct axiado_pwm_waveform *wfhw = _wfhw; + u32 period = wfhw->period; + u32 duty = wfhw->duty; + + if (!wfhw->enabled) { + /* + * Disabling the controller drives its output high. Implement + * the logically disabled state using the constant-low hardware + * representation instead. + */ + writel(AXIADO_PWM_PERIOD_CONST_LOW, + axpwm->base + AXIADO_PWM_PERIOD_REG); + writel(AXIADO_PWM_DUTY_MIN, + axpwm->base + AXIADO_PWM_HIGH_REG); + writel(AXIADO_PWM_CTRL_ENABLE, + axpwm->base + AXIADO_PWM_CTRL_REG); + axpwm->logically_disabled = true; + + return 0; + } + + /* + * A zero high time produces a constant high output, so use the + * constant-low period encoding for a 0% duty cycle. Keep the high time + * non-zero because a zero value takes precedence over that encoding. + * Cache the requested period for .read_waveform(). + */ + if (!duty) { + axpwm->cached_period = period; + period = AXIADO_PWM_PERIOD_CONST_LOW; + duty = AXIADO_PWM_DUTY_MIN; + } + + /* + * The hardware has no shadow registers. These writes may alter the + * active waveform before the current period has completed. + */ + writel(period, axpwm->base + AXIADO_PWM_PERIOD_REG); + writel(duty, axpwm->base + AXIADO_PWM_HIGH_REG); + writel(AXIADO_PWM_CTRL_ENABLE, axpwm->base + AXIADO_PWM_CTRL_REG); + axpwm->logically_disabled = false; + + return 0; +} + +static const struct pwm_ops axiado_pwm_ops = { + .sizeof_wfhw = sizeof(struct axiado_pwm_waveform), + .round_waveform_tohw = axiado_pwm_round_waveform_tohw, + .round_waveform_fromhw = axiado_pwm_round_waveform_fromhw, + .read_waveform = axiado_pwm_read_waveform, + .write_waveform = axiado_pwm_write_waveform, +}; + +static int axiado_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct axiado_pwm_chip *axpwm; + struct pwm_chip *chip; + struct clk *clk; + int ret; + + chip = devm_pwmchip_alloc(dev, 1, sizeof(*axpwm)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + axpwm = pwmchip_get_drvdata(chip); + + axpwm->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(axpwm->base)) + return dev_err_probe(dev, PTR_ERR(axpwm->base), + "Failed to map registers\n"); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get/enable clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, clk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to lock clock rate\n"); + + axpwm->rate = clk_get_rate(clk); + if (!axpwm->rate) + return dev_err_probe(dev, -EINVAL, + "Failed to get clock rate\n"); + + /* + * Provide a valid fallback period if the bootloader left the + * constant-low encoding programmed. + */ + axpwm->cached_period = AXIADO_PWM_PERIOD_MIN; + + chip->ops = &axiado_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 axiado_pwm_match[] = { + { .compatible = "axiado,ax3000-pwm" }, + { } +}; +MODULE_DEVICE_TABLE(of, axiado_pwm_match); + +static struct platform_driver axiado_pwm_driver = { + .driver = { + .name = "axiado-pwm", + .of_match_table = axiado_pwm_match, + }, + .probe = axiado_pwm_probe, +}; +module_platform_driver(axiado_pwm_driver); + +MODULE_AUTHOR("Axiado Corporation"); +MODULE_DESCRIPTION("Axiado PWM driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v4 2/2] pwm: add Axiado AX3000 PWM driver 2026-09-08 8:28 ` [PATCH v4 2/2] pwm: add Axiado AX3000 PWM driver Petar Stepanovic @ 2026-09-09 10:25 ` Uwe Kleine-König 0 siblings, 0 replies; 5+ messages in thread From: Uwe Kleine-König @ 2026-09-09 10:25 UTC (permalink / raw) To: Petar Stepanovic Cc: Akhila Kavi, Prasad Bolisetty, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah, linux-pwm, devicetree, linux-arm-kernel, linux-kernel, openbmc [-- Attachment #1: Type: text/plain, Size: 10144 bytes --] Hello, On Tue, Sep 08, 2026 at 01:28:35AM -0700, Petar Stepanovic wrote: > The Axiado AX3000 and AX3005 SoCs include a single-channel PWM > controller. > > Add a driver supporting period, duty-cycle, and enable-state > configuration through the Linux PWM framework. > > Disabling the hardware controller drives its output high. Implement a > logically disabled PWM using the controller's constant-low encoding > instead. The same encoding is used for an enabled PWM with a 0% duty > cycle, so cache the logical state and requested period for readback. The purpose of disabling the hardware is to save power. If the result is that the output goes to the high state that's mildly strange, but not unseen. Still disable the hardware in that case. (And any consumer driver that relies on a low output on disable is wrong and needs fixing.) > diff --git a/drivers/pwm/pwm-axiado.c b/drivers/pwm/pwm-axiado.c > new file mode 100644 > index 000000000000..e1d408809694 > --- /dev/null > +++ b/drivers/pwm/pwm-axiado.c > @@ -0,0 +1,332 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Copyright (c) 2021-2026 Axiado Corporation. It's not universally done in the kernel, but I'd be happy to see this formalized as: SPDX-FileCopyrightText: 2021-2026 Axiado Corporation. > + */ > + > +/* Please put "Limitations:" here such that sed -rn '/Limitations:/,/\*\/?$/p' drivers/pwm/*.c gives the details. > + * - Supports normal polarity. Configuration changes take effect immediately > + * without waiting for the current period to complete. > + * - The hardware output remains high when the controller is disabled. > + * Therefore, a logically disabled PWM is implemented using the hardware > + * constant-low representation. > + * - Supported period range: 2 through 0xfffffffe PWM input clock cycles; > + * 0xffffffff is reserved by the hardware for a constant-low output. > + * Longer periods are clamped to the maximum. > + * - 0% duty cycle: Programmed as a constant-low period because the > + * hardware interprets a zero high time as a constant-high output. > + * Consequently, the requested period cannot be read back while the > + * duty cycle is 0%. > + * - 100% duty cycle: Fully supported and produces a constant-high output. > + */ > + > +#include <linux/bits.h> > +#include <linux/clk.h> > +#include <linux/err.h> > +#include <linux/io.h> > +#include <linux/math64.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/pwm.h> > + > +/* Register offsets */ > +#define AXIADO_PWM_CTRL_REG 0x0000 > +#define AXIADO_PWM_PERIOD_REG 0x0004 > +#define AXIADO_PWM_HIGH_REG 0x0008 > + > +/* Period and duty cycle limits */ > +#define AXIADO_PWM_PERIOD_MIN 2 > +#define AXIADO_PWM_PERIOD_MAX 0xfffffffe > +#define AXIADO_PWM_PERIOD_CONST_LOW 0xffffffff > +#define AXIADO_PWM_DUTY_MIN 1 > + > +/* Control register bits */ > +#define AXIADO_PWM_CTRL_ENABLE BIT(0) > + > +struct axiado_pwm_chip { > + void __iomem *base; > + unsigned long rate; > + u32 cached_period; > + bool logically_disabled; > +}; > + > +struct axiado_pwm_waveform { > + u32 period; > + u32 duty; > + bool enabled; > +}; > + > +static int > +axiado_pwm_round_waveform_tohw(struct pwm_chip *chip, > + struct pwm_device *pwm, > + const struct pwm_waveform *wf, > + void *_wfhw) > +{ > + struct axiado_pwm_chip *axpwm = pwmchip_get_drvdata(chip); > + struct axiado_pwm_waveform *wfhw = _wfhw; > + u64 period; > + u64 duty; > + int ret = 0; > + > + /* Encode a disabled request as a zeroed hardware waveform. */ > + if (!wf->period_length_ns) { > + *wfhw = (struct axiado_pwm_waveform) {}; { } please (i.e. a space in the empty initializer). > + return 0; > + } > + > + /* Only an edge-aligned waveform starting at offset zero is supported. */ > + if (wf->duty_offset_ns) > + return -EINVAL; That is wrong. You're supposed to implement the biggest duty_offset_ns not bigger than the requested value. That's 0 and should happen successfully (and silently). > + if (wf->duty_length_ns > wf->period_length_ns) > + return -EINVAL; No need to check that. If you see this happen, that's a bug in the pwm core. > + period = mul_u64_u64_div_u64(wf->period_length_ns, axpwm->rate, > + NSEC_PER_SEC); > + > + if (period < AXIADO_PWM_PERIOD_MIN) { > + period = AXIADO_PWM_PERIOD_MIN; > + ret = 1; > + } else if (period > AXIADO_PWM_PERIOD_MAX) { > + period = AXIADO_PWM_PERIOD_MAX; > + } > + > + /* > + * Keep the rounded period for a 0% duty cycle. .write_waveform() > + * translates it to the hardware constant-low representation. > + */ > + if (!wf->duty_length_ns) { > + *wfhw = (struct axiado_pwm_waveform) { > + .period = period, > + .duty = 0, > + .enabled = true, > + }; This happens also without the special handling, right? So the if could be dropped. > + return ret; > + } > + > + duty = mul_u64_u64_div_u64(wf->duty_length_ns, axpwm->rate, > + NSEC_PER_SEC); > + > + /* > + * Preserve an exact 100% duty request when the hardware period has > + * been clamped. > + */ > + if (wf->duty_length_ns == wf->period_length_ns) > + duty = period; With the check below for duty > period this isn't needed. > + /* > + * Period clamping can leave the converted duty greater than the > + * final hardware period. In that case, clamp it to 100% duty. > + */ > + if (duty > period) > + duty = period; > + > + *wfhw = (struct axiado_pwm_waveform) { > + .period = period, > + .duty = duty, > + .enabled = true, > + }; > + > + return ret; > +} > + > +static int > +axiado_pwm_round_waveform_fromhw(struct pwm_chip *chip, > + struct pwm_device *pwm, > + const void *_wfhw, > + struct pwm_waveform *wf) > +{ > + struct axiado_pwm_chip *axpwm = pwmchip_get_drvdata(chip); > + const struct axiado_pwm_waveform *wfhw = _wfhw; > + > + if (!wfhw->enabled) { > + *wf = (struct pwm_waveform) { > + .period_length_ns = 0, > + .duty_length_ns = 0, > + .duty_offset_ns = 0, > + }; nitpick: setting .period_length_ns = 0 is sufficient here and that's what other drivers do. > + return 0; > + } > + > + *wf = (struct pwm_waveform) { > + .period_length_ns = > + mul_u64_u64_div_u64_roundup(wfhw->period, NSEC_PER_SEC, > + axpwm->rate), > + .duty_length_ns = > + mul_u64_u64_div_u64_roundup(wfhw->duty, NSEC_PER_SEC, > + axpwm->rate), > + .duty_offset_ns = 0, > + }; > + > + return 0; > +} > + > +static int axiado_pwm_read_waveform(struct pwm_chip *chip, > + struct pwm_device *pwm, > + void *_wfhw) > +{ > + struct axiado_pwm_chip *axpwm = pwmchip_get_drvdata(chip); > + struct axiado_pwm_waveform *wfhw = _wfhw; > + u32 period; > + u32 duty; > + u32 ctrl; > + > + /* > + * A logically disabled request is implemented as a constant-low > + * hardware waveform because disabling the controller drives the > + * output high. > + */ > + if (axpwm->logically_disabled) { > + *wfhw = (struct axiado_pwm_waveform) {}; > + return 0; that looks wrong. At least the first readout before .write_waveform() was called might be bogus then. > + } > + > + ctrl = readl(axpwm->base + AXIADO_PWM_CTRL_REG); > + period = readl(axpwm->base + AXIADO_PWM_PERIOD_REG); > + duty = readl(axpwm->base + AXIADO_PWM_HIGH_REG); > + > + /* The constant-low encoding doesn't hold the period, so restore it. */ This isn't needed. If the output is constant low, just report period = 1 and drop .cached_period. > + if (period == AXIADO_PWM_PERIOD_CONST_LOW) { > + period = axpwm->cached_period; > + duty = 0; > + } else if (duty > period) { > + duty = period; > + } > + > + *wfhw = (struct axiado_pwm_waveform) { > + .period = period, > + .duty = duty, > + .enabled = !!(ctrl & AXIADO_PWM_CTRL_ENABLE), > + }; > + > + return 0; > +} > + > +static int axiado_pwm_write_waveform(struct pwm_chip *chip, > + struct pwm_device *pwm, > + const void *_wfhw) > +{ > + struct axiado_pwm_chip *axpwm = pwmchip_get_drvdata(chip); > + const struct axiado_pwm_waveform *wfhw = _wfhw; > + u32 period = wfhw->period; > + u32 duty = wfhw->duty; > + > + if (!wfhw->enabled) { > + /* > + * Disabling the controller drives its output high. Implement > + * the logically disabled state using the constant-low hardware > + * representation instead. > + */ > + writel(AXIADO_PWM_PERIOD_CONST_LOW, > + axpwm->base + AXIADO_PWM_PERIOD_REG); > + writel(AXIADO_PWM_DUTY_MIN, > + axpwm->base + AXIADO_PWM_HIGH_REG); > + writel(AXIADO_PWM_CTRL_ENABLE, > + axpwm->base + AXIADO_PWM_CTRL_REG); > + axpwm->logically_disabled = true; > + > + return 0; > + } > + > + /* > + * A zero high time produces a constant high output, so use the > + * constant-low period encoding for a 0% duty cycle. Keep the high time > + * non-zero because a zero value takes precedence over that encoding. > + * Cache the requested period for .read_waveform(). > + */ > + if (!duty) { > + axpwm->cached_period = period; > + period = AXIADO_PWM_PERIOD_CONST_LOW; > + duty = AXIADO_PWM_DUTY_MIN; > + } > + > + /* > + * The hardware has no shadow registers. These writes may alter the > + * active waveform before the current period has completed. > + */ > + writel(period, axpwm->base + AXIADO_PWM_PERIOD_REG); > + writel(duty, axpwm->base + AXIADO_PWM_HIGH_REG); > + writel(AXIADO_PWM_CTRL_ENABLE, axpwm->base + AXIADO_PWM_CTRL_REG); > + axpwm->logically_disabled = false; Ideally all the special case handling happens in the .round_waveform_tohw() callback such that .write_waveform() is as quick as possible and just writes the content of axiado_pwm_waveform to the registers. > + > + return 0; > +} I was about to write something else, but was interrupted and don't remember. :-\ So I might still find something when I look at your next revision. Best regards Uwe [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-09 10:26 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-08 8:28 [PATCH v4 0/2] pwm: add Axiado AX3000 PWM support Petar Stepanovic 2026-09-08 8:28 ` [PATCH v4 1/2] dt-bindings: pwm: add Axiado AX3000 PWM Petar Stepanovic 2026-09-08 17:56 ` Conor Dooley 2026-09-08 8:28 ` [PATCH v4 2/2] pwm: add Axiado AX3000 PWM driver Petar Stepanovic 2026-09-09 10:25 ` Uwe Kleine-König
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox