mbox series

[v3,0/7] Add support for MAX7360

Message ID 20250113-mdb-max7360-support-v3-0-9519b4acb0b1@bootlin.com
Headers show
Series Add support for MAX7360 | expand

Message

Mathieu Dubois-Briand Jan. 13, 2025, 12:42 p.m. UTC
This series implements a set of drivers allowing to support the Maxim
Integrated MAX7360 device.

The MAX7360 is an I2C key-switch and led controller, with following
functionalities:
- Keypad controller for a key matrix of up to 8 rows and 8 columns.
- Rotary encoder support, for a single rotary encoder.
- Up to 8 PWM outputs.
- Up to 8 GPIOs with support for interrupts and 6 GPOs.

Chipset pins are shared between all functionalities, so all cannot be
used at the same time.

Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
Changes in v3:
- Fix MFD device tree binding to add gpio child nodes.
- Fix various small issues in device tree bindings.
- Add missing line returns in error messages.
- Use dev_err_probe() when possible.
- Link to v2: https://lore.kernel.org/r/20241223-mdb-max7360-support-v2-0-37a8d22c36ed@bootlin.com

Changes in v2:
- Removing device tree subnodes for keypad, rotary encoder and pwm
  functionalities.
- Fixed dt-bindings syntax and naming.
- Fixed missing handling of requested period in PWM driver.
- Cleanup of the code
- Link to v1: https://lore.kernel.org/r/20241219-mdb-max7360-support-v1-0-8e8317584121@bootlin.com

---
Kamel Bouhara (2):
      mfd: Add max7360 support
      pwm: max7360: Add MAX7360 PWM support

Mathieu Dubois-Briand (5):
      dt-bindings: mfd: gpio: Add MAX7360
      gpio: max7360: Add MAX7360 gpio support
      input: keyboard: Add support for MAX7360 keypad
      input: misc: Add support for MAX7360 rotary
      MAINTAINERS: Add entry on MAX7360 driver

 .../bindings/gpio/maxim,max7360-gpio.yaml          |  90 ++++
 .../devicetree/bindings/mfd/maxim,max7360.yaml     | 140 +++++++
 MAINTAINERS                                        |  12 +
 drivers/gpio/Kconfig                               |  11 +
 drivers/gpio/Makefile                              |   1 +
 drivers/gpio/gpio-max7360.c                        | 454 +++++++++++++++++++++
 drivers/input/keyboard/Kconfig                     |  12 +
 drivers/input/keyboard/Makefile                    |   1 +
 drivers/input/keyboard/max7360-keypad.c            | 284 +++++++++++++
 drivers/input/misc/Kconfig                         |  11 +
 drivers/input/misc/Makefile                        |   1 +
 drivers/input/misc/max7360-rotary.c                | 183 +++++++++
 drivers/mfd/Kconfig                                |  12 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/max7360.c                              | 296 ++++++++++++++
 drivers/pwm/Kconfig                                |  11 +
 drivers/pwm/Makefile                               |   1 +
 drivers/pwm/pwm-max7360.c                          | 220 ++++++++++
 include/linux/mfd/max7360.h                        | 109 +++++
 19 files changed, 1850 insertions(+)
---
base-commit: 78d4f34e2115b517bcbfe7ec0d018bbbb6f9b0b8
change-id: 20241219-mdb-max7360-support-223a8ce45ba3

Best regards,

Comments

Krzysztof Kozlowski Jan. 14, 2025, 8:11 a.m. UTC | #1
On Mon, Jan 13, 2025 at 01:42:25PM +0100, Mathieu Dubois-Briand wrote:
> +  interrupt-controller: true
> +
> +  "#interrupt-cells":
> +    const: 2
> +
> +  maxim,constant-current-disable:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: >

Drop >

> +      Bit field, each bit disables constant-current output of the associated
> +      GPIO, starting from the least significant bit for the first GPIO.

maximum: 0xff?

> +
> +required:
> +  - compatible
> +  - gpio-controller
> +  - ngpios
> +

allOf: here, so you won't re-indent it later.

