Skip to contentSkip to conversation

pwm: add support for NXPs high-side switch MC33XS2410

PATCH Superseded Dimitri Fedrau 2 participants 12 messages Sep 27, 2024 – Nov 6, 2024

Subsystems (2)

v6 of 8 Superseded Latest is v8 →

lore
Share & tools
lore
#0 lore raw

v6 cover pwm: add support for NXPs high-side switch MC33XS2410

The MC33XS2410 is a four channel high-side switch. Featuring advanced monitoring and control function, the device is operational from 3.0 V to 60 V. The device is controlled by SPI port for configuration.

Changes in V2:
- fix title in devicetree binding
- fix commit message in devicetree binding patch
- remove external clock from pwms and create clocks property
- switch to unevaluatedProperties: false
- add missing properties for complete example:
- pwm-names
- pwms
- interrupts
- clocks

Changes in V3:
- Add description of the general behaviour of the device (limitations)
- Drop unused defines
- Add ranges comments for defines with parameters
- Drop MC33XS2410_PERIOD_MAX, MC33XS2410_PERIOD_MIN defines
- Drop mc33xs2410_period variable
- Round down when calculating period and duty cycle
- Use switch instead of loop when calculating frequency
- Removed ret variable in mc33xs2410_pwm_get_freq
- Handle all accesses in a single call to spi_sync_transfer
- Fix comments in function mc33xs2410_pwm_get_period
- Fix call pwm_set_relative_duty_cycle(state, ret, 255), instead

    pwm_set_relative_duty_cycle(state, val[1] + 1, 256);

- Use devm_pwmchip_alloc
- Fix typo s/Transitition/Transition/
- Drop driver_data
- Removed patch for direct inputs from series
- Tested with PWM_DEBUG enabled, didn't before !

Changes in V4:
- removed include of math.h, already included in math64.h
- removed include of mutex.h, no mutexes are used
- added include of bitfield.h(FIELD_GET, FIELD_PREP), fixes errors discovered by kernel test robot
- cast parameters in DIV_ROUND_UP to u32, fixes errors discovered by kernel test robot

Changes in V5:
- Fix comment in mc33xs2410_pwm_get_freq, selecting steps instead of period
- Add comment in mc33xs2410_pwm_get_relative_duty_cycle that duty_cycle cannot overflow and and period is not zero, guaranteed by the caller
- Hardware emits a low level when disabled, disable if duty_cycle = 0 is requested.
- Fix complaints when PWM_DEBUG enabled, round-down division in

    mc33xs2410_pwm_apply and round-up in mc33xs2410_pwm_get_state.

- Add comment for disabling watchdog in probe

Changes in V6:
- Add link to manual
- Redefine MC33XS2410_GLB_CTRL_MODE_MASK as MC33XS2410_GLB_CTRL_MODE and

    MC33XS2410_GLB_CTRL_NORMAL_MODE as MC33XS2410_GLB_CTRL_MODE_NORMAL
  - Remove define MC33XS2410_MIN_PERIOD_STEP(x) as there is no need for
    parameters and use instead MC33XS2410_MIN_PERIOD
  - Rename function to_pwm_mc33xs2410_chip to mc33xs2410_from_chip
  - Add comment in mc33xs2410_pwm_get_freq why count should be rounded up
  - Fix incorrect comment in mc33xs2410_pwm_get_period
  - Rename steps variable to doubled_steps in function
    mc33xs2410_pwm_get_freq

- remove cast in mc33xs2410_pwm_set_relative_duty_cycle
- remove duty_cycle from if in mc33xs2410_pwm_set_relative_duty_cycle

Dimitri Fedrau (2):
dt-bindings: pwm: add support for MC33XS2410
pwm: add support for NXPs high-side switch MC33XS2410

 .../bindings/pwm/nxp,mc33xs2410.yaml          | 118 +++++
 drivers/pwm/Kconfig                           |  12 +
 drivers/pwm/Makefile                          |   1 +
 drivers/pwm/pwm-mc33xs2410.c                  | 422 ++++++++++++++++++
 4 files changed, 553 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/nxp,mc33xs2410.yaml
 create mode 100644 drivers/pwm/pwm-mc33xs2410.c
Signature · 1 line
2.39.5
#1 lore raw

PATCH v6 1/2 dt-bindings: pwm: add support for MC33XS2410

Adding documentation for NXPs MC33XS2410 high side switch.

---
 .../bindings/pwm/nxp,mc33xs2410.yaml          | 118 ++++++++++++++++++
 1 file changed, 118 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/nxp,mc33xs2410.yaml
Documentation/devicetree/bindings/pwm/nxp,mc33xs2410.yaml new file +118 -0
new file mode 100644
index 000000000000..1729fe5c3dfb
@@ -0,0 +1,118 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/nxp,mc33xs2410.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: High-side switch MC33XS2410
+
+maintainers:
+  - Dimitri Fedrau <[email protected]>
+
+allOf:
+  - $ref: pwm.yaml#
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    const: nxp,mc33xs2410
+
+  reg:
+    maxItems: 1
+
+  spi-max-frequency:
+    maximum: 10000000
+
+  spi-cpha: true
+
+  spi-cs-setup-delay-ns:
+    minimum: 100
+    default: 100
+
+  spi-cs-hold-delay-ns:
+    minimum: 10
+    default: 10
+
+  spi-cs-inactive-delay-ns:
+    minimum: 300
+    default: 300
+
+  reset-gpios:
+    description:
+      GPIO connected to the active low reset pin.
+    maxItems: 1
+
+  "#pwm-cells":
+    const: 3
+
+  pwm-names:
+    items:
+      - const: di0
+      - const: di1
+      - const: di2
+      - const: di3
+
+  pwms:
+    description:
+      Direct inputs(di0-3) are used to directly turn-on or turn-off the
+      outputs.
+    maxItems: 4
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    description:
+      The external clock can be used if the internal clock doesn't meet
+      timing requirements over temperature and voltage operating range.
+    maxItems: 1
+
+  vdd-supply:
+    description:
+      Logic supply voltage
+
+  vspi-supply:
+    description:
+      Supply voltage for SPI
+
+  vpwr-supply:
+    description:
+      Power switch supply
+
+required:
+  - compatible
+  - reg
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+       pwm@0 {
+           compatible = "nxp,mc33xs2410";
+           reg = <0x0>;
+           spi-max-frequency = <4000000>;
+           spi-cpha;
+           spi-cs-setup-delay-ns = <100>;
+           spi-cs-hold-delay-ns = <10>;
+           spi-cs-inactive-delay-ns = <300>;
+           reset-gpios = <&gpio3 22 GPIO_ACTIVE_LOW>;
+           #pwm-cells = <3>;
+           pwm-names = "di0", "di1", "di2", "di3";
+           pwms = <&pwm0 0 1000000>,
+                  <&pwm1 0 1000000>,
+                  <&pwm2 0 1000000>,
+                  <&pwm3 0 1000000>;
+           interrupt-parent = <&gpio0>;
+           interrupts = <31 IRQ_TYPE_LEVEL_LOW>;
+           clocks = <&clk_ext_fixed>;
+           vdd-supply = <&reg_3v3>;
+           vspi-supply = <&reg_3v3>;
+           vpwr-supply = <&reg_24v0>;
+       };
+    };
Signature · 1 line
2.39.5
#2 lore raw

