realtek: Add pinctrl support for RTL8231
authorSander Vanheule <sander@svanheule.net>
Thu, 26 Dec 2024 19:55:16 +0000 (20:55 +0100)
committerSander Vanheule <sander@svanheule.net>
Tue, 7 Jan 2025 13:36:34 +0000 (14:36 +0100)
Add pending patches to add RTL8231 support as a MDIO-bus attached
multi-functional device. This includes subdrivers for the pincontrol and
GPIO features, as well as the LED matrix support.

Leave the drivers disabled until required by a device.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
target/linux/realtek/patches-6.6/800-gpio-regmap-Bypass-cache-for-shadowed-outputs.patch [new file with mode: 0644]
target/linux/realtek/patches-6.6/801-gpio-regmap-Use-generic-request-free-ops.patch [new file with mode: 0644]
target/linux/realtek/patches-6.6/802-mfd-Add-RTL8231-core-device.patch [new file with mode: 0644]
target/linux/realtek/patches-6.6/803-pinctrl-Add-RTL8231-pin-control-and-GPIO-support.patch [new file with mode: 0644]
target/linux/realtek/patches-6.6/804-leds-Add-support-for-RTL8231-LED-scan-matrix.patch [new file with mode: 0644]
target/linux/realtek/rtl838x/config-6.6
target/linux/realtek/rtl839x/config-6.6
target/linux/realtek/rtl930x/config-6.6
target/linux/realtek/rtl931x/config-6.6

