mbox series

[v7,00/11] Add support for MAX7360

Message ID 20250428-mdb-max7360-support-v7-0-4e0608d0a7ff@bootlin.com
Headers show
Series Add support for MAX7360 | expand

Message

Mathieu Dubois-Briand April 28, 2025, 11:57 a.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 v7:
- Add rotary encoder absolute axis support in device tree bindings and
  driver.
- Lot of small changes in keypad, rotary encoder and GPIO drivers.
- Rebased on v6.15-rc4
- Link to v6: https://lore.kernel.org/r/20250409-mdb-max7360-support-v6-0-7a2535876e39@bootlin.com

Changes in v6:
- Rebased on v6.15-rc1.
- Use device_set_of_node_from_dev() instead of creating PWM and Pinctrl
  on parent device.
- Various small fixes in all drivers.
- Fix pins property pattern in pinctrl dt bindings.
- Link to v5: https://lore.kernel.org/r/20250318-mdb-max7360-support-v5-0-fb20baf97da0@bootlin.com

Changes in v5:
- Add pinctrl driver to replace the previous use of request()/free()
  callbacks for PORT pins.
- Remove ngpios property from GPIO device tree bindings.
- Use GPIO valid_mask to mark unusable keypad columns GPOs, instead of
  changing ngpios.
- Drop patches adding support for request()/free() callbacks in GPIO
  regmap and gpio_regmap_get_ngpio().
- Allow gpio_regmap_register() to create the associated regmap IRQ.
- Various fixes in MFD, PWM, GPIO and KEYPAD drivers.
- Link to v4: https://lore.kernel.org/r/20250214-mdb-max7360-support-v4-0-8a35c6dbb966@bootlin.com

Changes in v4:
- Modified the GPIO driver to use gpio-regmap and regmap-irq.
- Add support for request()/free() callbacks in gpio-regmap.
- Add support for status_is_level in regmap-irq.
- Switched the PWM driver to waveform callbacks.
- Various small fixes in MFD, PWM, GPIO drivers and dt bindings.
- Rebased on v6.14-rc2.
- Link to v3: https://lore.kernel.org/r/20250113-mdb-max7360-support-v3-0-9519b4acb0b1@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 (9):
      dt-bindings: mfd: gpio: Add MAX7360
      pinctrl: Add MAX7360 pinctrl driver
      regmap: irq: Add support for chips without separate IRQ status
      gpio: regmap: Allow to allocate regmap-irq device
      gpio: regmap: Allow to provide init_valid_mask callback
      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          |  83 ++++++
 .../devicetree/bindings/mfd/maxim,max7360.yaml     | 191 +++++++++++++
 MAINTAINERS                                        |  13 +
 drivers/base/regmap/regmap-irq.c                   |  98 ++++---
 drivers/gpio/Kconfig                               |  12 +
 drivers/gpio/Makefile                              |   1 +
 drivers/gpio/gpio-max7360.c                        | 255 +++++++++++++++++
 drivers/gpio/gpio-regmap.c                         |  22 +-
 drivers/input/keyboard/Kconfig                     |  12 +
 drivers/input/keyboard/Makefile                    |   1 +
 drivers/input/keyboard/max7360-keypad.c            | 302 +++++++++++++++++++++
 drivers/input/misc/Kconfig                         |  10 +
 drivers/input/misc/Makefile                        |   1 +
 drivers/input/misc/max7360-rotary.c                | 198 ++++++++++++++
 drivers/mfd/Kconfig                                |  14 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/max7360.c                              | 184 +++++++++++++
 drivers/pinctrl/Kconfig                            |  11 +
 drivers/pinctrl/Makefile                           |   1 +
 drivers/pinctrl/pinctrl-max7360.c                  | 208 ++++++++++++++
 drivers/pwm/Kconfig                                |  10 +
 drivers/pwm/Makefile                               |   1 +
 drivers/pwm/pwm-max7360.c                          | 181 ++++++++++++
 include/linux/gpio/regmap.h                        |  18 ++
 include/linux/mfd/max7360.h                        | 109 ++++++++
 include/linux/regmap.h                             |   3 +
 26 files changed, 1907 insertions(+), 33 deletions(-)
---
base-commit: b4432656b36e5cc1d50a1f2dc15357543add530e
change-id: 20241219-mdb-max7360-support-223a8ce45ba3

Best regards,

Comments

Lee Jones May 1, 2025, 12:59 p.m. UTC | #1
On Mon, 28 Apr 2025, mathieu.dubois-briand@bootlin.com wrote:

> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
> 
> Add core driver to support MAX7360 i2c chip, multi function device
> with keypad, GPIO, PWM, GPO and rotary encoder submodules.
> 
> Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
> Co-developed-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> ---
>  drivers/mfd/Kconfig         |  14 ++++
>  drivers/mfd/Makefile        |   1 +
>  drivers/mfd/max7360.c       | 184 ++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/max7360.h | 109 ++++++++++++++++++++++++++
>  4 files changed, 308 insertions(+)

Getting there.  Couple of nits.  Last push!

> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 22b936310039..c2998c6ce54c 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -2422,5 +2422,19 @@ config MFD_UPBOARD_FPGA
>  	  To compile this driver as a module, choose M here: the module will be
>  	  called upboard-fpga.
>  
> +config MFD_MAX7360
> +	tristate "Maxim MAX7360 I2C IO Expander"
> +	depends on I2C
> +	select MFD_CORE
> +	select REGMAP_I2C
> +	select REGMAP_IRQ
> +	help
> +	  Say yes here to add support for Maxim MAX7360 device, embedding
> +	  keypad, rotary encoder, PWM and GPIO features.
> +
> +	  This driver provides common support for accessing the device;
> +	  additional drivers must be enabled in order to use the functionality
> +	  of the device.
> +
>  endmenu
>  endif
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 948cbdf42a18..add9ff58eb25 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -162,6 +162,7 @@ obj-$(CONFIG_MFD_DA9063)	+= da9063.o
>  obj-$(CONFIG_MFD_DA9150)	+= da9150-core.o
>  
>  obj-$(CONFIG_MFD_MAX14577)	+= max14577.o
> +obj-$(CONFIG_MFD_MAX7360)	+= max7360.o
>  obj-$(CONFIG_MFD_MAX77541)	+= max77541.o
>  obj-$(CONFIG_MFD_MAX77620)	+= max77620.o
>  obj-$(CONFIG_MFD_MAX77650)	+= max77650.o
> diff --git a/drivers/mfd/max7360.c b/drivers/mfd/max7360.c
> new file mode 100644
> index 000000000000..9a223a9b409d
> --- /dev/null
> +++ b/drivers/mfd/max7360.c
> @@ -0,0 +1,184 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Maxim MAX7360 Core Driver
> + *
> + * Copyright 2025 Bootlin
> + *
> + * Author: Kamel Bouhara <kamel.bouhara@bootlin.com>
> + * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> + */
> +
> +#include <linux/array_size.h>
> +#include <linux/bits.h>
> +#include <linux/delay.h>
> +#include <linux/device/devres.h>
> +#include <linux/dev_printk.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/max7360.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +
> +static const struct mfd_cell max7360_cells[] = {
> +	{
> +		.name           = "max7360-pinctrl",
> +	},

All of these single line entries should be placed on a single line.

	{ .name = "max7360-pinctrl" },
	{ .name = "max7360-pwm" },

If ordering is not important.  Please group them.

> +	{
> +		.name           = "max7360-pwm",
> +	},
> +	{
> +		.name           = "max7360-gpo",
> +		.of_compatible	= "maxim,max7360-gpo",
> +	},
> +	{
> +		.name           = "max7360-gpio",
> +		.of_compatible	= "maxim,max7360-gpio",
> +	},
> +	{
> +		.name           = "max7360-keypad",
> +	},
> +	{
> +		.name           = "max7360-rotary",
> +	},
> +};
> +
> +static const struct regmap_range max7360_volatile_ranges[] = {
> +	{
> +		.range_min = MAX7360_REG_KEYFIFO,
> +		.range_max = MAX7360_REG_KEYFIFO,
> +	}, {
> +		.range_min = MAX7360_REG_I2C_TIMEOUT,
> +		.range_max = MAX7360_REG_RTR_CNT,
> +	},
> +};

Use regmap_reg_range()

> +static const struct regmap_access_table max7360_volatile_table = {
> +	.yes_ranges = max7360_volatile_ranges,
> +	.n_yes_ranges = ARRAY_SIZE(max7360_volatile_ranges),
> +};
> +
> +static const struct regmap_config max7360_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.max_register = MAX7360_REG_PWMCFG(MAX7360_PORT_PWM_COUNT - 1),
> +	.volatile_table = &max7360_volatile_table,
> +	.cache_type = REGCACHE_MAPLE,
> +};
> +
> +static int max7360_mask_irqs(struct regmap *regmap)
> +{
> +	struct device *dev = regmap_get_device(regmap);
> +	unsigned int val;
> +	int ret;
> +
> +	/*
> +	 * GPIO/PWM interrupts are not masked on reset: as the MAX7360 "INTI"
> +	 * interrupt line is shared between GPIOs and rotary encoder, this could
> +	 * result in repeated spurious interrupts on the rotary encoder driver
> +	 * if the GPIO driver is not loaded. Mask them now to avoid this
> +	 * situation.
> +	 */
> +	for (unsigned int i = 0; i < MAX7360_PORT_PWM_COUNT; i++) {
> +		ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
> +					MAX7360_PORT_CFG_INTERRUPT_MASK,
> +					MAX7360_PORT_CFG_INTERRUPT_MASK);
> +		if (ret) {
> +			dev_err(dev, "Failed to write max7360 port configuration");

MAX7360

> +			return ret;
> +		}
> +	}
> +
> +	/* Read GPIO in register, to ACK any pending IRQ. */
> +	ret = regmap_read(regmap, MAX7360_REG_GPIOIN, &val);
> +	if (ret)
> +		dev_err(dev, "Failed to read gpio values: %d\n", ret);

GPIO