PATCH v6 2/2 pwm: add support for NXPs high-side switch MC33XS2410

In reply to Dimitri Fedrau

The MC33XS2410 is a four channel high-side switch. Featuring advanced monitoring and control function, the device is operational from 3.0 V to 60 V. The device is controlled by SPI port for configuration.

---
 drivers/pwm/Kconfig          |  12 +
 drivers/pwm/Makefile         |   1 +
 drivers/pwm/pwm-mc33xs2410.c | 422 +++++++++++++++++++++++++++++++++++
 3 files changed, 435 insertions(+)
 create mode 100644 drivers/pwm/pwm-mc33xs2410.c
drivers/pwm/Kconfig +12 -0
index 0915c1e7df16..f513513f9b2f 100644
@@ -411,6 +411,18 @@ config PWM_LPSS_PLATFORM
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-lpss-platform.
 
+config PWM_MC33XS2410
+	tristate "MC33XS2410 PWM support"
+	depends on OF
+	depends on SPI
+	help
+	  NXP MC33XS2410 high-side switch driver. The MC33XS2410 is a four
+	  channel high-side switch. The device is operational from 3.0 V
+	  to 60 V. The device is controlled by SPI port for configuration.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-mc33xs2410.
+
 config PWM_MESON
 	tristate "Amlogic Meson PWM driver"
 	depends on ARCH_MESON || COMPILE_TEST
drivers/pwm/Makefile +1 -0
index 9081e0c0e9e0..c75deeeace40 100644
@@ -36,6 +36,7 @@ obj-$(CONFIG_PWM_LPC32XX)	+= pwm-lpc32xx.o
 obj-$(CONFIG_PWM_LPSS)		+= pwm-lpss.o
 obj-$(CONFIG_PWM_LPSS_PCI)	+= pwm-lpss-pci.o
 obj-$(CONFIG_PWM_LPSS_PLATFORM)	+= pwm-lpss-platform.o
+obj-$(CONFIG_PWM_MC33XS2410)	+= pwm-mc33xs2410.o
 obj-$(CONFIG_PWM_MESON)		+= pwm-meson.o
 obj-$(CONFIG_PWM_MEDIATEK)	+= pwm-mediatek.o
 obj-$(CONFIG_PWM_MICROCHIP_CORE)	+= pwm-microchip-core.o
drivers/pwm/pwm-mc33xs2410.c new file +422 -0

422 lines not on this page · raw message

Signature · 1 line
2.39.5
#3 lore raw

Hello Dimitri,

Quoted 55 lines · Dimitri Fedrau
drivers/pwm/pwm-mc33xs2410.c new file +49 -0
new file mode 100644
index 000000000000..f9a334a5e69b
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH
+ *
+ * Reference Manual : https://www.nxp.com/docs/en/data-sheet/MC33XS2410.pdf
+ *
+ * Limitations:
+ * - Supports frequencies between 0.5Hz and 2048Hz with following steps:
+ *   - 0.5 Hz steps from 0.5 Hz to 32 Hz
+ *   - 2 Hz steps from 2 Hz to 128 Hz
+ *   - 8 Hz steps from 8 Hz to 512 Hz
+ *   - 32 Hz steps from 32 Hz to 2048 Hz
+ * - Cannot generate a 0 % duty cycle.
+ * - Always produces low output if disabled.
+ * - Configuration isn't atomic. When changing polarity, duty cycle or period
+ *   the data is taken immediately, counters not being affected, resulting in a
+ *   behavior of the output pin that is neither the old nor the new state,
+ *   rather something in between.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pwm.h>
+
+#include <asm/unaligned.h>
+
+#include <linux/spi/spi.h>
+
+#define MC33XS2410_GLB_CTRL		0x00
+#define MC33XS2410_GLB_CTRL_MODE	GENMASK(7, 6)
+#define MC33XS2410_GLB_CTRL_MODE_NORMAL	FIELD_PREP(MC33XS2410_GLB_CTRL_MODE, 1)
+#define MC33XS2410_PWM_CTRL1		0x05
+#define MC33XS2410_PWM_CTRL1_POL_INV(x)	BIT(x)
+#define MC33XS2410_PWM_CTRL3		0x07
+/* x in { 0 ... 3 } */
+#define MC33XS2410_PWM_CTRL3_EN(x)	BIT(4 + (x))
+#define MC33XS2410_PWM_FREQ1		0x08
+/* x in { 1 ... 4 } */
+#define MC33XS2410_PWM_FREQ(x)		(MC33XS2410_PWM_FREQ1 + (x - 1))
+#define MC33XS2410_PWM_FREQ_STEP_MASK	GENMASK(7, 6)
+#define MC33XS2410_PWM_FREQ_COUNT_MASK	GENMASK(5, 0)
+#define MC33XS2410_PWM_DC1		0x0c
+/* x in { 1 ... 4 } */
+#define MC33XS2410_PWM_DC(x)		(MC33XS2410_PWM_DC1 + (x - 1))

drivers/pwm/pwm-mc33xs2410.c line 59

Quoted 10 lines
drivers/pwm/pwm-mc33xs2410.c new file +10 -0
+#define MC33XS2410_WDT			0x14
+
+#define MC33XS2410_WR			BIT(7)
+#define MC33XS2410_RD_CTRL		BIT(7)
+#define MC33XS2410_RD_DATA_MASK		GENMASK(13, 0)
+
+#define MC33XS2410_MIN_PERIOD		488282
+#define MC33XS2410_MAX_PERIOD_STEP0	2000000000
+/* x in { 0 ... 3 } */
+#define MC33XS2410_MAX_PERIOD_STEP(x)	(MC33XS2410_MAX_PERIOD_STEP0 >> (2 * x))

Nitpick: These register definition become easier to parse for a human if you indent the RHS of register fields one tab further and add an empty line between the definitions for different registers.

MC33XS2410_PWM_DC1 is only used once, I'd hard-code it into the definition of MC33XS2410_PWM_DC.

The register fields [7:4] in MC33XS2410_PWM_CTRL3 are called PWM_ON4 .. PWM_ON1. So your x in { 0 ... 3 } is wrong. (Luckily, having some x range over { 0 ... 3 } and others orver { 1 ... 4 } is prone to error and confusion.)

Also I'd drop all _MASK suffixes.

For MC33XS2410_MAX_PERIOD_STEP maybe use a different variable name than for the others. For the register definitions the range is over hwpwm (which might be a good name there?), for MC33XS2410_MAX_PERIOD_STEP it's about MC33XS2410_PWM_FREQ_STEP.

