[PATCH RFC leds + net-next v3 1/2] net: phy: add API for LEDs controlled by PHY HW
From: Marek Behún <hidden>
Date: 2020-07-24 16:46:25
Also in:
linux-leds, lkml
Subsystem:
ethernet phy library, networking drivers, the rest · Maintainers:
Andrew Lunn, Heiner Kallweit, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Many PHYs support various HW control modes for LEDs connected directly to them. This code adds a new private LED trigger called phydev-hw-mode. When this trigger is enabled for a LED, the various HW control modes which the PHY supports for given LED can be get/set via hw_mode sysfs file. A PHY driver wishing to utilize this API needs to register the LEDs on its own and set the .trigger_type member of LED classdev to &phy_hw_led_trig_type. It also needs to implement the methods .led_iter_hw_mode, .led_set_hw_mode and .led_get_hw_mode in struct phydev. Signed-off-by: Marek Behún <redacted> --- drivers/net/phy/Kconfig | 9 +++ drivers/net/phy/Makefile | 1 + drivers/net/phy/phy_hw_led_mode.c | 96 +++++++++++++++++++++++++++++++ include/linux/phy.h | 15 +++++ 4 files changed, 121 insertions(+) create mode 100644 drivers/net/phy/phy_hw_led_mode.c
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index dd20c2c27c2f..ffea11f73acd 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig@@ -283,6 +283,15 @@ config LED_TRIGGER_PHY <Speed in megabits>Mbps OR <Speed in gigabits>Gbps OR link for any speed known to the PHY. +config LED_TRIGGER_PHY_HW + bool "Support HW LED control modes" + depends on LEDS_TRIGGERS + help + Many PHYs can control blinking of LEDs connected directly to them. + This adds a special LED trigger called phydev-hw-mode. When enabled, + the various control modes supported by the PHY on given LED can be + chosen via hw_mode sysfs file. + comment "MII PHY device drivers"
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index d84bab489a53..fd0253ab8097 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile@@ -20,6 +20,7 @@ endif obj-$(CONFIG_MDIO_DEVRES) += mdio_devres.o libphy-$(CONFIG_SWPHY) += swphy.o libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.o +libphy-$(CONFIG_LED_TRIGGER_PHY_HW) += phy_hw_led_mode.o obj-$(CONFIG_PHYLINK) += phylink.o obj-$(CONFIG_PHYLIB) += libphy.o
diff --git a/drivers/net/phy/phy_hw_led_mode.c b/drivers/net/phy/phy_hw_led_mode.c
new file mode 100644
index 000000000000..b4c2f25266a5
--- /dev/null
+++ b/drivers/net/phy/phy_hw_led_mode.c@@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * drivers/net/phy/phy_hw_led_mode.c + * + * PHY HW LED mode trigger + * + * Copyright (C) 2020 Marek Behun <marek.behun@nic.cz> + */ +#include <linux/leds.h> +#include <linux/phy.h> + +static void phy_hw_led_trig_deactivate(struct led_classdev *cdev) +{ + struct phy_device *phydev = to_phy_device(cdev->dev->parent); + int ret; + + ret = phydev->drv->led_set_hw_mode(phydev, cdev, NULL); + if (ret < 0) { + phydev_err(phydev, "failed deactivating HW mode on LED %s\n", cdev->name); + return; + } +} + +static ssize_t hw_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct led_classdev *cdev = led_trigger_get_led(dev); + struct phy_device *phydev = to_phy_device(cdev->dev->parent); + const char *mode, *cur_mode; + void *iter = NULL; + int len = 0; + + cur_mode = phydev->drv->led_get_hw_mode(phydev, cdev); + + for (mode = phydev->drv->led_iter_hw_mode(phydev, cdev, &iter); + mode; + mode = phydev->drv->led_iter_hw_mode(phydev, cdev, &iter)) { + bool sel; + + sel = cur_mode && !strcmp(mode, cur_mode); + + len += scnprintf(buf + len, PAGE_SIZE - len, "%s%s%s ", sel ? "[" : "", mode, + sel ? "]" : ""); + } + + if (buf[len - 1] == ' ') + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t hw_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev *cdev = led_trigger_get_led(dev); + struct phy_device *phydev = to_phy_device(cdev->dev->parent); + int ret; + + ret = phydev->drv->led_set_hw_mode(phydev, cdev, buf); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(hw_mode); + +static struct attribute *phy_hw_led_trig_attrs[] = { + &dev_attr_hw_mode.attr, + NULL +}; +ATTRIBUTE_GROUPS(phy_hw_led_trig); + +struct led_hw_trigger_type phy_hw_led_trig_type; +EXPORT_SYMBOL_GPL(phy_hw_led_trig_type); + +struct led_trigger phy_hw_led_trig = { + .name = "phydev-hw-mode", + .deactivate = phy_hw_led_trig_deactivate, + .trigger_type = &phy_hw_led_trig_type, + .groups = phy_hw_led_trig_groups, +}; +EXPORT_SYMBOL_GPL(phy_hw_led_trig); + +static int __init phy_led_triggers_init(void) +{ + return led_trigger_register(&phy_hw_led_trig); +} + +module_init(phy_led_triggers_init); + +static void __exit phy_led_triggers_exit(void) +{ + led_trigger_unregister(&phy_hw_led_trig); +} + +module_exit(phy_led_triggers_exit);
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 0403eb799913..3abaed18f63d 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h@@ -14,6 +14,7 @@ #include <linux/compiler.h> #include <linux/spinlock.h> #include <linux/ethtool.h> +#include <linux/leds.h> #include <linux/linkmode.h> #include <linux/netlink.h> #include <linux/mdio.h>
@@ -736,6 +737,14 @@ struct phy_driver { int (*set_loopback)(struct phy_device *dev, bool enable); int (*get_sqi)(struct phy_device *dev); int (*get_sqi_max)(struct phy_device *dev); + +#if IS_ENABLED(CONFIG_LED_TRIGGER_PHY_HW) + /* PHY LED HW modes support */ + const char *(*led_iter_hw_mode)(struct phy_device *dev, struct led_classdev *cdev, + void ** iter); + int (*led_set_hw_mode)(struct phy_device *dev, struct led_classdev *cdev, const char *mode); + const char *(*led_get_hw_mode)(struct phy_device *dev, struct led_classdev *cdev); +#endif }; #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \ struct phy_driver, mdiodrv)
@@ -747,6 +756,12 @@ struct phy_driver { #define PHY_ID_MATCH_MODEL(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 4) #define PHY_ID_MATCH_VENDOR(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 10) +#if IS_ENABLED(CONFIG_LED_TRIGGER_PHY_HW) +/* PHY LED HW mode private trigger */ +extern struct led_hw_trigger_type phy_hw_led_trig_type; +extern struct led_trigger phy_hw_led_trig; +#endif + /* A Structure for boards to register fixups with the PHY Lib */ struct phy_fixup { struct list_head list;
--
2.26.2