> +
> +	return ret;
> +}
> +
> +static int max7360_reset(struct regmap *regmap)
> +{
> +	struct device *dev = regmap_get_device(regmap);
> +	int ret;
> +
> +	ret = regmap_write(regmap, MAX7360_REG_GPIOCFG, MAX7360_GPIO_CFG_GPIO_RST);
> +	if (ret) {
> +		dev_err(dev, "Failed to reset GPIO configuration: %x\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regcache_drop_region(regmap, MAX7360_REG_GPIOCFG, MAX7360_REG_GPIO_LAST);
> +	if (ret) {
> +		dev_err(dev, "Failed to drop regmap cache: %x\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_write(regmap, MAX7360_REG_SLEEP, 0);
> +	if (ret) {
> +		dev_err(dev, "Failed to reset autosleep configuration: %x\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_write(regmap, MAX7360_REG_DEBOUNCE, 0);
> +	if (ret)
> +		dev_err(dev, "Failed to reset GPO port count: %x\n", ret);
> +
> +	return ret;
> +}
> +
> +static int max7360_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct regmap *regmap;
> +	int ret;
> +
> +	regmap = devm_regmap_init_i2c(client, &max7360_regmap_config);
> +	if (IS_ERR(regmap))
> +		return dev_err_probe(dev, PTR_ERR(regmap), "Failed to initialise regmap\n");

dev_err_ptr_probe()

> +
> +	ret = max7360_reset(regmap);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to reset device\n");
> +
> +	/* Get the device out of shutdown mode. */
> +	ret = regmap_write_bits(regmap, MAX7360_REG_GPIOCFG,
> +				MAX7360_GPIO_CFG_GPIO_EN,
> +				MAX7360_GPIO_CFG_GPIO_EN);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to enable GPIO and PWM module\n");
> +
> +	ret = max7360_mask_irqs(regmap);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Could not mask interrupts\n");
> +
> +	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
> +				   max7360_cells, ARRAY_SIZE(max7360_cells),
> +				   NULL, 0, NULL);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to register child devices\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id max7360_dt_match[] = {
> +	{ .compatible = "maxim,max7360" },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, max7360_dt_match);
> +
> +static struct i2c_driver max7360_driver = {
> +	.driver = {
> +		.name = "max7360",
> +		.of_match_table = max7360_dt_match,
> +	},
> +	.probe = max7360_probe,
> +};
> +module_i2c_driver(max7360_driver);
> +
> +MODULE_DESCRIPTION("Maxim MAX7360 I2C IO Expander core driver");
> +MODULE_AUTHOR("Kamel Bouhara <kamel.bouhara@bootlin.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mfd/max7360.h b/include/linux/mfd/max7360.h
> new file mode 100644
> index 000000000000..b1d4cbee2385
> --- /dev/null
> +++ b/include/linux/mfd/max7360.h
> @@ -0,0 +1,109 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +#ifndef __LINUX_MFD_MAX7360_H
> +#define __LINUX_MFD_MAX7360_H
> +
> +#include <linux/bits.h>
> +
> +#define MAX7360_MAX_KEY_ROWS		8
> +#define MAX7360_MAX_KEY_COLS		8
> +#define MAX7360_MAX_KEY_NUM		(MAX7360_MAX_KEY_ROWS * MAX7360_MAX_KEY_COLS)
> +#define MAX7360_ROW_SHIFT		3
> +
> +#define MAX7360_MAX_GPIO		8
> +#define MAX7360_MAX_GPO			6
> +#define MAX7360_PORT_PWM_COUNT		8
> +#define MAX7360_PORT_RTR_PIN		(MAX7360_PORT_PWM_COUNT - 1)
> +
> +/*
> + * MAX7360 registers
> + */
> +#define MAX7360_REG_KEYFIFO		0x00
> +#define MAX7360_REG_CONFIG		0x01
> +#define MAX7360_REG_DEBOUNCE		0x02
> +#define MAX7360_REG_INTERRUPT		0x03
> +#define MAX7360_REG_PORTS		0x04
> +#define MAX7360_REG_KEYREP		0x05
> +#define MAX7360_REG_SLEEP		0x06
> +
> +/*
> + * MAX7360 GPIO registers
> + *
> + * All these registers are reset together when writing bit 3 of
> + * MAX7360_REG_GPIOCFG.
> + */
> +#define MAX7360_REG_GPIOCFG		0x40
> +#define MAX7360_REG_GPIOCTRL		0x41
> +#define MAX7360_REG_GPIODEB		0x42
> +#define MAX7360_REG_GPIOCURR		0x43
> +#define MAX7360_REG_GPIOOUTM		0x44
> +#define MAX7360_REG_PWMCOM		0x45
> +#define MAX7360_REG_RTRCFG		0x46
> +#define MAX7360_REG_I2C_TIMEOUT		0x48
> +#define MAX7360_REG_GPIOIN		0x49
> +#define MAX7360_REG_RTR_CNT		0x4A
> +#define MAX7360_REG_PWMBASE		0x50
> +#define MAX7360_REG_PWMCFGBASE		0x58
> +
> +#define MAX7360_REG_GPIO_LAST		0x5F
> +
> +#define MAX7360_REG_PWM(x)		(MAX7360_REG_PWMBASE + (x))
> +#define MAX7360_REG_PWMCFG(x)		(MAX7360_REG_PWMCFGBASE + (x))
> +
> +/*
> + * Configuration register bits
> + */
> +#define MAX7360_FIFO_EMPTY		0x3f
> +#define MAX7360_FIFO_OVERFLOW		0x7f
> +#define MAX7360_FIFO_RELEASE		BIT(6)
> +#define MAX7360_FIFO_COL		GENMASK(5, 3)
> +#define MAX7360_FIFO_ROW		GENMASK(2, 0)
> +
> +#define MAX7360_CFG_SLEEP		BIT(7)
> +#define MAX7360_CFG_INTERRUPT		BIT(5)
> +#define MAX7360_CFG_KEY_RELEASE		BIT(3)
> +#define MAX7360_CFG_WAKEUP		BIT(1)
> +#define MAX7360_CFG_TIMEOUT		BIT(0)
> +
> +#define MAX7360_DEBOUNCE		GENMASK(4, 0)
> +#define MAX7360_DEBOUNCE_MIN		9
> +#define MAX7360_DEBOUNCE_MAX		40
> +#define MAX7360_PORTS			GENMASK(8, 5)
> +
> +#define MAX7360_INTERRUPT_TIME_MASK	GENMASK(4, 0)
> +#define MAX7360_INTERRUPT_FIFO_MASK	GENMASK(7, 5)
> +
> +#define MAX7360_PORT_CFG_INTERRUPT_MASK		BIT(7)
> +#define MAX7360_PORT_CFG_INTERRUPT_EDGES	BIT(6)
> +#define MAX7360_PORT_CFG_COMMON_PWM		BIT(5)
> +
> +/*
> + * Autosleep register values
> + */
> +#define MAX7360_AUTOSLEEP_8192MS	0x01
> +#define MAX7360_AUTOSLEEP_4096MS	0x02
> +#define MAX7360_AUTOSLEEP_2048MS	0x03
> +#define MAX7360_AUTOSLEEP_1024MS	0x04
> +#define MAX7360_AUTOSLEEP_512MS		0x05
> +#define MAX7360_AUTOSLEEP_256MS		0x06
> +
> +#define MAX7360_GPIO_CFG_RTR_EN		BIT(7)
> +#define MAX7360_GPIO_CFG_GPIO_EN	BIT(4)
> +#define MAX7360_GPIO_CFG_GPIO_RST	BIT(3)
> +
> +#define MAX7360_ROT_DEBOUNCE		GENMASK(3, 0)
> +#define MAX7360_ROT_DEBOUNCE_MIN	0
> +#define MAX7360_ROT_DEBOUNCE_MAX	15
> +#define MAX7360_ROT_INTCNT		GENMASK(6, 4)
> +#define MAX7360_ROT_INTCNT_DLY		BIT(7)
> +
> +#define MAX7360_INT_INTI		0
> +#define MAX7360_INT_INTK		1
> +
> +#define MAX7360_INT_GPIO		0
> +#define MAX7360_INT_KEYPAD		1
> +#define MAX7360_INT_ROTARY		2
> +
> +#define MAX7360_NR_INTERNAL_IRQS	3
> +
> +#endif
> 
> -- 
> 2.39.5
>
Mathieu Dubois-Briand May 2, 2025, 7:35 a.m. UTC | #2
On Thu May 1, 2025 at 2:59 PM CEST, Lee Jones wrote:
> On Mon, 28 Apr 2025, mathieu.dubois-briand@bootlin.com wrote:
>
>> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
>> +static int max7360_probe(struct i2c_client *client)
>> +{
>> +	struct device *dev = &client->dev;
>> +	struct regmap *regmap;
>> +	int ret;
>> +
>> +	regmap = devm_regmap_init_i2c(client, &max7360_regmap_config);
>> +	if (IS_ERR(regmap))
>> +		return dev_err_probe(dev, PTR_ERR(regmap), "Failed to initialise regmap\n");
>
> dev_err_ptr_probe()
>

I believe dev_err_ptr_probe() is meant to be used for the opposite case,
where the variable holding the error is an int but the function has to
return a pointer. Here regmap is a pointer but we have to return an int,
so I believe neither dev_err_ptr_probe() or any similar macro can really
help us.

OK for all other points.

Thanks for your review,
Mathieu
Lee Jones May 2, 2025, 8:26 a.m. UTC | #3
On Fri, 02 May 2025, Mathieu Dubois-Briand wrote:

> On Thu May 1, 2025 at 2:59 PM CEST, Lee Jones wrote:
> > On Mon, 28 Apr 2025, mathieu.dubois-briand@bootlin.com wrote:
> >
> >> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
> >> +static int max7360_probe(struct i2c_client *client)
> >> +{
> >> +	struct device *dev = &client->dev;
> >> +	struct regmap *regmap;
> >> +	int ret;
> >> +
> >> +	regmap = devm_regmap_init_i2c(client, &max7360_regmap_config);
> >> +	if (IS_ERR(regmap))
> >> +		return dev_err_probe(dev, PTR_ERR(regmap), "Failed to initialise regmap\n");
> >
> > dev_err_ptr_probe()
> >
> 
> I believe dev_err_ptr_probe() is meant to be used for the opposite case,
> where the variable holding the error is an int but the function has to
> return a pointer. Here regmap is a pointer but we have to return an int,
> so I believe neither dev_err_ptr_probe() or any similar macro can really
> help us.

Ah yes, you're right.  Disregard.
Andy Shevchenko May 2, 2025, 9:13 a.m. UTC | #4
On Thu, May 01, 2025 at 01:59:43PM +0100, Lee Jones wrote:
> On Mon, 28 Apr 2025, mathieu.dubois-briand@bootlin.com wrote:
> > 
> > Add core driver to support MAX7360 i2c chip, multi function device
> > with keypad, GPIO, PWM, GPO and rotary encoder submodules.

...

> > +static int max7360_probe(struct i2c_client *client)
> > +{
> > +	struct device *dev = &client->dev;
> > +	struct regmap *regmap;
> > +	int ret;
> > +
> > +	regmap = devm_regmap_init_i2c(client, &max7360_regmap_config);
> > +	if (IS_ERR(regmap))
> > +		return dev_err_probe(dev, PTR_ERR(regmap), "Failed to initialise regmap\n");
> 
> dev_err_ptr_probe()

Hmm... This one to return an error pointer casted to the needed type. I think
the given code is correct as is.

> > +	ret = max7360_reset(regmap);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "Failed to reset device\n");
> > +
> > +	/* Get the device out of shutdown mode. */
> > +	ret = regmap_write_bits(regmap, MAX7360_REG_GPIOCFG,
> > +				MAX7360_GPIO_CFG_GPIO_EN,
> > +				MAX7360_GPIO_CFG_GPIO_EN);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "Failed to enable GPIO and PWM module\n");
> > +
> > +	ret = max7360_mask_irqs(regmap);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "Could not mask interrupts\n");
> > +
> > +	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
> > +				   max7360_cells, ARRAY_SIZE(max7360_cells),
> > +				   NULL, 0, NULL);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "Failed to register child devices\n");
> > +
> > +	return 0;
> > +}
Andy Shevchenko May 2, 2025, 10:10 a.m. UTC | #5
On Mon, Apr 28, 2025 at 01:57:20PM +0200, mathieu.dubois-briand@bootlin.com wrote:
> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
> 
> Add core driver to support MAX7360 i2c chip, multi function device
> with keypad, GPIO, PWM, GPO and rotary encoder submodules.

...

> +static int max7360_mask_irqs(struct regmap *regmap)

So, AFAICS this is used only at probe stage...

> +{
> +	struct device *dev = regmap_get_device(regmap);
> +	unsigned int val;
> +	int ret;
> +
> +	/*
> +	 * GPIO/PWM interrupts are not masked on reset: as the MAX7360 "INTI"
> +	 * interrupt line is shared between GPIOs and rotary encoder, this could
> +	 * result in repeated spurious interrupts on the rotary encoder driver
> +	 * if the GPIO driver is not loaded. Mask them now to avoid this
> +	 * situation.
> +	 */
> +	for (unsigned int i = 0; i < MAX7360_PORT_PWM_COUNT; i++) {
> +		ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
> +					MAX7360_PORT_CFG_INTERRUPT_MASK,
> +					MAX7360_PORT_CFG_INTERRUPT_MASK);
> +		if (ret) {
> +			dev_err(dev, "Failed to write max7360 port configuration");
> +			return ret;

...if it's the case, use return dev_err_probe(...) here...

> +		}
> +	}
> +
> +	/* Read GPIO in register, to ACK any pending IRQ. */
> +	ret = regmap_read(regmap, MAX7360_REG_GPIOIN, &val);
> +	if (ret)
> +		dev_err(dev, "Failed to read gpio values: %d\n", ret);
> +
> +	return ret;

...and here.

> +}
Andy Shevchenko May 2, 2025, 10:19 a.m. UTC | #6
On Mon, Apr 28, 2025 at 01:57:22PM +0200, 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.

...

> +static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct regmap *regmap;
> +	int ret;

> +	regmap = pwmchip_get_drvdata(chip);

Just unify this assignment with the definition. Will save you 1 LoC.

> +	ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
> +				MAX7360_PORT_CFG_COMMON_PWM, 0);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write_bits(regmap, MAX7360_REG_PORTS, BIT(pwm->hwpwm), BIT(pwm->hwpwm));
> +}