diff --git a/target/linux/realtek/patches-6.6/800-gpio-regmap-Bypass-cache-for-shadowed-outputs.patch b/target/linux/realtek/patches-6.6/800-gpio-regmap-Bypass-cache-for-shadowed-outputs.patch
new file mode 100644 (file)
index 0000000..b4dbf32
--- /dev/null
@@ -0,0 +1,56 @@
+From b3f79468c90d8770f007d628a1e32b2d5d44a5c2 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sat, 15 May 2021 11:57:32 +0200
+Subject: [PATCH] gpio: regmap: Bypass cache for shadowed outputs
+
+Some chips have the read-only input and write-only output data registers
+aliased to the same offset, but do not perform direction multiplexing on
+writes. Upon writing the register, this then always updates the output
+value, even when the pin is configured as input. As a result it is not
+safe to perform read-modify-writes on output pins, when other pins are
+still configured as input.
+
+For example, on a bit-banged I2C bus, where the lines are switched
+between out-low and in (with external pull-up)
+
+    OUT(L) IN     OUT(H)
+SCK ....../''''''|''''''
+
+SDA '''''''''\..........
+             ^   ^- SCK switches to direction to OUT, but now has a high
+             |      value, breaking the clock.
+             |
+             \- Perform RMW to update SDA. This reads the current input
+                value for SCK, updates the SDA value and writes back a 1
+                for SCK as well.
+
+If a register is used for both the data input and data output (and is
+not marked as volatile) the driver should ensure the cache is not
+updated on register reads. This ensures proper functioning of writing
+the output register with regmap_update_bits(), which will then use and
+update the cache only on register writes.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+---
+ drivers/gpio/gpio-regmap.c | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+--- a/drivers/gpio/gpio-regmap.c
++++ b/drivers/gpio/gpio-regmap.c
+@@ -74,7 +74,15 @@ static int gpio_regmap_get(struct gpio_c
+       if (ret)
+               return ret;
+-      ret = regmap_read(gpio->regmap, reg, &val);
++      /*
++       * Ensure we don't spoil the register cache with pin input values and
++       * perform a bypassed read. This way the cache (if any) is only used and
++       * updated on register writes.
++       */
++      if (gpio->reg_dat_base == gpio->reg_set_base)
++              ret = regmap_read_bypassed(gpio->regmap, reg, &val);
++      else
++              ret = regmap_read(gpio->regmap, reg, &val);
+       if (ret)
+               return ret;
diff --git a/target/linux/realtek/patches-6.6/801-gpio-regmap-Use-generic-request-free-ops.patch b/target/linux/realtek/patches-6.6/801-gpio-regmap-Use-generic-request-free-ops.patch
new file mode 100644 (file)
index 0000000..a06ba74
--- /dev/null
@@ -0,0 +1,26 @@
+From f21b15dfe254b51f80c552750eb20b1dc752507a Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Mon, 30 Dec 2024 17:59:24 +0100
+Subject: [PATCH] gpio: regmap: Use generic request/free ops
+
+Set the gpiochip request and free ops to the generic implementations.
+This way a user can provide a gpio-ranges property defined for a pinmux,
+allowing pins to automatically be muxed to their GPIO function when
+requested.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+---
+ drivers/gpio/gpio-regmap.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/gpio/gpio-regmap.c
++++ b/drivers/gpio/gpio-regmap.c
+@@ -270,6 +270,8 @@ struct gpio_regmap *gpio_regmap_register
+       chip->label = config->label ?: dev_name(config->parent);
+       chip->can_sleep = regmap_might_sleep(config->regmap);
++      chip->request = gpiochip_generic_request;
++      chip->free = gpiochip_generic_free;
+       chip->get = gpio_regmap_get;
+       if (gpio->reg_set_base && gpio->reg_clr_base)
+               chip->set = gpio_regmap_set_with_clear;
diff --git a/target/linux/realtek/patches-6.6/802-mfd-Add-RTL8231-core-device.patch b/target/linux/realtek/patches-6.6/802-mfd-Add-RTL8231-core-device.patch
new file mode 100644 (file)
index 0000000..df4d4aa
--- /dev/null
@@ -0,0 +1,330 @@
+From 4e3455e058d40eb2a7326016494e3c81dc506c33 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Mon, 10 May 2021 18:33:01 +0200
+Subject: [PATCH] mfd: Add RTL8231 core device
+
+The RTL8231 is implemented as an MDIO device, and provides a regmap
+interface for register access by the core and child devices.
+
+The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
+Since kernel support for SMI is limited, and no real-world SMI
+implementations have been encountered for this device, this is currently
+unimplemented. The use of the regmap interface should make any future
+support relatively straightforward.
+
+After reset, all pins are muxed to GPIO inputs before the pin drivers
+are enabled. This is done to prevent accidental system resets, when a
+pin is connected to the parent SoC's reset line.
+
+To provide different read and write semantics for the GPIO data
+registers, a secondary virtual register range is used to enable separate
+caching properties of pin input and output values.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+---
+ drivers/mfd/Kconfig         |   9 ++
+ drivers/mfd/Makefile        |   1 +
+ drivers/mfd/rtl8231.c       | 193 ++++++++++++++++++++++++++++++++++++
+ include/linux/mfd/rtl8231.h |  71 +++++++++++++
+ 4 files changed, 274 insertions(+)
+ create mode 100644 drivers/mfd/rtl8231.c
+ create mode 100644 include/linux/mfd/rtl8231.h
+
+--- a/drivers/mfd/Kconfig
++++ b/drivers/mfd/Kconfig
+@@ -1171,6 +1171,15 @@ config MFD_RDC321X
+         southbridge which provides access to GPIOs and Watchdog using the
+         southbridge PCI device configuration space.
++config MFD_RTL8231
++      tristate "Realtek RTL8231 GPIO and LED expander"
++      select MFD_CORE
++      select REGMAP_MDIO
++      help
++        Support for the Realtek RTL8231 GPIO and LED expander.
++        Provides up to 37 GPIOs, 88 LEDs, and one PWM output.
++        When built as a module, this module will be named rtl8231.
++
+ config MFD_RT4831
+       tristate "Richtek RT4831 four channel WLED and Display Bias Voltage"
+       depends on I2C
+--- a/drivers/mfd/Makefile
++++ b/drivers/mfd/Makefile
+@@ -240,6 +240,7 @@ obj-$(CONFIG_MFD_HI6421_PMIC)      += hi6421-
+ obj-$(CONFIG_MFD_HI6421_SPMI) += hi6421-spmi-pmic.o
+ obj-$(CONFIG_MFD_HI655X_PMIC)   += hi655x-pmic.o
+ obj-$(CONFIG_MFD_DLN2)                += dln2.o
++obj-$(CONFIG_MFD_RTL8231)     += rtl8231.o
+ obj-$(CONFIG_MFD_RT4831)      += rt4831.o
+ obj-$(CONFIG_MFD_RT5033)      += rt5033.o
+ obj-$(CONFIG_MFD_RT5120)      += rt5120.o
+--- /dev/null
++++ b/drivers/mfd/rtl8231.c
+@@ -0,0 +1,193 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/bits.h>
++#include <linux/bitfield.h>
++#include <linux/delay.h>
++#include <linux/gpio/consumer.h>
++#include <linux/mfd/core.h>
++#include <linux/mdio.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++#include <linux/regmap.h>
++
++#include <linux/mfd/rtl8231.h>
++
++static bool rtl8231_volatile_reg(struct device *dev, unsigned int reg)
++{
++      switch (reg) {
++      /*
++       * Registers with self-clearing bits, strapping pin values.
++       * Don't mark the data registers as volatile, since we need
++       * caching for the output values.
++       */
++      case RTL8231_REG_FUNC0:
++      case RTL8231_REG_FUNC1:
++      case RTL8231_REG_PIN_HI_CFG:
++      case RTL8231_REG_LED_END:
++              return true;
++      default:
++              return false;
++      }
++}
++
++static const struct reg_field RTL8231_FIELD_LED_START = REG_FIELD(RTL8231_REG_FUNC0, 1, 1);
++
++static const struct mfd_cell rtl8231_cells[] = {
++      {
++              .name = "rtl8231-pinctrl",
++      },
++      {
++              .name = "rtl8231-leds",
++              .of_compatible = "realtek,rtl8231-leds",
++      },
++};
++
++static int rtl8231_soft_reset(struct regmap *map)
++{
++      const unsigned int all_pins_mask = GENMASK(RTL8231_BITS_VAL - 1, 0);
++      unsigned int val;
++      int err;
++
++      /* SOFT_RESET bit self-clears when done */
++      regmap_write_bits(map, RTL8231_REG_PIN_HI_CFG,
++              RTL8231_PIN_HI_CFG_SOFT_RESET, RTL8231_PIN_HI_CFG_SOFT_RESET);
++      err = regmap_read_poll_timeout(map, RTL8231_REG_PIN_HI_CFG, val,
++              !(val & RTL8231_PIN_HI_CFG_SOFT_RESET), 50, 1000);
++      if (err)
++              return err;
++
++      regcache_mark_dirty(map);
++
++      /*
++       * Chip reset results in a pin configuration that is a mix of LED and GPIO outputs.
++       * Select GPI functionality for all pins before enabling pin outputs.
++       */
++      regmap_write(map, RTL8231_REG_PIN_MODE0, all_pins_mask);
++      regmap_write(map, RTL8231_REG_GPIO_DIR0, all_pins_mask);
++      regmap_write(map, RTL8231_REG_PIN_MODE1, all_pins_mask);
++      regmap_write(map, RTL8231_REG_GPIO_DIR1, all_pins_mask);
++      regmap_write(map, RTL8231_REG_PIN_HI_CFG,
++              RTL8231_PIN_HI_CFG_MODE_MASK | RTL8231_PIN_HI_CFG_DIR_MASK);
++
++      return 0;
++}
++
++static int rtl8231_init(struct device *dev, struct regmap *map)
++{
++      struct regmap_field *led_start;
++      unsigned int started;
++      unsigned int val;
++      int err;
++
++      err = regmap_read(map, RTL8231_REG_FUNC1, &val);
++      if (err) {
++              dev_err(dev, "failed to read READY_CODE\n");
++              return err;
++      }
++
++      val = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, val);
++      if (val != RTL8231_FUNC1_READY_CODE_VALUE) {
++              dev_err(dev, "RTL8231 not present or ready 0x%x != 0x%x\n",
++                      val, RTL8231_FUNC1_READY_CODE_VALUE);
++              return -ENODEV;
++      }
++
++      led_start = dev_get_drvdata(dev);
++      err = regmap_field_read(led_start, &started);
++      if (err)
++              return err;
++
++      if (!started) {
++              err = rtl8231_soft_reset(map);
++              if (err)
++                      return err;
++              /* LED_START enables power to output pins, and starts the LED engine */
++              err = regmap_field_force_write(led_start, 1);
++      }
++
++      return err;
++}
++
++static const struct regmap_config rtl8231_mdio_regmap_config = {
++      .val_bits = RTL8231_BITS_VAL,
++      .reg_bits = RTL8231_BITS_REG,
++      .volatile_reg = rtl8231_volatile_reg,
++      .max_register = RTL8231_REG_COUNT - 1,
++      .use_single_read = true,
++      .use_single_write = true,
++      .reg_format_endian = REGMAP_ENDIAN_BIG,
++      .val_format_endian = REGMAP_ENDIAN_BIG,
++      /* Cannot use REGCACHE_FLAT because it's not smart enough about cache invalidation  */
++      .cache_type = REGCACHE_RBTREE,
++};
++
++static int rtl8231_mdio_probe(struct mdio_device *mdiodev)
++{
++      struct device *dev = &mdiodev->dev;
++      struct regmap_field *led_start;
++      struct regmap *map;
++      int err;
++
++      map = devm_regmap_init_mdio(mdiodev, &rtl8231_mdio_regmap_config);
++      if (IS_ERR(map)) {
++              dev_err(dev, "failed to init regmap\n");
++              return PTR_ERR(map);
++      }
++
++      led_start = devm_regmap_field_alloc(dev, map, RTL8231_FIELD_LED_START);
++      if (IS_ERR(led_start))
++              return PTR_ERR(led_start);
++
++      dev_set_drvdata(dev, led_start);
++
++      mdiodev->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
++      if (IS_ERR(mdiodev->reset_gpio))
++              return PTR_ERR(mdiodev->reset_gpio);
++
++      device_property_read_u32(dev, "reset-assert-delay", &mdiodev->reset_assert_delay);
++      device_property_read_u32(dev, "reset-deassert-delay", &mdiodev->reset_deassert_delay);
++
++      err = rtl8231_init(dev, map);
++      if (err)
++              return err;
++
++      return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
++              ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);
++}
++
++__maybe_unused static int rtl8231_suspend(struct device *dev)
++{
++      struct regmap_field *led_start = dev_get_drvdata(dev);
++
++      return regmap_field_force_write(led_start, 0);
++}
++
++__maybe_unused static int rtl8231_resume(struct device *dev)
++{
++      struct regmap_field *led_start = dev_get_drvdata(dev);
++
++      return regmap_field_force_write(led_start, 1);
++}
++
++static SIMPLE_DEV_PM_OPS(rtl8231_pm_ops, rtl8231_suspend, rtl8231_resume);
++
++static const struct of_device_id rtl8231_of_match[] = {
++      { .compatible = "realtek,rtl8231" },
++      {}
++};
++MODULE_DEVICE_TABLE(of, rtl8231_of_match);
++
++static struct mdio_driver rtl8231_mdio_driver = {
++      .mdiodrv.driver = {
++              .name = "rtl8231-expander",
++              .of_match_table = rtl8231_of_match,
++              .pm = pm_ptr(&rtl8231_pm_ops),
++      },
++      .probe = rtl8231_mdio_probe,
++};
++mdio_module_driver(rtl8231_mdio_driver);
++
++MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
++MODULE_DESCRIPTION("Realtek RTL8231 GPIO and LED expander");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/include/linux/mfd/rtl8231.h
+@@ -0,0 +1,71 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Register definitions the RTL8231 GPIO and LED expander chip
++ */
++
++#ifndef __LINUX_MFD_RTL8231_H
++#define __LINUX_MFD_RTL8231_H
++
++#include <linux/bits.h>
++
++/*
++ * Registers addresses are 5 bit, values are 16 bit
++ * Also define a duplicated range of virtual addresses, to enable
++ * different read/write behaviour on the GPIO data registers
++ */
++#define RTL8231_BITS_VAL              16
++#define RTL8231_BITS_REG              5
++
++/* Chip control */
++#define RTL8231_REG_FUNC0             0x00
++#define RTL8231_FUNC0_SCAN_MODE               BIT(0)
++#define RTL8231_FUNC0_SCAN_SINGLE     0
++#define RTL8231_FUNC0_SCAN_BICOLOR    BIT(0)
++
++#define RTL8231_REG_FUNC1             0x01
++#define RTL8231_FUNC1_READY_CODE_VALUE        0x37
++#define RTL8231_FUNC1_READY_CODE_MASK GENMASK(9, 4)
++#define RTL8231_FUNC1_DEBOUNCE_MASK   GENMASK(15, 10)
++
++/* Pin control */
++#define RTL8231_REG_PIN_MODE0         0x02
++#define RTL8231_REG_PIN_MODE1         0x03
++
++#define RTL8231_PIN_MODE_LED          0
++#define RTL8231_PIN_MODE_GPIO         1
++
++/* Pin high config: pin and GPIO control for pins 32-26 */
++#define RTL8231_REG_PIN_HI_CFG                0x04
++#define RTL8231_PIN_HI_CFG_MODE_MASK  GENMASK(4, 0)
++#define RTL8231_PIN_HI_CFG_DIR_MASK   GENMASK(9, 5)
++#define RTL8231_PIN_HI_CFG_INV_MASK   GENMASK(14, 10)
++#define RTL8231_PIN_HI_CFG_SOFT_RESET BIT(15)
++
++/* GPIO control registers */
++#define RTL8231_REG_GPIO_DIR0         0x05
++#define RTL8231_REG_GPIO_DIR1         0x06
++#define RTL8231_REG_GPIO_INVERT0      0x07
++#define RTL8231_REG_GPIO_INVERT1      0x08
++
++#define RTL8231_GPIO_DIR_IN           1
++#define RTL8231_GPIO_DIR_OUT          0
++
++/*
++ * GPIO data registers
++ * Only the output data can be written to these registers, and only the input
++ * data can be read.
++ */
++#define RTL8231_REG_GPIO_DATA0                0x1c
++#define RTL8231_REG_GPIO_DATA1                0x1d
++#define RTL8231_REG_GPIO_DATA2                0x1e
++#define RTL8231_PIN_HI_DATA_MASK      GENMASK(4, 0)
++
++/* LED control base registers */
++#define RTL8231_REG_LED0_BASE         0x09
++#define RTL8231_REG_LED1_BASE         0x10
++#define RTL8231_REG_LED2_BASE         0x17
++#define RTL8231_REG_LED_END           0x1b
++
++#define RTL8231_REG_COUNT             0x1f
++
++#endif /* __LINUX_MFD_RTL8231_H */
diff --git a/target/linux/realtek/patches-6.6/803-pinctrl-Add-RTL8231-pin-control-and-GPIO-support.patch b/target/linux/realtek/patches-6.6/803-pinctrl-Add-RTL8231-pin-control-and-GPIO-support.patch
new file mode 100644 (file)
index 0000000..de0f5ee
--- /dev/null
@@ -0,0 +1,581 @@
+From 098324288a63a6dcc44e96cc381aef3d5c48d89e Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Mon, 10 May 2021 22:15:31 +0200
+Subject: [PATCH] pinctrl: Add RTL8231 pin control and GPIO support
+
+This driver implements the GPIO and pin muxing features provided by the
+RTL8231. The device should be instantiated as an MFD child, where the
+parent device has already configured the regmap used for register
+access.
+
+Debouncing is only available for the six highest GPIOs, and must be
+emulated when other pins are used for (button) inputs. Although
+described in the bindings, drive strength selection is currently not
+implemented.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+---
+ drivers/pinctrl/Kconfig           |  11 +
+ drivers/pinctrl/Makefile          |   1 +
+ drivers/pinctrl/pinctrl-rtl8231.c | 521 ++++++++++++++++++++++++++++++
+ 3 files changed, 533 insertions(+)
+ create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c
+
+--- a/drivers/pinctrl/Kconfig
++++ b/drivers/pinctrl/Kconfig
+@@ -417,6 +417,17 @@ config PINCTRL_ROCKCHIP
+       help
+           This support pinctrl and GPIO driver for Rockchip SoCs.
++config PINCTRL_RTL8231
++      tristate "Realtek RTL8231 GPIO expander's pin controller"
++      depends on MFD_RTL8231
++      default MFD_RTL8231
++      select GPIO_REGMAP
++      select GENERIC_PINCONF
++      select GENERIC_PINMUX_FUNCTIONS
++      help
++        Support for RTL8231 expander's GPIOs and pin controller.
++        When built as a module, the module will be called pinctrl-rtl8231.
++
+ config PINCTRL_SINGLE
+       tristate "One-register-per-pin type device tree based pinctrl driver"
+       depends on OF
+--- a/drivers/pinctrl/Makefile
++++ b/drivers/pinctrl/Makefile
+@@ -43,6 +43,7 @@ obj-$(CONFIG_PINCTRL_PIC32)  += pinctrl-p
+ obj-$(CONFIG_PINCTRL_PISTACHIO)       += pinctrl-pistachio.o
+ obj-$(CONFIG_PINCTRL_RK805)   += pinctrl-rk805.o
+ obj-$(CONFIG_PINCTRL_ROCKCHIP)        += pinctrl-rockchip.o
++obj-$(CONFIG_PINCTRL_RTL8231) += pinctrl-rtl8231.o
+ obj-$(CONFIG_PINCTRL_SINGLE)  += pinctrl-single.o
+ obj-$(CONFIG_PINCTRL_ST)      += pinctrl-st.o
+ obj-$(CONFIG_PINCTRL_STMFX)   += pinctrl-stmfx.o
+--- /dev/null
++++ b/drivers/pinctrl/pinctrl-rtl8231.c
+@@ -0,0 +1,525 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/bitfield.h>
++#include <linux/gpio/driver.h>
++#include <linux/gpio/regmap.h>
++#include <linux/module.h>
++#include <linux/pinctrl/pinconf.h>
++#include <linux/pinctrl/pinctrl.h>
++#include <linux/pinctrl/pinmux.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++#include "core.h"
++#include "pinmux.h"
++#include <linux/mfd/rtl8231.h>
++
++#define RTL8231_NUM_GPIOS             37
++#define RTL8231_DEBOUNCE_USEC         100000
++#define RTL8231_DEBOUNCE_MIN_OFFSET   31
++
++struct rtl8231_pin_ctrl {
++      struct pinctrl_desc pctl_desc;
++      struct regmap *map;
++};
++
++/*
++ * Pin controller functionality
++ */
++static const char * const rtl8231_pin_function_names[] = {
++      "gpio",
++      "led",
++      "pwm",
++};
++
++enum rtl8231_pin_function {
++      RTL8231_PIN_FUNCTION_GPIO = BIT(0),
++      RTL8231_PIN_FUNCTION_LED = BIT(1),
++      RTL8231_PIN_FUNCTION_PWM = BIT(2),
++};
++
++struct rtl8231_pin_desc {
++      const enum rtl8231_pin_function functions;
++      const u8 reg;
++      const u8 offset;
++      const u8 gpio_function_value;
++};
++
++#define RTL8231_PIN_DESC(_num, _func, _reg, _fld, _val)               \
++      [_num] = {                                              \
++              .functions = RTL8231_PIN_FUNCTION_GPIO | _func, \
++              .reg = _reg,                                    \
++              .offset = _fld,                                 \
++              .gpio_function_value = _val,                    \
++      }
++#define RTL8231_GPIO_PIN_DESC(_num, _reg, _fld)                       \
++      RTL8231_PIN_DESC(_num, 0, _reg, _fld, RTL8231_PIN_MODE_GPIO)
++#define RTL8231_LED_PIN_DESC(_num, _reg, _fld)                        \
++      RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_LED, _reg, _fld, RTL8231_PIN_MODE_GPIO)
++#define RTL8231_PWM_PIN_DESC(_num, _reg, _fld)                        \
++      RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_PWM, _reg, _fld, 0)
++
++/*
++ * All pins have a GPIO/LED mux bit, but the bits for pins 35/36 are read-only. Use this bit
++ * for the GPIO-only pin instead of a placeholder, so the rest of the logic can stay generic.
++ */
++static struct rtl8231_pin_desc rtl8231_pin_data[RTL8231_NUM_GPIOS] = {
++      RTL8231_LED_PIN_DESC(0, RTL8231_REG_PIN_MODE0, 0),
++      RTL8231_LED_PIN_DESC(1, RTL8231_REG_PIN_MODE0, 1),
++      RTL8231_LED_PIN_DESC(2, RTL8231_REG_PIN_MODE0, 2),
++      RTL8231_LED_PIN_DESC(3, RTL8231_REG_PIN_MODE0, 3),
++      RTL8231_LED_PIN_DESC(4, RTL8231_REG_PIN_MODE0, 4),
++      RTL8231_LED_PIN_DESC(5, RTL8231_REG_PIN_MODE0, 5),
++      RTL8231_LED_PIN_DESC(6, RTL8231_REG_PIN_MODE0, 6),
++      RTL8231_LED_PIN_DESC(7, RTL8231_REG_PIN_MODE0, 7),
++      RTL8231_LED_PIN_DESC(8, RTL8231_REG_PIN_MODE0, 8),
++      RTL8231_LED_PIN_DESC(9, RTL8231_REG_PIN_MODE0, 9),
++      RTL8231_LED_PIN_DESC(10, RTL8231_REG_PIN_MODE0, 10),
++      RTL8231_LED_PIN_DESC(11, RTL8231_REG_PIN_MODE0, 11),
++      RTL8231_LED_PIN_DESC(12, RTL8231_REG_PIN_MODE0, 12),
++      RTL8231_LED_PIN_DESC(13, RTL8231_REG_PIN_MODE0, 13),
++      RTL8231_LED_PIN_DESC(14, RTL8231_REG_PIN_MODE0, 14),
++      RTL8231_LED_PIN_DESC(15, RTL8231_REG_PIN_MODE0, 15),
++      RTL8231_LED_PIN_DESC(16, RTL8231_REG_PIN_MODE1, 0),
++      RTL8231_LED_PIN_DESC(17, RTL8231_REG_PIN_MODE1, 1),
++      RTL8231_LED_PIN_DESC(18, RTL8231_REG_PIN_MODE1, 2),
++      RTL8231_LED_PIN_DESC(19, RTL8231_REG_PIN_MODE1, 3),
++      RTL8231_LED_PIN_DESC(20, RTL8231_REG_PIN_MODE1, 4),
++      RTL8231_LED_PIN_DESC(21, RTL8231_REG_PIN_MODE1, 5),
++      RTL8231_LED_PIN_DESC(22, RTL8231_REG_PIN_MODE1, 6),
++      RTL8231_LED_PIN_DESC(23, RTL8231_REG_PIN_MODE1, 7),
++      RTL8231_LED_PIN_DESC(24, RTL8231_REG_PIN_MODE1, 8),
++      RTL8231_LED_PIN_DESC(25, RTL8231_REG_PIN_MODE1, 9),
++      RTL8231_LED_PIN_DESC(26, RTL8231_REG_PIN_MODE1, 10),
++      RTL8231_LED_PIN_DESC(27, RTL8231_REG_PIN_MODE1, 11),
++      RTL8231_LED_PIN_DESC(28, RTL8231_REG_PIN_MODE1, 12),
++      RTL8231_LED_PIN_DESC(29, RTL8231_REG_PIN_MODE1, 13),
++      RTL8231_LED_PIN_DESC(30, RTL8231_REG_PIN_MODE1, 14),
++      RTL8231_LED_PIN_DESC(31, RTL8231_REG_PIN_MODE1, 15),
++      RTL8231_LED_PIN_DESC(32, RTL8231_REG_PIN_HI_CFG, 0),
++      RTL8231_LED_PIN_DESC(33, RTL8231_REG_PIN_HI_CFG, 1),
++      RTL8231_LED_PIN_DESC(34, RTL8231_REG_PIN_HI_CFG, 2),
++      RTL8231_PWM_PIN_DESC(35, RTL8231_REG_FUNC1, 3),
++      RTL8231_GPIO_PIN_DESC(36, RTL8231_REG_PIN_HI_CFG, 4),
++};
++
++#define RTL8231_PIN(_num)                             \
++      {                                               \
++              .number = _num,                         \
++              .name = "gpio" #_num,                   \
++              .drv_data = &rtl8231_pin_data[_num]     \
++      }
++
++static const struct pinctrl_pin_desc rtl8231_pins[RTL8231_NUM_GPIOS] = {
++      RTL8231_PIN(0),
++      RTL8231_PIN(1),
++      RTL8231_PIN(2),
++      RTL8231_PIN(3),
++      RTL8231_PIN(4),
++      RTL8231_PIN(5),
++      RTL8231_PIN(6),
++      RTL8231_PIN(7),
++      RTL8231_PIN(8),
++      RTL8231_PIN(9),
++      RTL8231_PIN(10),
++      RTL8231_PIN(11),
++      RTL8231_PIN(12),
++      RTL8231_PIN(13),
++      RTL8231_PIN(14),
++      RTL8231_PIN(15),
++      RTL8231_PIN(16),
++      RTL8231_PIN(17),
++      RTL8231_PIN(18),
++      RTL8231_PIN(19),
++      RTL8231_PIN(20),
++      RTL8231_PIN(21),
++      RTL8231_PIN(22),
++      RTL8231_PIN(23),
++      RTL8231_PIN(24),
++      RTL8231_PIN(25),
++      RTL8231_PIN(26),
++      RTL8231_PIN(27),
++      RTL8231_PIN(28),
++      RTL8231_PIN(29),
++      RTL8231_PIN(30),
++      RTL8231_PIN(31),
++      RTL8231_PIN(32),
++      RTL8231_PIN(33),
++      RTL8231_PIN(34),
++      RTL8231_PIN(35),
++      RTL8231_PIN(36),
++};
++
++static int rtl8231_get_groups_count(struct pinctrl_dev *pctldev)
++{
++      return ARRAY_SIZE(rtl8231_pins);
++}
++
++static const char *rtl8231_get_group_name(struct pinctrl_dev *pctldev, unsigned int selector)
++{
++      return rtl8231_pins[selector].name;
++}
++
++static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
++      const unsigned int **pins, unsigned int *num_pins)
++{
++      if (selector >= ARRAY_SIZE(rtl8231_pins))
++              return -EINVAL;
++
++      *pins = &rtl8231_pins[selector].number;
++      *num_pins = 1;
++
++      return 0;
++}
++
++static const struct pinctrl_ops rtl8231_pinctrl_ops = {
++      .get_groups_count = rtl8231_get_groups_count,
++      .get_group_name = rtl8231_get_group_name,
++      .get_group_pins = rtl8231_get_group_pins,
++      .dt_node_to_map = pinconf_generic_dt_node_to_map_all,
++      .dt_free_map = pinconf_generic_dt_free_map,
++};
++
++static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
++      unsigned int group_selector)
++{
++      const struct function_desc *func = pinmux_generic_get_function(pctldev, func_selector);
++      const struct rtl8231_pin_desc *desc = rtl8231_pins[group_selector].drv_data;
++      const struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
++      unsigned int func_flag = (uintptr_t) func->data;
++      unsigned int function_mask;
++      unsigned int gpio_function;
++
++      if (!(desc->functions & func_flag))
++              return -EINVAL;
++
++      function_mask = BIT(desc->offset);
++      gpio_function = desc->gpio_function_value << desc->offset;
++
++      if (func_flag == RTL8231_PIN_FUNCTION_GPIO)
++              return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
++      else
++              return regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
++}
++
++static int rtl8231_gpio_request_enable(struct pinctrl_dev *pctldev,
++      struct pinctrl_gpio_range *range, unsigned int offset)
++{
++      const struct rtl8231_pin_desc *desc = rtl8231_pins[offset].drv_data;
++      struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
++      unsigned int function_mask;
++      unsigned int gpio_function;
++
++      function_mask = BIT(desc->offset);
++      gpio_function = desc->gpio_function_value << desc->offset;
++
++      return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
++}
++
++static const struct pinmux_ops rtl8231_pinmux_ops = {
++      .get_functions_count = pinmux_generic_get_function_count,
++      .get_function_name = pinmux_generic_get_function_name,
++      .get_function_groups = pinmux_generic_get_function_groups,
++      .set_mux = rtl8231_set_mux,
++      .gpio_request_enable = rtl8231_gpio_request_enable,
++      .strict = true,
++};
++
++static int rtl8231_pin_config_get(struct pinctrl_dev *pctldev, unsigned int offset,
++      unsigned long *config)
++{
++      struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
++      unsigned int param = pinconf_to_config_param(*config);
++      unsigned int arg;
++      int err;
++      int v;
++
++      switch (param) {
++      case PIN_CONFIG_INPUT_DEBOUNCE:
++              if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
++                      return -EINVAL;
++
++              err = regmap_read(ctrl->map, RTL8231_REG_FUNC1, &v);
++              if (err)
++                      return err;
++
++              v = FIELD_GET(RTL8231_FUNC1_DEBOUNCE_MASK, v);
++              if (v & BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET))
++                      arg = RTL8231_DEBOUNCE_USEC;
++              else
++                      arg = 0;
++              break;
++      default:
++              return -ENOTSUPP;
++      }
++
++      *config = pinconf_to_config_packed(param, arg);
++
++      return 0;
++}
++
++static int rtl8231_pin_config_set(struct pinctrl_dev *pctldev, unsigned int offset,
++      unsigned long *configs, unsigned int num_configs)
++{
++      struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
++      unsigned int param, arg;
++      unsigned int pin_mask;
++      int err;
++      int i;
++
++      for (i = 0; i < num_configs; i++) {
++              param = pinconf_to_config_param(configs[i]);
++              arg = pinconf_to_config_argument(configs[i]);
++
++              switch (param) {
++              case PIN_CONFIG_INPUT_DEBOUNCE:
++                      if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
++                              return -EINVAL;
++
++                      pin_mask = FIELD_PREP(RTL8231_FUNC1_DEBOUNCE_MASK,
++                              BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET));
++
++                      switch (arg) {
++                      case 0:
++                              err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
++                                      pin_mask, 0);
++                              break;
++                      case RTL8231_DEBOUNCE_USEC:
++                              err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
++                                      pin_mask, pin_mask);
++                              break;
++                      default:
++                              return -EINVAL;
++                      }
++
++                      break;
++              default:
++                      return -ENOTSUPP;
++              }
++      }
++
++      return err;
++}
++
++static const struct pinconf_ops rtl8231_pinconf_ops = {
++      .is_generic = true,
++      .pin_config_get = rtl8231_pin_config_get,
++      .pin_config_set = rtl8231_pin_config_set,
++};
++
++static int rtl8231_pinctrl_init_functions(struct pinctrl_dev *pctl, struct rtl8231_pin_ctrl *ctrl)
++{
++      const char *function_name;
++      const char **groups;
++      unsigned int f_idx;
++      unsigned int pin;
++      int num_groups;
++      int err;
++
++      for (f_idx = 0; f_idx < ARRAY_SIZE(rtl8231_pin_function_names); f_idx++) {
++              function_name = rtl8231_pin_function_names[f_idx];
++
++              for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
++                      if (rtl8231_pin_data[pin].functions & BIT(f_idx))
++                              num_groups++;
++
++              groups = devm_kcalloc(pctl->dev, num_groups, sizeof(*groups), GFP_KERNEL);
++              if (!groups)
++                      return -ENOMEM;
++
++              for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
++                      if (rtl8231_pin_data[pin].functions & BIT(f_idx))
++                              groups[num_groups++] = rtl8231_pins[pin].name;
++
++              err = pinmux_generic_add_function(pctl, function_name, groups, num_groups,
++                      (void *) BIT(f_idx));
++              if (err < 0)
++                      return err;
++      }
++
++      return 0;
++}
++
++struct pin_field_info {
++      const struct reg_field gpio_data;
++      const struct reg_field gpio_dir;
++      const struct reg_field mode;
++};
++
++static const struct pin_field_info pin_fields[] = {
++      {
++              .gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA0, 0, 15),
++              .gpio_dir = REG_FIELD(RTL8231_REG_GPIO_DIR0, 0, 15),
++              .mode = REG_FIELD(RTL8231_REG_PIN_MODE0, 0, 15),
++      },
++      {
++              .gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA1, 0, 15),
++              .gpio_dir = REG_FIELD(RTL8231_REG_GPIO_DIR1, 0, 15),
++              .mode = REG_FIELD(RTL8231_REG_PIN_MODE1, 0, 15),
++      },
++      {
++              .gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA2, 0, 4),
++              .gpio_dir = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 5, 9),
++              .mode = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 0, 4),
++      },
++};
++
++static int rtl8231_configure_safe(struct device *dev, struct regmap *map)
++{
++      struct regmap_field *field_data;
++      struct regmap_field *field_mode;
++      struct regmap_field *field_dir;
++      unsigned int is_output;
++      unsigned int is_gpio;
++      unsigned int data;
++      unsigned int mode;
++      unsigned int dir;
++      int err;
++
++      for (unsigned int i = 0; i < ARRAY_SIZE(pin_fields); i++) {
++              field_data = devm_regmap_field_alloc(dev, map, pin_fields[i].gpio_data);
++              if (IS_ERR(field_data))
++                      return PTR_ERR(field_data);
++
++              field_dir = devm_regmap_field_alloc(dev, map, pin_fields[i].gpio_dir);
++              if (IS_ERR(field_dir))
++                      return PTR_ERR(field_dir);
++
++              field_mode = devm_regmap_field_alloc(dev, map, pin_fields[i].mode);
++              if (IS_ERR(field_mode))
++                      return PTR_ERR(field_mode);
++
++              /* The register cache is invalid at start-up, so this should read from HW */
++              err = regmap_field_read(field_data, &data);
++              if (err)
++                      return err;
++
++              err = regmap_field_read(field_dir, &dir);
++              if (err)
++                      return err;
++
++              err = regmap_field_read(field_mode, &mode);
++              if (err)
++                      return err;
++
++              /* Write back only the GPIO-out values to fix the cache */
++              data &= ~dir;
++              regmap_field_write(field_data, data);
++
++              /*
++               * Set every pin that is configured as gpio-output but muxed for the alternative
++               * (LED) function to gpio-in. That way the pin will be high impedance when it is
++               * muxed to GPIO, preventing unwanted glitches.
++               * The pin muxes are left as-is, so there are no signal changes.
++               */
++              is_gpio = mode;
++              is_output = ~dir;
++              regmap_field_write(field_dir, dir | (~is_gpio & is_output));
++
++              devm_regmap_field_free(dev, field_data);
++              devm_regmap_field_free(dev, field_dir);
++              devm_regmap_field_free(dev, field_mode);
++      }
++
++      return 0;
++}
++
++static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
++{
++      struct pinctrl_dev *pctldev;
++      int err;
++
++      ctrl->pctl_desc.name = "rtl8231-pinctrl";
++      ctrl->pctl_desc.owner = THIS_MODULE;
++      ctrl->pctl_desc.confops = &rtl8231_pinconf_ops;
++      ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops;
++      ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops;
++      ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
++      ctrl->pctl_desc.pins = rtl8231_pins;
++
++      err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctldev);
++      if (err) {
++              dev_err(dev, "failed to register pin controller\n");
++              return err;
++      }
++
++      err = rtl8231_pinctrl_init_functions(pctldev, ctrl);
++      if (err)
++              return err;
++
++      err = pinctrl_enable(pctldev);
++      if (err)
++              dev_err(dev, "failed to enable pin controller\n");
++
++      return err;
++}
++
++/*
++ * GPIO controller functionality
++ */
++static int rtl8231_gpio_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
++      unsigned int offset, unsigned int *reg, unsigned int *mask)
++{
++      unsigned int pin_mask = BIT(offset % RTL8231_BITS_VAL);
++
++      if (base == RTL8231_REG_GPIO_DATA0 || offset < 32) {
++              *reg = base + offset / RTL8231_BITS_VAL;
++              *mask = pin_mask;
++      } else if (base == RTL8231_REG_GPIO_DIR0) {
++              *reg = RTL8231_REG_PIN_HI_CFG;
++              *mask = FIELD_PREP(RTL8231_PIN_HI_CFG_DIR_MASK, pin_mask);
++      } else {
++              return -EINVAL;
++      }
++
++      return 0;
++}
++
++static int rtl8231_pinctrl_probe(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      struct rtl8231_pin_ctrl *ctrl;
++      struct gpio_regmap_config gpio_cfg = {};
++      int err;
++
++      ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
++      if (!ctrl)
++              return -ENOMEM;
++
++      ctrl->map = dev_get_regmap(dev->parent, NULL);
++      if (!ctrl->map)
++              return -ENODEV;
++
++      err = rtl8231_configure_safe(dev, ctrl->map);
++      if (err)
++              return err;
++
++      err = rtl8231_pinctrl_init(dev, ctrl);
++      if (err)
++              return err;
++
++      gpio_cfg.regmap = ctrl->map;
++      gpio_cfg.parent = dev->parent;
++      gpio_cfg.ngpio = RTL8231_NUM_GPIOS;
++      gpio_cfg.ngpio_per_reg = RTL8231_BITS_VAL;
++
++      gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
++      gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
++      gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
++
++      gpio_cfg.reg_mask_xlate = rtl8231_gpio_reg_mask_xlate;
++
++      return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_cfg));
++}
++
++static struct platform_driver rtl8231_pinctrl_driver = {
++      .driver = {
++              .name = "rtl8231-pinctrl",
++      },
++      .probe = rtl8231_pinctrl_probe,
++};
++module_platform_driver(rtl8231_pinctrl_driver);
++
++MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
++MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/realtek/patches-6.6/804-leds-Add-support-for-RTL8231-LED-scan-matrix.patch b/target/linux/realtek/patches-6.6/804-leds-Add-support-for-RTL8231-LED-scan-matrix.patch
new file mode 100644 (file)
index 0000000..37542b8
--- /dev/null
@@ -0,0 +1,338 @@
+From 6b797a97c007e46d6081fc6f4b41ce8407078605 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Mon, 10 May 2021 22:16:11 +0200
+Subject: [PATCH] leds: Add support for RTL8231 LED scan matrix
+
+Both single and bi-color scanning modes are supported. The driver will
+verify that the addresses are valid for the current mode, before
+registering the LEDs. LEDs can be turned on, off, or toggled at one of
+six predefined rates from 40ms to 1280ms.
+
+Implements a platform device for use as a child device with RTL8231 MFD,
+and uses the parent regmap to access the required registers.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+---
+ drivers/leds/Kconfig        |  10 ++
+ drivers/leds/Makefile       |   1 +
+ drivers/leds/leds-rtl8231.c | 291 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 302 insertions(+)
+ create mode 100644 drivers/leds/leds-rtl8231.c
+
+--- a/drivers/leds/Kconfig
++++ b/drivers/leds/Kconfig
+@@ -586,6 +586,16 @@ config LEDS_REGULATOR
+       help
+         This option enables support for regulator driven LEDs.
++config LEDS_RTL8231
++      tristate "RTL8231 LED matrix support"
++      depends on LEDS_CLASS
++      depends on MFD_RTL8231
++      default MFD_RTL8231
++      help
++        This option enables support for using the LED scanning matrix output
++        of the RTL8231 GPIO and LED expander chip.
++        When built as a module, this module will be named leds-rtl8231.
++
+ config LEDS_BD2606MVV
+       tristate "LED driver for BD2606MVV"
+       depends on LEDS_CLASS
+--- a/drivers/leds/Makefile
++++ b/drivers/leds/Makefile
+@@ -77,6 +77,7 @@ obj-$(CONFIG_LEDS_PM8058)            += leds-pm805
+ obj-$(CONFIG_LEDS_POWERNV)            += leds-powernv.o
+ obj-$(CONFIG_LEDS_PWM)                        += leds-pwm.o
+ obj-$(CONFIG_LEDS_REGULATOR)          += leds-regulator.o
++obj-$(CONFIG_LEDS_RTL8231)            += leds-rtl8231.o
+ obj-$(CONFIG_LEDS_SC27XX_BLTC)                += leds-sc27xx-bltc.o
+ obj-$(CONFIG_LEDS_ST1202)             += leds-st1202.o
+ obj-$(CONFIG_LEDS_SUNFIRE)            += leds-sunfire.o
+--- /dev/null
++++ b/drivers/leds/leds-rtl8231.c
+@@ -0,0 +1,285 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/device.h>
++#include <linux/leds.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++#include <linux/regmap.h>
++
++#include <linux/mfd/rtl8231.h>
++
++/**
++ * struct led_toggle_rate - description of an LED blinking mode
++ * @interval_ms:      LED toggle rate in milliseconds
++ * @mode:             Register field value used to activate this mode
++ *
++ * For LED hardware accelerated blinking, with equal on and off delay.
++ * Both delays are given by @interval, so the interval at which the LED blinks
++ * (i.e. turn on and off once) is double this value.
++ */
++struct led_toggle_rate {
++      u16 interval_ms;
++      u8 mode;
++};
++
++/**
++ * struct led_modes - description of all LED modes
++ * @toggle_rates:     Array of led_toggle_rate values, sorted by ascending interval
++ * @num_toggle_rates: Number of elements in @led_toggle_rate
++ * @off:              Register field value to turn LED off
++ * @on:                       Register field value to turn LED on
++ */
++struct led_modes {
++      const struct led_toggle_rate *toggle_rates;
++      unsigned int num_toggle_rates;
++      u8 off;
++      u8 on;
++};
++
++struct rtl8231_led {
++      struct led_classdev led;
++      const struct led_modes *modes;
++      struct regmap_field *reg_field;
++};
++#define to_rtl8231_led(_cdev) container_of(_cdev, struct rtl8231_led, led)
++
++#define RTL8231_NUM_LEDS      3
++#define RTL8231_LED_PER_REG   5
++#define RTL8231_BITS_PER_LED  3
++
++static const unsigned int rtl8231_led_port_counts_single[RTL8231_NUM_LEDS] = {32, 32, 24};
++static const unsigned int rtl8231_led_port_counts_bicolor[RTL8231_NUM_LEDS] = {24, 24, 24};
++
++static const unsigned int rtl8231_led_base[RTL8231_NUM_LEDS] = {
++      RTL8231_REG_LED0_BASE,
++      RTL8231_REG_LED1_BASE,
++      RTL8231_REG_LED2_BASE,
++};
++
++#define RTL8231_DEFAULT_TOGGLE_INTERVAL_MS    500
++
++static const struct led_toggle_rate rtl8231_toggle_rates[] = {
++      {  40, 1},
++      {  80, 2},
++      { 160, 3},
++      { 320, 4},
++      { 640, 5},
++      {1280, 6},
++};
++
++static const struct led_modes rtl8231_led_modes = {
++      .off = 0,
++      .on = 7,
++      .num_toggle_rates = ARRAY_SIZE(rtl8231_toggle_rates),
++      .toggle_rates = rtl8231_toggle_rates,
++};
++
++static void rtl8231_led_brightness_set(struct led_classdev *led_cdev,
++      enum led_brightness brightness)
++{
++      struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
++
++      if (brightness)
++              regmap_field_write(pled->reg_field, pled->modes->on);
++      else
++              regmap_field_write(pled->reg_field, pled->modes->off);
++}
++
++static enum led_brightness rtl8231_led_brightness_get(struct led_classdev *led_cdev)
++{
++      struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
++      u32 current_mode = pled->modes->off;
++
++      regmap_field_read(pled->reg_field, &current_mode);
++
++      if (current_mode == pled->modes->off)
++              return LED_OFF;
++      else
++              return LED_ON;
++}
++
++static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
++{
++      unsigned int mode;
++      unsigned int i;
++
++      if (regmap_field_read(pled->reg_field, &mode))
++              return 0;
++
++      for (i = 0; i < pled->modes->num_toggle_rates; i++)
++              if (mode == pled->modes->toggle_rates[i].mode)
++                      return pled->modes->toggle_rates[i].interval_ms;
++
++      return 0;
++}
++
++static int rtl8231_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
++      unsigned long *delay_off)
++{
++      struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
++      const struct led_toggle_rate *rates = pled->modes->toggle_rates;
++      unsigned int num_rates = pled->modes->num_toggle_rates;
++      unsigned int interval_ms;
++      unsigned int i;
++      int err;
++
++      if (*delay_on == 0 && *delay_off == 0) {
++              interval_ms = RTL8231_DEFAULT_TOGGLE_INTERVAL_MS;
++      } else {
++              /*
++               * If the current mode is blinking, choose the delay that (likely) changed.
++               * Otherwise, choose the interval that would have the same total delay.
++               */
++              interval_ms = rtl8231_led_current_interval(pled);
++              if (interval_ms > 0 && interval_ms == *delay_off)
++                      interval_ms = *delay_on;
++              else if (interval_ms > 0 && interval_ms == *delay_on)
++                      interval_ms = *delay_off;
++              else
++                      interval_ms = (*delay_on + *delay_off) / 2;
++      }
++
++      /* Find clamped toggle interval */
++      for (i = 0; i < (num_rates - 1); i++)
++              if (interval_ms > rates[i].interval_ms)
++                      break;
++
++      interval_ms = rates[i].interval_ms;
++
++      err = regmap_field_write(pled->reg_field, rates[i].mode);
++      if (err)
++              return err;
++
++      *delay_on = interval_ms;
++      *delay_off = interval_ms;
++
++      return 0;
++}
++
++static int rtl8231_led_read_address(struct fwnode_handle *fwnode, unsigned int *addr_port,
++      unsigned int *addr_led)
++{
++      u32 addr[2];
++      int err;
++
++      err = fwnode_property_count_u32(fwnode, "reg");
++      if (err < 0)
++              return err;
++      if (err != ARRAY_SIZE(addr))
++              return -EINVAL;
++
++      err = fwnode_property_read_u32_array(fwnode, "reg", addr, ARRAY_SIZE(addr));
++      if (err)
++              return err;
++
++      *addr_port = addr[0];
++      *addr_led = addr[1];
++
++      return 0;
++}
++
++static const struct regmap_field *rtl8231_led_get_field(struct device *dev, struct regmap *map,
++              unsigned int port_index, unsigned int led_index)
++{
++      unsigned int offset = port_index / RTL8231_LED_PER_REG;
++      unsigned int shift = (port_index % RTL8231_LED_PER_REG) * RTL8231_BITS_PER_LED;
++      const struct reg_field field = REG_FIELD(rtl8231_led_base[led_index] + offset, shift,
++                      shift + RTL8231_BITS_PER_LED - 1);
++
++      return devm_regmap_field_alloc(dev, map, field);
++}
++
++static int rtl8231_led_probe_single(struct device *dev, struct regmap *map,
++      const unsigned int *port_counts, struct fwnode_handle *fwnode)
++{
++      struct led_init_data init_data = {};
++      struct rtl8231_led *pled;
++      unsigned int port_index;
++      unsigned int led_index;
++      int err;
++
++      pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
++      if (!pled)
++              return -ENOMEM;
++
++      err = rtl8231_led_read_address(fwnode, &port_index, &led_index);
++      if (err) {
++              dev_err(dev, "LED address invalid");
++              return err;
++      }
++
++      if (led_index >= RTL8231_NUM_LEDS || port_index >= port_counts[led_index]) {
++              dev_err(dev, "LED address (%d.%d) invalid", port_index, led_index);
++              return -EINVAL;
++      }
++
++      pled->reg_field = rtl8231_led_get_field(dev, map, port_index, led_index);
++      if (IS_ERR(pled->reg_field))
++              return PTR_ERR(pled->reg_field);
++
++      pled->modes = &rtl8231_led_modes;
++
++      pled->led.max_brightness = 1;
++      pled->led.brightness_get = rtl8231_led_brightness_get;
++      pled->led.brightness_set = rtl8231_led_brightness_set;
++      pled->led.blink_set = rtl8231_led_blink_set;
++
++      init_data.fwnode = fwnode;
++
++      return devm_led_classdev_register_ext(dev, &pled->led, &init_data);
++}
++
++static int rtl8231_led_probe(struct platform_device *pdev)
++{
++      struct device *dev = &pdev->dev;
++      const unsigned int *port_counts;
++      struct fwnode_handle *child;
++      struct regmap *map;
++      int err;
++
++      map = dev_get_regmap(dev->parent, NULL);
++      if (!map)
++              return -ENODEV;
++
++      if (device_property_match_string(dev, "realtek,led-scan-mode", "single-color") >= 0) {
++              port_counts = rtl8231_led_port_counts_single;
++              regmap_update_bits(map, RTL8231_REG_FUNC0,
++                      RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
++      } else if (device_property_match_string(dev, "realtek,led-scan-mode", "bi-color") >= 0) {
++              port_counts = rtl8231_led_port_counts_bicolor;
++              regmap_update_bits(map, RTL8231_REG_FUNC0,
++                      RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
++      } else {
++              dev_err(dev, "scan mode missing or invalid");
++              return -EINVAL;
++      }
++
++      fwnode_for_each_available_child_node(dev->fwnode, child) {
++              err = rtl8231_led_probe_single(dev, map, port_counts, child);
++              if (err)
++                      dev_warn(dev, "failed to register LED %pfwP", child);
++      }
++
++      return 0;
++}
++
++static const struct of_device_id of_rtl8231_led_match[] = {
++      { .compatible = "realtek,rtl8231-leds" },
++      {}
++};
++MODULE_DEVICE_TABLE(of, of_rtl8231_led_match);
++
++static struct platform_driver rtl8231_led_driver = {
++      .driver = {
++              .name = "rtl8231-leds",
++              .of_match_table = of_rtl8231_led_match,
++      },
++      .probe = rtl8231_led_probe,
++};
++module_platform_driver(rtl8231_led_driver);
++
++MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
++MODULE_DESCRIPTION("Realtek RTL8231 LED support");
++MODULE_LICENSE("GPL");
index 9364888e47b49b291bafd136d4404bab26cd933c..23f3d9adfb4d9833307644f12ffa82aab4ae34ba 100644 (file)
@@ -132,6 +132,7 @@ CONFIG_MDIO_DEVRES=y
 CONFIG_MDIO_I2C=y
 CONFIG_MDIO_REALTEK_OTTO_AUX=y
 CONFIG_MDIO_SMBUS=y
+# CONFIG_MFD_RTL8231 is not set
 CONFIG_MFD_SYSCON=y
 CONFIG_MIGRATION=y
 CONFIG_MIPS=y
index ebf473db61c814d2293528e88408f15f94702310..2616286a1729a7a6e3d56790fd941cc69d06ac88 100644 (file)
@@ -132,6 +132,7 @@ CONFIG_MDIO_DEVRES=y
 CONFIG_MDIO_I2C=y
 # CONFIG_MDIO_REALTEK_OTTO_AUX is not set
 CONFIG_MDIO_SMBUS=y
+# CONFIG_MFD_RTL8231 is not set
 CONFIG_MFD_SYSCON=y
 CONFIG_MIGRATION=y
 CONFIG_MIPS=y
index 35d088fe08984db0d5743e8f5d48db43713720cf..5b070a8371ca1e1762f4141eb680dc34764edf45 100644 (file)
@@ -114,6 +114,7 @@ CONFIG_MDIO_DEVRES=y
 CONFIG_MDIO_I2C=y
 # CONFIG_MDIO_REALTEK_OTTO_AUX is not set
 CONFIG_MDIO_SMBUS=y
+# CONFIG_MFD_RTL8231 is not set
 CONFIG_MFD_SYSCON=y
 CONFIG_MIGRATION=y
 CONFIG_MIPS=y
index d843382f515448ec929dc9ecf4864de7590c07a0..716d805e51f73f69cf298a0159229834d0a31c88 100644 (file)
@@ -122,6 +122,7 @@ CONFIG_MDIO_DEVRES=y
 CONFIG_MDIO_I2C=y
 # CONFIG_MDIO_REALTEK_OTTO_AUX is not set
 CONFIG_MDIO_SMBUS=y
+# CONFIG_MFD_RTL8231 is not set
 CONFIG_MFD_SYSCON=y
 CONFIG_MIGRATION=y
 CONFIG_MIPS=y