* [PATCH v11 00/10] Add support for MAX7360
@ 2025-07-11 9:29 Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 01/10] dt-bindings: mfd: gpio: Add MAX7360 Mathieu Dubois-Briand
` (9 more replies)
0 siblings, 10 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Krzysztof Kozlowski, Andy Shevchenko,
Bartosz Golaszewski
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.
Lee Jones suggested the whole series goes through MFD subsystem, once
all patches got the needed Acks.
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
Changes in v11:
- Rebased on v6.16-rc5.
- Small fixes in keypad and rotary encoder input drivers: typos and off
by one errors.
- Various fixes in PWM driver and PWM Kconfig.
- Link to v10: https://lore.kernel.org/r/20250530-mdb-max7360-support-v10-0-ce3b9e60a588@bootlin.com
Changes in v10:
- Rebased on v6.15
- Do not use devm_ functions to allocate regmap-irq in gpio-remap.c
- Link to v9: https://lore.kernel.org/r/20250522-mdb-max7360-support-v9-0-74fc03517e41@bootlin.com
Changes in v9:
- Fix build issue with bad usage of array_size() on intermediate commit.
- MFD: Fix error strings. Also fix #define style in the header file.
- Pinctrl: Fix missing include.
- PWM: Fix register writes in max7360_pwm_waveform() and
max7360_pwm_round_waveform_tohw().
- GPIO: Fix GPIO valid mask initialization.
- Link to v8: https://lore.kernel.org/r/20250509-mdb-max7360-support-v8-0-bbe486f6bcb7@bootlin.com
Changes in v8:
- Small changes in drivers.
- Rebased on v6.15-rc5
- Link to v7: https://lore.kernel.org/r/20250428-mdb-max7360-support-v7-0-4e0608d0a7ff@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.
- Dropping Reviewed-by tags on device-tree binding commit, because of
modifications related to the previous point.
- 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 (8):
dt-bindings: mfd: gpio: Add MAX7360
pinctrl: Add MAX7360 pinctrl driver
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/gpio/Kconfig | 12 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-max7360.c | 257 +++++++++++++++++
drivers/gpio/gpio-regmap.c | 30 +-
drivers/input/keyboard/Kconfig | 12 +
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/max7360-keypad.c | 308 +++++++++++++++++++++
drivers/input/misc/Kconfig | 10 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/max7360-rotary.c | 192 +++++++++++++
drivers/mfd/Kconfig | 14 +
drivers/mfd/Makefile | 1 +
drivers/mfd/max7360.c | 171 ++++++++++++
drivers/pinctrl/Kconfig | 11 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-max7360.c | 215 ++++++++++++++
drivers/pwm/Kconfig | 10 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-max7360.c | 193 +++++++++++++
include/linux/gpio/regmap.h | 18 ++
include/linux/mfd/max7360.h | 109 ++++++++
24 files changed, 1853 insertions(+), 2 deletions(-)
---
base-commit: d7b8f8e20813f0179d8ef519541a3527e7661d3a
change-id: 20241219-mdb-max7360-support-223a8ce45ba3
Best regards,
--
Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v11 01/10] dt-bindings: mfd: gpio: Add MAX7360
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 02/10] mfd: Add max7360 support Mathieu Dubois-Briand
` (8 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Krzysztof Kozlowski
Add device tree bindings for Maxim Integrated MAX7360 device with
support for keypad, rotary, gpios and pwm functionalities.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
.../bindings/gpio/maxim,max7360-gpio.yaml | 83 +++++++++
.../devicetree/bindings/mfd/maxim,max7360.yaml | 191 +++++++++++++++++++++
2 files changed, 274 insertions(+)
diff --git a/Documentation/devicetree/bindings/gpio/maxim,max7360-gpio.yaml b/Documentation/devicetree/bindings/gpio/maxim,max7360-gpio.yaml
new file mode 100644
index 000000000000..c5c3fc4c816f
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/maxim,max7360-gpio.yaml
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/gpio/maxim,max7360-gpio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX7360 GPIO controller
+
+maintainers:
+ - Kamel Bouhara <kamel.bouhara@bootlin.com>
+ - Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+
+description: |
+ Maxim MAX7360 GPIO controller, in MAX7360 chipset
+ https://www.analog.com/en/products/max7360.html
+
+ The device provides two series of GPIOs, referred here as GPIOs and GPOs.
+
+ PORT0 to PORT7 pins can be used as GPIOs, with support for interrupts and
+ constant-current mode. These pins will also be used by the rotary encoder and
+ PWM functionalities.
+
+ COL2 to COL7 pins can be used as GPOs, there is no input capability. COL pins
+ will be partitioned, with the first pins being affected to the keypad
+ functionality and the last ones as GPOs.
+
+properties:
+ compatible:
+ enum:
+ - maxim,max7360-gpio
+ - maxim,max7360-gpo
+
+ gpio-controller: true
+
+ "#gpio-cells":
+ const: 2
+
+ interrupt-controller: true
+
+ "#interrupt-cells":
+ const: 2
+
+ maxim,constant-current-disable:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ 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
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - maxim,max7360-gpio
+ ngpios: false
+ then:
+ required:
+ - interrupt-controller
+ else:
+ properties:
+ interrupt-controller: false
+ maxim,constant-current-disable: false
+
+additionalProperties: false
+
+examples:
+ - |
+ gpio {
+ compatible = "maxim,max7360-gpio";
+
+ gpio-controller;
+ #gpio-cells = <2>;
+ maxim,constant-current-disable = <0x06>;
+
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
diff --git a/Documentation/devicetree/bindings/mfd/maxim,max7360.yaml b/Documentation/devicetree/bindings/mfd/maxim,max7360.yaml
new file mode 100644
index 000000000000..3fc920c8639d
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/maxim,max7360.yaml
@@ -0,0 +1,191 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/maxim,max7360.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX7360 Keypad, Rotary encoder, PWM and GPIO controller
+
+maintainers:
+ - Kamel Bouhara <kamel.bouhara@bootlin.com>
+ - Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+
+description: |
+ Maxim MAX7360 device, with following functions:
+ - keypad controller
+ - rotary controller
+ - GPIO and GPO controller
+ - PWM controller
+
+ https://www.analog.com/en/products/max7360.html
+
+allOf:
+ - $ref: /schemas/input/matrix-keymap.yaml#
+ - $ref: /schemas/input/input.yaml#
+
+properties:
+ compatible:
+ enum:
+ - maxim,max7360
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 2
+
+ interrupt-names:
+ items:
+ - const: inti
+ - const: intk
+
+ keypad-debounce-delay-ms:
+ description: Keypad debounce delay in ms
+ minimum: 9
+ maximum: 40
+ default: 9
+
+ rotary-debounce-delay-ms:
+ description: Rotary encoder debounce delay in ms
+ minimum: 0
+ maximum: 15
+ default: 0
+
+ linux,axis:
+ $ref: /schemas/input/rotary-encoder.yaml#/properties/linux,axis
+
+ rotary-encoder,relative-axis:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Register a relative axis rather than an absolute one.
+
+ rotary-encoder,steps:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ default: 24
+ description:
+ Number of steps in a full turnaround of the
+ encoder. Only relevant for absolute axis. Defaults to 24 which is a
+ typical value for such devices.
+
+ rotary-encoder,rollover:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Automatic rollover when the rotary value becomes
+ greater than the specified steps or smaller than 0. For absolute axis only.
+
+ "#pwm-cells":
+ const: 3
+
+ gpio:
+ $ref: /schemas/gpio/maxim,max7360-gpio.yaml#
+ description:
+ PORT0 to PORT7 general purpose input/output pins configuration.
+
+ gpo:
+ $ref: /schemas/gpio/maxim,max7360-gpio.yaml#
+ description: >
+ 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. GPIOs
+ numbers used for this gpio-controller node do correspond to the column
+ numbers: values 0 and 1 are never valid, values from 2 to 7 might be valid
+ depending on the value of the keypad,num-column property.
+
+patternProperties:
+ '-pins$':
+ type: object
+ description:
+ Pinctrl node's client devices use subnodes for desired pin configuration.
+ Client device subnodes use below standard properties.
+ $ref: /schemas/pinctrl/pincfg-node.yaml
+
+ properties:
+ pins:
+ description:
+ List of gpio pins affected by the properties specified in this
+ subnode.
+ items:
+ pattern: '^(PORT[0-7]|ROTARY)$'
+ minItems: 1
+ maxItems: 8
+
+ function:
+ description:
+ Specify the alternative function to be configured for the specified
+ pins.
+ enum: [gpio, pwm, rotary]
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-names
+ - linux,keymap
+ - linux,axis
+ - "#pwm-cells"
+ - gpio
+ - gpo
+
+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 */
+ rotary-encoder,relative-axis;
+
+ #pwm-cells = <3>;
+
+ max7360_gpio: gpio {
+ compatible = "maxim,max7360-gpio";
+
+ gpio-controller;
+ #gpio-cells = <2>;
+ maxim,constant-current-disable = <0x06>;
+
+ interrupt-controller;
+ #interrupt-cells = <0x2>;
+ };
+
+ max7360_gpo: gpo {
+ compatible = "maxim,max7360-gpo";
+
+ gpio-controller;
+ #gpio-cells = <2>;
+ };
+
+ backlight_pins: backlight-pins {
+ pins = "PORT2";
+ function = "pwm";
+ };
+ };
+ };
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v11 02/10] mfd: Add max7360 support
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 01/10] dt-bindings: mfd: gpio: Add MAX7360 Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 03/10] pinctrl: Add MAX7360 pinctrl driver Mathieu Dubois-Briand
` (7 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko
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>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/mfd/Kconfig | 14 ++++
drivers/mfd/Makefile | 1 +
drivers/mfd/max7360.c | 171 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/max7360.h | 109 ++++++++++++++++++++++++++++
4 files changed, 295 insertions(+)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6fb3768e3d71..b9092ba6c7c5 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2463,5 +2463,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 79495f9f3457..ed5a7ea0b1dd 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..5ee459c490ec
--- /dev/null
+++ b/drivers/mfd/max7360.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Maxim MAX7360 Core Driver
+ *
+ * Copyright 2025 Bootlin
+ *
+ * Authors:
+ * Kamel Bouhara <kamel.bouhara@bootlin.com>
+ * 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" },
+ { .name = "max7360-pwm" },
+ { .name = "max7360-keypad" },
+ { .name = "max7360-rotary" },
+ {
+ .name = "max7360-gpo",
+ .of_compatible = "maxim,max7360-gpo",
+ },
+ {
+ .name = "max7360-gpio",
+ .of_compatible = "maxim,max7360-gpio",
+ },
+};
+
+static const struct regmap_range max7360_volatile_ranges[] = {
+ regmap_reg_range(MAX7360_REG_KEYFIFO, MAX7360_REG_KEYFIFO),
+ regmap_reg_range(MAX7360_REG_I2C_TIMEOUT, MAX7360_REG_RTR_CNT),
+};
+
+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)
+ return dev_err_probe(dev, ret,
+ "Failed to write MAX7360 port configuration\n");
+ }
+
+ /* Read GPIO in register, to ACK any pending IRQ. */
+ ret = regmap_read(regmap, MAX7360_REG_GPIOIN, &val);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read GPIO values\n");
+
+ return 0;
+}
+
+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");
+
+ 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..44cf2bf651a2
--- /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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v11 03/10] pinctrl: Add MAX7360 pinctrl driver
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 01/10] dt-bindings: mfd: gpio: Add MAX7360 Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 02/10] mfd: Add max7360 support Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 04/10] pwm: max7360: Add MAX7360 PWM support Mathieu Dubois-Briand
` (6 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko
Add driver for Maxim Integrated MAX7360 pinctrl on the PORT pins. Pins
can be used either for GPIO, PWM or rotary encoder functionalities.
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/pinctrl/Kconfig | 11 ++
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-max7360.c | 215 ++++++++++++++++++++++++++++++++++++++
3 files changed, 227 insertions(+)
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 33db9104df17..98448db31456 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -346,6 +346,17 @@ config PINCTRL_LPC18XX
help
Pinctrl driver for NXP LPC18xx/43xx System Control Unit (SCU).
+config PINCTRL_MAX7360
+ tristate "MAX7360 Pincontrol support"
+ depends on MFD_MAX7360
+ select PINMUX
+ select GENERIC_PINCONF
+ help
+ Say Y here to enable pin control support for Maxim MAX7360 keypad
+ controller.
+ This keypad controller has 8 GPIO pins that may work as GPIO, or PWM,
+ or rotary encoder alternate modes.
+
config PINCTRL_MAX77620
tristate "MAX77620/MAX20024 Pincontrol support"
depends on MFD_MAX77620 && OF
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index ac27e88677d1..974a179b5593 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_PINCTRL_FALCON) += pinctrl-falcon.o
obj-$(CONFIG_PINCTRL_LOONGSON2) += pinctrl-loongson2.o
obj-$(CONFIG_PINCTRL_XWAY) += pinctrl-xway.o
obj-$(CONFIG_PINCTRL_LPC18XX) += pinctrl-lpc18xx.o
+obj-$(CONFIG_PINCTRL_MAX7360) += pinctrl-max7360.o
obj-$(CONFIG_PINCTRL_MAX77620) += pinctrl-max77620.o
obj-$(CONFIG_PINCTRL_MCP23S08_I2C) += pinctrl-mcp23s08_i2c.o
obj-$(CONFIG_PINCTRL_MCP23S08_SPI) += pinctrl-mcp23s08_spi.o
diff --git a/drivers/pinctrl/pinctrl-max7360.c b/drivers/pinctrl/pinctrl-max7360.c
new file mode 100644
index 000000000000..abfaff468bad
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-max7360.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/mfd/max7360.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/stddef.h>
+
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinmux.h>
+
+#include "core.h"
+#include "pinmux.h"
+
+struct max7360_pinctrl {
+ struct pinctrl_dev *pctldev;
+ struct pinctrl_desc pinctrl_desc;
+};
+
+static const struct pinctrl_pin_desc max7360_pins[] = {
+ PINCTRL_PIN(0, "PORT0"),
+ PINCTRL_PIN(1, "PORT1"),
+ PINCTRL_PIN(2, "PORT2"),
+ PINCTRL_PIN(3, "PORT3"),
+ PINCTRL_PIN(4, "PORT4"),
+ PINCTRL_PIN(5, "PORT5"),
+ PINCTRL_PIN(6, "PORT6"),
+ PINCTRL_PIN(7, "PORT7"),
+};
+
+static const unsigned int port0_pins[] = {0};
+static const unsigned int port1_pins[] = {1};
+static const unsigned int port2_pins[] = {2};
+static const unsigned int port3_pins[] = {3};
+static const unsigned int port4_pins[] = {4};
+static const unsigned int port5_pins[] = {5};
+static const unsigned int port6_pins[] = {6};
+static const unsigned int port7_pins[] = {7};
+static const unsigned int rotary_pins[] = {6, 7};
+
+static const struct pingroup max7360_groups[] = {
+ PINCTRL_PINGROUP("PORT0", port0_pins, ARRAY_SIZE(port0_pins)),
+ PINCTRL_PINGROUP("PORT1", port1_pins, ARRAY_SIZE(port1_pins)),
+ PINCTRL_PINGROUP("PORT2", port2_pins, ARRAY_SIZE(port2_pins)),
+ PINCTRL_PINGROUP("PORT3", port3_pins, ARRAY_SIZE(port3_pins)),
+ PINCTRL_PINGROUP("PORT4", port4_pins, ARRAY_SIZE(port4_pins)),
+ PINCTRL_PINGROUP("PORT5", port5_pins, ARRAY_SIZE(port5_pins)),
+ PINCTRL_PINGROUP("PORT6", port6_pins, ARRAY_SIZE(port6_pins)),
+ PINCTRL_PINGROUP("PORT7", port7_pins, ARRAY_SIZE(port7_pins)),
+ PINCTRL_PINGROUP("ROTARY", rotary_pins, ARRAY_SIZE(rotary_pins)),
+};
+
+static int max7360_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(max7360_groups);
+}
+
+static const char *max7360_pinctrl_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned int group)
+{
+ return max7360_groups[group].name;
+}
+
+static int max7360_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned int group,
+ const unsigned int **pins,
+ unsigned int *num_pins)
+{
+ *pins = max7360_groups[group].pins;
+ *num_pins = max7360_groups[group].npins;
+ return 0;
+}
+
+static const struct pinctrl_ops max7360_pinctrl_ops = {
+ .get_groups_count = max7360_pinctrl_get_groups_count,
+ .get_group_name = max7360_pinctrl_get_group_name,
+ .get_group_pins = max7360_pinctrl_get_group_pins,
+#ifdef CONFIG_OF
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
+ .dt_free_map = pinconf_generic_dt_free_map,
+#endif
+};
+
+static const char * const simple_groups[] = {
+ "PORT0", "PORT1", "PORT2", "PORT3",
+ "PORT4", "PORT5", "PORT6", "PORT7",
+};
+
+static const char * const rotary_groups[] = { "ROTARY" };
+
+#define MAX7360_PINCTRL_FN_GPIO 0
+#define MAX7360_PINCTRL_FN_PWM 1
+#define MAX7360_PINCTRL_FN_ROTARY 2
+static const struct pinfunction max7360_functions[] = {
+ [MAX7360_PINCTRL_FN_GPIO] = PINCTRL_PINFUNCTION("gpio", simple_groups,
+ ARRAY_SIZE(simple_groups)),
+ [MAX7360_PINCTRL_FN_PWM] = PINCTRL_PINFUNCTION("pwm", simple_groups,
+ ARRAY_SIZE(simple_groups)),
+ [MAX7360_PINCTRL_FN_ROTARY] = PINCTRL_PINFUNCTION("rotary", rotary_groups,
+ ARRAY_SIZE(rotary_groups)),
+};
+
+static int max7360_get_functions_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(max7360_functions);
+}
+
+static const char *max7360_get_function_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+ return max7360_functions[selector].name;
+}
+
+static int max7360_get_function_groups(struct pinctrl_dev *pctldev, unsigned int selector,
+ const char * const **groups,
+ unsigned int * const num_groups)
+{
+ *groups = max7360_functions[selector].groups;
+ *num_groups = max7360_functions[selector].ngroups;
+
+ return 0;
+}
+
+static int max7360_set_mux(struct pinctrl_dev *pctldev, unsigned int selector,
+ unsigned int group)
+{
+ struct regmap *regmap = dev_get_regmap(pctldev->dev->parent, NULL);
+ int val;
+
+ /*
+ * GPIO and PWM functions are the same: we only need to handle the
+ * rotary encoder function, on pins 6 and 7.
+ */
+ if (max7360_groups[group].pins[0] >= 6) {
+ if (selector == MAX7360_PINCTRL_FN_ROTARY)
+ val = MAX7360_GPIO_CFG_RTR_EN;
+ else
+ val = 0;
+
+ return regmap_write_bits(regmap, MAX7360_REG_GPIOCFG, MAX7360_GPIO_CFG_RTR_EN, val);
+ }
+
+ return 0;
+}
+
+static const struct pinmux_ops max7360_pmxops = {
+ .get_functions_count = max7360_get_functions_count,
+ .get_function_name = max7360_get_function_name,
+ .get_function_groups = max7360_get_function_groups,
+ .set_mux = max7360_set_mux,
+ .strict = true,
+};
+
+static int max7360_pinctrl_probe(struct platform_device *pdev)
+{
+ struct regmap *regmap;
+ struct pinctrl_desc *pd;
+ struct max7360_pinctrl *chip;
+ struct device *dev = &pdev->dev;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ pd = &chip->pinctrl_desc;
+
+ pd->pctlops = &max7360_pinctrl_ops;
+ pd->pmxops = &max7360_pmxops;
+ pd->name = dev_name(dev);
+ pd->pins = max7360_pins;
+ pd->npins = MAX7360_MAX_GPIO;
+ pd->owner = THIS_MODULE;
+
+ /*
+ * This MFD sub-device does not have any associated device tree node:
+ * properties are stored in the device node of the parent (MFD) device
+ * and this same node is used in phandles of client devices.
+ * Reuse this device tree node here, as otherwise the pinctrl subsystem
+ * would be confused by this topology.
+ */
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ chip->pctldev = devm_pinctrl_register(dev, pd, chip);
+ if (IS_ERR(chip->pctldev))
+ return dev_err_probe(dev, PTR_ERR(chip->pctldev), "can't register controller\n");
+
+ return 0;
+}
+
+static struct platform_driver max7360_pinctrl_driver = {
+ .driver = {
+ .name = "max7360-pinctrl",
+ },
+ .probe = max7360_pinctrl_probe,
+};
+module_platform_driver(max7360_pinctrl_driver);
+
+MODULE_DESCRIPTION("MAX7360 pinctrl driver");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v11 04/10] pwm: max7360: Add MAX7360 PWM support
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
` (2 preceding siblings ...)
2025-07-11 9:29 ` [PATCH v11 03/10] pinctrl: Add MAX7360 pinctrl driver Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
2025-07-11 14:50 ` Uwe Kleine-König
2025-07-11 9:29 ` [PATCH v11 05/10] gpio: regmap: Allow to allocate regmap-irq device Mathieu Dubois-Briand
` (5 subsequent siblings)
9 siblings, 1 reply; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko
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>
Co-developed-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/pwm/Kconfig | 10 +++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-max7360.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 204 insertions(+)
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index d9bcd1e8413e..c8352e503e3b 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -423,6 +423,16 @@ config PWM_LPSS_PLATFORM
To compile this driver as a module, choose M here: the module
will be called pwm-lpss-platform.
+config PWM_MAX7360
+ tristate "MAX7360 PWMs"
+ depends on MFD_MAX7360
+ 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.
+
config PWM_MC33XS2410
tristate "MC33XS2410 PWM support"
depends on OF
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 96160f4257fc..7e30f30bec50 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -37,6 +37,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_MC33XS2410) += pwm-mc33xs2410.o
obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o
obj-$(CONFIG_PWM_MESON) += pwm-meson.o
diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
new file mode 100644
index 000000000000..0eb83135f658
--- /dev/null
+++ b/drivers/pwm/pwm-max7360.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@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 immediately.
+ */
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/mfd/max7360.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+#define MAX7360_NUM_PWMS 8
+#define MAX7360_PWM_MAX_RES 255
+#define MAX7360_PWM_PERIOD_NS (2 * NSEC_PER_MSEC)
+
+struct max7360_pwm_waveform {
+ u8 duty_steps;
+ bool enabled;
+};
+
+static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+
+ /*
+ * Make sure we use the individual PWM configuration register and not
+ * the global one.
+ * We never need to use the global one, so there is no need to revert
+ * that in the .free() callback.
+ */
+ return regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
+ MAX7360_PORT_CFG_COMMON_PWM, 0);
+}
+
+static int max7360_pwm_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ struct max7360_pwm_waveform *wfhw = _wfhw;
+ u64 duty_steps;
+
+ /*
+ * Ignore user provided values for period_length_ns and duty_offset_ns:
+ * we only support fixed period of MAX7360_PWM_PERIOD_NS and offset of 0.
+ */
+ if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS)
+ duty_steps = MAX7360_PWM_MAX_RES;
+ else
+ duty_steps = (u32)wf->duty_length_ns * MAX7360_PWM_MAX_RES / MAX7360_PWM_PERIOD_NS;
+
+ wfhw->duty_steps = min(MAX7360_PWM_MAX_RES, duty_steps);
+ wfhw->enabled = !!wf->period_length_ns;
+
+ return 0;
+}
+
+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;
+
+ if (wfhw->enabled)
+ wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
+ MAX7360_PWM_MAX_RES);
+ else
+ wf->duty_length_ns = 0;
+
+ return 0;
+}
+
+static int max7360_pwm_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+ const struct max7360_pwm_waveform *wfhw = _wfhw;
+ unsigned int val;
+ int ret;
+
+ if (wfhw->enabled) {
+ ret = regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);
+ if (ret)
+ return ret;
+ }
+
+ val = wfhw->enabled ? BIT(pwm->hwpwm) : 0;
+ return regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val);
+}
+
+static int max7360_pwm_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+ struct max7360_pwm_waveform *wfhw = _wfhw;
+ unsigned int val;
+ int ret;
+
+ 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 const struct pwm_ops max7360_pwm_ops = {
+ .request = max7360_pwm_request,
+ .round_waveform_tohw = max7360_pwm_round_waveform_tohw,
+ .round_waveform_fromhw = max7360_pwm_round_waveform_fromhw,
+ .read_waveform = max7360_pwm_read_waveform,
+ .write_waveform = max7360_pwm_write_waveform,
+};
+
+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");
+
+ /*
+ * This MFD sub-device does not have any associated device tree node:
+ * properties are stored in the device node of the parent (MFD) device
+ * and this same node is used in phandles of client devices.
+ * Reuse this device tree node here, as otherwise the PWM subsystem
+ * would be confused by this topology.
+ */
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ 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;
+}
+
+static struct platform_driver max7360_pwm_driver = {
+ .driver = {
+ .name = "max7360-pwm",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = max7360_pwm_probe,
+};
+module_platform_driver(max7360_pwm_driver);
+
+MODULE_DESCRIPTION("MAX7360 PWM driver");
+MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v11 05/10] gpio: regmap: Allow to allocate regmap-irq device
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
` (3 preceding siblings ...)
2025-07-11 9:29 ` [PATCH v11 04/10] pwm: max7360: Add MAX7360 PWM support Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 06/10] gpio: regmap: Allow to provide init_valid_mask callback Mathieu Dubois-Briand
` (4 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko, Bartosz Golaszewski
GPIO controller often have support for IRQ: allow to easily allocate
both gpio-regmap and regmap-irq in one operation.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/gpio/gpio-regmap.c | 29 +++++++++++++++++++++++++++--
include/linux/gpio/regmap.h | 11 +++++++++++
2 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index 87c4225784cf..039dbe70d009 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -32,6 +32,11 @@ struct gpio_regmap {
unsigned int reg_dir_in_base;
unsigned int reg_dir_out_base;
+#ifdef CONFIG_REGMAP_IRQ
+ int regmap_irq_line;
+ struct regmap_irq_chip_data *irq_chip_data;
+#endif
+
int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
unsigned int offset, unsigned int *reg,
unsigned int *mask);
@@ -215,6 +220,7 @@ EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata);
*/
struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config)
{
+ struct irq_domain *irq_domain;
struct gpio_regmap *gpio;
struct gpio_chip *chip;
int ret;
@@ -295,8 +301,22 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
if (ret < 0)
goto err_free_gpio;
- if (config->irq_domain) {
- ret = gpiochip_irqchip_add_domain(chip, config->irq_domain);
+#ifdef CONFIG_REGMAP_IRQ
+ if (config->regmap_irq_chip) {
+ gpio->regmap_irq_line = config->regmap_irq_line;
+ ret = regmap_add_irq_chip_fwnode(dev_fwnode(config->parent), config->regmap,
+ config->regmap_irq_line, config->regmap_irq_flags,
+ 0, config->regmap_irq_chip, &gpio->irq_chip_data);
+ if (ret)
+ goto err_free_gpio;
+
+ irq_domain = regmap_irq_get_domain(gpio->irq_chip_data);
+ } else
+#endif
+ irq_domain = config->irq_domain;
+
+ if (irq_domain) {
+ ret = gpiochip_irqchip_add_domain(chip, irq_domain);
if (ret)
goto err_remove_gpiochip;
}
@@ -317,6 +337,11 @@ EXPORT_SYMBOL_GPL(gpio_regmap_register);
*/
void gpio_regmap_unregister(struct gpio_regmap *gpio)
{
+#ifdef CONFIG_REGMAP_IRQ
+ if (gpio->irq_chip_data)
+ regmap_del_irq_chip(gpio->regmap_irq_line, gpio->irq_chip_data);
+#endif
+
gpiochip_remove(&gpio->gpio_chip);
kfree(gpio);
}
diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
index c722c67668c6..19b52ac03a5d 100644
--- a/include/linux/gpio/regmap.h
+++ b/include/linux/gpio/regmap.h
@@ -40,6 +40,11 @@ struct regmap;
* @drvdata: (Optional) Pointer to driver specific data which is
* not used by gpio-remap but is provided "as is" to the
* driver callback(s).
+ * @regmap_irq_chip: (Optional) Pointer on an regmap_irq_chip structure. If
+ * set, a regmap-irq device will be created and the IRQ
+ * domain will be set accordingly.
+ * @regmap_irq_line (Optional) The IRQ the device uses to signal interrupts.
+ * @regmap_irq_flags (Optional) The IRQF_ flags to use for the interrupt.
*
* The ->reg_mask_xlate translates a given base address and GPIO offset to
* register and mask pair. The base address is one of the given register
@@ -78,6 +83,12 @@ struct gpio_regmap_config {
int ngpio_per_reg;
struct irq_domain *irq_domain;
+#ifdef CONFIG_REGMAP_IRQ
+ struct regmap_irq_chip *regmap_irq_chip;
+ int regmap_irq_line;
+ unsigned long regmap_irq_flags;
+#endif
+
int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
unsigned int offset, unsigned int *reg,
unsigned int *mask);
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v11 06/10] gpio: regmap: Allow to provide init_valid_mask callback
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
` (4 preceding siblings ...)
2025-07-11 9:29 ` [PATCH v11 05/10] gpio: regmap: Allow to allocate regmap-irq device Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 07/10] gpio: max7360: Add MAX7360 gpio support Mathieu Dubois-Briand
` (3 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko, Bartosz Golaszewski
Allows to populate the gpio_regmap_config structure with
init_valid_mask() callback to set on the final gpio_chip structure.
Reviewed-by: Michael Walle <mwalle@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/gpio/gpio-regmap.c | 1 +
include/linux/gpio/regmap.h | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index 039dbe70d009..cf1413fa950d 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -261,6 +261,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
chip->names = config->names;
chip->label = config->label ?: dev_name(config->parent);
chip->can_sleep = regmap_might_sleep(config->regmap);
+ chip->init_valid_mask = config->init_valid_mask;
chip->request = gpiochip_generic_request;
chip->free = gpiochip_generic_free;
diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
index 19b52ac03a5d..622a2939ebe0 100644
--- a/include/linux/gpio/regmap.h
+++ b/include/linux/gpio/regmap.h
@@ -6,6 +6,7 @@
struct device;
struct fwnode_handle;
struct gpio_regmap;
+struct gpio_chip;
struct irq_domain;
struct regmap;
@@ -40,6 +41,8 @@ struct regmap;
* @drvdata: (Optional) Pointer to driver specific data which is
* not used by gpio-remap but is provided "as is" to the
* driver callback(s).
+ * @init_valid_mask: (Optional) Routine to initialize @valid_mask, to be used
+ * if not all GPIOs are valid.
* @regmap_irq_chip: (Optional) Pointer on an regmap_irq_chip structure. If
* set, a regmap-irq device will be created and the IRQ
* domain will be set accordingly.
@@ -93,6 +96,10 @@ struct gpio_regmap_config {
unsigned int offset, unsigned int *reg,
unsigned int *mask);
+ int (*init_valid_mask)(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios);
+
void *drvdata;
};
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v11 07/10] gpio: max7360: Add MAX7360 gpio support
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
` (5 preceding siblings ...)
2025-07-11 9:29 ` [PATCH v11 06/10] gpio: regmap: Allow to provide init_valid_mask callback Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 08/10] input: keyboard: Add support for MAX7360 keypad Mathieu Dubois-Briand
` (2 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Bartosz Golaszewski, Andy Shevchenko
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.
Co-developed-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/gpio/Kconfig | 12 +++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-max7360.c | 257 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 270 insertions(+)
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 44f922e10db2..39c3ce6145f3 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1470,6 +1470,18 @@ config GPIO_MADERA
help
Support for GPIOs on Cirrus Logic Madera class codecs.
+config GPIO_MAX7360
+ tristate "MAX7360 GPIO support"
+ depends on MFD_MAX7360
+ select GPIO_REGMAP
+ select REGMAP_IRQ
+ help
+ Allows to use MAX7360 I/O Expander PWM lines as GPIO and keypad COL
+ lines as GPO.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-max7360.
+
config GPIO_MAX77620
tristate "GPIO support for PMIC MAX77620 and MAX20024"
depends on MFD_MAX77620
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 88dedd298256..360a47b2793d 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -105,6 +105,7 @@ obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o
obj-$(CONFIG_GPIO_MAX7301) += gpio-max7301.o
obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o
obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o
+obj-$(CONFIG_GPIO_MAX7360) += gpio-max7360.o
obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o
obj-$(CONFIG_GPIO_MAX77650) += gpio-max77650.o
obj-$(CONFIG_GPIO_MAX77759) += gpio-max77759.o
diff --git a/drivers/gpio/gpio-max7360.c b/drivers/gpio/gpio-max7360.c
new file mode 100644
index 000000000000..db92a43776a9
--- /dev/null
+++ b/drivers/gpio/gpio-max7360.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max7360.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#define MAX7360_GPIO_PORT 1
+#define MAX7360_GPIO_COL 2
+
+struct max7360_gpio_plat_data {
+ unsigned int function;
+};
+
+static struct max7360_gpio_plat_data max7360_gpio_port_plat = { .function = MAX7360_GPIO_PORT };
+static struct max7360_gpio_plat_data max7360_gpio_col_plat = { .function = MAX7360_GPIO_COL };
+
+static int max7360_get_available_gpos(struct device *dev, unsigned int *available_gpios)
+{
+ u32 columns;
+ int ret;
+
+ ret = device_property_read_u32(dev->parent, "keypad,num-columns", &columns);
+ if (ret) {
+ dev_err(dev, "Failed to read columns count\n");
+ return ret;
+ }
+
+ *available_gpios = min(MAX7360_MAX_GPO, MAX7360_MAX_KEY_COLS - columns);
+
+ return 0;
+}
+
+static int max7360_gpo_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ unsigned int available_gpios;
+ int ret;
+
+ ret = max7360_get_available_gpos(gc->parent, &available_gpios);
+ if (ret)
+ return ret;
+
+ bitmap_clear(valid_mask, 0, MAX7360_MAX_KEY_COLS - available_gpios);
+
+ return 0;
+}
+
+static int max7360_set_gpos_count(struct device *dev, struct regmap *regmap)
+{
+ /*
+ * MAX7360 COL0 to COL7 pins can be used either as keypad columns,
+ * general purpose output or a mix of both.
+ * By default, all pins are used as keypad, here we update this
+ * configuration to allow to use some of them as GPIOs.
+ */
+ unsigned int available_gpios;
+ unsigned int val;
+ int ret;
+
+ ret = max7360_get_available_gpos(dev, &available_gpios);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure which GPIOs will be used for keypad.
+ * MAX7360_REG_DEBOUNCE contains configuration both for keypad debounce
+ * timings and gpos/keypad columns repartition. Only the later is
+ * modified here.
+ */
+ val = FIELD_PREP(MAX7360_PORTS, available_gpios);
+ ret = regmap_write_bits(regmap, MAX7360_REG_DEBOUNCE, MAX7360_PORTS, val);
+ if (ret)
+ dev_err(dev, "Failed to write max7360 columns/gpos configuration");
+
+ return ret;
+}
+
+static int max7360_gpio_reg_mask_xlate(struct gpio_regmap *gpio,
+ unsigned int base, unsigned int offset,
+ unsigned int *reg, unsigned int *mask)
+{
+ if (base == MAX7360_REG_PWMBASE) {
+ /*
+ * GPIO output is using PWM duty cycle registers: one register
+ * per line, with value being either 0 or 255.
+ */
+ *reg = base + offset;
+ *mask = GENMASK(7, 0);
+ } else {
+ *reg = base;
+ *mask = BIT(offset);
+ }
+
+ return 0;
+}
+
+static const struct regmap_irq max7360_regmap_irqs[MAX7360_MAX_GPIO] = {
+ REGMAP_IRQ_REG(0, 0, BIT(0)),
+ REGMAP_IRQ_REG(1, 0, BIT(1)),
+ REGMAP_IRQ_REG(2, 0, BIT(2)),
+ REGMAP_IRQ_REG(3, 0, BIT(3)),
+ REGMAP_IRQ_REG(4, 0, BIT(4)),
+ REGMAP_IRQ_REG(5, 0, BIT(5)),
+ REGMAP_IRQ_REG(6, 0, BIT(6)),
+ REGMAP_IRQ_REG(7, 0, BIT(7)),
+};
+
+static int max7360_handle_mask_sync(const int index,
+ const unsigned int mask_buf_def,
+ const unsigned int mask_buf,
+ void *const irq_drv_data)
+{
+ struct regmap *regmap = irq_drv_data;
+ int ret;
+
+ for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
+ ret = regmap_assign_bits(regmap, MAX7360_REG_PWMCFG(i),
+ MAX7360_PORT_CFG_INTERRUPT_MASK, mask_buf & BIT(i));
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+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");
+
+ /* Create custom IRQ configuration. */
+ irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL);
+ 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));
+}
+
+static const struct of_device_id max7360_gpio_of_match[] = {
+ {
+ .compatible = "maxim,max7360-gpo",
+ .data = &max7360_gpio_col_plat
+ }, {
+ .compatible = "maxim,max7360-gpio",
+ .data = &max7360_gpio_port_plat
+ }, {
+ }
+};
+MODULE_DEVICE_TABLE(of, max7360_gpio_of_match);
+
+static struct platform_driver max7360_gpio_driver = {
+ .driver = {
+ .name = "max7360-gpio",
+ .of_match_table = max7360_gpio_of_match,
+ },
+ .probe = max7360_gpio_probe,
+};
+module_platform_driver(max7360_gpio_driver);
+
+MODULE_DESCRIPTION("MAX7360 GPIO driver");
+MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v11 08/10] input: keyboard: Add support for MAX7360 keypad
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
` (6 preceding siblings ...)
2025-07-11 9:29 ` [PATCH v11 07/10] gpio: max7360: Add MAX7360 gpio support Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 09/10] input: misc: Add support for MAX7360 rotary Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 10/10] MAINTAINERS: Add entry on MAX7360 driver Mathieu Dubois-Briand
9 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand
Add driver for Maxim Integrated MAX7360 keypad controller, providing
support for up to 64 keys, with a matrix of 8 columns and 8 rows.
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/input/keyboard/Kconfig | 12 ++
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/max7360-keypad.c | 308 ++++++++++++++++++++++++++++++++
3 files changed, 321 insertions(+)
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 721ab69e84ac..93b5cccf6892 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -421,6 +421,18 @@ config KEYBOARD_MAX7359
To compile this driver as a module, choose M here: the
module will be called max7359_keypad.
+config KEYBOARD_MAX7360
+ tristate "Maxim MAX7360 Key Switch Controller"
+ select INPUT_MATRIXKMAP
+ depends on I2C
+ depends on MFD_MAX7360
+ help
+ If you say yes here you get support for the keypad controller on the
+ Maxim MAX7360 I/O Expander.
+
+ To compile this driver as a module, choose M here: the module will be
+ called max7360_keypad.
+
config KEYBOARD_MPR121
tristate "Freescale MPR121 Touchkey"
depends on I2C
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 1e0721c30709..b49d32d4003d 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_KEYBOARD_LPC32XX) += lpc32xx-keys.o
obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o
obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o
obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o
+obj-$(CONFIG_KEYBOARD_MAX7360) += max7360-keypad.o
obj-$(CONFIG_KEYBOARD_MPR121) += mpr121_touchkey.o
obj-$(CONFIG_KEYBOARD_MT6779) += mt6779-keypad.o
obj-$(CONFIG_KEYBOARD_MTK_PMIC) += mtk-pmic-keys.o
diff --git a/drivers/input/keyboard/max7360-keypad.c b/drivers/input/keyboard/max7360-keypad.c
new file mode 100644
index 000000000000..503be952b0a6
--- /dev/null
+++ b/drivers/input/keyboard/max7360-keypad.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max7360.h>
+#include <linux/mod_devicetable.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/regmap.h>
+
+struct max7360_keypad {
+ struct input_dev *input;
+ unsigned int rows;
+ unsigned int cols;
+ unsigned int debounce_ms;
+ int irq;
+ struct regmap *regmap;
+ unsigned short keycodes[MAX7360_MAX_KEY_ROWS * MAX7360_MAX_KEY_COLS];
+};
+
+static irqreturn_t max7360_keypad_irq(int irq, void *data)
+{
+ struct max7360_keypad *max7360_keypad = data;
+ struct device *dev = max7360_keypad->input->dev.parent;
+ unsigned int val;
+ unsigned int row, col;
+ unsigned int release;
+ unsigned int code;
+ int error;
+
+ error = regmap_read(max7360_keypad->regmap, MAX7360_REG_KEYFIFO, &val);
+ if (error) {
+ dev_err(dev, "Failed to read MAX7360 FIFO");
+ return IRQ_NONE;
+ }
+
+ /* FIFO overflow: ignore it and get next event. */
+ if (val == MAX7360_FIFO_OVERFLOW) {
+ dev_warn(dev, "max7360 FIFO overflow");
+ error = regmap_read_poll_timeout(max7360_keypad->regmap, MAX7360_REG_KEYFIFO,
+ val, val != MAX7360_FIFO_OVERFLOW, 0, 1000);
+ if (error) {
+ dev_err(dev, "Failed to empty MAX7360 FIFO");
+ return IRQ_NONE;
+ }
+ }
+
+ if (val == MAX7360_FIFO_EMPTY) {
+ dev_dbg(dev, "Got a spurious interrupt");
+
+ return IRQ_NONE;
+ }
+
+ row = FIELD_GET(MAX7360_FIFO_ROW, val);
+ col = FIELD_GET(MAX7360_FIFO_COL, val);
+ release = val & MAX7360_FIFO_RELEASE;
+
+ code = MATRIX_SCAN_CODE(row, col, get_count_order(max7360_keypad->cols));
+
+ dev_dbg(dev, "key[%d:%d] %s\n", row, col, release ? "release" : "press");
+
+ input_event(max7360_keypad->input, EV_MSC, MSC_SCAN, code);
+ input_report_key(max7360_keypad->input, max7360_keypad->keycodes[code], !release);
+ input_sync(max7360_keypad->input);
+
+ return IRQ_HANDLED;
+}
+
+static int max7360_keypad_open(struct input_dev *pdev)
+{
+ struct max7360_keypad *max7360_keypad = input_get_drvdata(pdev);
+ struct device *dev = max7360_keypad->input->dev.parent;
+ int error;
+
+ /* Somebody is using the device: get out of sleep. */
+ error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_CONFIG,
+ MAX7360_CFG_SLEEP, MAX7360_CFG_SLEEP);
+ if (error)
+ dev_err(dev, "Failed to write max7360 configuration: %d\n", error);
+
+ return error;
+}
+
+static void max7360_keypad_close(struct input_dev *pdev)
+{
+ struct max7360_keypad *max7360_keypad = input_get_drvdata(pdev);
+ struct device *dev = max7360_keypad->input->dev.parent;
+ int error;
+
+ /* Nobody is using the device anymore: go to sleep. */
+ error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_CONFIG, MAX7360_CFG_SLEEP, 0);
+ if (error)
+ dev_err(dev, "Failed to write max7360 configuration: %d\n", error);
+}
+
+static int max7360_keypad_hw_init(struct max7360_keypad *max7360_keypad)
+{
+ struct device *dev = max7360_keypad->input->dev.parent;
+ unsigned int val;
+ int error;
+
+ val = max7360_keypad->debounce_ms - MAX7360_DEBOUNCE_MIN;
+ error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_DEBOUNCE,
+ MAX7360_DEBOUNCE,
+ FIELD_PREP(MAX7360_DEBOUNCE, val));
+ if (error)
+ return dev_err_probe(dev, error,
+ "Failed to write max7360 debounce configuration\n");
+
+ error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_INTERRUPT,
+ MAX7360_INTERRUPT_TIME_MASK,
+ FIELD_PREP(MAX7360_INTERRUPT_TIME_MASK, 1));
+ if (error)
+ return dev_err_probe(dev, error,
+ "Failed to write max7360 keypad interrupt configuration\n");
+
+ return 0;
+}
+
+static int max7360_keypad_build_keymap(struct max7360_keypad *max7360_keypad)
+{
+ struct input_dev *input_dev = max7360_keypad->input;
+ struct device *dev = input_dev->dev.parent->parent;
+ struct matrix_keymap_data keymap_data;
+ const char *propname = "linux,keymap";
+ unsigned int max_keys;
+ int error;
+ int size;
+
+ size = device_property_count_u32(dev, propname);
+ if (size <= 0) {
+ dev_err(dev, "missing or malformed property %s: %d\n", propname, size);
+ return size < 0 ? size : -EINVAL;
+ }
+
+ max_keys = max7360_keypad->cols * max7360_keypad->rows;
+ if (size > max_keys) {
+ dev_err(dev, "%s size overflow (%d vs max %u)\n", propname, size, max_keys);
+ return -EINVAL;
+ }
+
+ u32 *keys __free(kfree) = kmalloc_array(size, sizeof(*keys), GFP_KERNEL);
+ if (!keys)
+ return -ENOMEM;
+
+ error = device_property_read_u32_array(dev, propname, keys, size);
+ if (error) {
+ dev_err(dev, "failed to read %s property: %d\n", propname, error);
+ return error;
+ }
+
+ keymap_data.keymap = keys;
+ keymap_data.keymap_size = size;
+ error = matrix_keypad_build_keymap(&keymap_data, NULL,
+ max7360_keypad->rows, max7360_keypad->cols,
+ max7360_keypad->keycodes, max7360_keypad->input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int max7360_keypad_parse_fw(struct device *dev,
+ struct max7360_keypad *max7360_keypad,
+ bool *autorepeat)
+{
+ int error;
+
+ error = matrix_keypad_parse_properties(dev->parent, &max7360_keypad->rows,
+ &max7360_keypad->cols);
+ if (error)
+ return error;
+
+ if (!max7360_keypad->rows || !max7360_keypad->cols ||
+ max7360_keypad->rows > MAX7360_MAX_KEY_ROWS ||
+ max7360_keypad->cols > MAX7360_MAX_KEY_COLS) {
+ dev_err(dev, "Invalid number of columns or rows (%ux%u)\n",
+ max7360_keypad->cols, max7360_keypad->rows);
+ return -EINVAL;
+ }
+
+ *autorepeat = device_property_read_bool(dev->parent, "autorepeat");
+
+ max7360_keypad->debounce_ms = MAX7360_DEBOUNCE_MIN;
+ error = device_property_read_u32(dev->parent, "keypad-debounce-delay-ms",
+ &max7360_keypad->debounce_ms);
+ if (error == -EINVAL) {
+ dev_info(dev, "Using default keypad-debounce-delay-ms: %u\n",
+ max7360_keypad->debounce_ms);
+ } else if (error < 0) {
+ dev_err(dev, "Failed to read keypad-debounce-delay-ms property\n");
+ return error;
+ }
+
+ if (!in_range(max7360_keypad->debounce_ms, MAX7360_DEBOUNCE_MIN,
+ MAX7360_DEBOUNCE_MAX - MAX7360_DEBOUNCE_MIN + 1)) {
+ dev_err(dev, "Invalid keypad-debounce-delay-ms: %u, should be between %u and %u.\n",
+ max7360_keypad->debounce_ms, MAX7360_DEBOUNCE_MIN, MAX7360_DEBOUNCE_MAX);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int max7360_keypad_probe(struct platform_device *pdev)
+{
+ struct max7360_keypad *max7360_keypad;
+ struct device *dev = &pdev->dev;
+ struct input_dev *input;
+ struct regmap *regmap;
+ bool autorepeat;
+ int error;
+ int irq;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
+
+ irq = fwnode_irq_get_byname(dev_fwnode(dev->parent), "intk");
+ if (irq < 0)
+ return dev_err_probe(dev, irq, "Failed to get IRQ\n");
+
+ max7360_keypad = devm_kzalloc(dev, sizeof(*max7360_keypad), GFP_KERNEL);
+ if (!max7360_keypad)
+ return -ENOMEM;
+
+ max7360_keypad->regmap = regmap;
+
+ error = max7360_keypad_parse_fw(dev, max7360_keypad, &autorepeat);
+ if (error)
+ return error;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ max7360_keypad->input = input;
+
+ input->id.bustype = BUS_I2C;
+ input->name = pdev->name;
+ input->open = max7360_keypad_open;
+ input->close = max7360_keypad_close;
+
+ error = max7360_keypad_build_keymap(max7360_keypad);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to build keymap\n");
+
+ input_set_capability(input, EV_MSC, MSC_SCAN);
+ if (autorepeat)
+ __set_bit(EV_REP, input->evbit);
+
+ input_set_drvdata(input, max7360_keypad);
+
+ error = devm_request_threaded_irq(dev, irq, NULL, max7360_keypad_irq,
+ IRQF_ONESHOT,
+ "max7360-keypad", max7360_keypad);
+ 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_keypad_hw_init(max7360_keypad);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to initialize max7360 keypad\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_keypad_remove(struct platform_device *pdev)
+{
+ dev_pm_clear_wake_irq(&pdev->dev);
+ device_init_wakeup(&pdev->dev, false);
+}
+
+static struct platform_driver max7360_keypad_driver = {
+ .driver = {
+ .name = "max7360-keypad",
+ },
+ .probe = max7360_keypad_probe,
+ .remove = max7360_keypad_remove,
+};
+module_platform_driver(max7360_keypad_driver);
+
+MODULE_DESCRIPTION("MAX7360 Keypad driver");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v11 09/10] input: misc: Add support for MAX7360 rotary
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
` (7 preceding siblings ...)
2025-07-11 9:29 ` [PATCH v11 08/10] input: keyboard: Add support for MAX7360 keypad Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 10/10] MAINTAINERS: Add entry on MAX7360 driver Mathieu Dubois-Briand
9 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand
Add driver for Maxim Integrated MAX7360 rotary encoder controller,
supporting a single rotary switch.
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/input/misc/Kconfig | 10 ++
drivers/input/misc/Makefile | 1 +
drivers/input/misc/max7360-rotary.c | 192 ++++++++++++++++++++++++++++++++++++
3 files changed, 203 insertions(+)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index f5496ca0c0d2..0bc9d121c984 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -230,6 +230,16 @@ config INPUT_M68K_BEEP
tristate "M68k Beeper support"
depends on M68K
+config INPUT_MAX7360_ROTARY
+ tristate "Maxim MAX7360 Rotary Encoder"
+ depends on MFD_MAX7360
+ help
+ If you say yes here you get support for the rotary encoder on the
+ Maxim MAX7360 I/O Expander.
+
+ To compile this driver as a module, choose M here: the module will be
+ called max7360_rotary.
+
config INPUT_MAX77650_ONKEY
tristate "Maxim MAX77650 ONKEY support"
depends on MFD_MAX77650
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 6d91804d0a6f..c454fba3a3ae 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
+obj-$(CONFIG_INPUT_MAX7360_ROTARY) += max7360-rotary.o
obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o
obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
diff --git a/drivers/input/misc/max7360-rotary.c b/drivers/input/misc/max7360-rotary.c
new file mode 100644
index 000000000000..385831ef34b6
--- /dev/null
+++ b/drivers/input/misc/max7360-rotary.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device/devres.h>
+#include <linux/dev_printk.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max7360.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define MAX7360_ROTARY_DEFAULT_STEPS 24
+
+struct max7360_rotary {
+ struct input_dev *input;
+ struct regmap *regmap;
+ unsigned int debounce_ms;
+
+ unsigned int pos;
+
+ u32 steps;
+ u32 axis;
+ bool relative_axis;
+ bool rollover;
+};
+
+static void max7360_rotary_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 {
+ int pos = max7360_rotary->pos;
+ int maxval = max7360_rotary->steps;
+
+ /*
+ * Add steps to the position.
+ * Make sure added steps are always in ]-maxval; maxval[
+ * interval, so (pos + maxval) is always >= 0.
+ * Then set back pos to the [0; maxval[ interval.
+ */
+ pos += steps % maxval;
+ if (max7360_rotary->rollover)
+ pos = (pos + maxval) % maxval;
+ else
+ pos = clamp(pos, 0, maxval - 1);
+
+ 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)
+ return IRQ_NONE;
+
+ max7360_rotary_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;
+ 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)
+ return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
+
+ 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);
+ device_init_wakeup(&pdev->dev, false);
+}
+
+static struct platform_driver max7360_rotary_driver = {
+ .driver = {
+ .name = "max7360-rotary",
+ },
+ .probe = max7360_rotary_probe,
+ .remove = max7360_rotary_remove,
+};
+module_platform_driver(max7360_rotary_driver);
+
+MODULE_DESCRIPTION("MAX7360 Rotary driver");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v11 10/10] MAINTAINERS: Add entry on MAX7360 driver
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
` (8 preceding siblings ...)
2025-07-11 9:29 ` [PATCH v11 09/10] input: misc: Add support for MAX7360 rotary Mathieu Dubois-Briand
@ 2025-07-11 9:29 ` Mathieu Dubois-Briand
9 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-11 9:29 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand
Add myself as maintainer of Maxim MAX7360 driver and device-tree bindings.
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
MAINTAINERS | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index fad6cb025a19..81423ac75720 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14806,6 +14806,19 @@ L: linux-iio@vger.kernel.org
S: Maintained
F: drivers/iio/temperature/max30208.c
+MAXIM MAX7360 KEYPAD LED MFD DRIVER
+M: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+S: Maintained
+F: Documentation/devicetree/bindings/gpio/maxim,max7360-gpio.yaml
+F: Documentation/devicetree/bindings/mfd/maxim,max7360.yaml
+F: drivers/gpio/gpio-max7360.c
+F: drivers/input/keyboard/max7360-keypad.c
+F: drivers/input/misc/max7360-rotary.c
+F: drivers/mfd/max7360.c
+F: drivers/pinctrl/pinctrl-max7360.c
+F: drivers/pwm/pwm-max7360.c
+F: include/linux/mfd/max7360.h
+
MAXIM MAX77650 PMIC MFD DRIVER
M: Bartosz Golaszewski <brgl@bgdev.pl>
L: linux-kernel@vger.kernel.org
--
2.39.5
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v11 04/10] pwm: max7360: Add MAX7360 PWM support
2025-07-11 9:29 ` [PATCH v11 04/10] pwm: max7360: Add MAX7360 PWM support Mathieu Dubois-Briand
@ 2025-07-11 14:50 ` Uwe Kleine-König
2025-07-16 7:46 ` Mathieu Dubois-Briand
0 siblings, 1 reply; 13+ messages in thread
From: Uwe Kleine-König @ 2025-07-11 14:50 UTC (permalink / raw)
To: Mathieu Dubois-Briand
Cc: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Michael Walle, Mark Brown, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, devicetree, linux-kernel,
linux-gpio, linux-input, linux-pwm, andriy.shevchenko,
Grégory Clement, Thomas Petazzoni, Andy Shevchenko
[-- Attachment #1: Type: text/plain, Size: 4139 bytes --]
Hello Mathieu,
On Fri, Jul 11, 2025 at 11:29:44AM +0200, Mathieu Dubois-Briand wrote:
> diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
> new file mode 100644
> index 000000000000..0eb83135f658
> --- /dev/null
> +++ b/drivers/pwm/pwm-max7360.c
> @@ -0,0 +1,193 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright 2025 Bootlin
> + *
> + * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
> + * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> + *
A link to the data sheet here would be awesome. I found it at
https://www.analog.com/media/en/technical-documentation/data-sheets/MAX7360.pdf
> [...]
> +static int max7360_pwm_round_waveform_tohw(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const struct pwm_waveform *wf,
> + void *_wfhw)
> +{
> + struct max7360_pwm_waveform *wfhw = _wfhw;
> + u64 duty_steps;
> +
> + /*
> + * Ignore user provided values for period_length_ns and duty_offset_ns:
> + * we only support fixed period of MAX7360_PWM_PERIOD_NS and offset of 0.
> + */
> + if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS)
> + duty_steps = MAX7360_PWM_MAX_RES;
> + else
> + duty_steps = (u32)wf->duty_length_ns * MAX7360_PWM_MAX_RES / MAX7360_PWM_PERIOD_NS;
I read through the data sheet and I think the right formula for
duty_steps is:
if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS) {
duty_steps = 255;
} else {
duty_steps = (u32)wf->duty_length_ns * 256 / MAX7360_PWM_PERIOD_NS;
if (duty_steps == 255)
duty_steps = 254;
}
(Using magic constants here, but in the end these should be cpp symbols
of course.)
> + wfhw->duty_steps = min(MAX7360_PWM_MAX_RES, duty_steps);
> + wfhw->enabled = !!wf->period_length_ns;
> +
> + return 0;
> +}
> +
> +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;
> +
> + if (wfhw->enabled)
> + wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
> + MAX7360_PWM_MAX_RES);
> + else
> + wf->duty_length_ns = 0;
The matching code here is:
if (wfhw->duty_steps == 255)
wf->duty_length_ns = MAX7360_PWM_PERIOD_NS;
else
wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS, 256)
This is arguably a strange design, but f_OSC = 128 kHz and the fixed
period being 2 ms is a strong indication that the divider is 256 and not
255. If you don't agree to the manual (e.g. because you measured the
output and saw your formula to be true), please add a code comment about
that.
When you have measureing equipment at hand it would be great if you
could verify that the right fromhw implementation isn't:
wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS, 256)
even for wfhw->duty_steps == 255. (Which would mean that the PWM cannot
provide a 100% duty cycle.)
> +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");
> +
> + /*
> + * This MFD sub-device does not have any associated device tree node:
> + * properties are stored in the device node of the parent (MFD) device
> + * and this same node is used in phandles of client devices.
> + * Reuse this device tree node here, as otherwise the PWM subsystem
> + * would be confused by this topology.
> + */
> + device_set_of_node_from_dev(dev, dev->parent);
> +
> + 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");
Please start error messages with a capital letter.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v11 04/10] pwm: max7360: Add MAX7360 PWM support
2025-07-11 14:50 ` Uwe Kleine-König
@ 2025-07-16 7:46 ` Mathieu Dubois-Briand
0 siblings, 0 replies; 13+ messages in thread
From: Mathieu Dubois-Briand @ 2025-07-16 7:46 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Michael Walle, Mark Brown, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, devicetree, linux-kernel,
linux-gpio, linux-input, linux-pwm, andriy.shevchenko,
Grégory Clement, Thomas Petazzoni, Andy Shevchenko
On Fri Jul 11, 2025 at 4:50 PM CEST, Uwe Kleine-König wrote:
> Hello Mathieu,
>
> On Fri, Jul 11, 2025 at 11:29:44AM +0200, Mathieu Dubois-Briand wrote:
>> diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
>> new file mode 100644
>> index 000000000000..0eb83135f658
>> --- /dev/null
>> +++ b/drivers/pwm/pwm-max7360.c
>> @@ -0,0 +1,193 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright 2025 Bootlin
>> + *
>> + * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
>> + * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
>> + *
>
> A link to the data sheet here would be awesome. I found it at
>
> https://www.analog.com/media/en/technical-documentation/data-sheets/MAX7360.pdf
>
Sure, I will add the link.
>> [...]
>> +static int max7360_pwm_round_waveform_tohw(struct pwm_chip *chip,
>> + struct pwm_device *pwm,
>> + const struct pwm_waveform *wf,
>> + void *_wfhw)
>> +{
>> + struct max7360_pwm_waveform *wfhw = _wfhw;
>> + u64 duty_steps;
>> +
>> + /*
>> + * Ignore user provided values for period_length_ns and duty_offset_ns:
>> + * we only support fixed period of MAX7360_PWM_PERIOD_NS and offset of 0.
>> + */
>> + if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS)
>> + duty_steps = MAX7360_PWM_MAX_RES;
>> + else
>> + duty_steps = (u32)wf->duty_length_ns * MAX7360_PWM_MAX_RES / MAX7360_PWM_PERIOD_NS;
>
> I read through the data sheet and I think the right formula for
> duty_steps is:
>
> if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS) {
> duty_steps = 255;
> } else {
> duty_steps = (u32)wf->duty_length_ns * 256 / MAX7360_PWM_PERIOD_NS;
> if (duty_steps == 255)
> duty_steps = 254;
> }
>
> (Using magic constants here, but in the end these should be cpp symbols
> of course.)
>
>> + wfhw->duty_steps = min(MAX7360_PWM_MAX_RES, duty_steps);
>> + wfhw->enabled = !!wf->period_length_ns;
>> +
>> + return 0;
>> +}
>> +
>> +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;
>> +
>> + if (wfhw->enabled)
>> + wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
>> + MAX7360_PWM_MAX_RES);
>> + else
>> + wf->duty_length_ns = 0;
>
> The matching code here is:
>
> if (wfhw->duty_steps == 255)
> wf->duty_length_ns = MAX7360_PWM_PERIOD_NS;
> else
> wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS, 256)
>
> This is arguably a strange design, but f_OSC = 128 kHz and the fixed
> period being 2 ms is a strong indication that the divider is 256 and not
> 255. If you don't agree to the manual (e.g. because you measured the
> output and saw your formula to be true), please add a code comment about
> that.
>
Yes, I did a few measurements, and you are right. I'm fixing the code as
you described.
> When you have measureing equipment at hand it would be great if you
> could verify that the right fromhw implementation isn't:
>
> wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS, 256)
>
> even for wfhw->duty_steps == 255. (Which would mean that the PWM cannot
> provide a 100% duty cycle.)
>
No, I confirm, values from 0 to 254 provide a duty cycle from 0 to
254/256. A value of 255 provides a 100% duty cycle.
>> +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");
>> ...
>> +
>> + ret = devm_pwmchip_add(dev, chip);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "failed to add PWM chip\n");
>
> Please start error messages with a capital letter.
>
Fixed, thanks.
> Best regards
> Uwe
Thanks for your review,
Mathieu
--
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2025-07-16 7:46 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-11 9:29 [PATCH v11 00/10] Add support for MAX7360 Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 01/10] dt-bindings: mfd: gpio: Add MAX7360 Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 02/10] mfd: Add max7360 support Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 03/10] pinctrl: Add MAX7360 pinctrl driver Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 04/10] pwm: max7360: Add MAX7360 PWM support Mathieu Dubois-Briand
2025-07-11 14:50 ` Uwe Kleine-König
2025-07-16 7:46 ` Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 05/10] gpio: regmap: Allow to allocate regmap-irq device Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 06/10] gpio: regmap: Allow to provide init_valid_mask callback Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 07/10] gpio: max7360: Add MAX7360 gpio support Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 08/10] input: keyboard: Add support for MAX7360 keypad Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 09/10] input: misc: Add support for MAX7360 rotary Mathieu Dubois-Briand
2025-07-11 9:29 ` [PATCH v11 10/10] MAINTAINERS: Add entry on MAX7360 driver Mathieu Dubois-Briand
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).