diff mbox series

[RFC,4/7] realtek: add RTL8380 switch port LED driver

Message ID cc5c08bd233e7cb7f574dca801fc6a1fc31c4ee1.1657998467.git.sander@svanheule.net
State Superseded
Delegated to: Sander Vanheule
Headers show
Series realtek: MFD for switch core | expand

Commit Message

Sander Vanheule July 16, 2022, 7:09 p.m. UTC
RTL8380 switch SoCs can control up to 3×28 LEDs to display status of the
switch's ethernet port. This driver allows to address these LEDs and
provides direct control, blink offloading, and switch port status
offloading.

Since offloading of the generic netdev trigger does not exist, this
driver provides a private trigger which achieves the same and is named
"realtek-switchport". Each LED will have a file name "rtl_hw_trigger" in
/sys/class/leds/$LED, where the requested trigger mode can be written
to. If an unsupported mode is requested, this will be reported to the
user. When the trigger is then activated, the LED will be added to a
group of LEDs with the same trigger conditions. If it is not possible to
add the LED to a group, the user must use an already existing trigger
condition, or use direct LED control with a software trigger.

Some common modes (i.e. values for "rtl_hw_trigger") are:
  - Link present, blink on activity: 1f
  - 100M/10M link, blink on activity: f
  - 1G link present: 10

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 .../drivers/pinctrl/pinctrl-rtl-switchcore.c  | 370 ++++++++++++++++++
 ...s-add-RTL8380-switch-port-LED-driver.patch |  64 +++
 target/linux/realtek/rtl838x/config-5.10      |   1 +
 3 files changed, 435 insertions(+)
 create mode 100644 target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
 create mode 100644 target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-driver.patch

Comments

Olliver Schinagl July 29, 2022, 12:58 p.m. UTC | #1
On 16-07-2022 21:09, Sander Vanheule wrote:
> RTL8380 switch SoCs can control up to 3×28 LEDs to display status of the
> switch's ethernet port. This driver allows to address these LEDs and
> provides direct control, blink offloading, and switch port status
> offloading.
>
> Since offloading of the generic netdev trigger does not exist, this
> driver provides a private trigger which achieves the same and is named
> "realtek-switchport". Each LED will have a file name "rtl_hw_trigger" in
> /sys/class/leds/$LED, where the requested trigger mode can be written
> to. If an unsupported mode is requested, this will be reported to the
> user. When the trigger is then activated, the LED will be added to a
> group of LEDs with the same trigger conditions. If it is not possible to
> add the LED to a group, the user must use an already existing trigger
> condition, or use direct LED control with a software trigger.
>
> Some common modes (i.e. values for "rtl_hw_trigger") are:
>    - Link present, blink on activity: 1f
>    - 100M/10M link, blink on activity: f
>    - 1G link present: 10
>
> Signed-off-by: Sander Vanheule <sander@svanheule.net>
> ---
>   .../drivers/pinctrl/pinctrl-rtl-switchcore.c  | 370 ++++++++++++++++++
>   ...s-add-RTL8380-switch-port-LED-driver.patch |  64 +++
>   target/linux/realtek/rtl838x/config-5.10      |   1 +
>   3 files changed, 435 insertions(+)
>   create mode 100644 target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
>   create mode 100644 target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-driver.patch
>
> diff --git a/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c b/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
> new file mode 100644
> index 000000000000..0caf925989b6
> --- /dev/null
> +++ b/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
> @@ -0,0 +1,370 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <linux/mfd/syscon.h>
for ARRAY_SIZE()

+#include <linux/kernel.h>

> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/pinctrl/pinconf-generic.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"
> +
> +/**
> + * struct rtl_swcore_mux_desc - switchcore pin group information
> + *
> + * Pins are frequently muxed between alternative functions, but the control
> + * bits for the muxes are scattered throught the switchcore's register space.
> + * Provide a regmap-based interface to flexibly manage these mux fields, which
> + * may vary in size and do not always provide a GPIO function.
> + *
> + * @name: name to identify the pin group
> + * @field_desc: description of the register field with mux control bits
> + * @functions: NULL terminated array of function names
> + * @pins: array of numbers of the pins in this group
> + * @npins: number of pins in this group
> + */
> +struct rtl_swcore_mux_desc {
> +	const char *name;
> +	struct reg_field field;
> +	const unsigned int *pins;
> +	unsigned int npins;
> +};
> +
> +#define SWITCHCORE_MUX(_name, _field, _pins)		{	\
> +		.name = (_name),				\
> +		.field = _field,				\
> +		.pins = (_pins),				\
> +		.npins = ARRAY_SIZE(_pins),			\
> +	}
> +
> +/**
> + * struct rtl_swcore_mux_setting - stored mux setting
> + *
> + * @mux: pointer to the mux descriptor
> + * @value: value to write in the mux's register field to apply this setting
> + */
> +struct rtl_swcore_mux_setting {
> +	const struct rtl_swcore_mux_desc *mux;
> +	unsigned int value;
mux_setting? value is so ... undefined ;)
> +};
> +
> +/**
> + * struct rtl_swcore_function_desc - switchcore function information
> + *
> + * @name: name of this function
> + * @settings: list of mux settings that enable this function on said mux
> + * @nsettings: length of the @settings list
> + */
> +struct rtl_swcore_function_desc {
> +	const char *name;
> +	const struct rtl_swcore_mux_setting *settings;
> +	unsigned int nsettings;
> +};
> +
> +#define SWITCHCORE_FUNCTION(_name, _settings)		{	\
> +		.name = (_name),				\
> +		.settings = (_settings),			\
> +		.nsettings = ARRAY_SIZE(_settings),		\
> +	}
> +
> +struct rtl_swcore_config {
> +	const struct pinctrl_pin_desc *pins;
> +	unsigned int npins;
> +	const struct rtl_swcore_function_desc *functions;
> +	unsigned int nfunctions;
> +	const struct rtl_swcore_mux_desc **groups;
> +	unsigned int ngroups;
> +};
> +
> +struct rtl_swcore_pinctrl {
> +	struct pinctrl_desc pdesc;
> +	struct device *dev;
> +	const struct rtl_swcore_config *config;
> +	struct regmap_field **mux_fields;
> +};
> +
> +/*
> + * RTL838x chips come in LQFP packages with 216 pins. Pins are indexed
> + * counter-clockwise, starting with pin 1 at the bottom left.
> + * Map package pin {1..216} to pinctrl pin number {0..215}.
> + */
> +static const struct pinctrl_pin_desc rtl8380_swcore_pins[] = {
> +	/* JTAG pins */
> +	PINCTRL_PIN(27, "tck"),
> +	PINCTRL_PIN(28, "tms"),
> +	PINCTRL_PIN(29, "tdo"),
> +	PINCTRL_PIN(30, "tdi"),
> +	PINCTRL_PIN(31, "ntrst"),
maybe a new line to make grouping more clear
> +	/* aux MDIO bus pins */
Do we have anything other then aux? I mean, these are (external) pins so 
by definition auxiliary. So it's "just" our MDIO bus isn't it
> +	PINCTRL_PIN(109, "aux-mdio"),
> +	PINCTRL_PIN(110, "aux-mdc"),
> +	/* system LED pin */
> +	PINCTRL_PIN(112, "sys-led"),
> +	/* UART1/SPI slave pins */
> +	PINCTRL_PIN(115, "uart1-rx"),
> +	PINCTRL_PIN(116, "uart1-tx"),
you currently name the pads/pins uart1-tx, since we don't have a 
datasheet, is it worth to use something slightly more descriptive or 
generic? I know different soc vendors do different things, some use the 
pin name ("AB1"), some just use the gpioN name, some slash seperated 
strings ("GPIO112/RG2TXD2/DDRV2") or also ("U8 GMAC0 TXC") some exactly 
what you do (both underscore and hyphenized, though i'd use the hyphen 
to connect words, sys-led; underscore to define function uart1_tx (or 
uart1_tx-sp1_slave)? Anyway, there's a LOT of different flavors, and 
_personally_ (but hard for us unless we desolder a SoC to do an analsys) 
I personally a REALLY like the most decriptives ones ("pad name, func1 
... funcn") as it makes it super clear what it is what uses it has, and 
avoids easy confusion (why ist he spi pin named uart)
> +};
> +
> +static const unsigned int rtl8380_jtag_pins[] = {27, 28, 29, 30, 31};
> +static const unsigned int rtl8380_aux_mdio_pins[] = {109, 110};
> +static const unsigned int rtl8380_sys_led_pins[] = {112};
> +static const unsigned int rtl8380_uart1_pins[] = {115, 116};
> +
> +static const struct rtl_swcore_mux_desc rtl8380_mux_jtag =
> +	SWITCHCORE_MUX("jtag", REG_FIELD(0x1000, 2, 3), rtl8380_jtag_pins);
> +
> +static const struct rtl_swcore_mux_desc rtl8380_mux_aux_mdio =
> +	SWITCHCORE_MUX("aux-mdio", REG_FIELD(0xa0e0, 0, 0), rtl8380_aux_mdio_pins);
> +
> +static const struct rtl_swcore_mux_desc rtl8380_mux_sys_led =
> +	SWITCHCORE_MUX("sys-led", REG_FIELD(0xa000, 15, 15), rtl8380_sys_led_pins);
> +
> +static const struct rtl_swcore_mux_desc rtl8380_mux_uart1 =
> +	SWITCHCORE_MUX("uart1", REG_FIELD(0x1000, 4, 4), rtl8380_uart1_pins);
> +
> +static const struct rtl_swcore_mux_desc *rtl8380_groups[] = {
> +	&rtl8380_mux_jtag,
> +	&rtl8380_mux_aux_mdio,
> +	&rtl8380_mux_sys_led,
> +	&rtl8380_mux_uart1,
> +};
> +
> +static const struct rtl_swcore_mux_setting rtl8380_gpio_settings[] = {
> +	{&rtl8380_mux_jtag, 2},
> +	{&rtl8380_mux_aux_mdio, 0},
> +	{&rtl8380_mux_sys_led, 0},
> +};
> +static const struct rtl_swcore_mux_setting rtl8380_aux_mdio_settings[] = {
> +	{&rtl8380_mux_aux_mdio, 1},
> +};
> +static const struct rtl_swcore_mux_setting rtl8380_sys_led_settings[] = {
> +	{&rtl8380_mux_sys_led, 1},
> +};
> +static const struct rtl_swcore_mux_setting rtl8380_uart1_settings[] = {
> +	{&rtl8380_mux_uart1, 1},
> +};
> +static const struct rtl_swcore_mux_setting rtl8380_spi_slave_settings[] = {
> +	{&rtl8380_mux_uart1, 0},
> +};
> +
> +static const struct rtl_swcore_function_desc rtl8380_functions[] = {
> +	SWITCHCORE_FUNCTION("gpio", rtl8380_gpio_settings),
> +	SWITCHCORE_FUNCTION("aux-mdio", rtl8380_aux_mdio_settings),
> +	SWITCHCORE_FUNCTION("sys-led", rtl8380_sys_led_settings),
> +	SWITCHCORE_FUNCTION("uart1", rtl8380_uart1_settings),
> +	SWITCHCORE_FUNCTION("spi-slave", rtl8380_spi_slave_settings),
> +};
> +
> +static const struct rtl_swcore_config rtl8380_config = {
> +	.pins = rtl8380_swcore_pins,
> +	.npins = ARRAY_SIZE(rtl8380_swcore_pins),
> +	.functions = rtl8380_functions,
> +	.nfunctions = ARRAY_SIZE(rtl8380_functions),
> +	.groups = rtl8380_groups,
> +	.ngroups = ARRAY_SIZE(rtl8380_groups),
> +};
> +
> +/*
> + * RTL839x chips are in BGA packages with 26×26 positions. Board designs number
> + * these as 1..26 for the rows, and A..AF for the columns, with position A1 in
> + * the bottom left corner. Letters I, O, Q, S, X, and Z are skipped; presumably
> + * to avoid ambiguities.
> + * This gives a total of 676 positions. Note that not all positions will
> + * actually have a pad, and many pads will be used for power.
> + *
> + * Index pins using (ROW + 26×COL), where ROW and COL mapped as:
> + *   - ROW: {1..26} -> {0..25}
> + *   - COL: {A..AF} -> {0..25}
> + *
> + * Since there are no datasheets available, use a virtual pin range starting at
> + * 676 for pins with unknowns positions. When actual pin positions are found
> + * (if ever), these can the be mapped to their real values.
> + */
> +#define RTL8390_VPIN(num, name)		PINCTRL_PIN(26 * 26 + (num), (name))
this is unused code though, right? (i get the RFC nature here)
> +
> +/* TODO RTL8390 */
> +
> +/* TODO RTL9300 */
> +
> +/* TODO RTL9310 */
> +
> +static int rtl_swcore_group_count(struct pinctrl_dev *pctldev)
> +{
> +	struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
> +
> +	return priv->config->ngroups;
> +}
> +
> +static const char * rtl_swcore_group_name(struct pinctrl_dev *pctldev,
> +	unsigned int selector)
> +{
> +	struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
> +
> +	return priv->config->groups[selector]->name;
> +};
> +
> +static int rtl_swcore_group_pins(struct pinctrl_dev *pctldev,
> +	unsigned int selector, const unsigned int **pins,
> +	unsigned int *num_pins)
> +{
> +	struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
> +
> +	*pins = priv->config->groups[selector]->pins;
> +	*num_pins = priv->config->groups[selector]->npins;
> +
> +	return 0;
> +}
> +
> +static int rtl_swcore_set_mux(struct pinctrl_dev *pctldev,
> +	unsigned int selector, unsigned int group)
> +{
> +	struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
> +	const struct rtl_swcore_function_desc *function;
> +	const struct rtl_swcore_mux_setting *setting;
you sure you do't want to scope anything in the for/if part?
> +	const struct rtl_swcore_mux_desc *mux;
> +	unsigned int i;
> +
> +	dev_info(priv->dev, "requesting selector %d, group %d\n", selector, group);
> +
> +	function = &priv->config->functions[selector];
> +	mux = priv->config->groups[group];
> +
> +	for (i = 0; i < function->nsettings; i++) {
> +		setting = &function->settings[i];
> +		if (setting->mux == mux) {
> +			dev_info(priv->dev, "set mux %s to function %s (%d)\n",
> +				mux->name, function->name, setting->value);
> +			return regmap_field_write(priv->mux_fields[group], setting->value);
i'm generally not a fan of these 'deeply' hidden return statements; 
maybe do a goto to jump after the enodev error?
> +		}
> +	}
> +
> +	/* Should never hit this, unless something was misconfigured */
dev_err(dev, "something was miss-configured\n");
> +	return -ENODEV;
> +}
> +
> +static const struct pinctrl_ops rtl_swcore_pinctrl_ops = {
> +	.get_groups_count = rtl_swcore_group_count,
> +	.get_group_name = rtl_swcore_group_name,
> +	.get_group_pins = rtl_swcore_group_pins,
> +	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
> +	.dt_free_map = pinconf_generic_dt_free_map,
> +};
> +
> +static const struct pinmux_ops rtl_swcore_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 = rtl_swcore_set_mux,
> +	.strict = true,
> +};
> +
> +static int rtl_swcore_functions_init(struct pinctrl_dev *pctl, struct rtl_swcore_pinctrl *priv)
> +{
> +	const struct rtl_swcore_function_desc *function;
> +	unsigned int ngroups;
> +	const char **groups;
> +	unsigned int f_idx;
> +	unsigned int g_idx;
> +
> +	for (f_idx = 0; f_idx < priv->config->nfunctions; f_idx++) {
> +		function = &priv->config->functions[f_idx];
> +		ngroups = function->nsettings;
> +
> +		dev_info(priv->dev, "found %d groups for function %s\n", ngroups, function->name);
shouldn't this be after the add? we THINK all of this will work; but not 
until after the add we know we actually found them. Can the add function 
even fail btw?
> +
> +		groups = devm_kcalloc(priv->dev, ngroups, sizeof(*groups), GFP_KERNEL);
> +		if (!groups)
> +			return -ENOMEM;
> +
> +		for (g_idx = 0; g_idx < ngroups; g_idx++)
> +			groups[g_idx] = function->settings[g_idx].mux->name;
> +
> +		pinmux_generic_add_function(pctl, function->name, groups, ngroups, NULL);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id of_rtl_swcore_pinctrl_match[] = {
> +	{
> +		.compatible = "realtek,rtl8380-pinctrl",
> +		.data = &rtl8380_config,
> +	},
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, of_rtl_swcore_pinctrl_match);
> +
> +static int rtl_swcore_pinctrl_probe(struct platform_device *pdev)
> +{
> +	const struct rtl_swcore_config *config;
> +	struct rtl_swcore_pinctrl *priv;
> +	struct device *dev = &pdev->dev;
> +	struct pinctrl_dev *pctldev;
> +	struct regmap_field *field;
> +	struct regmap *regmap;
> +	int mux;
> +	int err;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	config = of_device_get_match_data(dev);
> +	if (!config)
> +		return dev_err_probe(dev, -EINVAL, "no config\n");

+		return dev_err_probe(dev, -EINVAL, "unable to match compatible from devicetree\n");

> +
> +	/* FIXME of_get_parent(dev_of_node(dev)) OR dev_of_node(dev->parent) */
is there even a functional difference between the two? I like what you 
have now, as that doesn't access struct members
> +	regmap = device_node_to_regmap(of_get_parent(dev_of_node(dev)));
> +	if (IS_ERR(regmap))
> +		return dev_err_probe(dev, PTR_ERR(regmap), "failed to find parent regmap\n");
> +
> +	priv->dev = dev;
> +	priv->config = config;
> +	priv->pdesc.name = "realtek-switchcore-pinctrl";
> +	priv->pdesc.owner = THIS_MODULE;
> +	priv->pdesc.pctlops = &rtl_swcore_pinctrl_ops;
> +	priv->pdesc.pmxops = &rtl_swcore_pinmux_ops;
> +	priv->pdesc.pins = config->pins;
> +	priv->pdesc.npins = config->npins;
> +
> +	priv->mux_fields = devm_kcalloc(dev, config->ngroups, sizeof(*priv->mux_fields),
> +		GFP_KERNEL);
> +	if (!priv->mux_fields)
> +		return -ENOMEM;
> +
> +	for (mux = 0; mux < config->ngroups; mux++) {
> +		field = devm_regmap_field_alloc(dev, regmap, config->groups[mux]->field);
> +		if (IS_ERR(field))
> +			return PTR_ERR(field);
> +
> +		priv->mux_fields[mux] = field;
> +	}
> +
> +	err = devm_pinctrl_register_and_init(dev, &priv->pdesc, priv, &pctldev);
> +	if (err)
> +		return dev_err_probe(dev, err, "failed to register\n");
> +
> +	err = rtl_swcore_functions_init(pctldev, priv);
> +	if (err)
> +		return dev_err_probe(dev, err, "failed to generate function list\n");
> +
> +	err = pinctrl_enable(pctldev);
> +	if (err)
> +		return dev_err_probe(dev, err, "failed to enable\n");
> +
> +	return 0;
> +};
> +
> +static struct platform_driver rtl_swcore_pinctrl_driver = {
> +	.driver = {
> +		.name = "realtek-switchcore-pinctrl",
> +		.of_match_table = of_rtl_swcore_pinctrl_match
> +	},
> +	.probe = rtl_swcore_pinctrl_probe,
> +};
> +module_platform_driver(rtl_swcore_pinctrl_driver);
> diff --git a/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-driver.patch b/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-driver.patch
> new file mode 100644
> index 000000000000..5da5a01cdb48
> --- /dev/null
> +++ b/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-driver.patch
> @@ -0,0 +1,64 @@
> +From d71ec8184236356c50088b00b2417fb142e72bd9 Mon Sep 17 00:00:00 2001
> +From: Sander Vanheule <sander@svanheule.net>
> +Date: Sun, 10 Jul 2022 11:31:53 +0200
> +Subject: [PATCH 03/14] leds: add RTL8380 switch port LED driver
> +MIME-Version: 1.0
> +Content-Type: text/plain; charset=UTF-8
> +Content-Transfer-Encoding: 8bit
> +
> +RTL8380 switch SoCs can control up to 3×28 LEDs to display status of the
> +switch's ethernet port. This driver allows to address these LEDs and
> +provides direct control, blink offloading, and switch port status
> +offloading.
> +
> +Since offloading of the generic netdev trigger does not exist, this
> +driver provides a private trigger which achieves the same and is named
> +"realtek-switchport". Each LED will have a file name "rtl_hw_trigger" in
> +/sys/class/leds/$LED, where the requested trigger mode can be written
> +to. If an unsupported mode is requested, this will be reported to the
> +user. When the trigger is then activated, the LED will be added to a
> +group of LEDs with the same trigger conditions. If it is not possible to
> +add the LED to a group, the user must use an already existing trigger
> +condition, or use direct LED control with a software trigger.
> +
> +Some common modes (i.e. values for "rtl_hw_trigger") are:
> +  - Link present, blink on activity: 1f
> +  - 100M/10M link, blink on activity: f
> +  - 1G link present: 10
> +
> +Signed-off-by: Sander Vanheule <sander@svanheule.net>
> +---
> + drivers/leds/Kconfig                |   9 +
> + drivers/leds/Makefile               |   1 +
> + drivers/leds/leds-rtl-switch-port.c | 668 ++++++++++++++++++++++++++++
> + 3 files changed, 678 insertions(+)
> + create mode 100644 drivers/leds/leds-rtl-switch-port.c
> +
> +--- a/drivers/leds/Kconfig
> ++++ b/drivers/leds/Kconfig
> +@@ -594,6 +594,15 @@ config LEDS_REGULATOR
> + 	help
> + 	  This option enables support for regulator driven LEDs.
> +
> ++config LEDS_RTL_SWITCH_PORT
> ++	tristate "Realtek SoC switch port LED support"
> ++	depends on LEDS_CLASS
> ++	depends on MFD_REALTEK_SWITCHCORE || COMPILE_TEST
> ++	select MFD_SYSCON
> ++	select REGMAP_MMIO
> ++	help
> ++	  This option enables support for Realtek switch SoC port LEDs.
> ++
> + config LEDS_BD2802
> + 	tristate "LED driver for BD2802 RGB LED"
> + 	depends on LEDS_CLASS
> +--- a/drivers/leds/Makefile
> ++++ b/drivers/leds/Makefile
> +@@ -80,6 +80,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_RTL_SWITCH_PORT)	+= leds-rtl-switch-port.o
> + obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
> + obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
> + obj-$(CONFIG_LEDS_SGM3140)		+= leds-sgm3140.o
> diff --git a/target/linux/realtek/rtl838x/config-5.10 b/target/linux/realtek/rtl838x/config-5.10
> index 5c69832be41d..a28366a7fd03 100644
> --- a/target/linux/realtek/rtl838x/config-5.10
> +++ b/target/linux/realtek/rtl838x/config-5.10
> @@ -164,6 +164,7 @@ CONFIG_PGTABLE_LEVELS=2
>   CONFIG_PHYLIB=y
>   CONFIG_PHYLINK=y
>   CONFIG_PINCTRL=y
> +CONFIG_PINCTRL_RTL_SWITCHCORE=y
>   CONFIG_POWER_RESET=y
>   CONFIG_POWER_RESET_GPIO_RESTART=y
>   CONFIG_POWER_RESET_SYSCON=y
>
Sander Vanheule July 29, 2022, 10:02 p.m. UTC | #2
On Fri, 2022-07-29 at 14:58 +0200, Olliver Schinagl wrote:
> On 16-07-2022 21:09, Sander Vanheule wrote:
> > RTL8380 switch SoCs can control up to 3×28 LEDs to display status of the
> > switch's ethernet port. This driver allows to address these LEDs and
> > provides direct control, blink offloading, and switch port status
> > offloading.
> > 
> > Since offloading of the generic netdev trigger does not exist, this
> > driver provides a private trigger which achieves the same and is named
> > "realtek-switchport". Each LED will have a file name "rtl_hw_trigger" in
> > /sys/class/leds/$LED, where the requested trigger mode can be written
> > to. If an unsupported mode is requested, this will be reported to the
> > user. When the trigger is then activated, the LED will be added to a
> > group of LEDs with the same trigger conditions. If it is not possible to
> > add the LED to a group, the user must use an already existing trigger
> > condition, or use direct LED control with a software trigger.
> > 
> > Some common modes (i.e. values for "rtl_hw_trigger") are:
> >    - Link present, blink on activity: 1f
> >    - 100M/10M link, blink on activity: f
> >    - 1G link present: 10
> > 
> > Signed-off-by: Sander Vanheule <sander@svanheule.net>
> > ---
> >   .../drivers/pinctrl/pinctrl-rtl-switchcore.c  | 370 ++++++++++++++++++
> >   ...s-add-RTL8380-switch-port-LED-driver.patch |  64 +++
> >   target/linux/realtek/rtl838x/config-5.10      |   1 +
> >   3 files changed, 435 insertions(+)
> >   create mode 100644 target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-
> > switchcore.c
> >   create mode 100644 target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-
> > port-LED-driver.patch
> > 
> > diff --git a/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
> > b/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
> > new file mode 100644
> > index 000000000000..0caf925989b6
> > --- /dev/null
> > +++ b/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
> > @@ -0,0 +1,370 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +#include <linux/mfd/syscon.h>
> for ARRAY_SIZE()
> 
> +#include <linux/kernel.h>
> 

Will add.

> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_platform.h>
> > +#include <linux/pinctrl/pinconf-generic.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"
> > +
> > +/**
> > + * struct rtl_swcore_mux_desc - switchcore pin group information
> > + *
> > + * Pins are frequently muxed between alternative functions, but the control
> > + * bits for the muxes are scattered throught the switchcore's register space.
> > + * Provide a regmap-based interface to flexibly manage these mux fields, which
> > + * may vary in size and do not always provide a GPIO function.
> > + *
> > + * @name: name to identify the pin group
> > + * @field_desc: description of the register field with mux control bits
> > + * @functions: NULL terminated array of function names
> > + * @pins: array of numbers of the pins in this group
> > + * @npins: number of pins in this group
> > + */
> > +struct rtl_swcore_mux_desc {
> > +       const char *name;
> > +       struct reg_field field;
> > +       const unsigned int *pins;
> > +       unsigned int npins;
> > +};
> > +
> > +#define SWITCHCORE_MUX(_name, _field, _pins)           {       \
> > +               .name = (_name),                                \
> > +               .field = _field,                                \
> > +               .pins = (_pins),                                \
> > +               .npins = ARRAY_SIZE(_pins),                     \
> > +       }
> > +
> > +/**
> > + * struct rtl_swcore_mux_setting - stored mux setting
> > + *
> > + * @mux: pointer to the mux descriptor
> > + * @value: value to write in the mux's register field to apply this setting
> > + */
> > +struct rtl_swcore_mux_setting {
> > +       const struct rtl_swcore_mux_desc *mux;
> > +       unsigned int value;
> mux_setting? value is so ... undefined ;)

That's because it is very generic, and just contains a register field value
The struct is already named mux_setting and mux_setting->mux_setting is too much
redundancy IMHO.

> > +};
> > +
> > +/**
> > + * struct rtl_swcore_function_desc - switchcore function information
> > + *
> > + * @name: name of this function
> > + * @settings: list of mux settings that enable this function on said mux
> > + * @nsettings: length of the @settings list
> > + */
> > +struct rtl_swcore_function_desc {
> > +       const char *name;
> > +       const struct rtl_swcore_mux_setting *settings;
> > +       unsigned int nsettings;
> > +};
> > +
> > +#define SWITCHCORE_FUNCTION(_name, _settings)          {       \
> > +               .name = (_name),                                \
> > +               .settings = (_settings),                        \
> > +               .nsettings = ARRAY_SIZE(_settings),             \
> > +       }
> > +
> > +struct rtl_swcore_config {
> > +       const struct pinctrl_pin_desc *pins;
> > +       unsigned int npins;
> > +       const struct rtl_swcore_function_desc *functions;
> > +       unsigned int nfunctions;
> > +       const struct rtl_swcore_mux_desc **groups;
> > +       unsigned int ngroups;
> > +};
> > +
> > +struct rtl_swcore_pinctrl {
> > +       struct pinctrl_desc pdesc;
> > +       struct device *dev;
> > +       const struct rtl_swcore_config *config;
> > +       struct regmap_field **mux_fields;
> > +};
> > +
> > +/*
> > + * RTL838x chips come in LQFP packages with 216 pins. Pins are indexed
> > + * counter-clockwise, starting with pin 1 at the bottom left.
> > + * Map package pin {1..216} to pinctrl pin number {0..215}.
> > + */
> > +static const struct pinctrl_pin_desc rtl8380_swcore_pins[] = {
> > +       /* JTAG pins */
> > +       PINCTRL_PIN(27, "tck"),
> > +       PINCTRL_PIN(28, "tms"),
> > +       PINCTRL_PIN(29, "tdo"),
> > +       PINCTRL_PIN(30, "tdi"),
> > +       PINCTRL_PIN(31, "ntrst"),
> maybe a new line to make grouping more clear
> > +       /* aux MDIO bus pins */
> Do we have anything other then aux? I mean, these are (external) pins so 
> by definition auxiliary. So it's "just" our MDIO bus isn't it

As explained in the other patch, this isn't for the main MDIO master. I don't even know if
the other MDIO master can be muxed as GPIO (or something else).

> > +       PINCTRL_PIN(109, "aux-mdio"),
> > +       PINCTRL_PIN(110, "aux-mdc"),
> > +       /* system LED pin */
> > +       PINCTRL_PIN(112, "sys-led"),
> > +       /* UART1/SPI slave pins */
> > +       PINCTRL_PIN(115, "uart1-rx"),
> > +       PINCTRL_PIN(116, "uart1-tx"),
> you currently name the pads/pins uart1-tx, since we don't have a 
> datasheet, is it worth to use something slightly more descriptive or 
> generic? I know different soc vendors do different things, some use the 
> pin name ("AB1"), some just use the gpioN name, some slash seperated 
> strings ("GPIO112/RG2TXD2/DDRV2") or also ("U8 GMAC0 TXC") some exactly 
> what you do (both underscore and hyphenized, though i'd use the hyphen 
> to connect words, sys-led; underscore to define function uart1_tx (or 
> uart1_tx-sp1_slave)? Anyway, there's a LOT of different flavors, and 
> _personally_ (but hard for us unless we desolder a SoC to do an analsys) 
> I personally a REALLY like the most decriptives ones ("pad name, func1 
> ... funcn") as it makes it super clear what it is what uses it has, and 
> avoids easy confusion (why ist he spi pin named uart)

But that's some many characters! As long as there's only a few functions (that aren't
gpio), we could put them all in the name. Pin index is already provided by the pin number,
so we don't have to duplicate that here.

> > +};
> > +
> > +static const unsigned int rtl8380_jtag_pins[] = {27, 28, 29, 30, 31};
> > +static const unsigned int rtl8380_aux_mdio_pins[] = {109, 110};
> > +static const unsigned int rtl8380_sys_led_pins[] = {112};
> > +static const unsigned int rtl8380_uart1_pins[] = {115, 116};
> > +
> > +static const struct rtl_swcore_mux_desc rtl8380_mux_jtag =
> > +       SWITCHCORE_MUX("jtag", REG_FIELD(0x1000, 2, 3), rtl8380_jtag_pins);
> > +
> > +static const struct rtl_swcore_mux_desc rtl8380_mux_aux_mdio =
> > +       SWITCHCORE_MUX("aux-mdio", REG_FIELD(0xa0e0, 0, 0), rtl8380_aux_mdio_pins);
> > +
> > +static const struct rtl_swcore_mux_desc rtl8380_mux_sys_led =
> > +       SWITCHCORE_MUX("sys-led", REG_FIELD(0xa000, 15, 15), rtl8380_sys_led_pins);
> > +
> > +static const struct rtl_swcore_mux_desc rtl8380_mux_uart1 =
> > +       SWITCHCORE_MUX("uart1", REG_FIELD(0x1000, 4, 4), rtl8380_uart1_pins);
> > +
> > +static const struct rtl_swcore_mux_desc *rtl8380_groups[] = {
> > +       &rtl8380_mux_jtag,
> > +       &rtl8380_mux_aux_mdio,
> > +       &rtl8380_mux_sys_led,
> > +       &rtl8380_mux_uart1,
> > +};
> > +
> > +static const struct rtl_swcore_mux_setting rtl8380_gpio_settings[] = {
> > +       {&rtl8380_mux_jtag, 2},
> > +       {&rtl8380_mux_aux_mdio, 0},
> > +       {&rtl8380_mux_sys_led, 0},
> > +};
> > +static const struct rtl_swcore_mux_setting rtl8380_aux_mdio_settings[] = {
> > +       {&rtl8380_mux_aux_mdio, 1},
> > +};
> > +static const struct rtl_swcore_mux_setting rtl8380_sys_led_settings[] = {
> > +       {&rtl8380_mux_sys_led, 1},
> > +};
> > +static const struct rtl_swcore_mux_setting rtl8380_uart1_settings[] = {
> > +       {&rtl8380_mux_uart1, 1},
> > +};
> > +static const struct rtl_swcore_mux_setting rtl8380_spi_slave_settings[] = {
> > +       {&rtl8380_mux_uart1, 0},
> > +};
> > +
> > +static const struct rtl_swcore_function_desc rtl8380_functions[] = {
> > +       SWITCHCORE_FUNCTION("gpio", rtl8380_gpio_settings),
> > +       SWITCHCORE_FUNCTION("aux-mdio", rtl8380_aux_mdio_settings),
> > +       SWITCHCORE_FUNCTION("sys-led", rtl8380_sys_led_settings),
> > +       SWITCHCORE_FUNCTION("uart1", rtl8380_uart1_settings),
> > +       SWITCHCORE_FUNCTION("spi-slave", rtl8380_spi_slave_settings),
> > +};
> > +
> > +static const struct rtl_swcore_config rtl8380_config = {
> > +       .pins = rtl8380_swcore_pins,
> > +       .npins = ARRAY_SIZE(rtl8380_swcore_pins),
> > +       .functions = rtl8380_functions,
> > +       .nfunctions = ARRAY_SIZE(rtl8380_functions),
> > +       .groups = rtl8380_groups,
> > +       .ngroups = ARRAY_SIZE(rtl8380_groups),
> > +};
> > +
> > +/*
> > + * RTL839x chips are in BGA packages with 26×26 positions. Board designs number
> > + * these as 1..26 for the rows, and A..AF for the columns, with position A1 in
> > + * the bottom left corner. Letters I, O, Q, S, X, and Z are skipped; presumably
> > + * to avoid ambiguities.
> > + * This gives a total of 676 positions. Note that not all positions will
> > + * actually have a pad, and many pads will be used for power.
> > + *
> > + * Index pins using (ROW + 26×COL), where ROW and COL mapped as:
> > + *   - ROW: {1..26} -> {0..25}
> > + *   - COL: {A..AF} -> {0..25}
> > + *
> > + * Since there are no datasheets available, use a virtual pin range starting at
> > + * 676 for pins with unknowns positions. When actual pin positions are found
> > + * (if ever), these can the be mapped to their real values.
> > + */
> > +#define RTL8390_VPIN(num, name)                PINCTRL_PIN(26 * 26 + (num), (name))
> this is unused code though, right? (i get the RFC nature here)

It will get used in v2. Didn't feel like stripping it out for the RFC, but sorry for the
confusion it caused.

> > +
> > +/* TODO RTL8390 */
> > +
> > +/* TODO RTL9300 */
> > +
> > +/* TODO RTL9310 */
> > +
> > +static int rtl_swcore_group_count(struct pinctrl_dev *pctldev)
> > +{
> > +       struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
> > +
> > +       return priv->config->ngroups;
> > +}
> > +
> > +static const char * rtl_swcore_group_name(struct pinctrl_dev *pctldev,
> > +       unsigned int selector)
> > +{
> > +       struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
> > +
> > +       return priv->config->groups[selector]->name;
> > +};
> > +
> > +static int rtl_swcore_group_pins(struct pinctrl_dev *pctldev,
> > +       unsigned int selector, const unsigned int **pins,
> > +       unsigned int *num_pins)
> > +{
> > +       struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
> > +
> > +       *pins = priv->config->groups[selector]->pins;
> > +       *num_pins = priv->config->groups[selector]->npins;
> > +
> > +       return 0;
> > +}
> > +
> > +static int rtl_swcore_set_mux(struct pinctrl_dev *pctldev,
> > +       unsigned int selector, unsigned int group)
> > +{
> > +       struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
> > +       const struct rtl_swcore_function_desc *function;
> > +       const struct rtl_swcore_mux_setting *setting;
> you sure you do't want to scope anything in the for/if part?

Can we do that now? I also prefer keeping the scope of things limited, but the old C
standard didn't use to allow for that.

> > +       const struct rtl_swcore_mux_desc *mux;
> > +       unsigned int i;
> > +
> > +       dev_info(priv->dev, "requesting selector %d, group %d\n", selector, group);
> > +
> > +       function = &priv->config->functions[selector];
> > +       mux = priv->config->groups[group];
> > +
> > +       for (i = 0; i < function->nsettings; i++) {
> > +               setting = &function->settings[i];
> > +               if (setting->mux == mux) {
> > +                       dev_info(priv->dev, "set mux %s to function %s (%d)\n",
> > +                               mux->name, function->name, setting->value);
> > +                       return regmap_field_write(priv->mux_fields[group], setting-
> > >value);
> i'm generally not a fan of these 'deeply' hidden return statements; 
> maybe do a goto to jump after the enodev error?

I get that you're not a fan of this, but I don't think a jump would be much better.

Maybe this is better (definitely not tested):

while (i < function->nsettings && function->settings[i].mux != mux)
	i++;

if (i == function->nsettings)
	return -ENODEV;

return regmap_field_write(priv->mux_fields[group], function->settings[i].value);



> > +               }
> > +       }
> > +
> > +       /* Should never hit this, unless something was misconfigured */
> dev_err(dev, "something was miss-configured\n");
> > +       return -ENODEV;
> > +}
> > +
> > +static const struct pinctrl_ops rtl_swcore_pinctrl_ops = {
> > +       .get_groups_count = rtl_swcore_group_count,
> > +       .get_group_name = rtl_swcore_group_name,
> > +       .get_group_pins = rtl_swcore_group_pins,
> > +       .dt_node_to_map = pinconf_generic_dt_node_to_map_all,
> > +       .dt_free_map = pinconf_generic_dt_free_map,
> > +};
> > +
> > +static const struct pinmux_ops rtl_swcore_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 = rtl_swcore_set_mux,
> > +       .strict = true,
> > +};
> > +
> > +static int rtl_swcore_functions_init(struct pinctrl_dev *pctl, struct
> > rtl_swcore_pinctrl *priv)
> > +{
> > +       const struct rtl_swcore_function_desc *function;
> > +       unsigned int ngroups;
> > +       const char **groups;
> > +       unsigned int f_idx;
> > +       unsigned int g_idx;
> > +
> > +       for (f_idx = 0; f_idx < priv->config->nfunctions; f_idx++) {
> > +               function = &priv->config->functions[f_idx];
> > +               ngroups = function->nsettings;
> > +
> > +               dev_info(priv->dev, "found %d groups for function %s\n", ngroups,
> > function->name);
> shouldn't this be after the add? we THINK all of this will work; but not 
> until after the add we know we actually found them. Can the add function 
> even fail btw?

Development print from times where I wasn't using pinmux_generic_add_function(). Can just
be removed I guess.

> > +
> > +               groups = devm_kcalloc(priv->dev, ngroups, sizeof(*groups),
> > GFP_KERNEL);
> > +               if (!groups)
> > +                       return -ENOMEM;
> > +
> > +               for (g_idx = 0; g_idx < ngroups; g_idx++)
> > +                       groups[g_idx] = function->settings[g_idx].mux->name;
> > +
> > +               pinmux_generic_add_function(pctl, function->name, groups, ngroups,
> > NULL);
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct of_device_id of_rtl_swcore_pinctrl_match[] = {
> > +       {
> > +               .compatible = "realtek,rtl8380-pinctrl",
> > +               .data = &rtl8380_config,
> > +       },
> > +       { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, of_rtl_swcore_pinctrl_match);
> > +
> > +static int rtl_swcore_pinctrl_probe(struct platform_device *pdev)
> > +{
> > +       const struct rtl_swcore_config *config;
> > +       struct rtl_swcore_pinctrl *priv;
> > +       struct device *dev = &pdev->dev;
> > +       struct pinctrl_dev *pctldev;
> > +       struct regmap_field *field;
> > +       struct regmap *regmap;
> > +       int mux;
> > +       int err;
> > +
> > +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > +       if (!priv)
> > +               return -ENOMEM;
> > +
> > +       config = of_device_get_match_data(dev);
> > +       if (!config)
> > +               return dev_err_probe(dev, -EINVAL, "no config\n");
> 
> +               return dev_err_probe(dev, -EINVAL, "unable to match compatible from
> devicetree\n");

Will improve the log statement

> 
> > +
> > +       /* FIXME of_get_parent(dev_of_node(dev)) OR dev_of_node(dev->parent) */
> is there even a functional difference between the two? I like what you 
> have now, as that doesn't access struct members

The first takes the parent going through the OF layer, the later going through the device
layer. I wouldn't assume these to always be identical. However, since we actually want the
DT parent, the current statement should be the right thing.

Thanks,
Sander

> > +       regmap = device_node_to_regmap(of_get_parent(dev_of_node(dev)));
> > +       if (IS_ERR(regmap))
> > +               return dev_err_probe(dev, PTR_ERR(regmap), "failed to find parent
> > regmap\n");
> > +
> > +       priv->dev = dev;
> > +       priv->config = config;
> > +       priv->pdesc.name = "realtek-switchcore-pinctrl";
> > +       priv->pdesc.owner = THIS_MODULE;
> > +       priv->pdesc.pctlops = &rtl_swcore_pinctrl_ops;
> > +       priv->pdesc.pmxops = &rtl_swcore_pinmux_ops;
> > +       priv->pdesc.pins = config->pins;
> > +       priv->pdesc.npins = config->npins;
> > +
> > +       priv->mux_fields = devm_kcalloc(dev, config->ngroups, sizeof(*priv-
> > >mux_fields),
> > +               GFP_KERNEL);
> > +       if (!priv->mux_fields)
> > +               return -ENOMEM;
> > +
> > +       for (mux = 0; mux < config->ngroups; mux++) {
> > +               field = devm_regmap_field_alloc(dev, regmap, config->groups[mux]-
> > >field);
> > +               if (IS_ERR(field))
> > +                       return PTR_ERR(field);
> > +
> > +               priv->mux_fields[mux] = field;
> > +       }
> > +
> > +       err = devm_pinctrl_register_and_init(dev, &priv->pdesc, priv, &pctldev);
> > +       if (err)
> > +               return dev_err_probe(dev, err, "failed to register\n");
> > +
> > +       err = rtl_swcore_functions_init(pctldev, priv);
> > +       if (err)
> > +               return dev_err_probe(dev, err, "failed to generate function list\n");
> > +
> > +       err = pinctrl_enable(pctldev);
> > +       if (err)
> > +               return dev_err_probe(dev, err, "failed to enable\n");
> > +
> > +       return 0;
> > +};
> > +
> > +static struct platform_driver rtl_swcore_pinctrl_driver = {
> > +       .driver = {
> > +               .name = "realtek-switchcore-pinctrl",
> > +               .of_match_table = of_rtl_swcore_pinctrl_match
> > +       },
> > +       .probe = rtl_swcore_pinctrl_probe,
> > +};
> > +module_platform_driver(rtl_swcore_pinctrl_driver);
> > diff --git a/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-
> > driver.patch b/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-
> > driver.patch
> > new file mode 100644
> > index 000000000000..5da5a01cdb48
> > --- /dev/null
> > +++ b/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-
> > driver.patch
> > @@ -0,0 +1,64 @@
> > +From d71ec8184236356c50088b00b2417fb142e72bd9 Mon Sep 17 00:00:00 2001
> > +From: Sander Vanheule <sander@svanheule.net>
> > +Date: Sun, 10 Jul 2022 11:31:53 +0200
> > +Subject: [PATCH 03/14] leds: add RTL8380 switch port LED driver
> > +MIME-Version: 1.0
> > +Content-Type: text/plain; charset=UTF-8
> > +Content-Transfer-Encoding: 8bit
> > +
> > +RTL8380 switch SoCs can control up to 3×28 LEDs to display status of the
> > +switch's ethernet port. This driver allows to address these LEDs and
> > +provides direct control, blink offloading, and switch port status
> > +offloading.
> > +
> > +Since offloading of the generic netdev trigger does not exist, this
> > +driver provides a private trigger which achieves the same and is named
> > +"realtek-switchport". Each LED will have a file name "rtl_hw_trigger" in
> > +/sys/class/leds/$LED, where the requested trigger mode can be written
> > +to. If an unsupported mode is requested, this will be reported to the
> > +user. When the trigger is then activated, the LED will be added to a
> > +group of LEDs with the same trigger conditions. If it is not possible to
> > +add the LED to a group, the user must use an already existing trigger
> > +condition, or use direct LED control with a software trigger.
> > +
> > +Some common modes (i.e. values for "rtl_hw_trigger") are:
> > +  - Link present, blink on activity: 1f
> > +  - 100M/10M link, blink on activity: f
> > +  - 1G link present: 10
> > +
> > +Signed-off-by: Sander Vanheule <sander@svanheule.net>
> > +---
> > + drivers/leds/Kconfig                |   9 +
> > + drivers/leds/Makefile               |   1 +
> > + drivers/leds/leds-rtl-switch-port.c | 668 ++++++++++++++++++++++++++++
> > + 3 files changed, 678 insertions(+)
> > + create mode 100644 drivers/leds/leds-rtl-switch-port.c
> > +
> > +--- a/drivers/leds/Kconfig
> > ++++ b/drivers/leds/Kconfig
> > +@@ -594,6 +594,15 @@ config LEDS_REGULATOR
> > +       help
> > +         This option enables support for regulator driven LEDs.
> > +
> > ++config LEDS_RTL_SWITCH_PORT
> > ++      tristate "Realtek SoC switch port LED support"
> > ++      depends on LEDS_CLASS
> > ++      depends on MFD_REALTEK_SWITCHCORE || COMPILE_TEST
> > ++      select MFD_SYSCON
> > ++      select REGMAP_MMIO
> > ++      help
> > ++        This option enables support for Realtek switch SoC port LEDs.
> > ++
> > + config LEDS_BD2802
> > +       tristate "LED driver for BD2802 RGB LED"
> > +       depends on LEDS_CLASS
> > +--- a/drivers/leds/Makefile
> > ++++ b/drivers/leds/Makefile
> > +@@ -80,6 +80,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_RTL_SWITCH_PORT)    += leds-rtl-switch-port.o
> > + obj-$(CONFIG_LEDS_S3C24XX)            += leds-s3c24xx.o
> > + obj-$(CONFIG_LEDS_SC27XX_BLTC)                += leds-sc27xx-bltc.o
> > + obj-$(CONFIG_LEDS_SGM3140)            += leds-sgm3140.o
> > diff --git a/target/linux/realtek/rtl838x/config-5.10
> > b/target/linux/realtek/rtl838x/config-5.10
> > index 5c69832be41d..a28366a7fd03 100644
> > --- a/target/linux/realtek/rtl838x/config-5.10
> > +++ b/target/linux/realtek/rtl838x/config-5.10
> > @@ -164,6 +164,7 @@ CONFIG_PGTABLE_LEVELS=2
> >   CONFIG_PHYLIB=y
> >   CONFIG_PHYLINK=y
> >   CONFIG_PINCTRL=y
> > +CONFIG_PINCTRL_RTL_SWITCHCORE=y
> >   CONFIG_POWER_RESET=y
> >   CONFIG_POWER_RESET_GPIO_RESTART=y
> >   CONFIG_POWER_RESET_SYSCON=y
> > 
> 
> 
> _______________________________________________
> openwrt-devel mailing list
> openwrt-devel@lists.openwrt.org
> https://lists.openwrt.org/mailman/listinfo/openwrt-devel
Olliver Schinagl Aug. 10, 2022, 9:22 a.m. UTC | #3
On 30-07-2022 00:02, Sander Vanheule wrote:
> On Fri, 2022-07-29 at 14:58 +0200, Olliver Schinagl wrote:
>> On 16-07-2022 21:09, Sander Vanheule wrote:
>>> RTL8380 switch SoCs can control up to 3×28 LEDs to display status of the
>>> switch's ethernet port. This driver allows to address these LEDs and
>>> provides direct control, blink offloading, and switch port status
>>> offloading.
>>>
>>> Since offloading of the generic netdev trigger does not exist, this
>>> driver provides a private trigger which achieves the same and is named
>>> "realtek-switchport". Each LED will have a file name "rtl_hw_trigger" in
>>> /sys/class/leds/$LED, where the requested trigger mode can be written
>>> to. If an unsupported mode is requested, this will be reported to the
>>> user. When the trigger is then activated, the LED will be added to a
>>> group of LEDs with the same trigger conditions. If it is not possible to
>>> add the LED to a group, the user must use an already existing trigger
>>> condition, or use direct LED control with a software trigger.
>>>
>>> Some common modes (i.e. values for "rtl_hw_trigger") are:
>>>     - Link present, blink on activity: 1f
>>>     - 100M/10M link, blink on activity: f
>>>     - 1G link present: 10
>>>
>>> Signed-off-by: Sander Vanheule <sander@svanheule.net>
>>> ---
>>>    .../drivers/pinctrl/pinctrl-rtl-switchcore.c  | 370 ++++++++++++++++++
>>>    ...s-add-RTL8380-switch-port-LED-driver.patch |  64 +++
>>>    target/linux/realtek/rtl838x/config-5.10      |   1 +
>>>    3 files changed, 435 insertions(+)
>>>    create mode 100644 target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-
>>> switchcore.c
>>>    create mode 100644 target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-
>>> port-LED-driver.patch
>>>
>>> diff --git a/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
>>> b/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
>>> new file mode 100644
>>> index 000000000000..0caf925989b6
>>> --- /dev/null
>>> +++ b/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
>>> @@ -0,0 +1,370 @@
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +
>>> +#include <linux/mfd/syscon.h>
>> for ARRAY_SIZE()
>>
>> +#include <linux/kernel.h>
>>
> Will add.
>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_address.h>
>>> +#include <linux/of_platform.h>
>>> +#include <linux/pinctrl/pinconf-generic.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"
>>> +
>>> +/**
>>> + * struct rtl_swcore_mux_desc - switchcore pin group information
>>> + *
>>> + * Pins are frequently muxed between alternative functions, but the control
>>> + * bits for the muxes are scattered throught the switchcore's register space.
>>> + * Provide a regmap-based interface to flexibly manage these mux fields, which
>>> + * may vary in size and do not always provide a GPIO function.
>>> + *
>>> + * @name: name to identify the pin group
>>> + * @field_desc: description of the register field with mux control bits
>>> + * @functions: NULL terminated array of function names
>>> + * @pins: array of numbers of the pins in this group
>>> + * @npins: number of pins in this group
>>> + */
>>> +struct rtl_swcore_mux_desc {
>>> +       const char *name;
>>> +       struct reg_field field;
>>> +       const unsigned int *pins;
>>> +       unsigned int npins;
>>> +};
>>> +
>>> +#define SWITCHCORE_MUX(_name, _field, _pins)           {       \
>>> +               .name = (_name),                                \
>>> +               .field = _field,                                \
>>> +               .pins = (_pins),                                \
>>> +               .npins = ARRAY_SIZE(_pins),                     \
>>> +       }
>>> +
>>> +/**
>>> + * struct rtl_swcore_mux_setting - stored mux setting
>>> + *
>>> + * @mux: pointer to the mux descriptor
>>> + * @value: value to write in the mux's register field to apply this setting
>>> + */
>>> +struct rtl_swcore_mux_setting {
>>> +       const struct rtl_swcore_mux_desc *mux;
>>> +       unsigned int value;
>> mux_setting? value is so ... undefined ;)
> That's because it is very generic, and just contains a register field value
> The struct is already named mux_setting and mux_setting->mux_setting is too much
> redundancy IMHO.

yeah true, but `value` is still a weird name, it must represent 
'something' right? In any case remember that naming things is hard ;) 
but a good name can make quite the difference. I fully agree having 
mux_setting->setting is dumb,

what about rtl_swcore_mux_ctrl->mux_setting? idk; just thinking out-loud 
here.

>
>>> +};
>>> +
>>> +/**
>>> + * struct rtl_swcore_function_desc - switchcore function information
>>> + *
>>> + * @name: name of this function
>>> + * @settings: list of mux settings that enable this function on said mux
>>> + * @nsettings: length of the @settings list
>>> + */
>>> +struct rtl_swcore_function_desc {
>>> +       const char *name;
>>> +       const struct rtl_swcore_mux_setting *settings;
>>> +       unsigned int nsettings;
>>> +};
>>> +
>>> +#define SWITCHCORE_FUNCTION(_name, _settings)          {       \
>>> +               .name = (_name),                                \
>>> +               .settings = (_settings),                        \
>>> +               .nsettings = ARRAY_SIZE(_settings),             \
>>> +       }
>>> +
>>> +struct rtl_swcore_config {
>>> +       const struct pinctrl_pin_desc *pins;
>>> +       unsigned int npins;
>>> +       const struct rtl_swcore_function_desc *functions;
>>> +       unsigned int nfunctions;
>>> +       const struct rtl_swcore_mux_desc **groups;
>>> +       unsigned int ngroups;
>>> +};
>>> +
>>> +struct rtl_swcore_pinctrl {
>>> +       struct pinctrl_desc pdesc;
>>> +       struct device *dev;
>>> +       const struct rtl_swcore_config *config;
>>> +       struct regmap_field **mux_fields;
>>> +};
>>> +
>>> +/*
>>> + * RTL838x chips come in LQFP packages with 216 pins. Pins are indexed
>>> + * counter-clockwise, starting with pin 1 at the bottom left.
>>> + * Map package pin {1..216} to pinctrl pin number {0..215}.
>>> + */
>>> +static const struct pinctrl_pin_desc rtl8380_swcore_pins[] = {
>>> +       /* JTAG pins */
>>> +       PINCTRL_PIN(27, "tck"),
>>> +       PINCTRL_PIN(28, "tms"),
>>> +       PINCTRL_PIN(29, "tdo"),
>>> +       PINCTRL_PIN(30, "tdi"),
>>> +       PINCTRL_PIN(31, "ntrst"),
>> maybe a new line to make grouping more clear
>>> +       /* aux MDIO bus pins */
>> Do we have anything other then aux? I mean, these are (external) pins so
>> by definition auxiliary. So it's "just" our MDIO bus isn't it
> As explained in the other patch, this isn't for the main MDIO master. I don't even know if
> the other MDIO master can be muxed as GPIO (or something else).
Oh for sure true, but in the datasheets, its also never called 
'aux-mdio' is it?
>
>>> +       PINCTRL_PIN(109, "aux-mdio"),
>>> +       PINCTRL_PIN(110, "aux-mdc"),
>>> +       /* system LED pin */
>>> +       PINCTRL_PIN(112, "sys-led"),
>>> +       /* UART1/SPI slave pins */
>>> +       PINCTRL_PIN(115, "uart1-rx"),
>>> +       PINCTRL_PIN(116, "uart1-tx"),
>> you currently name the pads/pins uart1-tx, since we don't have a
>> datasheet, is it worth to use something slightly more descriptive or
>> generic? I know different soc vendors do different things, some use the
>> pin name ("AB1"), some just use the gpioN name, some slash seperated
>> strings ("GPIO112/RG2TXD2/DDRV2") or also ("U8 GMAC0 TXC") some exactly
>> what you do (both underscore and hyphenized, though i'd use the hyphen
>> to connect words, sys-led; underscore to define function uart1_tx (or
>> uart1_tx-sp1_slave)? Anyway, there's a LOT of different flavors, and
>> _personally_ (but hard for us unless we desolder a SoC to do an analsys)
>> I personally a REALLY like the most decriptives ones ("pad name, func1
>> ... funcn") as it makes it super clear what it is what uses it has, and
>> avoids easy confusion (why ist he spi pin named uart)
> But that's some many characters! As long as there's only a few functions (that aren't
> gpio), we could put them all in the name. Pin index is already provided by the pin number,
> so we don't have to duplicate that here.
Was just listing some examples from other SoC's :) Don't look to hard at 
the pin number, it's a sequential number only on the rtl838x isn't it, 
on BGA's the index becomes a matrix, so then you'd have 
`PINCTRL_PIN(116, "AB1/UART1-TX/I2C-CLK)" or something?
>
>>> +};
>>> +
>>> +static const unsigned int rtl8380_jtag_pins[] = {27, 28, 29, 30, 31};
>>> +static const unsigned int rtl8380_aux_mdio_pins[] = {109, 110};
>>> +static const unsigned int rtl8380_sys_led_pins[] = {112};
>>> +static const unsigned int rtl8380_uart1_pins[] = {115, 116};
>>> +
>>> +static const struct rtl_swcore_mux_desc rtl8380_mux_jtag =
>>> +       SWITCHCORE_MUX("jtag", REG_FIELD(0x1000, 2, 3), rtl8380_jtag_pins);
>>> +
>>> +static const struct rtl_swcore_mux_desc rtl8380_mux_aux_mdio =
>>> +       SWITCHCORE_MUX("aux-mdio", REG_FIELD(0xa0e0, 0, 0), rtl8380_aux_mdio_pins);
>>> +
>>> +static const struct rtl_swcore_mux_desc rtl8380_mux_sys_led =
>>> +       SWITCHCORE_MUX("sys-led", REG_FIELD(0xa000, 15, 15), rtl8380_sys_led_pins);
>>> +
>>> +static const struct rtl_swcore_mux_desc rtl8380_mux_uart1 =
>>> +       SWITCHCORE_MUX("uart1", REG_FIELD(0x1000, 4, 4), rtl8380_uart1_pins);
>>> +
>>> +static const struct rtl_swcore_mux_desc *rtl8380_groups[] = {
>>> +       &rtl8380_mux_jtag,
>>> +       &rtl8380_mux_aux_mdio,
>>> +       &rtl8380_mux_sys_led,
>>> +       &rtl8380_mux_uart1,
>>> +};
>>> +
>>> +static const struct rtl_swcore_mux_setting rtl8380_gpio_settings[] = {
>>> +       {&rtl8380_mux_jtag, 2},
>>> +       {&rtl8380_mux_aux_mdio, 0},
>>> +       {&rtl8380_mux_sys_led, 0},
>>> +};
>>> +static const struct rtl_swcore_mux_setting rtl8380_aux_mdio_settings[] = {
>>> +       {&rtl8380_mux_aux_mdio, 1},
>>> +};
>>> +static const struct rtl_swcore_mux_setting rtl8380_sys_led_settings[] = {
>>> +       {&rtl8380_mux_sys_led, 1},
>>> +};
>>> +static const struct rtl_swcore_mux_setting rtl8380_uart1_settings[] = {
>>> +       {&rtl8380_mux_uart1, 1},
>>> +};
>>> +static const struct rtl_swcore_mux_setting rtl8380_spi_slave_settings[] = {
>>> +       {&rtl8380_mux_uart1, 0},
>>> +};
>>> +
>>> +static const struct rtl_swcore_function_desc rtl8380_functions[] = {
>>> +       SWITCHCORE_FUNCTION("gpio", rtl8380_gpio_settings),
>>> +       SWITCHCORE_FUNCTION("aux-mdio", rtl8380_aux_mdio_settings),
>>> +       SWITCHCORE_FUNCTION("sys-led", rtl8380_sys_led_settings),
>>> +       SWITCHCORE_FUNCTION("uart1", rtl8380_uart1_settings),
>>> +       SWITCHCORE_FUNCTION("spi-slave", rtl8380_spi_slave_settings),
>>> +};
>>> +
>>> +static const struct rtl_swcore_config rtl8380_config = {
>>> +       .pins = rtl8380_swcore_pins,
>>> +       .npins = ARRAY_SIZE(rtl8380_swcore_pins),
>>> +       .functions = rtl8380_functions,
>>> +       .nfunctions = ARRAY_SIZE(rtl8380_functions),
>>> +       .groups = rtl8380_groups,
>>> +       .ngroups = ARRAY_SIZE(rtl8380_groups),
>>> +};
>>> +
>>> +/*
>>> + * RTL839x chips are in BGA packages with 26×26 positions. Board designs number
>>> + * these as 1..26 for the rows, and A..AF for the columns, with position A1 in
>>> + * the bottom left corner. Letters I, O, Q, S, X, and Z are skipped; presumably
>>> + * to avoid ambiguities.
>>> + * This gives a total of 676 positions. Note that not all positions will
>>> + * actually have a pad, and many pads will be used for power.
>>> + *
>>> + * Index pins using (ROW + 26×COL), where ROW and COL mapped as:
>>> + *   - ROW: {1..26} -> {0..25}
>>> + *   - COL: {A..AF} -> {0..25}
>>> + *
>>> + * Since there are no datasheets available, use a virtual pin range starting at
>>> + * 676 for pins with unknowns positions. When actual pin positions are found
>>> + * (if ever), these can the be mapped to their real values.
>>> + */
>>> +#define RTL8390_VPIN(num, name)                PINCTRL_PIN(26 * 26 + (num), (name))
>> this is unused code though, right? (i get the RFC nature here)
> It will get used in v2. Didn't feel like stripping it out for the RFC, but sorry for the
> confusion it caused.
right, no problem; As I only have the 93xx, I actually intend to put 
some focus on that one, but as discussed before, clk first ;)
>
>>> +
>>> +/* TODO RTL8390 */
>>> +
>>> +/* TODO RTL9300 */
>>> +
>>> +/* TODO RTL9310 */
>>> +
>>> +static int rtl_swcore_group_count(struct pinctrl_dev *pctldev)
>>> +{
>>> +       struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
>>> +
>>> +       return priv->config->ngroups;
>>> +}
>>> +
>>> +static const char * rtl_swcore_group_name(struct pinctrl_dev *pctldev,
>>> +       unsigned int selector)
>>> +{
>>> +       struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
>>> +
>>> +       return priv->config->groups[selector]->name;
>>> +};
>>> +
>>> +static int rtl_swcore_group_pins(struct pinctrl_dev *pctldev,
>>> +       unsigned int selector, const unsigned int **pins,
>>> +       unsigned int *num_pins)
>>> +{
>>> +       struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
>>> +
>>> +       *pins = priv->config->groups[selector]->pins;
>>> +       *num_pins = priv->config->groups[selector]->npins;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int rtl_swcore_set_mux(struct pinctrl_dev *pctldev,
>>> +       unsigned int selector, unsigned int group)
>>> +{
>>> +       struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
>>> +       const struct rtl_swcore_function_desc *function;
>>> +       const struct rtl_swcore_mux_setting *setting;
>> you sure you do't want to scope anything in the for/if part?
> Can we do that now? I also prefer keeping the scope of things limited, but the old C
> standard didn't use to allow for that.

I think scoping variables as locally as possible has been possible for 
decades, and be done for a _very_ long time in the kernel as well.


The only thing that I have seen being avoided was `for (int i = 0; ...)` 
but even that is now allowed (not randomly creating variables 
everywhere, but specifically in the for loop context) as a) you can 
argue its a very local scope, and b) it doesn't pollute your namespace.


So yes, we can and should do this imo

>
>>> +       const struct rtl_swcore_mux_desc *mux;
>>> +       unsigned int i;
>>> +
>>> +       dev_info(priv->dev, "requesting selector %d, group %d\n", selector, group);
>>> +
>>> +       function = &priv->config->functions[selector];
>>> +       mux = priv->config->groups[group];
>>> +
>>> +       for (i = 0; i < function->nsettings; i++) {
>>> +               setting = &function->settings[i];
>>> +               if (setting->mux == mux) {
>>> +                       dev_info(priv->dev, "set mux %s to function %s (%d)\n",
>>> +                               mux->name, function->name, setting->value);
>>> +                       return regmap_field_write(priv->mux_fields[group], setting-
>>>> value);
>> i'm generally not a fan of these 'deeply' hidden return statements;
>> maybe do a goto to jump after the enodev error?
> I get that you're not a fan of this, but I don't think a jump would be much better.
>
> Maybe this is better (definitely not tested):
>
> while (i < function->nsettings && function->settings[i].mux != mux)
> 	i++;
>
> if (i == function->nsettings)
> 	return -ENODEV;
>
> return regmap_field_write(priv->mux_fields[group], function->settings[i].value);

Yeah this is a lot better!

As for the goto vs the return, the goto still helps to have your single 
exit point, the early returns on early checks is fine btw, but having a 
return so deeply nested and hidden just irks a bit, whereas a goto not 
as much.

>
>
>
>>> +               }
>>> +       }
>>> +
>>> +       /* Should never hit this, unless something was misconfigured */
>> dev_err(dev, "something was miss-configured\n");
>>> +       return -ENODEV;
>>> +}
>>> +
>>> +static const struct pinctrl_ops rtl_swcore_pinctrl_ops = {
>>> +       .get_groups_count = rtl_swcore_group_count,
>>> +       .get_group_name = rtl_swcore_group_name,
>>> +       .get_group_pins = rtl_swcore_group_pins,
>>> +       .dt_node_to_map = pinconf_generic_dt_node_to_map_all,
>>> +       .dt_free_map = pinconf_generic_dt_free_map,
>>> +};
>>> +
>>> +static const struct pinmux_ops rtl_swcore_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 = rtl_swcore_set_mux,
>>> +       .strict = true,
>>> +};
>>> +
>>> +static int rtl_swcore_functions_init(struct pinctrl_dev *pctl, struct
>>> rtl_swcore_pinctrl *priv)
>>> +{
>>> +       const struct rtl_swcore_function_desc *function;
>>> +       unsigned int ngroups;
>>> +       const char **groups;
>>> +       unsigned int f_idx;
>>> +       unsigned int g_idx;
>>> +
>>> +       for (f_idx = 0; f_idx < priv->config->nfunctions; f_idx++) {
>>> +               function = &priv->config->functions[f_idx];
>>> +               ngroups = function->nsettings;
>>> +
>>> +               dev_info(priv->dev, "found %d groups for function %s\n", ngroups,
>>> function->name);
>> shouldn't this be after the add? we THINK all of this will work; but not
>> until after the add we know we actually found them. Can the add function
>> even fail btw?
> Development print from times where I wasn't using pinmux_generic_add_function(). Can just
> be removed I guess.
>
>>> +
>>> +               groups = devm_kcalloc(priv->dev, ngroups, sizeof(*groups),
>>> GFP_KERNEL);
>>> +               if (!groups)
>>> +                       return -ENOMEM;
>>> +
>>> +               for (g_idx = 0; g_idx < ngroups; g_idx++)
>>> +                       groups[g_idx] = function->settings[g_idx].mux->name;
>>> +
>>> +               pinmux_generic_add_function(pctl, function->name, groups, ngroups,
>>> NULL);
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static const struct of_device_id of_rtl_swcore_pinctrl_match[] = {
>>> +       {
>>> +               .compatible = "realtek,rtl8380-pinctrl",
>>> +               .data = &rtl8380_config,
>>> +       },
>>> +       { /* sentinel */ }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, of_rtl_swcore_pinctrl_match);
>>> +
>>> +static int rtl_swcore_pinctrl_probe(struct platform_device *pdev)
>>> +{
>>> +       const struct rtl_swcore_config *config;
>>> +       struct rtl_swcore_pinctrl *priv;
>>> +       struct device *dev = &pdev->dev;
>>> +       struct pinctrl_dev *pctldev;
>>> +       struct regmap_field *field;
>>> +       struct regmap *regmap;
>>> +       int mux;
>>> +       int err;
>>> +
>>> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>> +       if (!priv)
>>> +               return -ENOMEM;
>>> +
>>> +       config = of_device_get_match_data(dev);
>>> +       if (!config)
>>> +               return dev_err_probe(dev, -EINVAL, "no config\n");
>> +               return dev_err_probe(dev, -EINVAL, "unable to match compatible from
>> devicetree\n");
> Will improve the log statement
>
>>> +
>>> +       /* FIXME of_get_parent(dev_of_node(dev)) OR dev_of_node(dev->parent) */
>> is there even a functional difference between the two? I like what you
>> have now, as that doesn't access struct members
> The first takes the parent going through the OF layer, the later going through the device
> layer. I wouldn't assume these to always be identical. However, since we actually want the
> DT parent, the current statement should be the right thing.
>
> Thanks,
> Sander
>
>>> +       regmap = device_node_to_regmap(of_get_parent(dev_of_node(dev)));
>>> +       if (IS_ERR(regmap))
>>> +               return dev_err_probe(dev, PTR_ERR(regmap), "failed to find parent
>>> regmap\n");
>>> +
>>> +       priv->dev = dev;
>>> +       priv->config = config;
>>> +       priv->pdesc.name = "realtek-switchcore-pinctrl";
>>> +       priv->pdesc.owner = THIS_MODULE;
>>> +       priv->pdesc.pctlops = &rtl_swcore_pinctrl_ops;
>>> +       priv->pdesc.pmxops = &rtl_swcore_pinmux_ops;
>>> +       priv->pdesc.pins = config->pins;
>>> +       priv->pdesc.npins = config->npins;
>>> +
>>> +       priv->mux_fields = devm_kcalloc(dev, config->ngroups, sizeof(*priv-
>>>> mux_fields),
>>> +               GFP_KERNEL);
>>> +       if (!priv->mux_fields)
>>> +               return -ENOMEM;
>>> +
>>> +       for (mux = 0; mux < config->ngroups; mux++) {
>>> +               field = devm_regmap_field_alloc(dev, regmap, config->groups[mux]-
>>>> field);
>>> +               if (IS_ERR(field))
>>> +                       return PTR_ERR(field);
>>> +
>>> +               priv->mux_fields[mux] = field;
>>> +       }
>>> +
>>> +       err = devm_pinctrl_register_and_init(dev, &priv->pdesc, priv, &pctldev);
>>> +       if (err)
>>> +               return dev_err_probe(dev, err, "failed to register\n");
>>> +
>>> +       err = rtl_swcore_functions_init(pctldev, priv);
>>> +       if (err)
>>> +               return dev_err_probe(dev, err, "failed to generate function list\n");
>>> +
>>> +       err = pinctrl_enable(pctldev);
>>> +       if (err)
>>> +               return dev_err_probe(dev, err, "failed to enable\n");
>>> +
>>> +       return 0;
>>> +};
>>> +
>>> +static struct platform_driver rtl_swcore_pinctrl_driver = {
>>> +       .driver = {
>>> +               .name = "realtek-switchcore-pinctrl",
>>> +               .of_match_table = of_rtl_swcore_pinctrl_match
>>> +       },
>>> +       .probe = rtl_swcore_pinctrl_probe,
>>> +};
>>> +module_platform_driver(rtl_swcore_pinctrl_driver);
>>> diff --git a/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-
>>> driver.patch b/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-
>>> driver.patch
>>> new file mode 100644
>>> index 000000000000..5da5a01cdb48
>>> --- /dev/null
>>> +++ b/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-
>>> driver.patch
>>> @@ -0,0 +1,64 @@
>>> +From d71ec8184236356c50088b00b2417fb142e72bd9 Mon Sep 17 00:00:00 2001
>>> +From: Sander Vanheule <sander@svanheule.net>
>>> +Date: Sun, 10 Jul 2022 11:31:53 +0200
>>> +Subject: [PATCH 03/14] leds: add RTL8380 switch port LED driver
>>> +MIME-Version: 1.0
>>> +Content-Type: text/plain; charset=UTF-8
>>> +Content-Transfer-Encoding: 8bit
>>> +
>>> +RTL8380 switch SoCs can control up to 3×28 LEDs to display status of the
>>> +switch's ethernet port. This driver allows to address these LEDs and
>>> +provides direct control, blink offloading, and switch port status
>>> +offloading.
>>> +
>>> +Since offloading of the generic netdev trigger does not exist, this
>>> +driver provides a private trigger which achieves the same and is named
>>> +"realtek-switchport". Each LED will have a file name "rtl_hw_trigger" in
>>> +/sys/class/leds/$LED, where the requested trigger mode can be written
>>> +to. If an unsupported mode is requested, this will be reported to the
>>> +user. When the trigger is then activated, the LED will be added to a
>>> +group of LEDs with the same trigger conditions. If it is not possible to
>>> +add the LED to a group, the user must use an already existing trigger
>>> +condition, or use direct LED control with a software trigger.
>>> +
>>> +Some common modes (i.e. values for "rtl_hw_trigger") are:
>>> +  - Link present, blink on activity: 1f
>>> +  - 100M/10M link, blink on activity: f
>>> +  - 1G link present: 10
>>> +
>>> +Signed-off-by: Sander Vanheule <sander@svanheule.net>
>>> +---
>>> + drivers/leds/Kconfig                |   9 +
>>> + drivers/leds/Makefile               |   1 +
>>> + drivers/leds/leds-rtl-switch-port.c | 668 ++++++++++++++++++++++++++++
>>> + 3 files changed, 678 insertions(+)
>>> + create mode 100644 drivers/leds/leds-rtl-switch-port.c
>>> +
>>> +--- a/drivers/leds/Kconfig
>>> ++++ b/drivers/leds/Kconfig
>>> +@@ -594,6 +594,15 @@ config LEDS_REGULATOR
>>> +       help
>>> +         This option enables support for regulator driven LEDs.
>>> +
>>> ++config LEDS_RTL_SWITCH_PORT
>>> ++      tristate "Realtek SoC switch port LED support"
>>> ++      depends on LEDS_CLASS
>>> ++      depends on MFD_REALTEK_SWITCHCORE || COMPILE_TEST
>>> ++      select MFD_SYSCON
>>> ++      select REGMAP_MMIO
>>> ++      help
>>> ++        This option enables support for Realtek switch SoC port LEDs.
>>> ++
>>> + config LEDS_BD2802
>>> +       tristate "LED driver for BD2802 RGB LED"
>>> +       depends on LEDS_CLASS
>>> +--- a/drivers/leds/Makefile
>>> ++++ b/drivers/leds/Makefile
>>> +@@ -80,6 +80,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_RTL_SWITCH_PORT)    += leds-rtl-switch-port.o
>>> + obj-$(CONFIG_LEDS_S3C24XX)            += leds-s3c24xx.o
>>> + obj-$(CONFIG_LEDS_SC27XX_BLTC)                += leds-sc27xx-bltc.o
>>> + obj-$(CONFIG_LEDS_SGM3140)            += leds-sgm3140.o
>>> diff --git a/target/linux/realtek/rtl838x/config-5.10
>>> b/target/linux/realtek/rtl838x/config-5.10
>>> index 5c69832be41d..a28366a7fd03 100644
>>> --- a/target/linux/realtek/rtl838x/config-5.10
>>> +++ b/target/linux/realtek/rtl838x/config-5.10
>>> @@ -164,6 +164,7 @@ CONFIG_PGTABLE_LEVELS=2
>>>    CONFIG_PHYLIB=y
>>>    CONFIG_PHYLINK=y
>>>    CONFIG_PINCTRL=y
>>> +CONFIG_PINCTRL_RTL_SWITCHCORE=y
>>>    CONFIG_POWER_RESET=y
>>>    CONFIG_POWER_RESET_GPIO_RESTART=y
>>>    CONFIG_POWER_RESET_SYSCON=y
>>>
>>
>> _______________________________________________
>> openwrt-devel mailing list
>> openwrt-devel@lists.openwrt.org
>> https://lists.openwrt.org/mailman/listinfo/openwrt-devel
diff mbox series

Patch

diff --git a/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c b/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
new file mode 100644
index 000000000000..0caf925989b6
--- /dev/null
+++ b/target/linux/realtek/files-5.10/drivers/pinctrl/pinctrl-rtl-switchcore.c
@@ -0,0 +1,370 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/pinctrl/pinconf-generic.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"
+
+/**
+ * struct rtl_swcore_mux_desc - switchcore pin group information
+ *
+ * Pins are frequently muxed between alternative functions, but the control
+ * bits for the muxes are scattered throught the switchcore's register space.
+ * Provide a regmap-based interface to flexibly manage these mux fields, which
+ * may vary in size and do not always provide a GPIO function.
+ *
+ * @name: name to identify the pin group
+ * @field_desc: description of the register field with mux control bits
+ * @functions: NULL terminated array of function names
+ * @pins: array of numbers of the pins in this group
+ * @npins: number of pins in this group
+ */
+struct rtl_swcore_mux_desc {
+	const char *name;
+	struct reg_field field;
+	const unsigned int *pins;
+	unsigned int npins;
+};
+
+#define SWITCHCORE_MUX(_name, _field, _pins)		{	\
+		.name = (_name),				\
+		.field = _field,				\
+		.pins = (_pins),				\
+		.npins = ARRAY_SIZE(_pins),			\
+	}
+
+/**
+ * struct rtl_swcore_mux_setting - stored mux setting
+ *
+ * @mux: pointer to the mux descriptor
+ * @value: value to write in the mux's register field to apply this setting
+ */
+struct rtl_swcore_mux_setting {
+	const struct rtl_swcore_mux_desc *mux;
+	unsigned int value;
+};
+
+/**
+ * struct rtl_swcore_function_desc - switchcore function information
+ *
+ * @name: name of this function
+ * @settings: list of mux settings that enable this function on said mux
+ * @nsettings: length of the @settings list
+ */
+struct rtl_swcore_function_desc {
+	const char *name;
+	const struct rtl_swcore_mux_setting *settings;
+	unsigned int nsettings;
+};
+
+#define SWITCHCORE_FUNCTION(_name, _settings)		{	\
+		.name = (_name),				\
+		.settings = (_settings),			\
+		.nsettings = ARRAY_SIZE(_settings),		\
+	}
+
+struct rtl_swcore_config {
+	const struct pinctrl_pin_desc *pins;
+	unsigned int npins;
+	const struct rtl_swcore_function_desc *functions;
+	unsigned int nfunctions;
+	const struct rtl_swcore_mux_desc **groups;
+	unsigned int ngroups;
+};
+
+struct rtl_swcore_pinctrl {
+	struct pinctrl_desc pdesc;
+	struct device *dev;
+	const struct rtl_swcore_config *config;
+	struct regmap_field **mux_fields;
+};
+
+/*
+ * RTL838x chips come in LQFP packages with 216 pins. Pins are indexed
+ * counter-clockwise, starting with pin 1 at the bottom left.
+ * Map package pin {1..216} to pinctrl pin number {0..215}.
+ */
+static const struct pinctrl_pin_desc rtl8380_swcore_pins[] = {
+	/* JTAG pins */
+	PINCTRL_PIN(27, "tck"),
+	PINCTRL_PIN(28, "tms"),
+	PINCTRL_PIN(29, "tdo"),
+	PINCTRL_PIN(30, "tdi"),
+	PINCTRL_PIN(31, "ntrst"),
+	/* aux MDIO bus pins */
+	PINCTRL_PIN(109, "aux-mdio"),
+	PINCTRL_PIN(110, "aux-mdc"),
+	/* system LED pin */
+	PINCTRL_PIN(112, "sys-led"),
+	/* UART1/SPI slave pins */
+	PINCTRL_PIN(115, "uart1-rx"),
+	PINCTRL_PIN(116, "uart1-tx"),
+};
+
+static const unsigned int rtl8380_jtag_pins[] = {27, 28, 29, 30, 31};
+static const unsigned int rtl8380_aux_mdio_pins[] = {109, 110};
+static const unsigned int rtl8380_sys_led_pins[] = {112};
+static const unsigned int rtl8380_uart1_pins[] = {115, 116};
+
+static const struct rtl_swcore_mux_desc rtl8380_mux_jtag =
+	SWITCHCORE_MUX("jtag", REG_FIELD(0x1000, 2, 3), rtl8380_jtag_pins);
+
+static const struct rtl_swcore_mux_desc rtl8380_mux_aux_mdio =
+	SWITCHCORE_MUX("aux-mdio", REG_FIELD(0xa0e0, 0, 0), rtl8380_aux_mdio_pins);
+
+static const struct rtl_swcore_mux_desc rtl8380_mux_sys_led =
+	SWITCHCORE_MUX("sys-led", REG_FIELD(0xa000, 15, 15), rtl8380_sys_led_pins);
+
+static const struct rtl_swcore_mux_desc rtl8380_mux_uart1 =
+	SWITCHCORE_MUX("uart1", REG_FIELD(0x1000, 4, 4), rtl8380_uart1_pins);
+
+static const struct rtl_swcore_mux_desc *rtl8380_groups[] = {
+	&rtl8380_mux_jtag,
+	&rtl8380_mux_aux_mdio,
+	&rtl8380_mux_sys_led,
+	&rtl8380_mux_uart1,
+};
+
+static const struct rtl_swcore_mux_setting rtl8380_gpio_settings[] = {
+	{&rtl8380_mux_jtag, 2},
+	{&rtl8380_mux_aux_mdio, 0},
+	{&rtl8380_mux_sys_led, 0},
+};
+static const struct rtl_swcore_mux_setting rtl8380_aux_mdio_settings[] = {
+	{&rtl8380_mux_aux_mdio, 1},
+};
+static const struct rtl_swcore_mux_setting rtl8380_sys_led_settings[] = {
+	{&rtl8380_mux_sys_led, 1},
+};
+static const struct rtl_swcore_mux_setting rtl8380_uart1_settings[] = {
+	{&rtl8380_mux_uart1, 1},
+};
+static const struct rtl_swcore_mux_setting rtl8380_spi_slave_settings[] = {
+	{&rtl8380_mux_uart1, 0},
+};
+
+static const struct rtl_swcore_function_desc rtl8380_functions[] = {
+	SWITCHCORE_FUNCTION("gpio", rtl8380_gpio_settings),
+	SWITCHCORE_FUNCTION("aux-mdio", rtl8380_aux_mdio_settings),
+	SWITCHCORE_FUNCTION("sys-led", rtl8380_sys_led_settings),
+	SWITCHCORE_FUNCTION("uart1", rtl8380_uart1_settings),
+	SWITCHCORE_FUNCTION("spi-slave", rtl8380_spi_slave_settings),
+};
+
+static const struct rtl_swcore_config rtl8380_config = {
+	.pins = rtl8380_swcore_pins,
+	.npins = ARRAY_SIZE(rtl8380_swcore_pins),
+	.functions = rtl8380_functions,
+	.nfunctions = ARRAY_SIZE(rtl8380_functions),
+	.groups = rtl8380_groups,
+	.ngroups = ARRAY_SIZE(rtl8380_groups),
+};
+
+/*
+ * RTL839x chips are in BGA packages with 26×26 positions. Board designs number
+ * these as 1..26 for the rows, and A..AF for the columns, with position A1 in
+ * the bottom left corner. Letters I, O, Q, S, X, and Z are skipped; presumably
+ * to avoid ambiguities.
+ * This gives a total of 676 positions. Note that not all positions will
+ * actually have a pad, and many pads will be used for power.
+ *
+ * Index pins using (ROW + 26×COL), where ROW and COL mapped as:
+ *   - ROW: {1..26} -> {0..25}
+ *   - COL: {A..AF} -> {0..25}
+ *
+ * Since there are no datasheets available, use a virtual pin range starting at
+ * 676 for pins with unknowns positions. When actual pin positions are found
+ * (if ever), these can the be mapped to their real values.
+ */
+#define RTL8390_VPIN(num, name)		PINCTRL_PIN(26 * 26 + (num), (name))
+
+/* TODO RTL8390 */
+
+/* TODO RTL9300 */
+
+/* TODO RTL9310 */
+
+static int rtl_swcore_group_count(struct pinctrl_dev *pctldev)
+{
+	struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
+
+	return priv->config->ngroups;
+}
+
+static const char * rtl_swcore_group_name(struct pinctrl_dev *pctldev,
+	unsigned int selector)
+{
+	struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
+
+	return priv->config->groups[selector]->name;
+};
+
+static int rtl_swcore_group_pins(struct pinctrl_dev *pctldev,
+	unsigned int selector, const unsigned int **pins,
+	unsigned int *num_pins)
+{
+	struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
+
+	*pins = priv->config->groups[selector]->pins;
+	*num_pins = priv->config->groups[selector]->npins;
+
+	return 0;
+}
+
+static int rtl_swcore_set_mux(struct pinctrl_dev *pctldev,
+	unsigned int selector, unsigned int group)
+{
+	struct rtl_swcore_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev);
+	const struct rtl_swcore_function_desc *function;
+	const struct rtl_swcore_mux_setting *setting;
+	const struct rtl_swcore_mux_desc *mux;
+	unsigned int i;
+
+	dev_info(priv->dev, "requesting selector %d, group %d\n", selector, group);
+
+	function = &priv->config->functions[selector];
+	mux = priv->config->groups[group];
+
+	for (i = 0; i < function->nsettings; i++) {
+		setting = &function->settings[i];
+		if (setting->mux == mux) {
+			dev_info(priv->dev, "set mux %s to function %s (%d)\n",
+				mux->name, function->name, setting->value);
+			return regmap_field_write(priv->mux_fields[group], setting->value);
+		}
+	}
+
+	/* Should never hit this, unless something was misconfigured */
+	return -ENODEV;
+}
+
+static const struct pinctrl_ops rtl_swcore_pinctrl_ops = {
+	.get_groups_count = rtl_swcore_group_count,
+	.get_group_name = rtl_swcore_group_name,
+	.get_group_pins = rtl_swcore_group_pins,
+	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+	.dt_free_map = pinconf_generic_dt_free_map,
+};
+
+static const struct pinmux_ops rtl_swcore_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 = rtl_swcore_set_mux,
+	.strict = true,
+};
+
+static int rtl_swcore_functions_init(struct pinctrl_dev *pctl, struct rtl_swcore_pinctrl *priv)
+{
+	const struct rtl_swcore_function_desc *function;
+	unsigned int ngroups;
+	const char **groups;
+	unsigned int f_idx;
+	unsigned int g_idx;
+
+	for (f_idx = 0; f_idx < priv->config->nfunctions; f_idx++) {
+		function = &priv->config->functions[f_idx];
+		ngroups = function->nsettings;
+
+		dev_info(priv->dev, "found %d groups for function %s\n", ngroups, function->name);
+
+		groups = devm_kcalloc(priv->dev, ngroups, sizeof(*groups), GFP_KERNEL);
+		if (!groups)
+			return -ENOMEM;
+
+		for (g_idx = 0; g_idx < ngroups; g_idx++)
+			groups[g_idx] = function->settings[g_idx].mux->name;
+
+		pinmux_generic_add_function(pctl, function->name, groups, ngroups, NULL);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id of_rtl_swcore_pinctrl_match[] = {
+	{
+		.compatible = "realtek,rtl8380-pinctrl",
+		.data = &rtl8380_config,
+	},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_rtl_swcore_pinctrl_match);
+
+static int rtl_swcore_pinctrl_probe(struct platform_device *pdev)
+{
+	const struct rtl_swcore_config *config;
+	struct rtl_swcore_pinctrl *priv;
+	struct device *dev = &pdev->dev;
+	struct pinctrl_dev *pctldev;
+	struct regmap_field *field;
+	struct regmap *regmap;
+	int mux;
+	int err;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	config = of_device_get_match_data(dev);
+	if (!config)
+		return dev_err_probe(dev, -EINVAL, "no config\n");
+
+	/* FIXME of_get_parent(dev_of_node(dev)) OR dev_of_node(dev->parent) */
+	regmap = device_node_to_regmap(of_get_parent(dev_of_node(dev)));
+	if (IS_ERR(regmap))
+		return dev_err_probe(dev, PTR_ERR(regmap), "failed to find parent regmap\n");
+
+	priv->dev = dev;
+	priv->config = config;
+	priv->pdesc.name = "realtek-switchcore-pinctrl";
+	priv->pdesc.owner = THIS_MODULE;
+	priv->pdesc.pctlops = &rtl_swcore_pinctrl_ops;
+	priv->pdesc.pmxops = &rtl_swcore_pinmux_ops;
+	priv->pdesc.pins = config->pins;
+	priv->pdesc.npins = config->npins;
+
+	priv->mux_fields = devm_kcalloc(dev, config->ngroups, sizeof(*priv->mux_fields),
+		GFP_KERNEL);
+	if (!priv->mux_fields)
+		return -ENOMEM;
+
+	for (mux = 0; mux < config->ngroups; mux++) {
+		field = devm_regmap_field_alloc(dev, regmap, config->groups[mux]->field);
+		if (IS_ERR(field))
+			return PTR_ERR(field);
+
+		priv->mux_fields[mux] = field;
+	}
+
+	err = devm_pinctrl_register_and_init(dev, &priv->pdesc, priv, &pctldev);
+	if (err)
+		return dev_err_probe(dev, err, "failed to register\n");
+
+	err = rtl_swcore_functions_init(pctldev, priv);
+	if (err)
+		return dev_err_probe(dev, err, "failed to generate function list\n");
+
+	err = pinctrl_enable(pctldev);
+	if (err)
+		return dev_err_probe(dev, err, "failed to enable\n");
+
+	return 0;
+};
+
+static struct platform_driver rtl_swcore_pinctrl_driver = {
+	.driver = {
+		.name = "realtek-switchcore-pinctrl",
+		.of_match_table = of_rtl_swcore_pinctrl_match
+	},
+	.probe = rtl_swcore_pinctrl_probe,
+};
+module_platform_driver(rtl_swcore_pinctrl_driver);
diff --git a/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-driver.patch b/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-driver.patch
new file mode 100644
index 000000000000..5da5a01cdb48
--- /dev/null
+++ b/target/linux/realtek/patches-5.10/202-leds-add-RTL8380-switch-port-LED-driver.patch
@@ -0,0 +1,64 @@ 
+From d71ec8184236356c50088b00b2417fb142e72bd9 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sun, 10 Jul 2022 11:31:53 +0200
+Subject: [PATCH 03/14] leds: add RTL8380 switch port LED driver
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RTL8380 switch SoCs can control up to 3×28 LEDs to display status of the
+switch's ethernet port. This driver allows to address these LEDs and
+provides direct control, blink offloading, and switch port status
+offloading.
+
+Since offloading of the generic netdev trigger does not exist, this
+driver provides a private trigger which achieves the same and is named
+"realtek-switchport". Each LED will have a file name "rtl_hw_trigger" in
+/sys/class/leds/$LED, where the requested trigger mode can be written
+to. If an unsupported mode is requested, this will be reported to the
+user. When the trigger is then activated, the LED will be added to a
+group of LEDs with the same trigger conditions. If it is not possible to
+add the LED to a group, the user must use an already existing trigger
+condition, or use direct LED control with a software trigger.
+
+Some common modes (i.e. values for "rtl_hw_trigger") are:
+  - Link present, blink on activity: 1f
+  - 100M/10M link, blink on activity: f
+  - 1G link present: 10
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+---
+ drivers/leds/Kconfig                |   9 +
+ drivers/leds/Makefile               |   1 +
+ drivers/leds/leds-rtl-switch-port.c | 668 ++++++++++++++++++++++++++++
+ 3 files changed, 678 insertions(+)
+ create mode 100644 drivers/leds/leds-rtl-switch-port.c
+
+--- a/drivers/leds/Kconfig
++++ b/drivers/leds/Kconfig
+@@ -594,6 +594,15 @@ config LEDS_REGULATOR
+ 	help
+ 	  This option enables support for regulator driven LEDs.
+ 
++config LEDS_RTL_SWITCH_PORT
++	tristate "Realtek SoC switch port LED support"
++	depends on LEDS_CLASS
++	depends on MFD_REALTEK_SWITCHCORE || COMPILE_TEST
++	select MFD_SYSCON
++	select REGMAP_MMIO
++	help
++	  This option enables support for Realtek switch SoC port LEDs.
++
+ config LEDS_BD2802
+ 	tristate "LED driver for BD2802 RGB LED"
+ 	depends on LEDS_CLASS
+--- a/drivers/leds/Makefile
++++ b/drivers/leds/Makefile
+@@ -80,6 +80,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_RTL_SWITCH_PORT)	+= leds-rtl-switch-port.o
+ obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
+ obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
+ obj-$(CONFIG_LEDS_SGM3140)		+= leds-sgm3140.o
diff --git a/target/linux/realtek/rtl838x/config-5.10 b/target/linux/realtek/rtl838x/config-5.10
index 5c69832be41d..a28366a7fd03 100644
--- a/target/linux/realtek/rtl838x/config-5.10
+++ b/target/linux/realtek/rtl838x/config-5.10
@@ -164,6 +164,7 @@  CONFIG_PGTABLE_LEVELS=2
 CONFIG_PHYLIB=y
 CONFIG_PHYLINK=y
 CONFIG_PINCTRL=y
+CONFIG_PINCTRL_RTL_SWITCHCORE=y
 CONFIG_POWER_RESET=y
 CONFIG_POWER_RESET_GPIO_RESTART=y
 CONFIG_POWER_RESET_SYSCON=y