From 6ef6014887c393dc07f0349028d93e4fa82e0733 Mon Sep 17 00:00:00 2001 From: Sander Vanheule Date: Thu, 26 Dec 2024 20:55:16 +0100 Subject: [PATCH] realtek: Add pinctrl support for RTL8231 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 --- ...ap-Bypass-cache-for-shadowed-outputs.patch | 56 ++ ...-regmap-Use-generic-request-free-ops.patch | 26 + .../802-mfd-Add-RTL8231-core-device.patch | 330 ++++++++++ ...RTL8231-pin-control-and-GPIO-support.patch | 581 ++++++++++++++++++ ...-support-for-RTL8231-LED-scan-matrix.patch | 338 ++++++++++ target/linux/realtek/rtl838x/config-6.6 | 1 + target/linux/realtek/rtl839x/config-6.6 | 1 + target/linux/realtek/rtl930x/config-6.6 | 1 + target/linux/realtek/rtl931x/config-6.6 | 1 + 9 files changed, 1335 insertions(+) create mode 100644 target/linux/realtek/patches-6.6/800-gpio-regmap-Bypass-cache-for-shadowed-outputs.patch create mode 100644 target/linux/realtek/patches-6.6/801-gpio-regmap-Use-generic-request-free-ops.patch create mode 100644 target/linux/realtek/patches-6.6/802-mfd-Add-RTL8231-core-device.patch create mode 100644 target/linux/realtek/patches-6.6/803-pinctrl-Add-RTL8231-pin-control-and-GPIO-support.patch create mode 100644 target/linux/realtek/patches-6.6/804-leds-Add-support-for-RTL8231-LED-scan-matrix.patch 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 index 0000000000..b4dbf320b2 --- /dev/null +++ b/target/linux/realtek/patches-6.6/800-gpio-regmap-Bypass-cache-for-shadowed-outputs.patch @@ -0,0 +1,56 @@ +From b3f79468c90d8770f007d628a1e32b2d5d44a5c2 Mon Sep 17 00:00:00 2001 +From: Sander Vanheule +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 +--- + 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 index 0000000000..a06ba7464a --- /dev/null +++ b/target/linux/realtek/patches-6.6/801-gpio-regmap-Use-generic-request-free-ops.patch @@ -0,0 +1,26 @@ +From f21b15dfe254b51f80c552750eb20b1dc752507a Mon Sep 17 00:00:00 2001 +From: Sander Vanheule +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 +--- + 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 index 0000000000..df4d4aa143 --- /dev/null +++ b/target/linux/realtek/patches-6.6/802-mfd-Add-RTL8231-core-device.patch @@ -0,0 +1,330 @@ +From 4e3455e058d40eb2a7326016494e3c81dc506c33 Mon Sep 17 00:00:00 2001 +From: Sander Vanheule +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++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 "); ++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 ++ ++/* ++ * 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 index 0000000000..de0f5ee868 --- /dev/null +++ b/target/linux/realtek/patches-6.6/803-pinctrl-Add-RTL8231-pin-control-and-GPIO-support.patch @@ -0,0 +1,581 @@ +From 098324288a63a6dcc44e96cc381aef3d5c48d89e Mon Sep 17 00:00:00 2001 +From: Sander Vanheule +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "core.h" ++#include "pinmux.h" ++#include ++ ++#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 "); ++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 index 0000000000..37542b8230 --- /dev/null +++ b/target/linux/realtek/patches-6.6/804-leds-Add-support-for-RTL8231-LED-scan-matrix.patch @@ -0,0 +1,338 @@ +From 6b797a97c007e46d6081fc6f4b41ce8407078605 Mon Sep 17 00:00:00 2001 +From: Sander Vanheule +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++/** ++ * 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, ¤t_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 "); ++MODULE_DESCRIPTION("Realtek RTL8231 LED support"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/realtek/rtl838x/config-6.6 b/target/linux/realtek/rtl838x/config-6.6 index 9364888e47..23f3d9adfb 100644 --- a/target/linux/realtek/rtl838x/config-6.6 +++ b/target/linux/realtek/rtl838x/config-6.6 @@ -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 diff --git a/target/linux/realtek/rtl839x/config-6.6 b/target/linux/realtek/rtl839x/config-6.6 index ebf473db61..2616286a17 100644 --- a/target/linux/realtek/rtl839x/config-6.6 +++ b/target/linux/realtek/rtl839x/config-6.6 @@ -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 diff --git a/target/linux/realtek/rtl930x/config-6.6 b/target/linux/realtek/rtl930x/config-6.6 index 35d088fe08..5b070a8371 100644 --- a/target/linux/realtek/rtl930x/config-6.6 +++ b/target/linux/realtek/rtl930x/config-6.6 @@ -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 diff --git a/target/linux/realtek/rtl931x/config-6.6 b/target/linux/realtek/rtl931x/config-6.6 index d843382f51..716d805e51 100644 --- a/target/linux/realtek/rtl931x/config-6.6 +++ b/target/linux/realtek/rtl931x/config-6.6 @@ -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 -- 2.30.2