Quoted 4 lines
drivers/pwm/pwm-mc33xs2410.c new file +4 -0
+#define MC33XS2410_MAX_TRANSFERS	5
+#define MC33XS2410_WORD_LEN		2
+
+struct mc33xs2410_pwm {

drivers/pwm/pwm-mc33xs2410.c line 74

Quoted 10 lines
drivers/pwm/pwm-mc33xs2410.c new file +10 -0
+	struct spi_device *spi;
+};
+
+static inline struct mc33xs2410_pwm *mc33xs2410_from_chip(struct pwm_chip *chip)
+{
+	return pwmchip_get_drvdata(chip);
+}
+
+static int mc33xs2410_xfer_regs(struct spi_device *spi, bool read, u8 *reg,
+				u16 *val, bool *ctrl, int len)

Should len better be unsigned?

Unless I missed something all ctrl[x] are always identical. If so represent that by a single bool.

Quoted 25 lines
drivers/pwm/pwm-mc33xs2410.c new file +25 -0
+{
+	struct spi_transfer t[MC33XS2410_MAX_TRANSFERS] = { { 0 } };
+	u8 tx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
+	u8 rx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
+	int i, ret, reg_i, val_i;
+
+	if (!len)
+		return 0;
+
+	if (read)
+		len++;
+
+	if (len > MC33XS2410_MAX_TRANSFERS)
+		return -EINVAL;
+
+	for (i = 0; i < len; i++) {
+		reg_i = i * MC33XS2410_WORD_LEN;
+		val_i = reg_i + 1;
+		if (read) {
+			if (i < len - 1) {
+				tx[reg_i] = reg[i];
+				tx[val_i] = ctrl[i] ? MC33XS2410_RD_CTRL : 0;
+				t[i].tx_buf = &tx[reg_i];
+			}
+

drivers/pwm/pwm-mc33xs2410.c line 109

Quoted 10 lines
drivers/pwm/pwm-mc33xs2410.c new file +10 -0
+			if (i > 0)
+				t[i].rx_buf = &rx[reg_i - MC33XS2410_WORD_LEN];
+		} else {
+			tx[reg_i] = reg[i] | MC33XS2410_WR;
+			tx[val_i] = val[i];
+			t[i].tx_buf = &tx[reg_i];
+		}
+
+		t[i].len = MC33XS2410_WORD_LEN;
+		t[i].cs_change = 1;

Not sure if MC33XS2410_WORD_LEN really improves readability here.

Why is this done using $len transfers, wouldn't a single one do (and maybe be more performant and not rely on a spi controller that supports cs_change)?

Quoted 11 lines
drivers/pwm/pwm-mc33xs2410.c new file +11 -0
+	}
+
+	t[len - 1].cs_change = 0;
+
+	ret = spi_sync_transfer(spi, &t[0], len);
+	if (ret < 0)
+		return ret;
+
+	if (read) {
+		for (i = 0; i < len - 1; i++) {
+			reg_i = i * MC33XS2410_WORD_LEN;

drivers/pwm/pwm-mc33xs2410.c line 149

Quoted 10 lines
drivers/pwm/pwm-mc33xs2410.c new file +9 -0
+			val[i] = FIELD_GET(MC33XS2410_RD_DATA_MASK,
+					   get_unaligned_be16(&rx[reg_i]));
+		}
+	}
+
+	return 0;
+}
[...]
+static
+int mc33xs2410_read_reg(struct spi_device *spi, u8 reg, u16 *val, bool ctrl)

My personal opinion: Better break the line in the argument list or not at all. Having a "static" on its own line looks ugly.

drivers/pwm/pwm-mc33xs2410.c line 232

Quoted 9 lines
drivers/pwm/pwm-mc33xs2410.c new file +7 -0
+{
+	return mc33xs2410_read_regs(spi, &reg, &ctrl, val, 1);
+}
[...]
+static u64 mc33xs2410_pwm_get_period(u8 reg)
+{
[...]
+	/* Convert frequency to period, considering the doubled frequency. */
+	return DIV_ROUND_UP((u32)(2 * NSEC_PER_SEC), freq);

That u32 cast isn't needed.

Quoted 5 lines
drivers/pwm/pwm-mc33xs2410.c new file +4 -0
+}
[...]
+static int mc33xs2410_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+				const struct pwm_state *state)
+{

drivers/pwm/pwm-mc33xs2410.c line 289

Quoted 10 lines
drivers/pwm/pwm-mc33xs2410.c new file +9 -0
[...]
+	/* frequency */
+	val[0] = mc33xs2410_pwm_get_freq(period);
+	/* Continue calculations with the possibly truncated period */
+	period = mc33xs2410_pwm_get_period(val[0]);
+
+	/* duty cycle */
+	duty_cycle = min(period, state->duty_cycle);
+	rel_dc = mc33xs2410_pwm_get_relative_duty_cycle(period, duty_cycle);
+	val[1] = rel_dc < 0 ? 0 : rel_dc;

Handling of the duty cycle is correct here, but misleading. I already added a comment here that using val[1] = 0 if rel_dc < 0 is wrong and just deleted it again after I saw (rel_dc >= 0) being used determining the value for MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm). An explicit if block would make this more obvious.

mc33xs2410_pwm_get_relative_duty_cycle() is simple enough and only used once that I'd unroll it here.

Quoted 13 lines
drivers/pwm/pwm-mc33xs2410.c new file +13 -0
+	/* polarity */
+	mask = MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm);
+	val[2] = (state->polarity == PWM_POLARITY_INVERSED) ?
+		 (val[2] | mask) : (val[2] & ~mask);
+
+	/* enable output */
+	mask = MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm);
+	val[3] = (state->enabled && rel_dc >= 0) ? (val[3] | mask) :
+						   (val[3] & ~mask);
+
+	return mc33xs2410_write_regs(spi, reg, val, 4);
+}
+

drivers/pwm/pwm-mc33xs2410.c line 328

Quoted 10 lines
drivers/pwm/pwm-mc33xs2410.c new file +9 -0
+static int mc33xs2410_pwm_get_state(struct pwm_chip *chip,
+				    struct pwm_device *pwm,
+				    struct pwm_state *state)
+{
[...]
+	state->period = mc33xs2410_pwm_get_period(val[0]);
+	state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm)) ?
+			  PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+	state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm));
+	mc33xs2410_pwm_set_relative_duty_cycle(state, val[1]);

No need to set state->duty_cycle = 0 if state->enabled is false. This is another function I suggest to unroll as it hides more than it abstracts.

Quoted 4 lines
drivers/pwm/pwm-mc33xs2410.c new file +3 -0
+	return 0;
+}
+
[...]

drivers/pwm/pwm-mc33xs2410.c line 389

Quoted 10 lines
drivers/pwm/pwm-mc33xs2410.c new file +9 -0
+static int mc33xs2410_probe(struct spi_device *spi)
+{
[...]
+	/* Transition to normal mode */
+	ret = mc33xs2410_modify_reg(spi, MC33XS2410_GLB_CTRL,
+				    MC33XS2410_GLB_CTRL_MODE,
+				    MC33XS2410_GLB_CTRL_MODE_NORMAL);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to transition to normal mode\n");

What is the effect of this register write if the PWM was already setup by the bootloader?

drivers/pwm/pwm-mc33xs2410.c line 396

Quoted 7 lines
drivers/pwm/pwm-mc33xs2410.c new file +7 -0
+
+	ret = devm_pwmchip_add(dev, chip);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to add pwm chip\n");
+
+	return 0;
+}