> +if:
> +  properties:
> +    compatible:
> +      contains:
> +        enum:
> +          - maxim,max7360-gpio
> +then:
> +  required:
> +    - interrupt-controller
> +else:
> +  properties:
> +    interrupt-controller: false
> +    maxim,constant-current-disable: false
> +
> +    ngpios:
> +      maximum: 6
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    gpio {
> +      compatible = "maxim,max7360-gpio";
> +
> +      gpio-controller;
> +      #gpio-cells = <2>;
> +      ngpios = <8>;
> +      maxim,constant-current-disable = <0x06>;
> +
> +      interrupt-controller;
> +      #interrupt-cells = <2>;
> +    };

...

> +  interrupt-names:
> +    items:
> +      - const: inti
> +      - const: intk
> +
> +  keypad-debounce-delay-ms:
> +    description: Keypad debounce delay in ms
> +    minimum: 9
> +    maximum: 40
> +    default: 9
> +
> +  autorepeat: true

Drop, not needed.

> +
> +  rotary-debounce-delay-ms:
> +    description: Rotary encoder debounce delay in ms
> +    minimum: 0
> +    maximum: 15
> +    default: 0
> +
> +  linux,axis:
> +    description: The input subsystem axis to map to this rotary encoder.

Missing type. I guess you wanted to reference rotary encoder schema,
next to input and matrix-keymap?

> +
> +  "#pwm-cells":
> +    const: 3
> +
> +  gpio:
> +    $ref: /schemas/gpio/maxim,max7360-gpio.yaml#
> +    description: >

Drop >

> +      PORT0 to PORT7 general purpose input/output pins configuration.
> +
> +  gpo:
> +    $ref: /schemas/gpio/maxim,max7360-gpio.yaml#
> +    description: >

Drop >

> +      COL2 to COL7 general purpose output pins configuration.
> +      Allows to use unused keypad columns as outputs.
> +      The MAX7360 has 8 column lines and 6 of them can be used as GPOs. Value
> +      of ngpios must be coherent with the value of keypad,num-columns, as their
> +      sum must not exceed the number of physical lines.
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - interrupt-names
> +  - linux,keymap
> +  - linux,axis
> +  - "#pwm-cells"

gpio and gpo nodes are optional? How would the driver behave? I assume
you need to define the partition between GPIOs, especially that 'ngpios'
are a required property in their schema.

> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/input/input.h>
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +    i2c {
> +      #address-cells = <1>;
> +      #size-cells = <0>;
> +
> +      io-expander@38 {
> +        compatible = "maxim,max7360";
> +        reg = <0x38>;
> +
> +        interrupt-parent = <&gpio1>;
> +        interrupts = <23 IRQ_TYPE_LEVEL_LOW>,
> +                     <24 IRQ_TYPE_LEVEL_LOW>;
> +        interrupt-names = "inti", "intk";
> +
> +        keypad,num-rows = <8>;
> +        keypad,num-columns = <4>;
> +        linux,keymap = <
> +          MATRIX_KEY(0x00, 0x00, KEY_F5)
> +          MATRIX_KEY(0x01, 0x00, KEY_F4)
> +          MATRIX_KEY(0x02, 0x01, KEY_F6)
> +          >;
> +        keypad-debounce-delay-ms = <10>;
> +        autorepeat;
> +
> +        rotary-debounce-delay-ms = <2>;
> +        linux,axis = <0>; /* REL_X */
> +
> +        #pwm-cells = <3>;
> +
> +        max7360_gpio: gpio {
> +          compatible = "maxim,max7360-gpio";
> +
> +          gpio-controller;
> +          #gpio-cells = <2>;
> +          ngpios = <8>;
> +          maxim,constant-current-disable = <0x06>;
> +
> +          interrupt-controller;
> +          #interrupt-cells = <0x2>;
> +        };
> +
> +        max7360_gpo: gpo {
> +          compatible = "maxim,max7360-gpo";
> +
> +          gpio-controller;
> +          #gpio-cells = <2>;
> +          ngpios = <4>;
> +        };
> +      };
> +    };
> 
> -- 
> 2.39.5
>
Mathieu Dubois-Briand Jan. 14, 2025, 1:16 p.m. UTC | #2
On Mon Jan 13, 2025 at 1:42 PM CET, Mathieu Dubois-Briand wrote:
> Add driver for Maxim Integrated MAX7360 rotary encoder controller,
> supporting a single rotary switch.
>
> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> ---
...
> +static irqreturn_t max7360_rotary_irq(int irq, void *data)
> +{
> +	struct max7360_rotary *max7360_rotary = data;
> +	int val;
> +	int ret;
> +
> +	ret = regmap_read(max7360_rotary->regmap, MAX7360_REG_RTR_CNT, &val);
> +	if (ret < 0) {
> +		dev_err(&max7360_rotary->input->dev,
> +			"Failed to read rotary counter\n");
> +		return IRQ_NONE;
> +	}
> +
> +	if (val == 0) {
> +		dev_dbg(&max7360_rotary->input->dev,
> +			"Got a spurious interrupt\n");
> +
> +		return IRQ_NONE;
> +	}
> +
> +	input_report_rel(max7360_rotary->input, max7360_rotary->axis,
> +			 (int8_t)val);
> +	input_sync(max7360_rotary->input);
> +

I have a question about the type of events reported here.

I used rotary_encoder.c as a reference, so I'm reporting some EV_REL
events on a given axis, such as REL_X.

On the other hand, I know there is an out-of-tree version of this
driver that is instead reporting key events, such as KEY_DOWN or KEY_UP.
I also know there are existing applications that do rely on this
behaviour.

So my question is, what is the best kind of events to report here ?
Is there any guideline that do apply here? Should I better try to mimic
the behaviour of the existing out-of-tree driver, or should I mimic the
behaviour of rotary_encoder.c, so we have a similar behaviour for all
in-kernel rotary encoder drivers?

> +	return IRQ_HANDLED;
> +}
> +
Uwe Kleine-König Jan. 17, 2025, 9:33 a.m. UTC | #3
Hello Mathieu,

On Mon, Jan 13, 2025 at 01:42:27PM +0100, mathieu.dubois-briand@bootlin.com wrote:
> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
> 
> Add driver for Maxim Integrated MAX7360 PWM controller, supporting up to
> 8 independent PWM outputs.
> 
> Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> ---
>  drivers/pwm/Kconfig       |  11 +++
>  drivers/pwm/Makefile      |   1 +
>  drivers/pwm/pwm-max7360.c | 220 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 232 insertions(+)
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 0915c1e7df16..399dc3f76e92 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -745,4 +745,15 @@ config PWM_XILINX
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-xilinx.
>  
> +config PWM_MAX7360
> +	tristate "MAX7360 PWMs"
> +	depends on MFD_MAX7360
> +	depends on OF_GPIO
> +	help
> +	  PWM driver for Maxim Integrated MAX7360 multifunction device, with
> +	  support for up to 8 PWM outputs.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-max7360.
> +
>  endif
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9081e0c0e9e0..ae8908ffc892 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -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_MAX7360)	+= pwm-max7360.o
>  obj-$(CONFIG_PWM_MESON)		+= pwm-meson.o
>  obj-$(CONFIG_PWM_MEDIATEK)	+= pwm-mediatek.o
>  obj-$(CONFIG_PWM_MICROCHIP_CORE)	+= pwm-microchip-core.o
> diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
> new file mode 100644
> index 000000000000..e76a8aa05fc4
> --- /dev/null
> +++ b/drivers/pwm/pwm-max7360.c
> @@ -0,0 +1,220 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright 2024 Bootlin
> + *
> + * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
> + *
> + * Limitations:
> + * - Only supports normal polarity.
> + * - The period is fixed to 2 ms.
> + * - Only the duty cycle can be changed, new values are applied at the beginning
> + *   of the next cycle.
> + * - When disabled, the output is put in Hi-Z.
> + */
> +#include <linux/math.h>
> +#include <linux/mfd/max7360.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +
> +#define MAX7360_NUM_PWMS			8
> +#define MAX7360_PWM_MAX_RES			256
> +#define MAX7360_PWM_PERIOD_NS			2000000 /* 500 Hz */
> +#define MAX7360_PWM_COMMON_PWN			BIT(5)
> +#define MAX7360_PWM_CTRL_ENABLE(n)		BIT(n)
> +#define MAX7360_PWM_PORT(n)			BIT(n)
> +
> +struct max7360_pwm {
> +	struct device *parent;
> +	struct regmap *regmap;
> +};
> +
> +static inline struct max7360_pwm *to_max7360_pwm(struct pwm_chip *chip)