...

> +static int max7360_pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
> +					     const void *_wfhw, struct pwm_waveform *wf)
> +{
> +	const struct max7360_pwm_waveform *wfhw = _wfhw;
> +
> +	wf->period_length_ns = wfhw->enabled ? MAX7360_PWM_PERIOD_NS : 0;
> +	wf->duty_offset_ns = 0;
> +	wf->duty_length_ns = DIV64_U64_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,

Does the numerator have already 64-bit type? Otherwise (u)int*(u)int will be
just an (u)int.

> +						MAX7360_PWM_MAX_RES);
> +
> +	return 0;
> +}

...

> +static int max7360_pwm_read_waveform(struct pwm_chip *chip,
> +				     struct pwm_device *pwm,
> +				     void *_wfhw)
> +{
> +	struct max7360_pwm_waveform *wfhw = _wfhw;
> +	struct regmap *regmap;
> +	unsigned int val;
> +	int ret;

> +	regmap = pwmchip_get_drvdata(chip);
> +

Save 2 LoCs.

> +	ret = regmap_read(regmap, MAX7360_REG_GPIOCTRL, &val);
> +	if (ret)
> +		return ret;
> +
> +	if (val & BIT(pwm->hwpwm)) {
> +		wfhw->enabled = true;
> +		ret = regmap_read(regmap, MAX7360_REG_PWM(pwm->hwpwm), &val);
> +		if (ret)
> +			return ret;
> +
> +		wfhw->duty_steps = val;
> +	} else {
> +		wfhw->enabled = false;
> +		wfhw->duty_steps = 0;
> +	}
> +
> +	return 0;
> +}