Best regards
Uwe

#4 lore raw

Hello Uwe,

Am Tue, Oct 22, 2024 at 09:54:50AM +0200 schrieb Uwe Kleine-König:

[...]

Quoted 10 lines

drivers/pwm/pwm-mc33xs2410.c line 59

Quoted 5 lines
drivers/pwm/pwm-mc33xs2410.c new file +5 -0
+
+#define MC33XS2410_MIN_PERIOD		488282
+#define MC33XS2410_MAX_PERIOD_STEP0	2000000000
+/* x in { 0 ... 3 } */
+#define MC33XS2410_MAX_PERIOD_STEP(x)	(MC33XS2410_MAX_PERIOD_STEP0 >> (2 * x))

Nitpick: These register definition become easier to parse for a human if you indent the RHS of register fields one tab further and add an empty line between the definitions for different registers.

Adding an empty line seems reasonable to me but the additional tab doesn't help me to improve readability.

Quoted 3 lines

MC33XS2410_PWM_DC1 is only used once, I'd hard-code it into the definition of MC33XS2410_PWM_DC.

Ok. Should I do the same for MC33XS2410_PWM_FREQ1 and MC33XS2410_MAX_PERIOD_STEP0 ?

Quoted 5 lines

The register fields [7:4] in MC33XS2410_PWM_CTRL3 are called PWM_ON4 .. PWM_ON1. So your x in { 0 ... 3 } is wrong. (Luckily, having some x range over { 0 ... 3 } and others orver { 1 ... 4 } is prone to error and confusion.)

Will fix it. Should I do the same for MC33XS2410_PWM_CTRL1_POL_INV ?

Quoted 2 lines

Also I'd drop all _MASK suffixes.

Ok.

Quoted 5 lines

For MC33XS2410_MAX_PERIOD_STEP maybe use a different variable name than for the others. For the register definitions the range is over hwpwm (which might be a good name there?), for MC33XS2410_MAX_PERIOD_STEP it's about MC33XS2410_PWM_FREQ_STEP.

What about MC33XS2410_PWM_MAX_PERIOD(x) ?

Quoted 8 lines
Quoted 8 lines
drivers/pwm/pwm-mc33xs2410.c new file +8 -0
+#define MC33XS2410_MAX_TRANSFERS	5
+#define MC33XS2410_WORD_LEN		2
+
+struct mc33xs2410_pwm {
+	struct spi_device *spi;
+};
+
+static inline struct mc33xs2410_pwm *mc33xs2410_from_chip(struct pwm_chip *chip)
Quoted 9 lines

drivers/pwm/pwm-mc33xs2410.c line 74

Quoted 6 lines
drivers/pwm/pwm-mc33xs2410.c new file +6 -0
+{
+	return pwmchip_get_drvdata(chip);
+}
+
+static int mc33xs2410_xfer_regs(struct spi_device *spi, bool read, u8 *reg,
+				u16 *val, bool *ctrl, int len)

Should len better be unsigned?

I switch to unsigned.

Quoted 3 lines

Unless I missed something all ctrl[x] are always identical. If so represent that by a single bool.

Yes, they are identical. I added the crtl[x] to be able read from ctrl and diag registers. I will change it so it is represented by a single bool, if the feature is needed in the future I can still add it.

Quoted 29 lines
Quoted 29 lines
drivers/pwm/pwm-mc33xs2410.c new file +29 -0
+{
+	struct spi_transfer t[MC33XS2410_MAX_TRANSFERS] = { { 0 } };
+	u8 tx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
+	u8 rx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
+	int i, ret, reg_i, val_i;
+
+	if (!len)
+		return 0;
+
+	if (read)
+		len++;
+
+	if (len > MC33XS2410_MAX_TRANSFERS)
+		return -EINVAL;
+
+	for (i = 0; i < len; i++) {
+		reg_i = i * MC33XS2410_WORD_LEN;
+		val_i = reg_i + 1;
+		if (read) {
+			if (i < len - 1) {
+				tx[reg_i] = reg[i];
+				tx[val_i] = ctrl[i] ? MC33XS2410_RD_CTRL : 0;
+				t[i].tx_buf = &tx[reg_i];
+			}
+
+			if (i > 0)
+				t[i].rx_buf = &rx[reg_i - MC33XS2410_WORD_LEN];
+		} else {
+			tx[reg_i] = reg[i] | MC33XS2410_WR;
Quoted 9 lines

drivers/pwm/pwm-mc33xs2410.c line 109

Quoted 6 lines
drivers/pwm/pwm-mc33xs2410.c new file +6 -0
+			tx[val_i] = val[i];
+			t[i].tx_buf = &tx[reg_i];
+		}
+
+		t[i].len = MC33XS2410_WORD_LEN;
+		t[i].cs_change = 1;

Not sure if MC33XS2410_WORD_LEN really improves readability here.

It is used throughout in the function and improves readability overall, maybe not here but for consistency I would stick to it.

Quoted 4 lines

Why is this done using $len transfers, wouldn't a single one do (and maybe be more performant and not rely on a spi controller that supports cs_change)?

Without cs_change after every 16 bit, requests aren't processed by the device. Reading/writing from/to device fails. The SPI controller therefore must support cs_change. Single transfer is not possible because of the cs_change after every 16bit.

Quoted 16 lines
Quoted 16 lines
drivers/pwm/pwm-mc33xs2410.c new file +16 -0
+	}
+
+	t[len - 1].cs_change = 0;
+
+	ret = spi_sync_transfer(spi, &t[0], len);
+	if (ret < 0)
+		return ret;
+
+	if (read) {
+		for (i = 0; i < len - 1; i++) {
+			reg_i = i * MC33XS2410_WORD_LEN;
+			val[i] = FIELD_GET(MC33XS2410_RD_DATA_MASK,
+					   get_unaligned_be16(&rx[reg_i]));
+		}
+	}
+
Quoted 9 lines

drivers/pwm/pwm-mc33xs2410.c line 149

Quoted 5 lines
drivers/pwm/pwm-mc33xs2410.c new file +4 -0
+	return 0;
+}
[...]
+static
+int mc33xs2410_read_reg(struct spi_device *spi, u8 reg, u16 *val, bool ctrl)

My personal opinion: Better break the line in the argument list or not at all. Having a "static" on its own line looks ugly.

Ok.

Quoted 12 lines

drivers/pwm/pwm-mc33xs2410.c line 232

