Hello,
This patch series introduces support for the Analog Devices ADP5585, a
GPIO expander, PWM and keyboard controller. It models the chip as an MFD
device, and includes DT bindings (1/4), an MFD driver (2/4) and drivers
for the GPIO (3/4) and PWM (4/4) functions.
Support for the keypad controller is left out, as I have no means to
test it at the moment. The chip also includes a tiny reset controller,
as well as a 3-bit input programmable logic block, which I haven't tried
to support (and also have no means to test).
The driver is based on an initial version from the NXP BSP kernel, then
extensively and nearly completely rewritten, with added DT bindings. I
have nonetheless retained original authorship. Clark, Haibo, if you
would prefer not being credited and/or listed as authors, please let me
know.
Compared to v3, this version addresses small review comments. I believe
it is ready to go, pending another review of the PWM side (Uwe reviewed
a previous version, and to the best of my knowledge, I've addressed all
his concerns) and the MFD driver. Once the PWM driver gets reviewed, I
think the simplest course of action is to merge the whole series through
the MFD tree.
Clark Wang (1):
pwm: adp5585: Add Analog Devices ADP5585 support
Haibo Chen (2):
mfd: adp5585: Add Analog Devices ADP5585 core support
gpio: adp5585: Add Analog Devices ADP5585 support
Laurent Pinchart (1):
dt-bindings: mfd: Add Analog Devices ADP5585
.../devicetree/bindings/mfd/adi,adp5585.yaml | 90 +++++++
.../devicetree/bindings/trivial-devices.yaml | 4 -
MAINTAINERS | 11 +
drivers/gpio/Kconfig | 7 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-adp5585.c | 231 ++++++++++++++++++
drivers/mfd/Kconfig | 12 +
drivers/mfd/Makefile | 1 +
drivers/mfd/adp5585.c | 199 +++++++++++++++
drivers/pwm/Kconfig | 7 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-adp5585.c | 189 ++++++++++++++
include/linux/mfd/adp5585.h | 126 ++++++++++
13 files changed, 875 insertions(+), 4 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mfd/adi,adp5585.yaml
create mode 100644 drivers/gpio/gpio-adp5585.c
create mode 100644 drivers/mfd/adp5585.c
create mode 100644 drivers/pwm/pwm-adp5585.c
create mode 100644 include/linux/mfd/adp5585.h
base-commit: c3f38fa61af77b49866b006939479069cd451173
--
Regards,
Laurent Pinchart
The ADP5585 is a 10/11 input/output port expander with a built in keypad
matrix decoder, programmable logic, reset generator, and PWM generator.
These bindings model the device as an MFD, and support the GPIO expander
and PWM functions.
These bindings support the GPIO and PWM functions.
Drop the existing adi,adp5585 and adi,adp5585-02 compatible strings from
trivial-devices.yaml. They have been added there by mistake as the
driver that was submitted at the same time used different compatible
strings. We can take them over safely.
Signed-off-by: Laurent Pinchart <[email protected]>
Reviewed-by: Krzysztof Kozlowski <[email protected]>
---
I've limited the bindings to GPIO and PWM as I lack hardware to design,
implement and test the rest of the features the chip supports.
Changes since v3:
- Fix prefix and drop redundant text in subject line
- Rename node in example from mfd@ to io-expander@
Changes since v2:
- Drop gpio property from required
- Drop second example
Changes since v1:
- Squash "dt-bindings: trivial-devices: Drop adi,adp5585 and
adi,adp5585-02" into this patch
- Merge child nodes into parent node
---
.../devicetree/bindings/mfd/adi,adp5585.yaml | 90 +++++++++++++++++++
.../devicetree/bindings/trivial-devices.yaml | 4 -
MAINTAINERS | 7 ++
3 files changed, 97 insertions(+), 4 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mfd/adi,adp5585.yaml
diff --git a/Documentation/devicetree/bindings/mfd/adi,adp5585.yaml b/Documentation/devicetree/bindings/mfd/adi,adp5585.yaml
new file mode 100644
index 000000000000..46487b9144f6
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/adi,adp5585.yaml
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/adi,adp5585.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADP5585 Keypad Decoder and I/O Expansion
+
+maintainers:
+ - Laurent Pinchart <[email protected]>
+
+description:
+ The ADP5585 is a 10/11 input/output port expander with a built in keypad
+ matrix decoder, programmable logic, reset generator, and PWM generator.
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - adi,adp5585-00 # Default
+ - adi,adp5585-01 # 11 GPIOs
+ - adi,adp5585-02 # No pull-up resistors by default on special pins
+ - adi,adp5585-03 # Alternate I2C address
+ - adi,adp5585-04 # Pull-down resistors on all pins by default
+ - const: adi,adp5585
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ vdd-supply: true
+
+ gpio-controller: true
+
+ '#gpio-cells':
+ const: 2
+
+ gpio-reserved-ranges: true
+
+ "#pwm-cells":
+ const: 3
+
+required:
+ - compatible
+ - reg
+ - gpio-controller
+ - "#gpio-cells"
+ - "#pwm-cells"
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: adi,adp5585-01
+ then:
+ properties:
+ gpio-reserved-ranges: false
+ else:
+ properties:
+ gpio-reserved-ranges:
+ items:
+ - const: 5
+ - const: 1
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ io-expander@34 {
+ compatible = "adi,adp5585-00", "adi,adp5585";
+ reg = <0x34>;
+
+ vdd-supply = <®_3v3>;
+
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-reserved-ranges = <5 1>;
+
+ #pwm-cells = <3>;
+ };
+ };
+
+...
diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml
index 0a419453d183..91e62df4b296 100644
--- a/Documentation/devicetree/bindings/trivial-devices.yaml
+++ b/Documentation/devicetree/bindings/trivial-devices.yaml
@@ -39,10 +39,6 @@ properties:
# AD5110 - Nonvolatile Digital Potentiometer
- adi,ad5110
# Analog Devices ADP5585 Keypad Decoder and I/O Expansion
- - adi,adp5585
- # Analog Devices ADP5585 Keypad Decoder and I/O Expansion with support for Row5
- - adi,adp5585-02
- # Analog Devices ADP5589 Keypad Decoder and I/O Expansion
- adi,adp5589
# Analog Devices LT7182S Dual Channel 6A, 20V PolyPhase Step-Down Silent Switcher
- adi,lt7182s
diff --git a/MAINTAINERS b/MAINTAINERS
index 08a154941ea4..8ebdde3106d3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -526,6 +526,13 @@ F: drivers/leds/leds-adp5520.c
F: drivers/mfd/adp5520.c
F: drivers/video/backlight/adp5520_bl.c
+ADP5585 GPIO EXPANDER, PWM AND KEYPAD CONTROLLER DRIVER
+M: Laurent Pinchart <[email protected]>
+L: [email protected]
+L: [email protected]
+S: Maintained
+F: Documentation/devicetree/bindings/*/adi,adp5585*.yaml
+
ADP5588 QWERTY KEYPAD AND IO EXPANDER DRIVER (ADP5588/ADP5587)
M: Michael Hennerich <[email protected]>
S: Supported
--
Regards,
Laurent Pinchart
From: Haibo Chen <[email protected]>
The ADP5585 is a 10/11 input/output port expander with a built in keypad
matrix decoder, programmable logic, reset generator, and PWM generator.
This driver supports the chip by modelling it as an MFD device, with two
child devices for the GPIO and PWM functions.
The driver is derived from an initial implementation from NXP, available
in commit 8059835bee19 ("MLK-25917-1 mfd: adp5585: add ADI adp5585 core
support") in their BSP kernel tree. It has been extensively rewritten.
Signed-off-by: Haibo Chen <[email protected]>
Signed-off-by: Laurent Pinchart <[email protected]>
---
Changes since v2:
- Add missing and remove extraneous headers
- Use i2c_get_match_data()
- Drop unneeded parentheses
- Use GENMASK()
- Drop of_match_ptr()
- Allow compilation on !OF with COMPILE_TEST
- Replace ADP5585_MAN_ID() macro with ADP5585_MAN_ID_MASK
- Drop unneeded macro
Changes since v1:
- Add comment to explain BANK and BIT macros
- Drop compatible strings from cells
- White space fixes
- Fix comparison to NULL
Changes compared to the NXP original version:
- Add MAINTAINERS entry
- Fix compatible strings for child devices
- Fix header guards
- Use lowercase hex constants
- White space fixes
- Use module_i2c_driver()
- Switch to regmap
- Drop I2C device ID table
- Drop ADP5585_REG_MASK
- Support R5 GPIO pin
- Drop dev field from adp5585_dev structure
- Check device ID at probe time
- Fix register field names
- Update copyright
- Update license to GPL-2.0-only
- Implement suspend/resume
---
MAINTAINERS | 2 +
drivers/mfd/Kconfig | 12 +++
drivers/mfd/Makefile | 1 +
drivers/mfd/adp5585.c | 199 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/adp5585.h | 126 +++++++++++++++++++++++
5 files changed, 340 insertions(+)
create mode 100644 drivers/mfd/adp5585.c
create mode 100644 include/linux/mfd/adp5585.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 8ebdde3106d3..1250992f7788 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,8 @@ L: [email protected]
L: [email protected]
S: Maintained
F: Documentation/devicetree/bindings/*/adi,adp5585*.yaml
+F: drivers/mfd/adp5585.c
+F: include/linux/mfd/adp5585.h
ADP5588 QWERTY KEYPAD AND IO EXPANDER DRIVER (ADP5588/ADP5587)
M: Michael Hennerich <[email protected]>
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 266b4f54af60..05e8e1f0b602 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -20,6 +20,18 @@ config MFD_CS5535
This is the core driver for CS5535/CS5536 MFD functions. This is
necessary for using the board's GPIO and MFGPT functionality.
+config MFD_ADP5585
+ tristate "Analog Devices ADP5585 MFD driver"
+ select MFD_CORE
+ select REGMAP_I2C
+ depends on I2C
+ depends on OF || COMPILE_TEST
+ help
+ Say yes here to add support for the Analog Devices ADP5585 GPIO
+ expander, PWM and keypad controller. This includes the I2C driver and
+ the core APIs _only_, you have to select individual components like
+ the GPIO and PWM functions under the corresponding menus.
+
config MFD_ALTERA_A10SR
bool "Altera Arria10 DevKit System Resource chip"
depends on ARCH_INTEL_SOCFPGA && SPI_MASTER=y && OF
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c66f07edcd0e..37f36a019a68 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -188,6 +188,7 @@ obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o
obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o
obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
+obj-$(CONFIG_MFD_ADP5585) += adp5585.o
obj-$(CONFIG_MFD_KEMPLD) += kempld-core.o
obj-$(CONFIG_MFD_INTEL_QUARK_I2C_GPIO) += intel_quark_i2c_gpio.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
diff --git a/drivers/mfd/adp5585.c b/drivers/mfd/adp5585.c
new file mode 100644
index 000000000000..5268229eed18
--- /dev/null
+++ b/drivers/mfd/adp5585.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices ADP5585 I/O expander, PWM controller and keypad controller
+ *
+ * Copyright 2022 NXP
+ * Copyright 2024 Ideas on Board Oy
+ */
+
+#include <linux/array_size.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/mfd/adp5585.h>
+#include <linux/mfd/core.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+static const struct mfd_cell adp5585_devs[] = {
+ { .name = "adp5585-gpio", },
+ { .name = "adp5585-pwm", },
+};
+
+static const struct regmap_range adp5585_volatile_ranges[] = {
+ regmap_reg_range(ADP5585_ID, ADP5585_GPI_STATUS_B),
+};
+
+static const struct regmap_access_table adp5585_volatile_regs = {
+ .yes_ranges = adp5585_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(adp5585_volatile_ranges),
+};
+
+static const u8 adp5585_regmap_defaults_00[ADP5585_MAX_REG + 1] = {
+ /* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x18 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x30 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x38 */ 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static const u8 adp5585_regmap_defaults_02[ADP5585_MAX_REG + 1] = {
+ /* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3,
+ /* 0x18 */ 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x30 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x38 */ 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static const u8 adp5585_regmap_defaults_04[ADP5585_MAX_REG + 1] = {
+ /* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55,
+ /* 0x18 */ 0x05, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x30 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x38 */ 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+enum adp5585_regmap_type {
+ ADP5585_REGMAP_00,
+ ADP5585_REGMAP_02,
+ ADP5585_REGMAP_04,
+};
+
+static const struct regmap_config adp5585_regmap_configs[] = {
+ [ADP5585_REGMAP_00] = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = ADP5585_MAX_REG,
+ .volatile_table = &adp5585_volatile_regs,
+ .cache_type = REGCACHE_MAPLE,
+ .reg_defaults_raw = adp5585_regmap_defaults_00,
+ .num_reg_defaults_raw = sizeof(adp5585_regmap_defaults_00),
+ },
+ [ADP5585_REGMAP_02] = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = ADP5585_MAX_REG,
+ .volatile_table = &adp5585_volatile_regs,
+ .cache_type = REGCACHE_MAPLE,
+ .reg_defaults_raw = adp5585_regmap_defaults_02,
+ .num_reg_defaults_raw = sizeof(adp5585_regmap_defaults_02),
+ },
+ [ADP5585_REGMAP_04] = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = ADP5585_MAX_REG,
+ .volatile_table = &adp5585_volatile_regs,
+ .cache_type = REGCACHE_MAPLE,
+ .reg_defaults_raw = adp5585_regmap_defaults_04,
+ .num_reg_defaults_raw = sizeof(adp5585_regmap_defaults_04),
+ },
+};
+
+static int adp5585_i2c_probe(struct i2c_client *i2c)
+{
+ const struct regmap_config *regmap_config;
+ struct adp5585_dev *adp5585;
+ unsigned int id;
+ int ret;
+
+ adp5585 = devm_kzalloc(&i2c->dev, sizeof(struct adp5585_dev),
+ GFP_KERNEL);
+ if (!adp5585)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, adp5585);
+
+ regmap_config = i2c_get_match_data(i2c);
+ adp5585->regmap = devm_regmap_init_i2c(i2c, regmap_config);
+ if (IS_ERR(adp5585->regmap))
+ return dev_err_probe(&i2c->dev, PTR_ERR(adp5585->regmap),
+ "Failed to initialize register map\n");
+
+ /* Verify the device ID. */
+ ret = regmap_read(adp5585->regmap, ADP5585_ID, &id);
+ if (ret)
+ return dev_err_probe(&i2c->dev, ret,
+ "Failed to read device ID\n");
+
+ if ((id & ADP5585_MAN_ID_MASK) != ADP5585_MAN_ID_VALUE)
+ return dev_err_probe(&i2c->dev, -ENODEV,
+ "Invalid device ID 0x%02x\n", id);
+
+ dev_dbg(&i2c->dev, "device ID 0x%02x\n", id);
+
+ /* Add MFD devices. */
+ ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO,
+ adp5585_devs, ARRAY_SIZE(adp5585_devs),
+ NULL, 0, NULL);
+ if (ret)
+ return dev_err_probe(&i2c->dev, ret,
+ "Failed to add MFD devices\n");
+
+ return 0;
+}
+
+static int adp5585_suspend(struct device *dev)
+{
+ struct adp5585_dev *adp5585 = dev_get_drvdata(dev);
+
+ regcache_cache_only(adp5585->regmap, true);
+
+ return 0;
+}
+
+static int adp5585_resume(struct device *dev)
+{
+ struct adp5585_dev *adp5585 = dev_get_drvdata(dev);
+
+ regcache_cache_only(adp5585->regmap, false);
+ regcache_mark_dirty(adp5585->regmap);
+
+ return regcache_sync(adp5585->regmap);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(adp5585_pm, adp5585_suspend, adp5585_resume);
+
+static const struct of_device_id adp5585_of_match[] = {
+ {
+ .compatible = "adi,adp5585-00",
+ .data = &adp5585_regmap_configs[ADP5585_REGMAP_00],
+ }, {
+ .compatible = "adi,adp5585-01",
+ .data = &adp5585_regmap_configs[ADP5585_REGMAP_00],
+ }, {
+ .compatible = "adi,adp5585-02",
+ .data = &adp5585_regmap_configs[ADP5585_REGMAP_02],
+ }, {
+ .compatible = "adi,adp5585-03",
+ .data = &adp5585_regmap_configs[ADP5585_REGMAP_00],
+ }, {
+ .compatible = "adi,adp5585-04",
+ .data = &adp5585_regmap_configs[ADP5585_REGMAP_04],
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, adp5585_of_match);
+
+static struct i2c_driver adp5585_i2c_driver = {
+ .driver = {
+ .name = "adp5585",
+ .of_match_table = adp5585_of_match,
+ .pm = pm_sleep_ptr(&adp5585_pm),
+ },
+ .probe = adp5585_i2c_probe,
+};
+module_i2c_driver(adp5585_i2c_driver);
+
+MODULE_DESCRIPTION("ADP5585 core driver");
+MODULE_AUTHOR("Haibo Chen <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/adp5585.h b/include/linux/mfd/adp5585.h
new file mode 100644
index 000000000000..92d90218762e
--- /dev/null
+++ b/include/linux/mfd/adp5585.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Analog Devices ADP5585 I/O expander, PWM controller and keypad controller
+ *
+ * Copyright 2022 NXP
+ * Copyright 2024 Ideas on Board Oy
+ */
+
+#ifndef __LINUX_MFD_ADP5585_H_
+#define __LINUX_MFD_ADP5585_H_
+
+#include <linux/bits.h>
+
+#define ADP5585_ID 0x00
+#define ADP5585_MAN_ID_VALUE 0x20
+#define ADP5585_MAN_ID_MASK GENMASK(7, 4)
+#define ADP5585_INT_STATUS 0x01
+#define ADP5585_STATUS 0x02
+#define ADP5585_FIFO_1 0x03
+#define ADP5585_FIFO_2 0x04
+#define ADP5585_FIFO_3 0x05
+#define ADP5585_FIFO_4 0x06
+#define ADP5585_FIFO_5 0x07
+#define ADP5585_FIFO_6 0x08
+#define ADP5585_FIFO_7 0x09
+#define ADP5585_FIFO_8 0x0a
+#define ADP5585_FIFO_9 0x0b
+#define ADP5585_FIFO_10 0x0c
+#define ADP5585_FIFO_11 0x0d
+#define ADP5585_FIFO_12 0x0e
+#define ADP5585_FIFO_13 0x0f
+#define ADP5585_FIFO_14 0x10
+#define ADP5585_FIFO_15 0x11
+#define ADP5585_FIFO_16 0x12
+#define ADP5585_GPI_INT_STAT_A 0x13
+#define ADP5585_GPI_INT_STAT_B 0x14
+#define ADP5585_GPI_STATUS_A 0x15
+#define ADP5585_GPI_STATUS_B 0x16
+#define ADP5585_RPULL_CONFIG_A 0x17
+#define ADP5585_RPULL_CONFIG_B 0x18
+#define ADP5585_RPULL_CONFIG_C 0x19
+#define ADP5585_RPULL_CONFIG_D 0x1a
+#define ADP5585_Rx_PULL_CFG_PU_300K 0
+#define ADP5585_Rx_PULL_CFG_PD_300K 1
+#define ADP5585_Rx_PULL_CFG_PU_100K 2
+#define ADP5585_Rx_PULL_CFG_DISABLE 3
+#define ADP5585_Rx_PULL_CFG_MASK 3
+#define ADP5585_GPI_INT_LEVEL_A 0x1b
+#define ADP5585_GPI_INT_LEVEL_B 0x1c
+#define ADP5585_GPI_EVENT_EN_A 0x1d
+#define ADP5585_GPI_EVENT_EN_B 0x1e
+#define ADP5585_GPI_INTERRUPT_EN_A 0x1f
+#define ADP5585_GPI_INTERRUPT_EN_B 0x20
+#define ADP5585_DEBOUNCE_DIS_A 0x21
+#define ADP5585_DEBOUNCE_DIS_B 0x22
+#define ADP5585_GPO_DATA_OUT_A 0x23
+#define ADP5585_GPO_DATA_OUT_B 0x24
+#define ADP5585_GPO_OUT_MODE_A 0x25
+#define ADP5585_GPO_OUT_MODE_B 0x26
+#define ADP5585_GPIO_DIRECTION_A 0x27
+#define ADP5585_GPIO_DIRECTION_B 0x28
+#define ADP5585_RESET1_EVENT_A 0x29
+#define ADP5585_RESET1_EVENT_B 0x2a
+#define ADP5585_RESET1_EVENT_C 0x2b
+#define ADP5585_RESET2_EVENT_A 0x2c
+#define ADP5585_RESET2_EVENT_B 0x2d
+#define ADP5585_RESET_CFG 0x2e
+#define ADP5585_PWM_OFFT_LOW 0x2f
+#define ADP5585_PWM_OFFT_HIGH 0x30
+#define ADP5585_PWM_ONT_LOW 0x31
+#define ADP5585_PWM_ONT_HIGH 0x32
+#define ADP5585_PWM_CFG 0x33
+#define ADP5585_PWM_IN_AND BIT(2)
+#define ADP5585_PWM_MODE BIT(1)
+#define ADP5585_PWM_EN BIT(0)
+#define ADP5585_LOGIC_CFG 0x34
+#define ADP5585_LOGIC_FF_CFG 0x35
+#define ADP5585_LOGIC_INT_EVENT_EN 0x36
+#define ADP5585_POLL_PTIME_CFG 0x37
+#define ADP5585_PIN_CONFIG_A 0x38
+#define ADP5585_PIN_CONFIG_B 0x39
+#define ADP5585_PIN_CONFIG_C 0x3a
+#define ADP5585_PULL_SELECT BIT(7)
+#define ADP5585_C4_EXTEND_CFG_GPIO11 (0U << 6)
+#define ADP5585_C4_EXTEND_CFG_RESET2 (1U << 6)
+#define ADP5585_C4_EXTEND_CFG_MASK GENMASK(6, 6)
+#define ADP5585_R4_EXTEND_CFG_GPIO5 (0U << 5)
+#define ADP5585_R4_EXTEND_CFG_RESET1 (1U << 5)
+#define ADP5585_R4_EXTEND_CFG_MASK GENMASK(5, 5)
+#define ADP5585_R3_EXTEND_CFG_GPIO4 (0U << 2)
+#define ADP5585_R3_EXTEND_CFG_LC (1U << 2)
+#define ADP5585_R3_EXTEND_CFG_PWM_OUT (2U << 2)
+#define ADP5585_R3_EXTEND_CFG_MASK GENMASK(3, 2)
+#define ADP5585_R0_EXTEND_CFG_GPIO1 (0U << 0)
+#define ADP5585_R0_EXTEND_CFG_LY (1U << 0)
+#define ADP5585_R0_EXTEND_CFG_MASK GENMASK(0, 0)
+#define ADP5585_GENERAL_CFG 0x3b
+#define ADP5585_OSC_EN BIT(7)
+#define ADP5585_OSC_FREQ_50KHZ (0U << 5)
+#define ADP5585_OSC_FREQ_100KHZ (1U << 5)
+#define ADP5585_OSC_FREQ_200KHZ (2U << 5)
+#define ADP5585_OSC_FREQ_500KHZ (3U << 5)
+#define ADP5585_OSC_FREQ_MASK (3U << 5)
+#define ADP5585_INT_CFG BIT(1)
+#define ADP5585_RST_CFG BIT(0)
+#define ADP5585_INT_EN 0x3c
+
+#define ADP5585_MAX_REG ADP5585_INT_EN
+
+/*
+ * Bank 0 covers pins "GPIO 1/R0" to "GPIO 6/R5", numbered 0 to 5 by the
+ * driver, and bank 1 covers pins "GPIO 7/C0" to "GPIO 11/C4", numbered 6 to
+ * 10. Some variants of the ADP5585 don't support "GPIO 6/R5". As the driver
+ * uses identical GPIO numbering for all variants to avoid confusion, GPIO 5 is
+ * marked as reserved in the device tree for variants that don't support it.
+ */
+#define ADP5585_BANK(n) ((n) >= 6 ? 1 : 0)
+#define ADP5585_BIT(n) ((n) >= 6 ? BIT((n) - 6) : BIT(n))
+
+struct regmap;
+
+struct adp5585_dev {
+ struct regmap *regmap;
+};
+
+#endif
--
Regards,
Laurent Pinchart
From: Haibo Chen <[email protected]>
The ADP5585 is a 10/11 input/output port expander with a built in keypad
matrix decoder, programmable logic, reset generator, and PWM generator.
This driver supports the GPIO function using the platform device
registered by the core MFD driver.
The driver is derived from an initial implementation from NXP, available
in commit 451f61b46b76 ("MLK-25917-2 gpio: adp5585-gpio: add
adp5585-gpio support") in their BSP kernel tree. It has been extensively
rewritten.
Signed-off-by: Haibo Chen <[email protected]>
Signed-off-by: Laurent Pinchart <[email protected]>
Reviewed-by: Linus Walleij <[email protected]>
Acked-by: Bartosz Golaszewski <[email protected]>
---
Changes compared to v2:
- Add missing headers
- Drop platform_set_drvdata()
- Fix bit shift in bias configuration
Changes compared to v1:
- Drop OF match table
- Fix .get() for GPOs
- Add platform ID table
- Set struct device of_node manually
- Merge child DT node into parent node
- Implement .get_direction()
- Drop mutex
Changes compared to the NXP original version
- Add MAINTAINERS entry
- Add prefix to compatible string
- Switch to regmap
- White space fixes
- Use sizeof(*variable)
- Initialize variables at declaration time
- Use mutex scope guards
- Cleanup header includes
- Support R5 GPIO pin
- Reorder functions
- Add bias support
- Return real pin value from .get()
- Add debounce support
- Add drive mode support
- Destroy mutex on remove
- Update copyright
- Update license to GPL-2.0-only
---
MAINTAINERS | 1 +
drivers/gpio/Kconfig | 7 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-adp5585.c | 231 ++++++++++++++++++++++++++++++++++++
4 files changed, 240 insertions(+)
create mode 100644 drivers/gpio/gpio-adp5585.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 1250992f7788..80f3544d07aa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -532,6 +532,7 @@ L: [email protected]
L: [email protected]
S: Maintained
F: Documentation/devicetree/bindings/*/adi,adp5585*.yaml
+F: drivers/gpio/gpio-adp5585.c
F: drivers/mfd/adp5585.c
F: include/linux/mfd/adp5585.h
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 3dbddec07028..67286886db5e 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1233,6 +1233,13 @@ config GPIO_ADP5520
This option enables support for on-chip GPIO found
on Analog Devices ADP5520 PMICs.
+config GPIO_ADP5585
+ tristate "GPIO Support for ADP5585"
+ depends on MFD_ADP5585
+ help
+ This option enables support for the GPIO function found in the Analog
+ Devices ADP5585.
+
config GPIO_ALTERA_A10SR
tristate "Altera Arria10 System Resource GPIO"
depends on MFD_ALTERA_A10SR
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index e2a53013780e..04bfa2bc7e11 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o
obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o
obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o
obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o
+obj-$(CONFIG_GPIO_ADP5585) += gpio-adp5585.o
obj-$(CONFIG_GPIO_AGGREGATOR) += gpio-aggregator.o
obj-$(CONFIG_GPIO_ALTERA_A10SR) += gpio-altera-a10sr.o
obj-$(CONFIG_GPIO_ALTERA) += gpio-altera.o
diff --git a/drivers/gpio/gpio-adp5585.c b/drivers/gpio/gpio-adp5585.c
new file mode 100644
index 000000000000..49f8d91623ba
--- /dev/null
+++ b/drivers/gpio/gpio-adp5585.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices ADP5585 GPIO driver
+ *
+ * Copyright 2022 NXP
+ * Copyright 2024 Ideas on Board Oy
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/adp5585.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define ADP5585_GPIO_MAX 11
+
+struct adp5585_gpio_dev {
+ struct gpio_chip gpio_chip;
+ struct regmap *regmap;
+};
+
+static int adp5585_gpio_get_direction(struct gpio_chip *chip, unsigned int off)
+{
+ struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip);
+ unsigned int bank = ADP5585_BANK(off);
+ unsigned int bit = ADP5585_BIT(off);
+ unsigned int val;
+
+ regmap_read(adp5585_gpio->regmap, ADP5585_GPIO_DIRECTION_A + bank, &val);
+
+ return val & bit ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
+static int adp5585_gpio_direction_input(struct gpio_chip *chip, unsigned int off)
+{
+ struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip);
+ unsigned int bank = ADP5585_BANK(off);
+ unsigned int bit = ADP5585_BIT(off);
+
+ return regmap_update_bits(adp5585_gpio->regmap,
+ ADP5585_GPIO_DIRECTION_A + bank,
+ bit, 0);
+}
+
+static int adp5585_gpio_direction_output(struct gpio_chip *chip, unsigned int off, int val)
+{
+ struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip);
+ unsigned int bank = ADP5585_BANK(off);
+ unsigned int bit = ADP5585_BIT(off);
+ int ret;
+
+ ret = regmap_update_bits(adp5585_gpio->regmap,
+ ADP5585_GPO_DATA_OUT_A + bank, bit,
+ val ? bit : 0);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(adp5585_gpio->regmap,
+ ADP5585_GPIO_DIRECTION_A + bank,
+ bit, bit);
+}
+
+static int adp5585_gpio_get_value(struct gpio_chip *chip, unsigned int off)
+{
+ struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip);
+ unsigned int bank = ADP5585_BANK(off);
+ unsigned int bit = ADP5585_BIT(off);
+ unsigned int reg;
+ unsigned int val;
+
+ /*
+ * The input status register doesn't reflect the pin state when the
+ * GPIO is configured as an output. Check the direction, and read the
+ * input status from GPI_STATUS or output value from GPO_DATA_OUT
+ * accordingly.
+ *
+ * We don't need any locking, as concurrent access to the same GPIO
+ * isn't allowed by the GPIO API, so there's no risk of the
+ * .direction_input(), .direction_output() or .set() operations racing
+ * with this.
+ */
+ regmap_read(adp5585_gpio->regmap, ADP5585_GPIO_DIRECTION_A + bank, &val);
+ reg = val & bit ? ADP5585_GPO_DATA_OUT_A : ADP5585_GPI_STATUS_A;
+ regmap_read(adp5585_gpio->regmap, reg + bank, &val);
+
+ return !!(val & bit);
+}
+
+static void adp5585_gpio_set_value(struct gpio_chip *chip, unsigned int off, int val)
+{
+ struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip);
+ unsigned int bank = ADP5585_BANK(off);
+ unsigned int bit = ADP5585_BIT(off);
+
+ regmap_update_bits(adp5585_gpio->regmap, ADP5585_GPO_DATA_OUT_A + bank,
+ bit, val ? bit : 0);
+}
+
+static int adp5585_gpio_set_bias(struct adp5585_gpio_dev *adp5585_gpio,
+ unsigned int off, unsigned int bias)
+{
+ unsigned int bit, reg, mask, val;
+
+ /*
+ * The bias configuration fields are 2 bits wide and laid down in
+ * consecutive registers ADP5585_RPULL_CONFIG_*, with a hole of 4 bits
+ * after R5.
+ */
+ bit = off * 2 + (off > 5 ? 4 : 0);
+ reg = ADP5585_RPULL_CONFIG_A + bit / 8;
+ mask = ADP5585_Rx_PULL_CFG_MASK << (bit % 8);
+ val = bias << (bit % 8);
+
+ return regmap_update_bits(adp5585_gpio->regmap, reg, mask, val);
+}
+
+static int adp5585_gpio_set_drive(struct adp5585_gpio_dev *adp5585_gpio,
+ unsigned int off, enum pin_config_param drive)
+{
+ unsigned int bank = ADP5585_BANK(off);
+ unsigned int bit = ADP5585_BIT(off);
+
+ return regmap_update_bits(adp5585_gpio->regmap,
+ ADP5585_GPO_OUT_MODE_A + bank, bit,
+ drive == PIN_CONFIG_DRIVE_OPEN_DRAIN ? 1 : 0);
+}
+
+static int adp5585_gpio_set_debounce(struct adp5585_gpio_dev *adp5585_gpio,
+ unsigned int off, unsigned int debounce)
+{
+ unsigned int bank = ADP5585_BANK(off);
+ unsigned int bit = ADP5585_BIT(off);
+
+ return regmap_update_bits(adp5585_gpio->regmap,
+ ADP5585_DEBOUNCE_DIS_A + bank, bit,
+ debounce ? 0 : 1);
+}
+
+static int adp5585_gpio_set_config(struct gpio_chip *chip, unsigned int off,
+ unsigned long config)
+{
+ struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip);
+ enum pin_config_param param = pinconf_to_config_param(config);
+ u32 arg = pinconf_to_config_argument(config);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ return adp5585_gpio_set_bias(adp5585_gpio, off,
+ ADP5585_Rx_PULL_CFG_DISABLE);
+
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ return adp5585_gpio_set_bias(adp5585_gpio, off, arg ?
+ ADP5585_Rx_PULL_CFG_PD_300K :
+ ADP5585_Rx_PULL_CFG_DISABLE);
+
+ case PIN_CONFIG_BIAS_PULL_UP:
+ return adp5585_gpio_set_bias(adp5585_gpio, off, arg ?
+ ADP5585_Rx_PULL_CFG_PU_300K :
+ ADP5585_Rx_PULL_CFG_DISABLE);
+
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return adp5585_gpio_set_drive(adp5585_gpio, off, param);
+
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ return adp5585_gpio_set_debounce(adp5585_gpio, off, arg);
+
+ default:
+ return -ENOTSUPP;
+ };
+}
+
+static int adp5585_gpio_probe(struct platform_device *pdev)
+{
+ struct adp5585_dev *adp5585 = dev_get_drvdata(pdev->dev.parent);
+ struct adp5585_gpio_dev *adp5585_gpio;
+ struct device *dev = &pdev->dev;
+ struct gpio_chip *gc;
+ int ret;
+
+ adp5585_gpio = devm_kzalloc(dev, sizeof(*adp5585_gpio), GFP_KERNEL);
+ if (!adp5585_gpio)
+ return -ENOMEM;
+
+ adp5585_gpio->regmap = adp5585->regmap;
+
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ gc = &adp5585_gpio->gpio_chip;
+ gc->parent = dev;
+ gc->get_direction = adp5585_gpio_get_direction;
+ gc->direction_input = adp5585_gpio_direction_input;
+ gc->direction_output = adp5585_gpio_direction_output;
+ gc->get = adp5585_gpio_get_value;
+ gc->set = adp5585_gpio_set_value;
+ gc->set_config = adp5585_gpio_set_config;
+ gc->can_sleep = true;
+
+ gc->base = -1;
+ gc->ngpio = ADP5585_GPIO_MAX;
+ gc->label = pdev->name;
+ gc->owner = THIS_MODULE;
+
+ ret = devm_gpiochip_add_data(dev, &adp5585_gpio->gpio_chip,
+ adp5585_gpio);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add GPIO chip\n");
+
+ return 0;
+}
+
+static const struct platform_device_id adp5585_gpio_id_table[] = {
+ { "adp5585-gpio" },
+ { /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, adp5585_gpio_id_table);
+
+static struct platform_driver adp5585_gpio_driver = {
+ .driver = {
+ .name = "adp5585-gpio",
+ },
+ .probe = adp5585_gpio_probe,
+ .id_table = adp5585_gpio_id_table,
+};
+module_platform_driver(adp5585_gpio_driver);
+
+MODULE_AUTHOR("Haibo Chen <[email protected]>");
+MODULE_DESCRIPTION("GPIO ADP5585 Driver");
+MODULE_LICENSE("GPL");
--
Regards,
Laurent Pinchart
From: Clark Wang <[email protected]>
The ADP5585 is a 10/11 input/output port expander with a built in keypad
matrix decoder, programmable logic, reset generator, and PWM generator.
This driver supports the PWM function using the platform device
registered by the core MFD driver.
The driver is derived from an initial implementation from NXP, available
in commit 113113742208 ("MLK-25922-1 pwm: adp5585: add adp5585 PWM
support") in their BSP kernel tree. It has been extensively rewritten.
Signed-off-by: Clark Wang <[email protected]>
Signed-off-by: Laurent Pinchart <[email protected]>
---
Changes since v2:
- Add missing headers
- Sort headers
Changes since v1:
- Drop mutex
- Restore R3 pinconfig to known value
- Simplify error check in pwm_adp5585_request()
- Don't fake PWM_POLARITY_INVERSED
- Fix rounding of period and duty cycle
- Drop OF match table
- Drop empty .remove() handler
- Allocate pwm_chip dynamically
- Document limitations
- Add platform ID table
- Set struct device of_node manually
- Merge child DT node into parent node
Changes compared to the NXP original version
- Add MAINTAINERS entry
- Drop pwm_ops.owner
- Fix compilation
- Add prefix to compatible string
- Switch to regmap
- Use devm_pwmchip_add()
- Cleanup header includes
- White space fixes
- Drop ADP5585_REG_MASK
- Fix register field names
- Use mutex scope guards
- Clear OSC_EN when freeing PWM
- Reorder functions
- Clear PWM_IN_AND and PWM_MODE bits
- Support inverted polarity
- Clean up on/off computations
- Fix duty cycle computation in .get_state()
- Destroy mutex on remove
- Update copyright
- Update license to GPL-2.0-only
---
MAINTAINERS | 1 +
drivers/pwm/Kconfig | 7 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-adp5585.c | 189 ++++++++++++++++++++++++++++++++++++++
4 files changed, 198 insertions(+)
create mode 100644 drivers/pwm/pwm-adp5585.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 80f3544d07aa..b15bf6e2485c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -534,6 +534,7 @@ S: Maintained
F: Documentation/devicetree/bindings/*/adi,adp5585*.yaml
F: drivers/gpio/gpio-adp5585.c
F: drivers/mfd/adp5585.c
+F: drivers/pwm/pwm-adp5585.c
F: include/linux/mfd/adp5585.h
ADP5588 QWERTY KEYPAD AND IO EXPANDER DRIVER (ADP5588/ADP5587)
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 1dd7921194f5..b778ecee3e9b 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -47,6 +47,13 @@ config PWM_AB8500
To compile this driver as a module, choose M here: the module
will be called pwm-ab8500.
+config PWM_ADP5585
+ tristate "ADP5585 PWM support"
+ depends on MFD_ADP5585
+ help
+ This option enables support for the PWM function found in the Analog
+ Devices ADP5585.
+
config PWM_APPLE
tristate "Apple SoC PWM support"
depends on ARCH_APPLE || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 90913519f11a..f24d518d20f2 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PWM) += core.o
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
+obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o
obj-$(CONFIG_PWM_APPLE) += pwm-apple.o
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o
diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c
new file mode 100644
index 000000000000..7f226c287efe
--- /dev/null
+++ b/drivers/pwm/pwm-adp5585.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices ADP5585 PWM driver
+ *
+ * Copyright 2022 NXP
+ * Copyright 2024 Ideas on Board Oy
+ *
+ * Limitations:
+ * - The .apply() operation executes atomically, but may not wait for the
+ * period to complete (this is not documented and would need to be tested).
+ * - Disabling the PWM drives the output pin to a low level immediately.
+ * - The hardware can only generate normal polarity output.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/mfd/adp5585.h>
+#include <linux/minmax.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 ADP5585_PWM_CHAN_NUM 1
+
+#define ADP5585_PWM_OSC_FREQ_HZ 1000000U
+#define ADP5585_PWM_MIN_PERIOD_NS (2ULL * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ)
+#define ADP5585_PWM_MAX_PERIOD_NS (2ULL * 0xffff * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ)
+
+static int pwm_adp5585_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+ int ret;
+
+ ret = regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C,
+ ADP5585_R3_EXTEND_CFG_MASK,
+ ADP5585_R3_EXTEND_CFG_PWM_OUT);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(regmap, ADP5585_GENERAL_CFG,
+ ADP5585_OSC_EN, ADP5585_OSC_EN);
+}
+
+static void pwm_adp5585_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+
+ regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C,
+ ADP5585_R3_EXTEND_CFG_MASK,
+ ADP5585_R3_EXTEND_CFG_GPIO4);
+ regmap_update_bits(regmap, ADP5585_GENERAL_CFG,
+ ADP5585_OSC_EN, 0);
+}
+
+static int pwm_adp5585_apply(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+ u64 period, duty_cycle;
+ u32 on, off;
+ int ret;
+
+ if (!state->enabled)
+ return regmap_update_bits(regmap, ADP5585_PWM_CFG,
+ ADP5585_PWM_EN, 0);
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ if (state->period < ADP5585_PWM_MIN_PERIOD_NS)
+ return -EINVAL;
+
+ period = min(state->period, ADP5585_PWM_MAX_PERIOD_NS);
+ duty_cycle = min(state->duty_cycle, period);
+
+ /*
+ * Compute the on and off time. As the internal oscillator frequency is
+ * 1MHz, the calculation can be simplified without loss of precision.
+ */
+ on = div_u64(duty_cycle, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
+ off = div_u64(period, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) - on;
+
+ ret = regmap_write(regmap, ADP5585_PWM_OFFT_LOW,
+ off & 0xff);
+ if (ret)
+ return ret;
+ ret = regmap_write(regmap, ADP5585_PWM_OFFT_HIGH,
+ (off >> 8) & 0xff);
+ if (ret)
+ return ret;
+ ret = regmap_write(regmap, ADP5585_PWM_ONT_LOW,
+ on & 0xff);
+ if (ret)
+ return ret;
+ ret = regmap_write(regmap, ADP5585_PWM_ONT_HIGH,
+ (on >> 8) & 0xff);
+ if (ret)
+ return ret;
+
+ /* Enable PWM in continuous mode and no external AND'ing. */
+ ret = regmap_update_bits(regmap, ADP5585_PWM_CFG,
+ ADP5585_PWM_IN_AND | ADP5585_PWM_MODE |
+ ADP5585_PWM_EN, ADP5585_PWM_EN);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int pwm_adp5585_get_state(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+ unsigned int on, off;
+ unsigned int val;
+
+ regmap_read(regmap, ADP5585_PWM_OFFT_LOW, &off);
+ regmap_read(regmap, ADP5585_PWM_OFFT_HIGH, &val);
+ off |= val << 8;
+
+ regmap_read(regmap, ADP5585_PWM_ONT_LOW, &on);
+ regmap_read(regmap, ADP5585_PWM_ONT_HIGH, &val);
+ on |= val << 8;
+
+ state->duty_cycle = on * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
+ state->period = (on + off) * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
+
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ regmap_read(regmap, ADP5585_PWM_CFG, &val);
+ state->enabled = !!(val & ADP5585_PWM_EN);
+
+ return 0;
+}
+
+static const struct pwm_ops adp5585_pwm_ops = {
+ .request = pwm_adp5585_request,
+ .free = pwm_adp5585_free,
+ .apply = pwm_adp5585_apply,
+ .get_state = pwm_adp5585_get_state,
+};
+
+static int adp5585_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent);
+ struct pwm_chip *chip;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM, 0);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ pwmchip_set_drvdata(chip, adp5585->regmap);
+ chip->ops = &adp5585_pwm_ops;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add PWM chip\n");
+
+ return 0;
+}
+
+static const struct platform_device_id adp5585_pwm_id_table[] = {
+ { "adp5585-pwm" },
+ { /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, adp5585_pwm_id_table);
+
+static struct platform_driver adp5585_pwm_driver = {
+ .driver = {
+ .name = "adp5585-pwm",
+ },
+ .probe = adp5585_pwm_probe,
+ .id_table = adp5585_pwm_id_table,
+};
+module_platform_driver(adp5585_pwm_driver);
+
+MODULE_AUTHOR("Xiaoning Wang <[email protected]>");
+MODULE_DESCRIPTION("ADP5585 PWM Driver");
+MODULE_LICENSE("GPL");
--
Regards,
Laurent Pinchart
Hi Laurent,
> diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml
> index 0a419453d183..91e62df4b296 100644
> --- a/Documentation/devicetree/bindings/trivial-devices.yaml
> +++ b/Documentation/devicetree/bindings/trivial-devices.yaml
> @@ -39,10 +39,6 @@ properties:
> # AD5110 - Nonvolatile Digital Potentiometer
> - adi,ad5110
> # Analog Devices ADP5585 Keypad Decoder and I/O Expansion
> - - adi,adp5585
> - # Analog Devices ADP5585 Keypad Decoder and I/O Expansion with support for Row5
> - - adi,adp5585-02
> - # Analog Devices ADP5589 Keypad Decoder and I/O Expansion
> - adi,adp5589
> # Analog Devices LT7182S Dual Channel 6A, 20V PolyPhase Step-Down Silent Switcher
> - adi,lt7182s
seems like you removed the wrong comment here? With this, ADP5589 would have
a comment describing ADP5585.
Regards,
Stanislav
Sat, Jun 08, 2024 at 05:16:33PM +0300, Laurent Pinchart kirjoitti:
> From: Clark Wang <[email protected]>
>
> The ADP5585 is a 10/11 input/output port expander with a built in keypad
> matrix decoder, programmable logic, reset generator, and PWM generator.
> This driver supports the PWM function using the platform device
> registered by the core MFD driver.
>
> The driver is derived from an initial implementation from NXP, available
> in commit 113113742208 ("MLK-25922-1 pwm: adp5585: add adp5585 PWM
> support") in their BSP kernel tree. It has been extensively rewritten.
...
> +#define ADP5585_PWM_OSC_FREQ_HZ 1000000U
(1 * HZ_PER_MHZ) ?
Variant to use MEGA. Or even #define MHz in units.h if you wish.
Putting a few 0:s in a row is error prone.
...
> + ret = regmap_write(regmap, ADP5585_PWM_OFFT_LOW,
> + off & 0xff);
> + if (ret)
> + return ret;
> + ret = regmap_write(regmap, ADP5585_PWM_OFFT_HIGH,
> + (off >> 8) & 0xff);
> + if (ret)
> + return ret;
This is regular I?C regmap, why do you avoid using regmap bulk APIs?
> + ret = regmap_write(regmap, ADP5585_PWM_ONT_LOW,
> + on & 0xff);
> + if (ret)
> + return ret;
> + ret = regmap_write(regmap, ADP5585_PWM_ONT_HIGH,
> + (on >> 8) & 0xff);
> + if (ret)
> + return ret;
Ditto.
...
> +static int pwm_adp5585_get_state(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + struct pwm_state *state)
> +{
> + struct regmap *regmap = pwmchip_get_drvdata(chip);
> + unsigned int on, off;
> + unsigned int val;
> +
> + regmap_read(regmap, ADP5585_PWM_OFFT_LOW, &off);
> + regmap_read(regmap, ADP5585_PWM_OFFT_HIGH, &val);
> + off |= val << 8;
Ditto.
> + regmap_read(regmap, ADP5585_PWM_ONT_LOW, &on);
> + regmap_read(regmap, ADP5585_PWM_ONT_HIGH, &val);
> + on |= val << 8;
Ditto.
> + state->duty_cycle = on * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
> + state->period = (on + off) * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
> +
> + state->polarity = PWM_POLARITY_NORMAL;
> +
> + regmap_read(regmap, ADP5585_PWM_CFG, &val);
> + state->enabled = !!(val & ADP5585_PWM_EN);
> +
> + return 0;
Besides that, how do you guarantee that no IO may happen in between of those
calls? Probably you want a driver explicit lock? In that case, would you still
want to have a regmap internal lock?
> +}
...
> +static int adp5585_pwm_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent);
> + struct pwm_chip *chip;
> + int ret;
> +
> + chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM, 0);
> + if (IS_ERR(chip))
> + return PTR_ERR(chip);
> + device_set_of_node_from_dev(dev, dev->parent);
Still unclear to me why only few drivers use this.
> + pwmchip_set_drvdata(chip, adp5585->regmap);
> + chip->ops = &adp5585_pwm_ops;
> +
> + ret = devm_pwmchip_add(dev, chip);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to add PWM chip\n");
> +
> + return 0;
> +}
...
> +static const struct platform_device_id adp5585_pwm_id_table[] = {
> + { "adp5585-pwm" },
> + { /* Sentinel */ },
Drop comma. Otherwise it's not a sentinel strictly speaking.
> +};
--
With Best Regards,
Andy Shevchenko
Sat, Jun 08, 2024 at 05:16:32PM +0300, Laurent Pinchart kirjoitti:
> From: Haibo Chen <[email protected]>
>
> The ADP5585 is a 10/11 input/output port expander with a built in keypad
> matrix decoder, programmable logic, reset generator, and PWM generator.
> This driver supports the GPIO function using the platform device
> registered by the core MFD driver.
>
> The driver is derived from an initial implementation from NXP, available
> in commit 451f61b46b76 ("MLK-25917-2 gpio: adp5585-gpio: add
> adp5585-gpio support") in their BSP kernel tree. It has been extensively
> rewritten.
...
> +static const struct platform_device_id adp5585_gpio_id_table[] = {
> + { "adp5585-gpio" },
> + { /* Sentinel */ },
Drop the comma.
> +};
--
With Best Regards,
Andy Shevchenko
Sat, Jun 08, 2024 at 05:16:31PM +0300, Laurent Pinchart kirjoitti:
> From: Haibo Chen <[email protected]>
>
> The ADP5585 is a 10/11 input/output port expander with a built in keypad
> matrix decoder, programmable logic, reset generator, and PWM generator.
> This driver supports the chip by modelling it as an MFD device, with two
> child devices for the GPIO and PWM functions.
>
> The driver is derived from an initial implementation from NXP, available
> in commit 8059835bee19 ("MLK-25917-1 mfd: adp5585: add ADI adp5585 core
> support") in their BSP kernel tree. It has been extensively rewritten.
...
> +#include <linux/array_size.h>
> +#include <linux/device.h>
+ err.h
> +#include <linux/i2c.h>
> +#include <linux/mfd/adp5585.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/types.h>
...
> +static const struct mfd_cell adp5585_devs[] = {
> + { .name = "adp5585-gpio", },
> + { .name = "adp5585-pwm", },
Inner commas are not needed.
> +};
...
> +#define ADP5585_Rx_PULL_CFG_PU_300K 0
> +#define ADP5585_Rx_PULL_CFG_PD_300K 1
> +#define ADP5585_Rx_PULL_CFG_PU_100K 2
> +#define ADP5585_Rx_PULL_CFG_DISABLE 3
> +#define ADP5585_Rx_PULL_CFG_MASK 3
Still supplying MASK is strange, why not supplying DISABLE?
...
> +#define ADP5585_OSC_EN BIT(7)
> +#define ADP5585_OSC_FREQ_50KHZ (0U << 5)
> +#define ADP5585_OSC_FREQ_100KHZ (1U << 5)
> +#define ADP5585_OSC_FREQ_200KHZ (2U << 5)
> +#define ADP5585_OSC_FREQ_500KHZ (3U << 5)
> +#define ADP5585_OSC_FREQ_MASK (3U << 5)
GENMASK() ? And possible the similar question as per above.
--
With Best Regards,
Andy Shevchenko
On Mon, Jun 10, 2024 at 06:15:40PM +0300, Andy Shevchenko wrote:
> Sat, Jun 08, 2024 at 05:16:32PM +0300, Laurent Pinchart kirjoitti:
> > From: Haibo Chen <[email protected]>
> >
> > The ADP5585 is a 10/11 input/output port expander with a built in keypad
> > matrix decoder, programmable logic, reset generator, and PWM generator.
> > This driver supports the GPIO function using the platform device
> > registered by the core MFD driver.
> >
> > The driver is derived from an initial implementation from NXP, available
> > in commit 451f61b46b76 ("MLK-25917-2 gpio: adp5585-gpio: add
> > adp5585-gpio support") in their BSP kernel tree. It has been extensively
> > rewritten.
>
> ...
>
> > +static const struct platform_device_id adp5585_gpio_id_table[] = {
> > + { "adp5585-gpio" },
>
> > + { /* Sentinel */ },
>
> Drop the comma.
I prefer keeping it.
> > +};
--
Regards,
Laurent Pinchart
Hi Stanislav,
On Mon, Jun 10, 2024 at 08:21:09AM +0200, Stanislav Jakubek wrote:
> Hi Laurent,
>
> > diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml
> > index 0a419453d183..91e62df4b296 100644
> > --- a/Documentation/devicetree/bindings/trivial-devices.yaml
> > +++ b/Documentation/devicetree/bindings/trivial-devices.yaml
> > @@ -39,10 +39,6 @@ properties:
> > # AD5110 - Nonvolatile Digital Potentiometer
> > - adi,ad5110
> > # Analog Devices ADP5585 Keypad Decoder and I/O Expansion
> > - - adi,adp5585
> > - # Analog Devices ADP5585 Keypad Decoder and I/O Expansion with support for Row5
> > - - adi,adp5585-02
> > - # Analog Devices ADP5589 Keypad Decoder and I/O Expansion
> > - adi,adp5589
> > # Analog Devices LT7182S Dual Channel 6A, 20V PolyPhase Step-Down Silent Switcher
> > - adi,lt7182s
>
> seems like you removed the wrong comment here? With this, ADP5589 would have
> a comment describing ADP5585.
Oops, my bad. You're absolutely right, thank you for catching this. I
will fix and send a new version.
--
Regards,
Laurent Pinchart
On Mon, Jun 10, 2024 at 06:06:51PM +0300, Andy Shevchenko wrote:
> Sat, Jun 08, 2024 at 05:16:33PM +0300, Laurent Pinchart kirjoitti:
> > From: Clark Wang <[email protected]>
> >
> > The ADP5585 is a 10/11 input/output port expander with a built in keypad
> > matrix decoder, programmable logic, reset generator, and PWM generator.
> > This driver supports the PWM function using the platform device
> > registered by the core MFD driver.
> >
> > The driver is derived from an initial implementation from NXP, available
> > in commit 113113742208 ("MLK-25922-1 pwm: adp5585: add adp5585 PWM
> > support") in their BSP kernel tree. It has been extensively rewritten.
>
> ...
>
> > +#define ADP5585_PWM_OSC_FREQ_HZ 1000000U
>
> (1 * HZ_PER_MHZ) ?
>
> Variant to use MEGA. Or even #define MHz in units.h if you wish.
> Putting a few 0:s in a row is error prone.
Feel free to send follow-up patches.
Andy, we're reaching a level of nitpicking and yakshaving that even I
can't deal with. I will have to simply ignore the comments I disagree
with.
> ...
>
> > + ret = regmap_write(regmap, ADP5585_PWM_OFFT_LOW,
> > + off & 0xff);
> > + if (ret)
> > + return ret;
> > + ret = regmap_write(regmap, ADP5585_PWM_OFFT_HIGH,
> > + (off >> 8) & 0xff);
> > + if (ret)
> > + return ret;
>
> This is regular I²C regmap, why do you avoid using regmap bulk APIs?
>
> > + ret = regmap_write(regmap, ADP5585_PWM_ONT_LOW,
> > + on & 0xff);
> > + if (ret)
> > + return ret;
> > + ret = regmap_write(regmap, ADP5585_PWM_ONT_HIGH,
> > + (on >> 8) & 0xff);
> > + if (ret)
> > + return ret;
>
> Ditto.
>
> ...
>
> > +static int pwm_adp5585_get_state(struct pwm_chip *chip,
> > + struct pwm_device *pwm,
> > + struct pwm_state *state)
> > +{
> > + struct regmap *regmap = pwmchip_get_drvdata(chip);
> > + unsigned int on, off;
> > + unsigned int val;
> > +
> > + regmap_read(regmap, ADP5585_PWM_OFFT_LOW, &off);
> > + regmap_read(regmap, ADP5585_PWM_OFFT_HIGH, &val);
> > + off |= val << 8;
>
> Ditto.
>
> > + regmap_read(regmap, ADP5585_PWM_ONT_LOW, &on);
> > + regmap_read(regmap, ADP5585_PWM_ONT_HIGH, &val);
> > + on |= val << 8;
>
> Ditto.
>
> > + state->duty_cycle = on * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
> > + state->period = (on + off) * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
> > +
> > + state->polarity = PWM_POLARITY_NORMAL;
> > +
> > + regmap_read(regmap, ADP5585_PWM_CFG, &val);
> > + state->enabled = !!(val & ADP5585_PWM_EN);
> > +
> > + return 0;
>
> Besides that, how do you guarantee that no IO may happen in between of those
> calls? Probably you want a driver explicit lock? In that case, would you still
> want to have a regmap internal lock?
>
> > +}
>
> ...
>
> > +static int adp5585_pwm_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent);
> > + struct pwm_chip *chip;
> > + int ret;
> > +
> > + chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM, 0);
> > + if (IS_ERR(chip))
> > + return PTR_ERR(chip);
>
> > + device_set_of_node_from_dev(dev, dev->parent);
>
> Still unclear to me why only few drivers use this.
>
> > + pwmchip_set_drvdata(chip, adp5585->regmap);
> > + chip->ops = &adp5585_pwm_ops;
> > +
> > + ret = devm_pwmchip_add(dev, chip);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "failed to add PWM chip\n");
> > +
> > + return 0;
> > +}
>
> ...
>
> > +static const struct platform_device_id adp5585_pwm_id_table[] = {
> > + { "adp5585-pwm" },
> > + { /* Sentinel */ },
>
> Drop comma. Otherwise it's not a sentinel strictly speaking.
>
> > +};
--
Regards,
Laurent Pinchart
On Mon, Jun 10, 2024 at 6:28 PM Laurent Pinchart
<[email protected]> wrote:
> On Mon, Jun 10, 2024 at 06:06:51PM +0300, Andy Shevchenko wrote:
> > Sat, Jun 08, 2024 at 05:16:33PM +0300, Laurent Pinchart kirjoitti:
...
> Andy, we're reaching a level of nitpicking and yakshaving that even I
> can't deal with. I will have to simply ignore the comments I disagree
> with.
Do you think using bulk APIs is nit-picking?
--
With Best Regards,
Andy Shevchenko
On Mon, Jun 10, 2024 at 6:26 PM Laurent Pinchart
<[email protected]> wrote:
> On Mon, Jun 10, 2024 at 06:15:40PM +0300, Andy Shevchenko wrote:
> > Sat, Jun 08, 2024 at 05:16:32PM +0300, Laurent Pinchart kirjoitti:
...
> > > +static const struct platform_device_id adp5585_gpio_id_table[] = {
> > > + { "adp5585-gpio" },
> >
> > > + { /* Sentinel */ },
> >
> > Drop the comma.
>
> I prefer keeping it.
For what reason?
The sentinel should be runtime and compile time one. Why should we
make our lives worse by neglecting help from a compiler?
> > > +};
--
With Best Regards,
Andy Shevchenko