...

> +static int max7360_pwm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct pwm_chip *chip;
> +	struct regmap *regmap;
> +	int ret;
> +
> +	regmap = dev_get_regmap(dev->parent, NULL);
> +	if (!regmap)
> +		return dev_err_probe(dev, -ENODEV, "could not get parent regmap\n");

> +	device_set_of_node_from_dev(dev, dev->parent);

Probably same comment as per pin control driver case?

> +	chip = devm_pwmchip_alloc(dev, MAX7360_NUM_PWMS, 0);
> +	if (IS_ERR(chip))
> +		return PTR_ERR(chip);
> +	chip->ops = &max7360_pwm_ops;

> +	pwmchip_set_drvdata(chip, regmap);

> +	ret = devm_pwmchip_add(dev, chip);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to add PWM chip\n");
> +
> +	return 0;
> +}
Andy Shevchenko May 2, 2025, 10:39 a.m. UTC | #7
On Mon, Apr 28, 2025 at 01:57:26PM +0200, Mathieu Dubois-Briand wrote:
> Add driver for Maxim Integrated MAX7360 GPIO/GPO controller.
> 
> Two sets of GPIOs are provided by the device:
> - Up to 8 GPIOs, shared with the PWM and rotary encoder functionalities.
>   These GPIOs also provide interrupts on input changes.
> - Up to 6 GPOs, on unused keypad columns pins.

...

> +#include <linux/bitmap.h>
> +#include <linux/err.h>

> +#include <linux/gpio/driver.h>

Is this still needed?

> +#include <linux/gpio/regmap.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/max7360.h>

+ minmax.h

> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>

...