Quoted 9 lines
drivers/pwm/pwm-mc33xs2410.c new file +7 -0
+{
+	return mc33xs2410_read_regs(spi, &reg, &ctrl, val, 1);
+}
[...]
+static u64 mc33xs2410_pwm_get_period(u8 reg)
+{
[...]
+	/* Convert frequency to period, considering the doubled frequency. */
+	return DIV_ROUND_UP((u32)(2 * NSEC_PER_SEC), freq);

That u32 cast isn't needed.

Will fix it.

Quoted 13 lines
Quoted 13 lines
drivers/pwm/pwm-mc33xs2410.c new file +11 -0
+}
[...]
+static int mc33xs2410_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+				const struct pwm_state *state)
+{
[...]
+	/* frequency */
+	val[0] = mc33xs2410_pwm_get_freq(period);
+	/* Continue calculations with the possibly truncated period */
+	period = mc33xs2410_pwm_get_period(val[0]);
+
+	/* duty cycle */
+	duty_cycle = min(period, state->duty_cycle);
Quoted 9 lines

drivers/pwm/pwm-mc33xs2410.c line 289

Quoted 2 lines
drivers/pwm/pwm-mc33xs2410.c new file +2 -0
+	rel_dc = mc33xs2410_pwm_get_relative_duty_cycle(period, duty_cycle);
+	val[1] = rel_dc < 0 ? 0 : rel_dc;

Handling of the duty cycle is correct here, but misleading. I already added a comment here that using val[1] = 0 if rel_dc < 0 is wrong and just deleted it again after I saw (rel_dc >= 0) being used determining the value for MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm). An explicit if block would make this more obvious.

Will add an explicit if block, should I do the same for the value for MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm) ?

Quoted 3 lines

mc33xs2410_pwm_get_relative_duty_cycle() is simple enough and only used once that I'd unroll it here.

You are right, will fix it.

Quoted 18 lines
Quoted 18 lines
drivers/pwm/pwm-mc33xs2410.c new file +17 -0
+	/* polarity */
+	mask = MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm);
+	val[2] = (state->polarity == PWM_POLARITY_INVERSED) ?
+		 (val[2] | mask) : (val[2] & ~mask);
+
+	/* enable output */
+	mask = MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm);
+	val[3] = (state->enabled && rel_dc >= 0) ? (val[3] | mask) :
+						   (val[3] & ~mask);
+
+	return mc33xs2410_write_regs(spi, reg, val, 4);
+}
+
+static int mc33xs2410_pwm_get_state(struct pwm_chip *chip,
+				    struct pwm_device *pwm,
+				    struct pwm_state *state)
+{
[...]
Quoted 9 lines

drivers/pwm/pwm-mc33xs2410.c line 328

Quoted 5 lines
drivers/pwm/pwm-mc33xs2410.c new file +5 -0
+	state->period = mc33xs2410_pwm_get_period(val[0]);
+	state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm)) ?
+			  PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+	state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm));
+	mc33xs2410_pwm_set_relative_duty_cycle(state, val[1]);

No need to set state->duty_cycle = 0 if state->enabled is false. This is another function I suggest to unroll as it hides more than it abstracts.

Function can be unrolled, but the check for state->enabled is needed. The device is unable to generate a 0% duty cycle, so it is turned off to generate a 0% duty cylce.

Quoted 9 lines
Quoted 9 lines
drivers/pwm/pwm-mc33xs2410.c new file +7 -0
+	return 0;
+}
+
[...]
+static int mc33xs2410_probe(struct spi_device *spi)
+{
[...]
+	/* Transition to normal mode */
+	ret = mc33xs2410_modify_reg(spi, MC33XS2410_GLB_CTRL,
Quoted 9 lines

drivers/pwm/pwm-mc33xs2410.c line 389

Quoted 5 lines
drivers/pwm/pwm-mc33xs2410.c new file +5 -0
+				    MC33XS2410_GLB_CTRL_MODE,
+				    MC33XS2410_GLB_CTRL_MODE_NORMAL);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to transition to normal mode\n");

What is the effect of this register write if the PWM was already setup by the bootloader?

When its setup is done in the bootloader and the watchdog is disabled in the bootloader it shouldn't have any impact.

Quoted 10 lines

drivers/pwm/pwm-mc33xs2410.c line 396

Quoted 7 lines
drivers/pwm/pwm-mc33xs2410.c new file +7 -0
+
+	ret = devm_pwmchip_add(dev, chip);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to add pwm chip\n");
+
+	return 0;
+}

Best regards
Uwe

Best regards
Dimitri

#5 lore raw

Hello Dimitri,

Quoted 13 lines · Dimitri Fedrau

Am Tue, Oct 22, 2024 at 09:54:50AM +0200 schrieb Uwe Kleine-König:

[...]

Quoted 9 lines

drivers/pwm/pwm-mc33xs2410.c line 59

Quoted 5 lines
drivers/pwm/pwm-mc33xs2410.c new file +5 -0
+
+#define MC33XS2410_MIN_PERIOD		488282
+#define MC33XS2410_MAX_PERIOD_STEP0	2000000000
+/* x in { 0 ... 3 } */
+#define MC33XS2410_MAX_PERIOD_STEP(x)	(MC33XS2410_MAX_PERIOD_STEP0 >> (2 * x))

Nitpick: These register definition become easier to parse for a human if you indent the RHS of register fields one tab further and add an empty line between the definitions for different registers.

Quoted 2 lines

Adding an empty line seems reasonable to me but the additional tab doesn't help me to improve readability.

OK, fine for me.

Quoted 5 lines
Quoted 2 lines

MC33XS2410_PWM_DC1 is only used once, I'd hard-code it into the definition of MC33XS2410_PWM_DC.

Ok. Should I do the same for MC33XS2410_PWM_FREQ1 and MC33XS2410_MAX_PERIOD_STEP0 ?

yepp.

Quoted 6 lines
Quoted 4 lines

The register fields [7:4] in MC33XS2410_PWM_CTRL3 are called PWM_ON4 .. PWM_ON1. So your x in { 0 ... 3 } is wrong. (Luckily, having some x range over { 0 ... 3 } and others orver { 1 ... 4 } is prone to error and confusion.)

Will fix it. Should I do the same for MC33XS2410_PWM_CTRL1_POL_INV ?

I guess so, otherwise you don't get consistent ranges.

Quoted 6 lines
Quoted 4 lines

For MC33XS2410_MAX_PERIOD_STEP maybe use a different variable name than for the others. For the register definitions the range is over hwpwm (which might be a good name there?), for MC33XS2410_MAX_PERIOD_STEP it's about MC33XS2410_PWM_FREQ_STEP.

What about MC33XS2410_PWM_MAX_PERIOD(x) ?

Consistency is trump.

Quoted 18 lines
Quoted 9 lines
Quoted 9 lines
drivers/pwm/pwm-mc33xs2410.c new file +9 -0
+#define MC33XS2410_MAX_TRANSFERS	5
+#define MC33XS2410_WORD_LEN		2
+
+struct mc33xs2410_pwm {
+	struct spi_device *spi;
+};
+
+static inline struct mc33xs2410_pwm *mc33xs2410_from_chip(struct pwm_chip *chip)
+{
Quoted 8 lines

drivers/pwm/pwm-mc33xs2410.c line 74

Quoted 5 lines
drivers/pwm/pwm-mc33xs2410.c new file +5 -0
+	return pwmchip_get_drvdata(chip);
+}
+
+static int mc33xs2410_xfer_regs(struct spi_device *spi, bool read, u8 *reg,
+				u16 *val, bool *ctrl, int len)