Please stick to the common function prefix also here. So something like
max7360_pwm_from_chip would work.

> +{
> +	return pwmchip_get_drvdata(chip);
> +}
> +
> +static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct max7360_pwm *max7360_pwm;
> +	int ret;
> +
> +	max7360_pwm = to_max7360_pwm(chip);
> +	ret = max7360_port_pin_request(max7360_pwm->parent, pwm->hwpwm,
> +				       true);

The statement fits on a single line just fine.

> +	if (ret) {
> +		dev_warn(&chip->dev, "failed to request pwm-%d\n", pwm->hwpwm);
> +		return ret;
> +	}
> +
> +	ret = regmap_write_bits(max7360_pwm->regmap,
> +				MAX7360_REG_PWMCFG + pwm->hwpwm,

Can you make MAX7360_REG_PWMCFG a macro accepting pwm->hwpwm as
parameter please?

> +				MAX7360_PWM_COMMON_PWN,
> +				0);
> +	if (ret) {
> +		dev_warn(&chip->dev,
> +			 "failed to write pwm-%d cfg register, error %d\n",
> +			 pwm->hwpwm, ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_write_bits(max7360_pwm->regmap, MAX7360_REG_PORTS,
> +				MAX7360_PWM_PORT(pwm->hwpwm),
> +				MAX7360_PWM_PORT(pwm->hwpwm));
> +	if (ret) {
> +		dev_warn(&chip->dev,
> +			 "failed to write pwm-%d ports register, error %d\n",
> +			 pwm->hwpwm, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void max7360_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct max7360_pwm *max7360_pwm;
> +	int ret;
> +
> +	max7360_pwm = to_max7360_pwm(chip);
> +	ret = regmap_write_bits(max7360_pwm->regmap, MAX7360_REG_GPIOCTRL,
> +				MAX7360_PWM_CTRL_ENABLE(pwm->hwpwm),
> +				0);
> +	if (ret)
> +		dev_warn(&chip->dev, "failed to disable pwm-%d , error %d\n",
> +			 pwm->hwpwm, ret);

.free is not supposed to stop the PWM. Though I admit this concept is a
bit fuzzy, because when unconfiguring the pin function this is kind of
moot. Still it's the responsibility of the consumer to stop the PWM
before pwm_put().

Also s/ ,/,/ and use %pe for error codes.

> +	max7360_port_pin_request(max7360_pwm->parent, pwm->hwpwm,
> +				 false);
> +}
> +
> +static int max7360_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			     const struct pwm_state *state)
> +{
> +	struct max7360_pwm *max7360_pwm;
> +	u64 duty_steps;
> +	int ret;
> +
> +	if (state->polarity != PWM_POLARITY_NORMAL)
> +		return -EINVAL;
> +
> +	if (state->period != MAX7360_PWM_PERIOD_NS) {
> +		dev_warn(&chip->dev,
> +			 "unsupported pwm period: %llu, should be %u\n",
> +			 state->period, MAX7360_PWM_PERIOD_NS);
> +		return -EINVAL;

Please don't emit error messages in .apply(). Also a driver is supposed
to round down .period, so any value >= MAX7360_PWM_PERIOD_NS should be
accepted.

Also note that you might want to implement the waveform callbacks
instead of .apply() and .get_state() for the more modern abstraction
(with slightly different rounding rules).

> +	}
> +
> +	duty_steps = mul_u64_u64_div_u64(state->duty_cycle, MAX7360_PWM_MAX_RES,
> +					 MAX7360_PWM_PERIOD_NS);
> +
> +	max7360_pwm = to_max7360_pwm(chip);
> +	ret = regmap_write_bits(max7360_pwm->regmap, MAX7360_REG_GPIOCTRL,
> +				MAX7360_PWM_CTRL_ENABLE(pwm->hwpwm),
> +				MAX7360_PWM_CTRL_ENABLE(pwm->hwpwm));
> +	if (ret) {
> +		dev_warn(&chip->dev, "failed to enable pwm-%d , error %d\n",
> +			 pwm->hwpwm, ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_write(max7360_pwm->regmap, MAX7360_REG_PWMBASE + pwm->hwpwm,
> +			   duty_steps >= 255 ? 255 : duty_steps);
> +	if (ret) {
> +		dev_warn(&chip->dev,
> +			 "failed to apply pwm duty_cycle %llu on pwm-%d, error %d\n",
> +			 duty_steps, pwm->hwpwm, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max7360_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> +				 struct pwm_state *state)
> +{
> +	struct max7360_pwm *max7360_pwm;
> +	unsigned int val;
> +	int ret;
> +
> +	max7360_pwm = to_max7360_pwm(chip);
> +
> +	state->period = MAX7360_PWM_PERIOD_NS;
> +	state->polarity = PWM_POLARITY_NORMAL;
> +
> +	ret = regmap_read(max7360_pwm->regmap, MAX7360_REG_GPIOCTRL, &val);
> +	if (ret) {
> +		dev_warn(&chip->dev,
> +			 "failed to read pwm configuration on pwm-%d, error %d\n",
> +			 pwm->hwpwm, ret);

Similar to .apply() please no messages in .get_state(). Just fail
silently.

> +		return ret;
> +	}
> +	state->enabled = !!(val & MAX7360_PWM_CTRL_ENABLE(pwm->hwpwm));
> +
> +	ret = regmap_read(max7360_pwm->regmap, MAX7360_REG_PWMBASE + pwm->hwpwm,
> +			  &val);
> +	if (ret) {
> +		dev_warn(&chip->dev,
> +			 "failed to read pwm duty_cycle on pwm-%d, error %d\n",
> +			 pwm->hwpwm, ret);
> +		return ret;
> +	}
> +	state->duty_cycle = mul_u64_u64_div_u64(val, MAX7360_PWM_PERIOD_NS,
> +						MAX7360_PWM_MAX_RES);

You have to round up here. I would expect that the checks in the core
(with PWM_DEBUG=1) help you catching this type of error. In your case
changing the configuration to

	.period = 2000000,
	.duty_cycle = 234379,

should yield some hint in the kernel log.

> +	return 0;
> +}

Best regards
Uwe
Mathieu Dubois-Briand Jan. 17, 2025, 2:11 p.m. UTC | #4
On Fri Jan 17, 2025 at 10:33 AM CET, Uwe Kleine-König wrote:
> Hello Mathieu,
>
> On Mon, Jan 13, 2025 at 01:42:27PM +0100, mathieu.dubois-briand@bootlin.com wrote:
> > From: Kamel Bouhara <kamel.bouhara@bootlin.com>
...
> > +static int max7360_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > +			     const struct pwm_state *state)
> > +{
> > +	struct max7360_pwm *max7360_pwm;
> > +	u64 duty_steps;
> > +	int ret;
> > +
> > +	if (state->polarity != PWM_POLARITY_NORMAL)
> > +		return -EINVAL;
> > +
> > +	if (state->period != MAX7360_PWM_PERIOD_NS) {
> > +		dev_warn(&chip->dev,
> > +			 "unsupported pwm period: %llu, should be %u\n",
> > +			 state->period, MAX7360_PWM_PERIOD_NS);
> > +		return -EINVAL;
>
> Please don't emit error messages in .apply(). Also a driver is supposed
> to round down .period, so any value >= MAX7360_PWM_PERIOD_NS should be
> accepted.
>
> Also note that you might want to implement the waveform callbacks
> instead of .apply() and .get_state() for the more modern abstraction
> (with slightly different rounding rules).
>

Sure, I just switched to the waveform callbacks, it was quite
straightforward.

> > +static int max7360_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> > +				 struct pwm_state *state)
> > +{
...
> > +	state->duty_cycle = mul_u64_u64_div_u64(val, MAX7360_PWM_PERIOD_NS,
> > +						MAX7360_PWM_MAX_RES);
>
> You have to round up here. I would expect that the checks in the core
> (with PWM_DEBUG=1) help you catching this type of error. In your case
> changing the configuration to
>
> 	.period = 2000000,
> 	.duty_cycle = 234379,
>
> should yield some hint in the kernel log.
>

Thanks for the reproduce steps: I saw the bug and fixed it. Also
MAX7360_PWM_MAX_RES had to be set to 255 and not 256...

> > +	return 0;
> > +}
>
> Best regards
> Uwe

I also fixed all other points mentioned in your mail. Thanks again for your review.
Uwe Kleine-König Jan. 17, 2025, 2:40 p.m. UTC | #5
On Fri, Jan 17, 2025 at 03:11:29PM +0100, Mathieu Dubois-Briand wrote:
> On Fri Jan 17, 2025 at 10:33 AM CET, Uwe Kleine-König wrote:
> > Hello Mathieu,
> >
> > On Mon, Jan 13, 2025 at 01:42:27PM +0100, mathieu.dubois-briand@bootlin.com wrote:
> > > From: Kamel Bouhara <kamel.bouhara@bootlin.com>
> ...
> > > +static int max7360_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > > +			     const struct pwm_state *state)
> > > +{
> > > +	struct max7360_pwm *max7360_pwm;
> > > +	u64 duty_steps;
> > > +	int ret;
> > > +
> > > +	if (state->polarity != PWM_POLARITY_NORMAL)
> > > +		return -EINVAL;
> > > +
> > > +	if (state->period != MAX7360_PWM_PERIOD_NS) {
> > > +		dev_warn(&chip->dev,
> > > +			 "unsupported pwm period: %llu, should be %u\n",
> > > +			 state->period, MAX7360_PWM_PERIOD_NS);
> > > +		return -EINVAL;
> >
> > Please don't emit error messages in .apply(). Also a driver is supposed
> > to round down .period, so any value >= MAX7360_PWM_PERIOD_NS should be
> > accepted.
> >
> > Also note that you might want to implement the waveform callbacks
> > instead of .apply() and .get_state() for the more modern abstraction
> > (with slightly different rounding rules).
> >
> 
> Sure, I just switched to the waveform callbacks, it was quite
> straightforward.

sounds great. Note that the detail in rounding that is different for
waveforms is that a value that cannot be round down to a valid value
(because it's too small) is round up. This is a bit ugly in the drivers
but simplifies usage considerably. So you never return -EINVAL because
the values don't fit.

> Thanks for the reproduce steps: I saw the bug and fixed it. Also
> MAX7360_PWM_MAX_RES had to be set to 255 and not 256...

A good test (for a driver doing .apply/.get_state) is a sequence of
increasing settings. So something like:

	for p in range(1000, 10000):
	    pwm_apply(period=p, duty_cycle=0, ...)

and also do the same for duty_cycle and also try decreasing series.

Best regards
Uwe
Mathieu Dubois-Briand Jan. 17, 2025, 3:47 p.m. UTC | #6
On Fri Jan 17, 2025 at 3:40 PM CET, Uwe Kleine-König wrote:
> On Fri, Jan 17, 2025 at 03:11:29PM +0100, Mathieu Dubois-Briand wrote:
> > On Fri Jan 17, 2025 at 10:33 AM CET, Uwe Kleine-König wrote:
> > > Hello Mathieu,
> > >
> > > On Mon, Jan 13, 2025 at 01:42:27PM +0100, mathieu.dubois-briand@bootlin.com wrote:
> > > > From: Kamel Bouhara <kamel.bouhara@bootlin.com>
> > ...
> > > > +static int max7360_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > > > +			     const struct pwm_state *state)
> > > > +{
> > > > +	struct max7360_pwm *max7360_pwm;
> > > > +	u64 duty_steps;
> > > > +	int ret;
> > > > +
> > > > +	if (state->polarity != PWM_POLARITY_NORMAL)
> > > > +		return -EINVAL;
> > > > +
> > > > +	if (state->period != MAX7360_PWM_PERIOD_NS) {
> > > > +		dev_warn(&chip->dev,
> > > > +			 "unsupported pwm period: %llu, should be %u\n",
> > > > +			 state->period, MAX7360_PWM_PERIOD_NS);
> > > > +		return -EINVAL;
> > >
> > > Please don't emit error messages in .apply(). Also a driver is supposed
> > > to round down .period, so any value >= MAX7360_PWM_PERIOD_NS should be
> > > accepted.
> > >
> > > Also note that you might want to implement the waveform callbacks
> > > instead of .apply() and .get_state() for the more modern abstraction
> > > (with slightly different rounding rules).
> > >
> > 
> > Sure, I just switched to the waveform callbacks, it was quite
> > straightforward.
>
> sounds great. Note that the detail in rounding that is different for
> waveforms is that a value that cannot be round down to a valid value
> (because it's too small) is round up. This is a bit ugly in the drivers
> but simplifies usage considerably. So you never return -EINVAL because
> the values don't fit.
>

Sorry, I'm not sure I got it right. Does this affect the three members
of pwm_waveform (period_length_ns, duty_offset_ns, duty_length_ns) ? So
on this device where the period is fixed and I cannot define an offset,
does that mean I will silently accept any value for period_length_ns and
duty_offset_ns ?

Best regards,
Mathieu
Uwe Kleine-König Jan. 20, 2025, 2:13 p.m. UTC | #7
Hello Mathieu,

On Fri, Jan 17, 2025 at 04:47:45PM +0100, Mathieu Dubois-Briand wrote:
> On Fri Jan 17, 2025 at 3:40 PM CET, Uwe Kleine-König wrote:
> > sounds great. Note that the detail in rounding that is different for
> > waveforms is that a value that cannot be round down to a valid value
> > (because it's too small) is round up. This is a bit ugly in the drivers
> > but simplifies usage considerably. So you never return -EINVAL because
> > the values don't fit.
> 
> Sorry, I'm not sure I got it right. Does this affect the three members
> of pwm_waveform (period_length_ns, duty_offset_ns, duty_length_ns) ? So
> on this device where the period is fixed and I cannot define an offset,
> does that mean I will silently accept any value for period_length_ns and
> duty_offset_ns ?

Yes. The fromhw callback obviously always fills the respective constants
into .period_length_ns and .duty_offset_ns and the tohw callback
essentially only looks at .duty_length_ns.

Best regards
Uwe
Mathieu Dubois-Briand Jan. 22, 2025, 12:37 p.m. UTC | #8
On Mon Jan 20, 2025 at 3:13 PM CET, Uwe Kleine-König wrote:
> Hello Mathieu,
>
> On Fri, Jan 17, 2025 at 04:47:45PM +0100, Mathieu Dubois-Briand wrote:
> > On Fri Jan 17, 2025 at 3:40 PM CET, Uwe Kleine-König wrote:
> > > sounds great. Note that the detail in rounding that is different for
> > > waveforms is that a value that cannot be round down to a valid value
> > > (because it's too small) is round up. This is a bit ugly in the drivers
> > > but simplifies usage considerably. So you never return -EINVAL because
> > > the values don't fit.
> > 
> > Sorry, I'm not sure I got it right. Does this affect the three members
> > of pwm_waveform (period_length_ns, duty_offset_ns, duty_length_ns) ? So
> > on this device where the period is fixed and I cannot define an offset,
> > does that mean I will silently accept any value for period_length_ns and
> > duty_offset_ns ?
>
> Yes. The fromhw callback obviously always fills the respective constants
> into .period_length_ns and .duty_offset_ns and the tohw callback
> essentially only looks at .duty_length_ns.
>
> Best regards
> Uwe

Ok, thanks! I will make these changes for the next version.

Best regards,
Mathieu