> +static int max7360_gpio_probe(struct platform_device *pdev)
> +{
> +	const struct max7360_gpio_plat_data *plat_data;
> +	struct gpio_regmap_config gpio_config = { };
> +	struct regmap_irq_chip *irq_chip;
> +	struct device *dev = &pdev->dev;
> +	struct regmap *regmap;
> +	unsigned int outconf;
> +	int ret;
> +
> +	regmap = dev_get_regmap(dev->parent, NULL);
> +	if (!regmap)
> +		return dev_err_probe(dev, -ENODEV, "could not get parent regmap\n");
> +
> +	plat_data = device_get_match_data(dev);
> +	if (plat_data->function == MAX7360_GPIO_PORT) {
> +		if (device_property_read_bool(dev, "interrupt-controller")) {
> +			/*
> +			 * Port GPIOs with interrupt-controller property: add IRQ
> +			 * controller.
> +			 */
> +			gpio_config.regmap_irq_flags = IRQF_ONESHOT | IRQF_SHARED;
> +			gpio_config.regmap_irq_line =
> +				fwnode_irq_get_byname(dev_fwnode(dev->parent), "inti");
> +			if (gpio_config.regmap_irq_line < 0)
> +				return dev_err_probe(dev, gpio_config.regmap_irq_line,
> +						     "Failed to get IRQ\n");

> +			irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL);

Can this be made static const instead?

> +			gpio_config.regmap_irq_chip = irq_chip;
> +			if (!irq_chip)
> +				return -ENOMEM;
> +
> +			irq_chip->name = dev_name(dev);
> +			irq_chip->status_base = MAX7360_REG_GPIOIN;
> +			irq_chip->status_is_level = true;
> +			irq_chip->num_regs = 1;
> +			irq_chip->num_irqs = MAX7360_MAX_GPIO;
> +			irq_chip->irqs = max7360_regmap_irqs;
> +			irq_chip->handle_mask_sync = max7360_handle_mask_sync;
> +			irq_chip->irq_drv_data = regmap;

> +			for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
> +				ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
> +							MAX7360_PORT_CFG_INTERRUPT_EDGES,
> +							MAX7360_PORT_CFG_INTERRUPT_EDGES);
> +				if (ret)
> +					return dev_err_probe(dev, ret,
> +							     "Failed to enable interrupts\n");
> +			}
> +		}
> +
> +		/*
> +		 * Port GPIOs: set output mode configuration (constant-current or not).
> +		 * This property is optional.
> +		 */
> +		ret = device_property_read_u32(dev, "maxim,constant-current-disable", &outconf);
> +		if (!ret) {
> +			ret = regmap_write(regmap, MAX7360_REG_GPIOOUTM, outconf);
> +			if (ret)
> +				return dev_err_probe(dev, ret,
> +						     "Failed to set constant-current configuration\n");
> +		}
> +	}
> +
> +	/* Add gpio device. */
> +	gpio_config.parent = dev;
> +	gpio_config.regmap = regmap;
> +	if (plat_data->function == MAX7360_GPIO_PORT) {
> +		gpio_config.ngpio = MAX7360_MAX_GPIO;
> +		gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(MAX7360_REG_GPIOIN);
> +		gpio_config.reg_set_base = GPIO_REGMAP_ADDR(MAX7360_REG_PWMBASE);
> +		gpio_config.reg_dir_out_base = GPIO_REGMAP_ADDR(MAX7360_REG_GPIOCTRL);
> +		gpio_config.ngpio_per_reg = MAX7360_MAX_GPIO;
> +		gpio_config.reg_mask_xlate = max7360_gpio_reg_mask_xlate;
> +	} else {
> +		ret = max7360_set_gpos_count(dev, regmap);
> +		if (ret)
> +			return dev_err_probe(dev, ret, "Failed to set GPOS pin count\n");
> +
> +		gpio_config.reg_set_base = GPIO_REGMAP_ADDR(MAX7360_REG_PORTS);
> +		gpio_config.ngpio = MAX7360_MAX_KEY_COLS;
> +		gpio_config.init_valid_mask = max7360_gpo_init_valid_mask;
> +	}
> +
> +	return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
> +}
Andy Shevchenko May 2, 2025, 10:52 a.m. UTC | #8
On Mon, Apr 28, 2025 at 01:57:28PM +0200, Mathieu Dubois-Briand wrote:
> Add driver for Maxim Integrated MAX7360 rotary encoder controller,
> supporting a single rotary switch.

...

> +struct max7360_rotary {
> +	struct input_dev *input;
> +	unsigned int debounce_ms;
> +	struct regmap *regmap;
> +
> +	u32 steps;
> +	u32 axis;
> +	bool relative_axis;
> +	bool rollover;
> +
> +	unsigned int pos;
> +};

I believe `pahole` can recommend better layout (look for the better position
of debounce_ms).

...

> +static void max7360_rotaty_report_event(struct max7360_rotary *max7360_rotary, int steps)
> +{
> +	if (max7360_rotary->relative_axis) {
> +		input_report_rel(max7360_rotary->input, max7360_rotary->axis, steps);
> +	} else {
> +		unsigned int pos = max7360_rotary->pos;
> +
> +		if (steps < 0) {
> +			/* turning counter-clockwise */
> +			if (max7360_rotary->rollover)
> +				pos += max7360_rotary->steps + steps;
> +			else

> +				pos = max(0, (int)pos + steps);

Please, no castings for min()/max()/clamp(). It diminishes the use of those
macros.

> +		} else {
> +			/* turning clockwise */
> +			if (max7360_rotary->rollover)
> +				pos += steps;
> +			else
> +				pos = min(max7360_rotary->steps, pos + steps);
> +		}
> +
> +		if (max7360_rotary->rollover)
> +			pos %= max7360_rotary->steps;
> +
> +		max7360_rotary->pos = pos;
> +		input_report_abs(max7360_rotary->input, max7360_rotary->axis, max7360_rotary->pos);
> +	}
> +
> +	input_sync(max7360_rotary->input);
> +}

...

> +static irqreturn_t max7360_rotary_irq(int irq, void *data)
> +{
> +	struct max7360_rotary *max7360_rotary = data;
> +	struct device *dev = max7360_rotary->input->dev.parent;
> +	unsigned int val;
> +	int error;
> +
> +	error = regmap_read(max7360_rotary->regmap, MAX7360_REG_RTR_CNT, &val);
> +	if (error < 0) {
> +		dev_err(dev, "Failed to read rotary counter\n");
> +		return IRQ_NONE;
> +	}
> +
> +	if (val == 0) {

> +		dev_dbg(dev, "Got a spurious interrupt\n");

This contradicts with the IRQF_SHARED. Drop one of them.

> +		return IRQ_NONE;
> +	}
> +
> +	max7360_rotaty_report_event(max7360_rotary, sign_extend32(val, 7));
> +
> +	return IRQ_HANDLED;
> +}

...

> +static int max7360_rotary_hw_init(struct max7360_rotary *max7360_rotary)
> +{
> +	struct device *dev = max7360_rotary->input->dev.parent;
> +	int val;
> +	int error;
> +
> +	val = FIELD_PREP(MAX7360_ROT_DEBOUNCE, max7360_rotary->debounce_ms) |
> +		FIELD_PREP(MAX7360_ROT_INTCNT, 1) | MAX7360_ROT_INTCNT_DLY;

Indentation is incorrect.

> +	error = regmap_write(max7360_rotary->regmap, MAX7360_REG_RTRCFG, val);
> +	if (error)
> +		dev_err(dev, "Failed to set max7360 rotary encoder configuration\n");
> +
> +	return error;
> +}

...