Unless I missed something all ctrl[x] are always identical. If so represent that by a single bool.

Quoted 3 lines

Yes, they are identical. I added the crtl[x] to be able read from ctrl and diag registers. I will change it so it is represented by a single bool, if the feature is needed in the future I can still add it.

ack.

Quoted 38 lines
Quoted 29 lines
Quoted 29 lines
drivers/pwm/pwm-mc33xs2410.c new file +29 -0
+{
+	struct spi_transfer t[MC33XS2410_MAX_TRANSFERS] = { { 0 } };
+	u8 tx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
+	u8 rx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
+	int i, ret, reg_i, val_i;
+
+	if (!len)
+		return 0;
+
+	if (read)
+		len++;
+
+	if (len > MC33XS2410_MAX_TRANSFERS)
+		return -EINVAL;
+
+	for (i = 0; i < len; i++) {
+		reg_i = i * MC33XS2410_WORD_LEN;
+		val_i = reg_i + 1;
+		if (read) {
+			if (i < len - 1) {
+				tx[reg_i] = reg[i];
+				tx[val_i] = ctrl[i] ? MC33XS2410_RD_CTRL : 0;
+				t[i].tx_buf = &tx[reg_i];
+			}
+
+			if (i > 0)
+				t[i].rx_buf = &rx[reg_i - MC33XS2410_WORD_LEN];
+		} else {
+			tx[reg_i] = reg[i] | MC33XS2410_WR;
Quoted 8 lines

drivers/pwm/pwm-mc33xs2410.c line 109

Quoted 6 lines
drivers/pwm/pwm-mc33xs2410.c new file +6 -0
+			tx[val_i] = val[i];
+			t[i].tx_buf = &tx[reg_i];
+		}
+
+		t[i].len = MC33XS2410_WORD_LEN;
+		t[i].cs_change = 1;

Not sure if MC33XS2410_WORD_LEN really improves readability here.

Quoted 2 lines

It is used throughout in the function and improves readability overall, maybe not here but for consistency I would stick to it.

Seems to be subjective.

Quoted 8 lines
Quoted 3 lines

Why is this done using $len transfers, wouldn't a single one do (and maybe be more performant and not rely on a spi controller that supports cs_change)?

Without cs_change after every 16 bit, requests aren't processed by the device. Reading/writing from/to device fails. The SPI controller therefore must support cs_change. Single transfer is not possible because of the cs_change after every 16bit.

There is SPI_CS_WORD for this usecase.

Quoted 27 lines
Quoted 18 lines
Quoted 18 lines
drivers/pwm/pwm-mc33xs2410.c new file +17 -0
+	/* polarity */
+	mask = MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm);
+	val[2] = (state->polarity == PWM_POLARITY_INVERSED) ?
+		 (val[2] | mask) : (val[2] & ~mask);
+
+	/* enable output */
+	mask = MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm);
+	val[3] = (state->enabled && rel_dc >= 0) ? (val[3] | mask) :
+						   (val[3] & ~mask);
+
+	return mc33xs2410_write_regs(spi, reg, val, 4);
+}
+
+static int mc33xs2410_pwm_get_state(struct pwm_chip *chip,
+				    struct pwm_device *pwm,
+				    struct pwm_state *state)
+{
[...]
Quoted 8 lines

drivers/pwm/pwm-mc33xs2410.c line 328

Quoted 5 lines
drivers/pwm/pwm-mc33xs2410.c new file +5 -0
+	state->period = mc33xs2410_pwm_get_period(val[0]);
+	state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm)) ?
+			  PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+	state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm));
+	mc33xs2410_pwm_set_relative_duty_cycle(state, val[1]);

No need to set state->duty_cycle = 0 if state->enabled is false. This is another function I suggest to unroll as it hides more than it abstracts.

Quoted 3 lines

Function can be unrolled, but the check for state->enabled is needed. The device is unable to generate a 0% duty cycle, so it is turned off to generate a 0% duty cylce.

What breaks if you drop the check for state->enabled?

Quoted 16 lines
Quoted 6 lines
Quoted 6 lines

[...]

drivers/pwm/pwm-mc33xs2410.c new file +4 -0
+static int mc33xs2410_probe(struct spi_device *spi)
+{
[...]
+	/* Transition to normal mode */
+	ret = mc33xs2410_modify_reg(spi, MC33XS2410_GLB_CTRL,
Quoted 9 lines

drivers/pwm/pwm-mc33xs2410.c line 389

Quoted 5 lines
drivers/pwm/pwm-mc33xs2410.c new file +5 -0
+				    MC33XS2410_GLB_CTRL_MODE,
+				    MC33XS2410_GLB_CTRL_MODE_NORMAL);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to transition to normal mode\n");

What is the effect of this register write if the PWM was already setup by the bootloader?

Quoted 2 lines

When its setup is done in the bootloader and the watchdog is disabled in the bootloader it shouldn't have any impact.

ok.

Best regards
Uwe

#6 lore raw

Hello Uwe,

Am Thu, Oct 24, 2024 at 11:19:16PM +0200 schrieb Uwe Kleine-König:

Quoted 42 lines

Hello Dimitri,

Quoted 39 lines · Dimitri Fedrau

Am Tue, Oct 22, 2024 at 09:54:50AM +0200 schrieb Uwe Kleine-König:

Quoted 37 lines
Quoted 25 lines
drivers/pwm/pwm-mc33xs2410.c new file +25 -0
+{
+	struct spi_transfer t[MC33XS2410_MAX_TRANSFERS] = { { 0 } };
+	u8 tx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
+	u8 rx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
+	int i, ret, reg_i, val_i;
+
+	if (!len)
+		return 0;
+
+	if (read)
+		len++;
+
+	if (len > MC33XS2410_MAX_TRANSFERS)
+		return -EINVAL;
+
+	for (i = 0; i < len; i++) {
+		reg_i = i * MC33XS2410_WORD_LEN;
+		val_i = reg_i + 1;
+		if (read) {
+			if (i < len - 1) {
+				tx[reg_i] = reg[i];
+				tx[val_i] = ctrl[i] ? MC33XS2410_RD_CTRL : 0;
+				t[i].tx_buf = &tx[reg_i];
+			}
+

drivers/pwm/pwm-mc33xs2410.c line 109

Quoted 10 lines
drivers/pwm/pwm-mc33xs2410.c new file +10 -0
+			if (i > 0)
+				t[i].rx_buf = &rx[reg_i - MC33XS2410_WORD_LEN];
+		} else {
+			tx[reg_i] = reg[i] | MC33XS2410_WR;
+			tx[val_i] = val[i];
+			t[i].tx_buf = &tx[reg_i];
+		}
+
+		t[i].len = MC33XS2410_WORD_LEN;
+		t[i].cs_change = 1;

Not sure if MC33XS2410_WORD_LEN really improves readability here.

Quoted 5 lines
Quoted 2 lines

It is used throughout in the function and improves readability overall, maybe not here but for consistency I would stick to it.

Seems to be subjective.

I will get rid of it. Due to your proposal below, to use SPI_CS_WORD, the code to read/write from/to the device can be simplified by using a single transaction.

Quoted 11 lines
Quoted 8 lines
Quoted 3 lines

Why is this done using $len transfers, wouldn't a single one do (and maybe be more performant and not rely on a spi controller that supports cs_change)?

Without cs_change after every 16 bit, requests aren't processed by the device. Reading/writing from/to device fails. The SPI controller therefore must support cs_change. Single transfer is not possible because of the cs_change after every 16bit.

There is SPI_CS_WORD for this usecase.

Thanks, didn't know about it. Helps a lot to simplify the code to read/write from/to device. Will switch to 16 bits per word and use SPI_CS_WORD.

Quoted 27 lines
Quoted 27 lines
Quoted 26 lines
Quoted 13 lines
drivers/pwm/pwm-mc33xs2410.c new file +13 -0
+	/* polarity */
+	mask = MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm);
+	val[2] = (state->polarity == PWM_POLARITY_INVERSED) ?
+		 (val[2] | mask) : (val[2] & ~mask);
+
+	/* enable output */
+	mask = MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm);
+	val[3] = (state->enabled && rel_dc >= 0) ? (val[3] | mask) :
+						   (val[3] & ~mask);
+
+	return mc33xs2410_write_regs(spi, reg, val, 4);
+}
+

