From fe6ffefe1458e5fc6d877849eb39ae8d7a24ae45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 23 Sep 2024 16:17:39 +0200 Subject: [PATCH] pwm: Replace axi-pwmgen driver by a backport of the mainline driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backporting involved several changes. These were marked using some cpp magic to simplify updates to newer kernel version and for documentation purposes. The relevant semantical changes are: - The apply callback uses round down now to match the policy upstream. - The compatible string changed. All devicetrees are updated accordingly. - The v1 hardware revision isn't supported any more. The v1 documentation isn't in the public documentations any more, so this is expected to be ok. Signed-off-by: Uwe Kleine-König --- .../bindings/pwm/pwm-axi-pwmgen.yaml | 2 +- .../dts/socfpga_cyclone5_sockit_dc2677a.dts | 2 +- .../arm/boot/dts/zynq-coraz7s-ad7687-pmdz.dts | 2 +- .../arm/boot/dts/zynq-coraz7s-ad7689-ardz.dts | 2 +- arch/arm/boot/dts/zynq-coraz7s-ad7946.dts | 2 +- arch/arm/boot/dts/zynq-coraz7s-ad7984.dts | 2 +- arch/arm/boot/dts/zynq-coraz7s-adaq4003.dts | 2 +- arch/arm/boot/dts/zynq-zed-adv7511-ad4003.dts | 2 +- arch/arm/boot/dts/zynq-zed-adv7511-ad4020.dts | 2 +- .../boot/dts/zynq-zed-adv7511-ad4030-24.dts | 2 +- .../boot/dts/zynq-zed-adv7511-ad4032-24.dts | 2 +- arch/arm/boot/dts/zynq-zed-adv7511-ad4134.dts | 2 +- .../boot/dts/zynq-zed-adv7511-ad4630-16.dts | 2 +- .../boot/dts/zynq-zed-adv7511-ad4630-24.dts | 2 +- arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts | 2 +- arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts | 2 +- arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts | 2 +- .../boot/dts/zynq-zed-adv7511-adaq4003.dts | 2 +- .../boot/dts/zynq-zed-adv7511-adaq4216.dts | 2 +- .../boot/dts/zynq-zed-adv7511-adaq4220.dts | 2 +- .../boot/dts/zynq-zed-adv7511-adaq4224-24.dts | 2 +- ...q-zed-adv7511-adaq4224-24_cm0_sdi4_cz2.dts | 2 +- arch/arm/boot/dts/zynq-zed-adv7511-cn0577.dts | 2 +- .../arm/boot/dts/zynq-zed-adv7511-ltc2387.dts | 2 +- drivers/pwm/pwm-axi-pwmgen.c | 442 +++++++++++------- 25 files changed, 293 insertions(+), 197 deletions(-) diff --git a/Documentation/devicetree/bindings/pwm/pwm-axi-pwmgen.yaml b/Documentation/devicetree/bindings/pwm/pwm-axi-pwmgen.yaml index 9fd965476c156d..6d58a838f81d97 100644 --- a/Documentation/devicetree/bindings/pwm/pwm-axi-pwmgen.yaml +++ b/Documentation/devicetree/bindings/pwm/pwm-axi-pwmgen.yaml @@ -33,7 +33,7 @@ additionalProperties: false examples: - | axi-pwmgen@44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "pwm_out1"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/socfpga_cyclone5_sockit_dc2677a.dts b/arch/arm/boot/dts/socfpga_cyclone5_sockit_dc2677a.dts index eb4c7388517f11..7790bac8bf1291 100644 --- a/arch/arm/boot/dts/socfpga_cyclone5_sockit_dc2677a.dts +++ b/arch/arm/boot/dts/socfpga_cyclone5_sockit_dc2677a.dts @@ -149,7 +149,7 @@ }; axi_pwm_gen: axi-pwm-gen@40000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x00040000 0x1000>; #pwm-cells = <2>; clocks = <&sys_clk>; diff --git a/arch/arm/boot/dts/zynq-coraz7s-ad7687-pmdz.dts b/arch/arm/boot/dts/zynq-coraz7s-ad7687-pmdz.dts index 9b6079b4a4950a..f87cd3e5558975 100644 --- a/arch/arm/boot/dts/zynq-coraz7s-ad7687-pmdz.dts +++ b/arch/arm/boot/dts/zynq-coraz7s-ad7687-pmdz.dts @@ -27,7 +27,7 @@ &fpga_axi { adc_trigger: pwm@0x44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "adc_conversion_trigger"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-coraz7s-ad7689-ardz.dts b/arch/arm/boot/dts/zynq-coraz7s-ad7689-ardz.dts index 10efeae186205b..01f553ddecf586 100644 --- a/arch/arm/boot/dts/zynq-coraz7s-ad7689-ardz.dts +++ b/arch/arm/boot/dts/zynq-coraz7s-ad7689-ardz.dts @@ -27,7 +27,7 @@ &fpga_axi { adc_trigger: pwm@0x44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "adc_conversion_trigger"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-coraz7s-ad7946.dts b/arch/arm/boot/dts/zynq-coraz7s-ad7946.dts index 51cad0a20e9ff0..e7d17b0997516e 100644 --- a/arch/arm/boot/dts/zynq-coraz7s-ad7946.dts +++ b/arch/arm/boot/dts/zynq-coraz7s-ad7946.dts @@ -27,7 +27,7 @@ &fpga_axi { adc_trigger: pwm@0x44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "adc_conversion_trigger"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-coraz7s-ad7984.dts b/arch/arm/boot/dts/zynq-coraz7s-ad7984.dts index 2c5d889434b48e..e9726618785dd0 100644 --- a/arch/arm/boot/dts/zynq-coraz7s-ad7984.dts +++ b/arch/arm/boot/dts/zynq-coraz7s-ad7984.dts @@ -27,7 +27,7 @@ &fpga_axi { adc_trigger: pwm@0x44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "adc_conversion_trigger"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-coraz7s-adaq4003.dts b/arch/arm/boot/dts/zynq-coraz7s-adaq4003.dts index bffb364e7b798a..9d515fdf797a45 100644 --- a/arch/arm/boot/dts/zynq-coraz7s-adaq4003.dts +++ b/arch/arm/boot/dts/zynq-coraz7s-adaq4003.dts @@ -26,7 +26,7 @@ &fpga_axi { adc_trigger: pwm@0x44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "adc_conversion_trigger"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad4003.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad4003.dts index 334fd00e8f739b..61408e417421ae 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad4003.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad4003.dts @@ -64,7 +64,7 @@ }; axi_pwm_gen: axi-pwm-gen@44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "adc_conversion_trigger"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad4020.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad4020.dts index 97f2252044d13a..b68a797e0f3b9d 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad4020.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad4020.dts @@ -65,7 +65,7 @@ }; axi_pwm_gen: axi-pwm-gen@44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "adc_conversion_trigger"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad4030-24.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad4030-24.dts index fc917ff474598b..b50725d0d38234 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad4030-24.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad4030-24.dts @@ -81,7 +81,7 @@ }; axi_pwm_gen: axi-pwm-gen@ { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "ad463x_cnv"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad4032-24.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad4032-24.dts index ef2b3e9df2be30..e82f0c4b4dd68f 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad4032-24.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad4032-24.dts @@ -81,7 +81,7 @@ }; axi_pwm_gen: axi-pwm-gen@ { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "ad463x_cnv"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad4134.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad4134.dts index fa141dc704bc1b..e7e90a1d23bc69 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad4134.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad4134.dts @@ -101,7 +101,7 @@ }; ad4134_odr_generator: odr_generator@44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x10000>; #pwm-cells = <2>; clocks = <&clkc 15>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad4630-16.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad4630-16.dts index 84b95c21bb3196..f11745fe445793 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad4630-16.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad4630-16.dts @@ -81,7 +81,7 @@ }; axi_pwm_gen: axi-pwm-gen@ { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "ad463x_cnv"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad4630-24.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad4630-24.dts index dbb01c869ad9cf..674a28728ec76a 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad4630-24.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad4630-24.dts @@ -81,7 +81,7 @@ }; axi_pwm_gen: axi-pwm-gen@ { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "ad463x_cnv"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts index 22fc09a540022b..e2accfa9466d09 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad7944.dts @@ -54,7 +54,7 @@ &fpga_axi { adc_trigger: pwm@44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; #pwm-cells = <2>; clocks = <&spi_clk>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts index 56c7646f5740cc..4c672ec2ba9c08 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad7985.dts @@ -54,7 +54,7 @@ &fpga_axi { adc_trigger: pwm@44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; #pwm-cells = <2>; clocks = <&spi_clk>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts index f9d0cf63ca8e09..3ba8596ce3e1b2 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ad7986.dts @@ -54,7 +54,7 @@ &fpga_axi { adc_trigger: pwm@44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; #pwm-cells = <2>; clocks = <&spi_clk>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4003.dts b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4003.dts index e99563704164f4..10253dd549b139 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4003.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4003.dts @@ -64,7 +64,7 @@ }; axi_pwm_gen: axi-pwm-gen@44b00000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "adc_conversion_trigger"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4216.dts b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4216.dts index 0d7a15858202c3..c87e3d836fdda6 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4216.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4216.dts @@ -97,7 +97,7 @@ }; axi_pwm_gen: axi-pwm-gen@ { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "ad463x_cnv"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4220.dts b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4220.dts index 6215aec10c2024..91ab19f0465f45 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4220.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4220.dts @@ -108,7 +108,7 @@ }; axi_pwm_gen: axi-pwm-gen@ { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "ad463x_cnv"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4224-24.dts b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4224-24.dts index 6e866c39ae2aa3..2dee96278abf33 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4224-24.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4224-24.dts @@ -108,7 +108,7 @@ }; axi_pwm_gen: axi-pwm-gen@ { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "ad463x_cnv"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4224-24_cm0_sdi4_cz2.dts b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4224-24_cm0_sdi4_cz2.dts index 00a88c04761905..e19c2cb9bcb3cb 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-adaq4224-24_cm0_sdi4_cz2.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-adaq4224-24_cm0_sdi4_cz2.dts @@ -95,7 +95,7 @@ }; axi_pwm_gen: axi-pwm-gen@ { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44b00000 0x1000>; label = "ad463x_cnv"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-cn0577.dts b/arch/arm/boot/dts/zynq-zed-adv7511-cn0577.dts index b8f7fa92afed0c..ea7cdff7e656a5 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-cn0577.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-cn0577.dts @@ -70,7 +70,7 @@ }; axi_pwm_gen: pwm@0x44a60000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44a60000 0x1000>; label = "ltc2387_if"; #pwm-cells = <2>; diff --git a/arch/arm/boot/dts/zynq-zed-adv7511-ltc2387.dts b/arch/arm/boot/dts/zynq-zed-adv7511-ltc2387.dts index 00b641a978b1a6..0d6aaddc384a24 100644 --- a/arch/arm/boot/dts/zynq-zed-adv7511-ltc2387.dts +++ b/arch/arm/boot/dts/zynq-zed-adv7511-ltc2387.dts @@ -57,7 +57,7 @@ }; axi_pwm_gen: pwm@0x44a60000 { - compatible = "adi,axi-pwmgen"; + compatible = "adi,axi-pwmgen-2.00.a"; reg = <0x44a60000 0x1000>; label = "ltc2387_if"; #pwm-cells = <2>; diff --git a/drivers/pwm/pwm-axi-pwmgen.c b/drivers/pwm/pwm-axi-pwmgen.c index 33f95463612b0d..d5c5ba994d7d53 100644 --- a/drivers/pwm/pwm-axi-pwmgen.c +++ b/drivers/pwm/pwm-axi-pwmgen.c @@ -2,265 +2,361 @@ /* * Analog Devices AXI PWM generator * - * Copyright 2020 Analog Devices Inc. + * Copyright 2024 Analog Devices Inc. + * Copyright 2024 Baylibre SAS + * + * Device docs: https://analogdevicesinc.github.io/hdl/library/axi_pwm_gen/index.html + * + * Limitations: + * - The writes to registers for period and duty are shadowed until + * LOAD_CONFIG is written to AXI_PWMGEN_REG_CONFIG, at which point + * they take effect. + * - Writing LOAD_CONFIG also has the effect of re-synchronizing all + * enabled channels, which could cause glitching on other channels. It + * is therefore expected that channels are assigned harmonic periods + * and all have a single user coordinating this. + * - Supports normal polarity. Does not support changing polarity. + * - On disable, the PWM output becomes low (inactive). */ #include #include #include +#include #include #include -#include #include #include +#include #include +#include -#define AXI_PWMGEN_VERSION_MAJOR(x) (((x) >> 16) & 0xff) -#define AXI_PWMGEN_VERSION_MINOR(x) (((x) >> 8) & 0xff) -#define AXI_PWMGEN_VERSION_PATCH(x) ((x) & 0xff) - -#define AXI_PWMGEN_REG_CORE_VERSION 0x00 #define AXI_PWMGEN_REG_ID 0x04 #define AXI_PWMGEN_REG_SCRATCHPAD 0x08 #define AXI_PWMGEN_REG_CORE_MAGIC 0x0C #define AXI_PWMGEN_REG_CONFIG 0x10 #define AXI_PWMGEN_REG_NPWM 0x14 -/* register layout is a bit different between v1 and v2 HDL */ -#define AXI_PWMGEN_V1_CHX_PERIOD(ch) (0x40 + 12 * (ch)) -#define AXI_PWMGEN_V1_CHX_DUTY(ch) (0x44 + 12 * (ch)) -#define AXI_PWMGEN_V1_CHX_PHASE(ch) (0x48 + 12 * (ch)) -#define AXI_PWMGEN_V2_CHX_PERIOD(ch) (0x40 + 4 * (ch)) -#define AXI_PWMGEN_V2_CHX_DUTY(ch) (0x80 + 4 * (ch)) -#define AXI_PWMGEN_V2_CHX_PHASE(ch) (0xC0 + 4 * (ch)) -#define AXI_PWMGEN_CHX_PERIOD(p, ch) \ - ((p)->hw_maj_ver == 1 ? AXI_PWMGEN_V1_CHX_PERIOD(ch) : AXI_PWMGEN_V2_CHX_PERIOD(ch)) -#define AXI_PWMGEN_CHX_DUTY(p, ch) \ - ((p)->hw_maj_ver == 1 ? AXI_PWMGEN_V1_CHX_DUTY(ch) : AXI_PWMGEN_V2_CHX_DUTY(ch)) -#define AXI_PWMGEN_CHX_PHASE(p, ch) \ - ((p)->hw_maj_ver == 1 ? AXI_PWMGEN_V1_CHX_PHASE(ch) : AXI_PWMGEN_V2_CHX_PHASE(ch)) -#define AXI_PWMGEN_TEST_DATA 0x5A0F0081 -#define AXI_PWMGEN_LOAD_CONIG BIT(1) -#define AXI_PWMGEN_RESET BIT(0) - -#define AXI_PWMGEN_N_MAX_PWMS 16 - -struct axi_pwmgen { - struct pwm_chip chip; - struct clk *clk; - void __iomem *base; - u8 hw_maj_ver; -}; +#define AXI_PWMGEN_CHX_PERIOD(ch) (0x40 + (4 * (ch))) +#define AXI_PWMGEN_CHX_DUTY(ch) (0x80 + (4 * (ch))) +#define AXI_PWMGEN_CHX_OFFSET(ch) (0xC0 + (4 * (ch))) +#define AXI_PWMGEN_REG_CORE_MAGIC_VAL 0x601A3471 /* Identification number to test during setup */ +#define AXI_PWMGEN_LOAD_CONFIG BIT(1) +#define AXI_PWMGEN_REG_CONFIG_RESET BIT(0) -static inline unsigned int axi_pwmgen_read(struct axi_pwmgen *pwmgen, - unsigned int reg) -{ - return readl(pwmgen->base + reg); -} +/* + * This driver has some cpp magic to make it work on older and newer kernels. + * Relevant changes are: + * v6.9-rc1~146^2~164 ("pwm: Provide pwmchip_alloc() function and a devm variant of it") + * v6.9-rc1~100^2^6 ("clk: Add a devm variant of clk_rate_exclusive_get()") + * v6.8-rc1~96^2~14 ("pwm: Make it possible to apply PWM changes in atomic context") + * v6.7-rc1~28^2~30 ("pwm: Manage owner assignment implicitly for drivers") + * v6.2-rc1~26^2~12 ("pwm: Make .get_state() callback return an error code") + * Define some cpp symbols for these. This allows to forward port using + * unifdef(1). + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 9, 0) +# define PWM_MISSING_PWMCHIP_ALLOC +# define MISSING_DEVM_CLK_RATE_EXCLUSIVE_GET +#endif -static inline void axi_pwmgen_write(struct axi_pwmgen *pwmgen, - unsigned int reg, - unsigned int value) -{ - writel(value, pwmgen->base + reg); -} +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) +# define PWM_MISSING_ATOMIC +#endif -static void axi_pwmgen_write_mask(struct axi_pwmgen *pwmgen, - unsigned int reg, - unsigned int mask, - unsigned int value) -{ - unsigned int temp; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0) +# define PWM_EXPLICIT_OWNER +#endif - temp = axi_pwmgen_read(pwmgen, reg); - axi_pwmgen_write(pwmgen, reg, (temp & ~mask) | value); -} +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) +# define PWM_GET_STATE_VOID +#endif -static inline struct axi_pwmgen *to_axi_pwmgen(struct pwm_chip *chip) +/* Another difference compared to the mainline driver is support for pwm_state::phase. */ +#define PWM_HAS_PHASE_SUPPORT + +#ifdef MISSING_DEVM_CLK_RATE_EXCLUSIVE_GET +static void devm_clk_rate_exclusive_put(void *data) { - return container_of(chip, struct axi_pwmgen, chip); + struct clk *clk = data; + + clk_rate_exclusive_put(clk); } -#ifndef mul_u64_u64_div_u64_roundclosest -static u64 mul_u64_u64_div_u64_roundclosest(u64 a, u64 b, u64 c) +static int devm_clk_rate_exclusive_get(struct device *dev, struct clk *clk) { - u64 res = mul_u64_u64_div_u64(a, b, c); - /* - * Those multiplications might overflow but after the subtraction the - * error cancels out. - */ - u64 rem = a * b - c * res; + int ret; - if (rem * 2 >= c) - res += 1; + ret = clk_rate_exclusive_get(clk); + if (ret) + return ret; - return res; + return devm_add_action_or_reset(dev, devm_clk_rate_exclusive_put, clk); } #endif +struct axi_pwmgen_ddata { + struct regmap *regmap; + unsigned long clk_rate_hz; +#ifdef PWM_MISSING_PWMCHIP_ALLOC + struct pwm_chip chip; +#endif +}; + +static const struct regmap_config axi_pwmgen_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xFC, +}; + +static struct axi_pwmgen_ddata *axi_pwmgen_ddata_from_chip(struct pwm_chip *chip) +{ +#ifdef PWM_MISSING_PWMCHIP_ALLOC + return container_of(chip, struct axi_pwmgen_ddata, chip); +#else + return pwmchip_get_drvdata(chip); +#endif +} + static int axi_pwmgen_apply(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) + const struct pwm_state *state) { - unsigned long rate; - unsigned long long cnt; + struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip); unsigned int ch = pwm->hwpwm; - struct axi_pwmgen *pwmgen = to_axi_pwmgen(chip); + struct regmap *regmap = ddata->regmap; + u64 period_cnt, duty_cnt; + int ret; - rate = clk_get_rate(pwmgen->clk); + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; - cnt = mul_u64_u64_div_u64_roundclosest(state->period, rate, NSEC_PER_SEC); - if (cnt > U32_MAX) - cnt = U32_MAX; - axi_pwmgen_write(pwmgen, AXI_PWMGEN_CHX_PERIOD(pwmgen, ch), - state->enabled ? cnt : 0); + if (state->enabled) { +#ifdef PWM_HAS_PHASE_SUPPORT + u64 phase_cnt; - cnt = mul_u64_u64_div_u64_roundclosest(state->duty_cycle, rate, NSEC_PER_SEC); - if (cnt > U32_MAX) - cnt = U32_MAX; - axi_pwmgen_write(pwmgen, AXI_PWMGEN_CHX_DUTY(pwmgen, ch), cnt); +#endif + period_cnt = mul_u64_u64_div_u64(state->period, ddata->clk_rate_hz, NSEC_PER_SEC); + if (period_cnt > UINT_MAX) + period_cnt = UINT_MAX; - cnt = mul_u64_u64_div_u64_roundclosest(state->phase, rate, NSEC_PER_SEC); - if (cnt > U32_MAX) - cnt = U32_MAX; - axi_pwmgen_write(pwmgen, AXI_PWMGEN_CHX_PHASE(pwmgen, ch), cnt); + if (period_cnt == 0) + return -EINVAL; - /* Apply the new config */ - axi_pwmgen_write(pwmgen, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONIG); + ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), period_cnt); + if (ret) + return ret; - return 0; + duty_cnt = mul_u64_u64_div_u64(state->duty_cycle, ddata->clk_rate_hz, NSEC_PER_SEC); + if (duty_cnt > UINT_MAX) + duty_cnt = UINT_MAX; + + ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), duty_cnt); + if (ret) + return ret; +#ifdef PWM_HAS_PHASE_SUPPORT + + phase_cnt = mul_u64_u64_div_u64(state->phase, ddata->clk_rate_hz, NSEC_PER_SEC); + if (duty_cnt > UINT_MAX) + duty_cnt = UINT_MAX; + + ret = regmap_write(regmap, AXI_PWMGEN_CHX_OFFSET(ch), phase_cnt); + if (ret) + return ret; +#endif + } else { + ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), 0); + if (ret) + return ret; + + ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), 0); + if (ret) + return ret; + } + + return regmap_write(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONFIG); } -static void axi_pwmgen_get_state(struct pwm_chip *chip, struct pwm_device *pwm, - struct pwm_state *state) +static +#ifdef PWM_GET_STATE_VOID +void +#else +int +#endif +axi_pwmgen_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) { - struct axi_pwmgen *pwmgen = to_axi_pwmgen(chip); - unsigned long rate; - unsigned long long cnt; + struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip); + struct regmap *regmap = ddata->regmap; unsigned int ch = pwm->hwpwm; + u32 cnt; + int ret; + + ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &cnt); + if (ret) + return +#ifndef PWM_GET_STATE_VOID + ret +#endif + ; + + state->enabled = cnt != 0; + + state->period = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz); + + ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &cnt); + if (ret) + return +#ifndef PWM_GET_STATE_VOID + ret +#endif + ; - rate = clk_get_rate(pwmgen->clk); - if (!rate) - return; - cnt = axi_pwmgen_read(pwmgen, AXI_PWMGEN_CHX_PERIOD(pwmgen, ch)); - state->period = DIV_ROUND_CLOSEST_ULL(cnt * NSEC_PER_SEC, rate); + state->duty_cycle = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz); - cnt = axi_pwmgen_read(pwmgen, AXI_PWMGEN_CHX_DUTY(pwmgen, ch)); - state->duty_cycle = DIV_ROUND_CLOSEST_ULL(cnt * NSEC_PER_SEC, rate); +#ifdef PWM_HAS_PHASE_SUPPORT + ret = regmap_read(regmap, AXI_PWMGEN_CHX_OFFSET(ch), &cnt); + if (ret) + return +#ifndef PWM_GET_STATE_VOID + ret +#endif + ; - cnt = axi_pwmgen_read(pwmgen, AXI_PWMGEN_CHX_PHASE(pwmgen, ch)); - state->phase = DIV_ROUND_CLOSEST_ULL(cnt * NSEC_PER_SEC, rate); + state->phase = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz); +#endif + state->polarity = PWM_POLARITY_NORMAL; - state->enabled = state->period > 0; + return +#ifndef PWM_GET_STATE_VOID + 0 +#endif + ; } static const struct pwm_ops axi_pwmgen_pwm_ops = { .apply = axi_pwmgen_apply, .get_state = axi_pwmgen_get_state, +#ifdef PWM_EXPLICIT_OWNER .owner = THIS_MODULE, +#endif }; -static const struct of_device_id axi_pwmgen_ids[] = { - { - .compatible = "adi,axi-pwmgen", - }, - { } -}; -MODULE_DEVICE_TABLE(of, axi_pwmgen_ids); - -static int axi_pwmgen_setup(struct pwm_chip *chip) +static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev) { - struct axi_pwmgen *pwmgen; - unsigned int reg; - int idx; - - pwmgen = to_axi_pwmgen(chip); - axi_pwmgen_write(pwmgen, AXI_PWMGEN_REG_SCRATCHPAD, AXI_PWMGEN_TEST_DATA); - reg = axi_pwmgen_read(pwmgen, AXI_PWMGEN_REG_SCRATCHPAD); - if (reg != AXI_PWMGEN_TEST_DATA) { - dev_err(chip->dev, "failed to access the device registers\n"); - return -EIO; - } + int ret; + u32 val; - reg = axi_pwmgen_read(pwmgen, AXI_PWMGEN_REG_CORE_VERSION); - pwmgen->hw_maj_ver = AXI_PWMGEN_VERSION_MAJOR(reg); + ret = regmap_read(regmap, AXI_PWMGEN_REG_CORE_MAGIC, &val); + if (ret) + return ret; - if (pwmgen->hw_maj_ver != 1 && pwmgen->hw_maj_ver != 2) { - dev_err(chip->dev, "Unsupported peripheral version %u.%u.%u\n", - AXI_PWMGEN_VERSION_MAJOR(reg), - AXI_PWMGEN_VERSION_MINOR(reg), - AXI_PWMGEN_VERSION_PATCH(reg)); - return -ENODEV; - } + if (val != AXI_PWMGEN_REG_CORE_MAGIC_VAL) + return dev_err_probe(dev, -ENODEV, + "failed to read expected value from register: got %08x, expected %08x\n", + val, AXI_PWMGEN_REG_CORE_MAGIC_VAL); - pwmgen->chip.npwm = axi_pwmgen_read(pwmgen, AXI_PWMGEN_REG_NPWM); - if (pwmgen->chip.npwm > AXI_PWMGEN_N_MAX_PWMS) - return -EINVAL; + ret = regmap_read(regmap, ADI_AXI_REG_VERSION, &val); + if (ret) + return ret; - /* Disable all the outputs */ - for (idx = 0; idx < pwmgen->chip.npwm; idx++) { - axi_pwmgen_write(pwmgen, AXI_PWMGEN_CHX_PERIOD(pwmgen, idx), 0); - axi_pwmgen_write(pwmgen, AXI_PWMGEN_CHX_DUTY(pwmgen, idx), 0); - axi_pwmgen_write(pwmgen, AXI_PWMGEN_CHX_PHASE(pwmgen, idx), 0); + if (ADI_AXI_PCORE_VER_MAJOR(val) != 2) { + return dev_err_probe(dev, -ENODEV, "Unsupported peripheral version %u.%u.%u\n", + ADI_AXI_PCORE_VER_MAJOR(val), + ADI_AXI_PCORE_VER_MINOR(val), + ADI_AXI_PCORE_VER_PATCH(val)); } /* Enable the core */ - axi_pwmgen_write_mask(pwmgen, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_RESET, 0); + ret = regmap_clear_bits(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_REG_CONFIG_RESET); + if (ret) + return ret; - return 0; -} + ret = regmap_read(regmap, AXI_PWMGEN_REG_NPWM, &val); + if (ret) + return ret; -static void axi_pwmgen_clk_disable(void *data) -{ - clk_disable_unprepare(data); + /* Return the number of PWMs */ + return val; } static int axi_pwmgen_probe(struct platform_device *pdev) { - struct axi_pwmgen *pwmgen; - struct resource *mem; + struct device *dev = &pdev->dev; + struct regmap *regmap; + struct pwm_chip *chip; + struct axi_pwmgen_ddata *ddata; + struct clk *clk; + void __iomem *io_base; int ret; - pwmgen = devm_kzalloc(&pdev->dev, sizeof(*pwmgen), GFP_KERNEL); - if (!pwmgen) - return -ENOMEM; - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - pwmgen->base = devm_ioremap_resource(&pdev->dev, mem); - if (IS_ERR(pwmgen->base)) - return PTR_ERR(pwmgen->base); + io_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); - pwmgen->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(pwmgen->clk)) - return PTR_ERR(pwmgen->clk); + regmap = devm_regmap_init_mmio(dev, io_base, &axi_pwmgen_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "failed to init register map\n"); - ret = clk_prepare_enable(pwmgen->clk); - if (ret) + ret = axi_pwmgen_setup(regmap, dev); + if (ret < 0) return ret; - ret = devm_add_action_or_reset(&pdev->dev, axi_pwmgen_clk_disable, - pwmgen->clk); + +#ifdef PWM_MISSING_PWMCHIP_ALLOC + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + chip = &ddata->chip; + chip->npwm = ret; +#else + chip = devm_pwmchip_alloc(dev, ret, sizeof(*ddata)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + ddata = pwmchip_get_drvdata(chip); +#endif + ddata->regmap = regmap; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n"); + + ret = devm_clk_rate_exclusive_get(dev, clk); if (ret) - return ret; + return dev_err_probe(dev, ret, "failed to get exclusive rate\n"); - pwmgen->chip.dev = &pdev->dev; - pwmgen->chip.ops = &axi_pwmgen_pwm_ops; - pwmgen->chip.base = -1; + ddata->clk_rate_hz = clk_get_rate(clk); + if (!ddata->clk_rate_hz || ddata->clk_rate_hz > NSEC_PER_SEC) + return dev_err_probe(dev, -EINVAL, + "Invalid clock rate: %lu\n", ddata->clk_rate_hz); - ret = axi_pwmgen_setup(&pwmgen->chip); - if (ret < 0) - return ret; + chip->ops = &axi_pwmgen_pwm_ops; +#ifndef PWM_MISSING_ATOMIC + chip->atomic = true; +#endif - return devm_pwmchip_add(&pdev->dev, &pwmgen->chip); + ret = devm_pwmchip_add(dev, chip); + if (ret) + return dev_err_probe(dev, ret, "could not add PWM chip\n"); + + return 0; } +static const struct of_device_id axi_pwmgen_ids[] = { + { .compatible = "adi,axi-pwmgen-2.00.a" }, + { } +}; +MODULE_DEVICE_TABLE(of, axi_pwmgen_ids); + static struct platform_driver axi_pwmgen_driver = { .driver = { - .name = "adi,axi-pwmgen", + .name = "axi-pwmgen", .of_match_table = axi_pwmgen_ids, }, .probe = axi_pwmgen_probe, }; - module_platform_driver(axi_pwmgen_driver); -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Sergiu Cuciurean "); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sergiu Cuciurean "); +MODULE_AUTHOR("Trevor Gamblin "); MODULE_DESCRIPTION("Driver for the Analog Devices AXI PWM generator");