> +static int max7360_rotary_probe(struct platform_device *pdev)
> +{
> +	struct max7360_rotary *max7360_rotary;
> +	struct device *dev = &pdev->dev;
> +	struct input_dev *input;
> +	struct regmap *regmap;
> +	int irq;
> +	int error;
> +
> +	regmap = dev_get_regmap(dev->parent, NULL);
> +	if (!regmap)
> +		dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");

Missing return. Copy'n'paste error over all drivers?

> +	irq = fwnode_irq_get_byname(dev_fwnode(dev->parent), "inti");
> +	if (irq < 0)
> +		return dev_err_probe(dev, irq, "Failed to get IRQ\n");
> +
> +	max7360_rotary = devm_kzalloc(dev, sizeof(*max7360_rotary), GFP_KERNEL);
> +	if (!max7360_rotary)
> +		return -ENOMEM;
> +
> +	max7360_rotary->regmap = regmap;
> +
> +	device_property_read_u32(dev->parent, "linux,axis", &max7360_rotary->axis);
> +	max7360_rotary->rollover = device_property_read_bool(dev->parent,
> +							     "rotary-encoder,rollover");
> +	max7360_rotary->relative_axis =
> +		device_property_read_bool(dev->parent, "rotary-encoder,relative-axis");
> +
> +	error = device_property_read_u32(dev->parent, "rotary-encoder,steps",
> +					 &max7360_rotary->steps);
> +	if (error)
> +		max7360_rotary->steps = MAX7360_ROTARY_DEFAULT_STEPS;
> +
> +	device_property_read_u32(dev->parent, "rotary-debounce-delay-ms",
> +				 &max7360_rotary->debounce_ms);
> +	if (max7360_rotary->debounce_ms > MAX7360_ROT_DEBOUNCE_MAX)
> +		return dev_err_probe(dev, -EINVAL, "Invalid debounce timing: %u\n",
> +				     max7360_rotary->debounce_ms);
> +
> +	input = devm_input_allocate_device(dev);
> +	if (!input)
> +		return -ENOMEM;
> +
> +	max7360_rotary->input = input;
> +
> +	input->id.bustype = BUS_I2C;
> +	input->name = pdev->name;
> +
> +	if (max7360_rotary->relative_axis)
> +		input_set_capability(input, EV_REL, max7360_rotary->axis);
> +	else
> +		input_set_abs_params(input, max7360_rotary->axis, 0, max7360_rotary->steps, 0, 1);
> +
> +	error = devm_request_threaded_irq(dev, irq, NULL, max7360_rotary_irq,
> +					  IRQF_ONESHOT | IRQF_SHARED,
> +					  "max7360-rotary", max7360_rotary);
> +	if (error)
> +		return dev_err_probe(dev, error, "Failed to register interrupt\n");
> +
> +	error = input_register_device(input);
> +	if (error)
> +		return dev_err_probe(dev, error, "Could not register input device\n");
> +
> +	error = max7360_rotary_hw_init(max7360_rotary);
> +	if (error)
> +		return dev_err_probe(dev, error, "Failed to initialize max7360 rotary\n");
> +
> +	device_init_wakeup(dev, true);
> +	error = dev_pm_set_wake_irq(dev, irq);
> +	if (error)
> +		dev_warn(dev, "Failed to set up wakeup irq: %d\n", error);
> +
> +	return 0;
> +}
> +
> +static void max7360_rotary_remove(struct platform_device *pdev)
> +{
> +	dev_pm_clear_wake_irq(&pdev->dev);

Shouldn't be here

	device_init_wakeup(dev, false);

?

> +}
Mathieu Dubois-Briand May 2, 2025, 12:13 p.m. UTC | #9
On Fri May 2, 2025 at 12:19 PM CEST, Andy Shevchenko wrote:
> On Mon, Apr 28, 2025 at 01:57:22PM +0200, mathieu.dubois-briand@bootlin.com wrote:
>> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
>> 
>> +static int max7360_pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
>> +					     const void *_wfhw, struct pwm_waveform *wf)
>> +{
>> +	const struct max7360_pwm_waveform *wfhw = _wfhw;
>> +
>> +	wf->period_length_ns = wfhw->enabled ? MAX7360_PWM_PERIOD_NS : 0;
>> +	wf->duty_offset_ns = 0;
>> +	wf->duty_length_ns = DIV64_U64_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
>
> Does the numerator have already 64-bit type? Otherwise (u)int*(u)int will be
> just an (u)int.
>

Err no, this section has been modified back and forth, but today we have
u8 * 2 * 1000000L, so we will always fit in a u32.

I will use DIV_ROUND_UP() instead.


> ...

OK with all other comments.

Thanks for your review.
Mathieu
Mathieu Dubois-Briand May 2, 2025, 12:31 p.m. UTC | #10
On Fri May 2, 2025 at 12:39 PM CEST, Andy Shevchenko wrote:
> On Mon, Apr 28, 2025 at 01:57:26PM +0200, Mathieu Dubois-Briand wrote:
>> +static int max7360_gpio_probe(struct platform_device *pdev)
>> +{
>> +	const struct max7360_gpio_plat_data *plat_data;
>> +	struct gpio_regmap_config gpio_config = { };
>> +	struct regmap_irq_chip *irq_chip;
>> +	struct device *dev = &pdev->dev;
>> +	struct regmap *regmap;
>> +	unsigned int outconf;
>> +	int ret;
>> +
>> +	regmap = dev_get_regmap(dev->parent, NULL);
>> +	if (!regmap)
>> +		return dev_err_probe(dev, -ENODEV, "could not get parent regmap\n");
>> +
>> +	plat_data = device_get_match_data(dev);
>> +	if (plat_data->function == MAX7360_GPIO_PORT) {
>> +		if (device_property_read_bool(dev, "interrupt-controller")) {
>> +			/*
>> +			 * Port GPIOs with interrupt-controller property: add IRQ
>> +			 * controller.
>> +			 */
>> +			gpio_config.regmap_irq_flags = IRQF_ONESHOT | IRQF_SHARED;
>> +			gpio_config.regmap_irq_line =
>> +				fwnode_irq_get_byname(dev_fwnode(dev->parent), "inti");
>> +			if (gpio_config.regmap_irq_line < 0)
>> +				return dev_err_probe(dev, gpio_config.regmap_irq_line,
>> +						     "Failed to get IRQ\n");
>
>> +			irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL);
>
> Can this be made static const instead?
>

Sorry, I don't think we can. We do have a few data that will vary:
->name, but above all ->irq_drv_data, as it will point on the regmap of
the specific device.

> ...

OK with all other comments.

Thanks for your review.
Mathieu
Andy Shevchenko May 2, 2025, 1:03 p.m. UTC | #11
On Fri, May 02, 2025 at 02:31:13PM +0200, Mathieu Dubois-Briand wrote:
> On Fri May 2, 2025 at 12:39 PM CEST, Andy Shevchenko wrote:
> > On Mon, Apr 28, 2025 at 01:57:26PM +0200, Mathieu Dubois-Briand wrote:

...

> >> +			irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL);
> >
> > Can this be made static const instead?
> 
> Sorry, I don't think we can. We do have a few data that will vary:
> ->name, but above all ->irq_drv_data, as it will point on the regmap of
> the specific device.

I see, perhaps a (oneline) comment?
Mathieu Dubois-Briand May 2, 2025, 1:58 p.m. UTC | #12
On Fri May 2, 2025 at 12:52 PM CEST, Andy Shevchenko wrote:
> On Mon, Apr 28, 2025 at 01:57:28PM +0200, Mathieu Dubois-Briand wrote:
>> Add driver for Maxim Integrated MAX7360 rotary encoder controller,
>> supporting a single rotary switch.
>
> ...
>
>> +struct max7360_rotary {
>> +	struct input_dev *input;
>> +	unsigned int debounce_ms;
>> +	struct regmap *regmap;
>> +
>> +	u32 steps;
>> +	u32 axis;
>> +	bool relative_axis;
>> +	bool rollover;
>> +
>> +	unsigned int pos;
>> +};
>
> I believe `pahole` can recommend better layout (look for the better position
> of debounce_ms).
>
> ...
>

Oh yes it does on arm64. Moving it after the regmap pointer, it should
be better.

>> +static void max7360_rotaty_report_event(struct max7360_rotary *max7360_rotary, int steps)
>> +{
>> +	if (max7360_rotary->relative_axis) {
>> +		input_report_rel(max7360_rotary->input, max7360_rotary->axis, steps);
>> +	} else {
>> +		unsigned int pos = max7360_rotary->pos;
>> +
>> +		if (steps < 0) {
>> +			/* turning counter-clockwise */
>> +			if (max7360_rotary->rollover)
>> +				pos += max7360_rotary->steps + steps;
>> +			else
>
>> +				pos = max(0, (int)pos + steps);
>
> Please, no castings for min()/max()/clamp(). It diminishes the use of those
> macros.
>

Sorry, I'm not sure to get the point. Should I use MIN_T() instead?

>
> ...
>
>> +static int max7360_rotary_probe(struct platform_device *pdev)
>> +{
>> +	struct max7360_rotary *max7360_rotary;
>> +	struct device *dev = &pdev->dev;
>> +	struct input_dev *input;
>> +	struct regmap *regmap;
>> +	int irq;
>> +	int error;
>> +
>> +	regmap = dev_get_regmap(dev->parent, NULL);
>> +	if (!regmap)
>> +		dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
>
> Missing return. Copy'n'paste error over all drivers?
>

Probably more an error while replacing all dev_err()+return by
dev_err_probe(), but yes. I will look for similar issues.

> ...

OK with all other comments.

Thanks for your review.
Mathieu
Andy Shevchenko May 2, 2025, 2:09 p.m. UTC | #13
On Fri, May 02, 2025 at 03:58:04PM +0200, Mathieu Dubois-Briand wrote:
> On Fri May 2, 2025 at 12:52 PM CEST, Andy Shevchenko wrote:
> > On Mon, Apr 28, 2025 at 01:57:28PM +0200, Mathieu Dubois-Briand wrote:

...

> >> +				pos = max(0, (int)pos + steps);
> >
> > Please, no castings for min()/max()/clamp(). It diminishes the use of those
> > macros.
> 
> Sorry, I'm not sure to get the point. Should I use MIN_T() instead?

Are the second argument is compile-time constant? I don't think so. Hence no
use for MIN*()/MAX*(). First of all, try to answer to the Q: Why is the explicit
casting being used? The second Q: How can it be easily fixed without using _t()
variants of the macros?
Mathieu Dubois-Briand May 5, 2025, 11:52 a.m. UTC | #14
On Fri May 2, 2025 at 4:09 PM CEST, Andy Shevchenko wrote:
> On Fri, May 02, 2025 at 03:58:04PM +0200, Mathieu Dubois-Briand wrote:
>> On Fri May 2, 2025 at 12:52 PM CEST, Andy Shevchenko wrote:
>> > On Mon, Apr 28, 2025 at 01:57:28PM +0200, Mathieu Dubois-Briand wrote:
>
> ...
>
>> >> +				pos = max(0, (int)pos + steps);
>> >
>> > Please, no castings for min()/max()/clamp(). It diminishes the use of those
>> > macros.
>> 
>> Sorry, I'm not sure to get the point. Should I use MIN_T() instead?
>
> Are the second argument is compile-time constant? I don't think so. Hence no
> use for MIN*()/MAX*(). First of all, try to answer to the Q: Why is the explicit
> casting being used? The second Q: How can it be easily fixed without using _t()
> variants of the macros?

Err right, no MIN/MAX of course.

Explicit cast is here because the unsigned int + int sum would result in
an unsigned int.

I will use an intermediate signed value. Also the whole logic here can
be simplified a bit, so I will rework the whole block.

Best regards,
Mathieu