drivers/pwm/pwm-mc33xs2410.c line 328

Quoted 10 lines
drivers/pwm/pwm-mc33xs2410.c new file +9 -0
+static int mc33xs2410_pwm_get_state(struct pwm_chip *chip,
+				    struct pwm_device *pwm,
+				    struct pwm_state *state)
+{
[...]
+	state->period = mc33xs2410_pwm_get_period(val[0]);
+	state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm)) ?
+			  PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+	state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm));
+	mc33xs2410_pwm_set_relative_duty_cycle(state, val[1]);

No need to set state->duty_cycle = 0 if state->enabled is false. This is another function I suggest to unroll as it hides more than it abstracts.

Quoted 6 lines
Quoted 3 lines

Function can be unrolled, but the check for state->enabled is needed. The device is unable to generate a 0% duty cycle, so it is turned off to generate a 0% duty cylce.

What breaks if you drop the check for state->enabled?

The device is unable to generate a 0% duty cycle, to support this you proposed in an earlier review to disable the output. Without checking if the output is disabled, the mc33xs2410_pwm_get_state function returns the wrong duty cycle for a previously setted 0% duty cycle. A "0" value in the MC33XS2410_PWM_DC register means that the relative duty cylce is 1/256. As a result there are complaints if PWM_DEBUG is enabled.

Best regards,
Dimitri

#7 lore raw

Hello Dimitri,

Quoted 9 lines · Dimitri Fedrau

Am Thu, Oct 24, 2024 at 11:19:16PM +0200 schrieb Uwe Kleine-König:

Quoted 2 lines

What breaks if you drop the check for state->enabled?

The device is unable to generate a 0% duty cycle, to support this you proposed in an earlier review to disable the output. Without checking if the output is disabled, the mc33xs2410_pwm_get_state function returns the wrong duty cycle for a previously setted 0% duty cycle. A "0" value in the MC33XS2410_PWM_DC register means that the relative duty cylce is 1/256. As a result there are complaints if PWM_DEBUG is enabled.

I fail to follow. If .enabled=true + .duty_cycle=0 is requested you disable. That's fine. However it shouldn't be necessary to use state->enabled in .get_state(). I didn't look at the actual code, but if you provide a sequence of writes to /sys that trigger a PWM_DEBUG output, I'll take another look.

Best regards
Uwe

#8 lore raw

Hello Uwe,

Am Sun, Nov 03, 2024 at 09:19:36PM +0100 schrieb Uwe Kleine-König:

Quoted 13 lines

Hello Dimitri,

Quoted 9 lines · Dimitri Fedrau

Am Thu, Oct 24, 2024 at 11:19:16PM +0200 schrieb Uwe Kleine-König:

Quoted 2 lines

What breaks if you drop the check for state->enabled?

The device is unable to generate a 0% duty cycle, to support this you proposed in an earlier review to disable the output. Without checking if the output is disabled, the mc33xs2410_pwm_get_state function returns the wrong duty cycle for a previously setted 0% duty cycle. A "0" value in the MC33XS2410_PWM_DC register means that the relative duty cylce is 1/256. As a result there are complaints if PWM_DEBUG is enabled.

Quoted 6 lines

I fail to follow. If .enabled=true + .duty_cycle=0 is requested you disable. That's fine. However it shouldn't be necessary to use state->enabled in .get_state(). I didn't look at the actual code, but if you provide a sequence of writes to /sys that trigger a PWM_DEBUG output, I'll take another look.

Apply 0% duty cycle: .enabled=false + .duty_cycle=0 Below some writes triggering PWM_DEBUG output:

# echo 488282 > /sys/class/pwm/pwmchip3/pwm0/period
# echo 244140 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
# echo 0 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
[   91.813513] mc33xs2410-pwm spi0.0: .apply is supposed to round down duty_cycle (requested: 0/488282, applied: 1908/488282)

Best regards,
Dimitri

#9 lore raw
Quoted 22 lines · Dimitri Fedrau

Hello Uwe,

Am Sun, Nov 03, 2024 at 09:19:36PM +0100 schrieb Uwe Kleine-König:

Quoted 13 lines

Hello Dimitri,

Quoted 9 lines · Dimitri Fedrau

Am Thu, Oct 24, 2024 at 11:19:16PM +0200 schrieb Uwe Kleine-König:

Quoted 2 lines

What breaks if you drop the check for state->enabled?

The device is unable to generate a 0% duty cycle, to support this you proposed in an earlier review to disable the output. Without checking if the output is disabled, the mc33xs2410_pwm_get_state function returns the wrong duty cycle for a previously setted 0% duty cycle. A "0" value in the MC33XS2410_PWM_DC register means that the relative duty cylce is 1/256. As a result there are complaints if PWM_DEBUG is enabled.

Quoted 6 lines

I fail to follow. If .enabled=true + .duty_cycle=0 is requested you disable. That's fine. However it shouldn't be necessary to use state->enabled in .get_state(). I didn't look at the actual code, but if you provide a sequence of writes to /sys that trigger a PWM_DEBUG output, I'll take another look.

Quoted 7 lines

Apply 0% duty cycle: .enabled=false + .duty_cycle=0 Below some writes triggering PWM_DEBUG output:

# echo 488282 > /sys/class/pwm/pwmchip3/pwm0/period
# echo 244140 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
# echo 0 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
[   91.813513] mc33xs2410-pwm spi0.0: .apply is supposed to round down duty_cycle (requested: 0/488282, applied: 1908/488282)

I don't understand that. We're talking about

drivers/pwm/pwm-mc33xs2410.c +1 -10
index f9a334a5e69b..14f5f7312d0a 100644
@@ -244,15 +244,6 @@ static int mc33xs2410_pwm_get_relative_duty_cycle(u64 period, u64 duty_cycle)
 	return duty_cycle - 1;
 }
 
-static void mc33xs2410_pwm_set_relative_duty_cycle(struct pwm_state *state,
-						   u16 duty_cycle)
-{
-	if (!state->enabled)
-		state->duty_cycle = 0;
-	else
-		state->duty_cycle = DIV_ROUND_UP_ULL((duty_cycle + 1) * state->period, 256);
-}
-
 static int mc33xs2410_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 				const struct pwm_state *state)
 {
@@ -325,7 +316,7 @@ static int mc33xs2410_pwm_get_state(struct pwm_chip *chip,
 	state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm)) ?
 			  PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
 	state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm));
-	mc33xs2410_pwm_set_relative_duty_cycle(state, val[1]);
+	state->duty_cycle = DIV_ROUND_UP_ULL((duty_cycle + 1) * state->period, 256);
 	return 0;
 }
 

on top of your patch, right?

echo 0 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle should result in MC33XS2410_PWM_CTRL3 having MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm) cleared. When mc33xs2410_pwm_get_state() is called then it returns state->enabled = false and in that case the above mentioned warning doesn't trigger.

Where is the misunderstanding?

Best regards
Uwe

#10 lore raw

Am Mon, Nov 04, 2024 at 09:52:51AM +0100 schrieb Uwe Kleine-König:

Quoted 62 lines · Dimitri Fedrau
Quoted 22 lines

Hello Uwe,

Am Sun, Nov 03, 2024 at 09:19:36PM +0100 schrieb Uwe Kleine-König:

Quoted 13 lines

Hello Dimitri,

Quoted 9 lines · Dimitri Fedrau

Am Thu, Oct 24, 2024 at 11:19:16PM +0200 schrieb Uwe Kleine-König:

Quoted 2 lines

What breaks if you drop the check for state->enabled?

The device is unable to generate a 0% duty cycle, to support this you proposed in an earlier review to disable the output. Without checking if the output is disabled, the mc33xs2410_pwm_get_state function returns the wrong duty cycle for a previously setted 0% duty cycle. A "0" value in the MC33XS2410_PWM_DC register means that the relative duty cylce is 1/256. As a result there are complaints if PWM_DEBUG is enabled.

Quoted 6 lines

I fail to follow. If .enabled=true + .duty_cycle=0 is requested you disable. That's fine. However it shouldn't be necessary to use state->enabled in .get_state(). I didn't look at the actual code, but if you provide a sequence of writes to /sys that trigger a PWM_DEBUG output, I'll take another look.

Quoted 7 lines

Apply 0% duty cycle: .enabled=false + .duty_cycle=0 Below some writes triggering PWM_DEBUG output:

# echo 488282 > /sys/class/pwm/pwmchip3/pwm0/period
# echo 244140 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
# echo 0 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
[   91.813513] mc33xs2410-pwm spi0.0: .apply is supposed to round down duty_cycle (requested: 0/488282, applied: 1908/488282)

I don't understand that. We're talking about

drivers/pwm/pwm-mc33xs2410.c +1 -10
index f9a334a5e69b..14f5f7312d0a 100644
@@ -244,15 +244,6 @@ static int mc33xs2410_pwm_get_relative_duty_cycle(u64 period, u64 duty_cycle)
 	return duty_cycle - 1;
 }
 
-static void mc33xs2410_pwm_set_relative_duty_cycle(struct pwm_state *state,
-						   u16 duty_cycle)
-{
-	if (!state->enabled)
-		state->duty_cycle = 0;
-	else
-		state->duty_cycle = DIV_ROUND_UP_ULL((duty_cycle + 1) * state->period, 256);
-}
-
 static int mc33xs2410_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 				const struct pwm_state *state)
 {
@@ -325,7 +316,7 @@ static int mc33xs2410_pwm_get_state(struct pwm_chip *chip,
 	state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm)) ?
 			  PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
 	state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm));
-	mc33xs2410_pwm_set_relative_duty_cycle(state, val[1]);
+	state->duty_cycle = DIV_ROUND_UP_ULL((duty_cycle + 1) * state->period, 256);
 	return 0;
 }
 
Quoted 2 lines

on top of your patch, right?

Yes.

Quoted 5 lines

echo 0 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle should result in MC33XS2410_PWM_CTRL3 having MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm) cleared. When mc33xs2410_pwm_get_state() is called then it returns state->enabled = false and in that case the above mentioned warning doesn't trigger.

Yes, as you explained. But the warning is shown.

Quoted 2 lines

Where is the misunderstanding?

if (state->enabled && state->duty_cycle < s2.duty_cycle)

	dev_warn(pwmchip_parent(chip),
		".apply is supposed to round down duty_cycle (requested: %llu/%llu, applied: %llu/%llu)\n",
		state->duty_cycle, state->period,
		s2.duty_cycle, s2.period);

state has previously applied settings and is parameter of pwm_apply_debug, in that case s2=s1, and s1 is returned by get_state:

state->enabled=true
state->duty_cycle=0
s2.enabled=false
s2.duty_cycle=1908

Due to the code the warning should be raised. If it shouldn't the check should be different, something like if (state->enabled && s2.enabled && ...)

Best regards,
Dimitri

#11 lore raw
Quoted 19 lines · Dimitri Fedrau

Am Mon, Nov 04, 2024 at 09:52:51AM +0100 schrieb Uwe Kleine-König:

Quoted 4 lines

echo 0 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle should result in MC33XS2410_PWM_CTRL3 having MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm) cleared. When mc33xs2410_pwm_get_state() is called then it returns state->enabled = false and in that case the above mentioned warning doesn't trigger.

Yes, as you explained. But the warning is shown.

Quoted 1 line

Where is the misunderstanding?

if (state->enabled && state->duty_cycle < s2.duty_cycle)

	dev_warn(pwmchip_parent(chip),
		".apply is supposed to round down duty_cycle (requested: %llu/%llu, applied: %llu/%llu)\n",
		state->duty_cycle, state->period,
		s2.duty_cycle, s2.period);

state has previously applied settings and is parameter of pwm_apply_debug, in that case s2=s1, and s1 is returned by get_state:

Quoted 8 lines

state->enabled=true
state->duty_cycle=0
s2.enabled=false
s2.duty_cycle=1908

Due to the code the warning should be raised. If it shouldn't the check should be different, something like if (state->enabled && s2.enabled && ...)

This should be fixed with https://lore.kernel.org/linux-pwm/[email protected]/T/#u
Test feedback and review very welcome.

Best regards
Uwe