Add the LED triggers for ethernet PHY link and activity status. The triggers are set mainly based on the PHY state machine.
Signed-off-by: Vishal Thanki <vishaltha...@gmail.com> --- drivers/net/phy/Kconfig | 7 +++++ drivers/net/phy/Makefile | 1 + drivers/net/phy/phy.c | 20 +++++++++++-- drivers/net/phy/phy_device.c | 4 +++ drivers/net/phy/phy_led.c | 70 ++++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/phy_led.h | 37 +++++++++++++++++++++++ include/linux/leds.h | 1 + include/linux/phy.h | 6 ++++ 8 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 drivers/net/phy/phy_led.c create mode 100644 drivers/net/phy/phy_led.h diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 6dad9a9..15712da 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -271,6 +271,13 @@ config MDIO_BCM_IPROC This module provides a driver for the MDIO busses found in the Broadcom iProc SoC's. +config ETH_PHY_LED + bool "Ethernet PHY LED trigger support (Experimental)" + depends on LEDS_TRIGGERS + help + This feature enables the Ethernet PHY LED triggers for PHY link + and data transfer activities. + endif # PHYLIB config MICREL_KS8995MA diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index fcdbb92..76ebd83 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -44,3 +44,4 @@ obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o obj-$(CONFIG_MICROCHIP_PHY) += microchip.o obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o +obj-$(CONFIG_ETH_PHY_LED) += phy_led.o diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 5590b9c..e422ff6 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -35,6 +35,7 @@ #include <linux/io.h> #include <linux/uaccess.h> #include <linux/atomic.h> +#include "phy_led.h" #include <asm/irq.h> @@ -831,15 +832,15 @@ void phy_state_machine(struct work_struct *work) switch (phydev->state) { case PHY_DOWN: + phy_trig_led_link(phydev, 0); case PHY_STARTING: case PHY_READY: case PHY_PENDING: break; case PHY_UP: needs_aneg = true; - phydev->link_timeout = PHY_AN_TIMEOUT; - + phy_trig_led_link(phydev, 1); break; case PHY_AN: err = phy_read_status(phydev); @@ -849,6 +850,7 @@ void phy_state_machine(struct work_struct *work) /* If the link is down, give up on negotiation for now */ if (!phydev->link) { phydev->state = PHY_NOLINK; + phy_trig_led_link(phydev, 0); netif_carrier_off(phydev->attached_dev); phydev->adjust_link(phydev->attached_dev); break; @@ -862,6 +864,7 @@ void phy_state_machine(struct work_struct *work) /* If AN is done, we're running */ if (err > 0) { phydev->state = PHY_RUNNING; + phy_trig_led_act(phydev); netif_carrier_on(phydev->attached_dev); phydev->adjust_link(phydev->attached_dev); @@ -889,6 +892,7 @@ void phy_state_machine(struct work_struct *work) } } phydev->state = PHY_RUNNING; + phy_trig_led_act(phydev); netif_carrier_on(phydev->attached_dev); phydev->adjust_link(phydev->attached_dev); } @@ -900,6 +904,8 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; + phy_trig_led_act(phydev); + phy_trig_led_link(phydev, 1); netif_carrier_on(phydev->attached_dev); } else { if (0 == phydev->link_timeout--) @@ -929,9 +935,12 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; + phy_trig_led_act(phydev); + phy_trig_led_link(phydev, 1); netif_carrier_on(phydev->attached_dev); } else { phydev->state = PHY_NOLINK; + phy_trig_led_link(phydev, 0); netif_carrier_off(phydev->attached_dev); } @@ -948,6 +957,7 @@ void phy_state_machine(struct work_struct *work) phydev->adjust_link(phydev->attached_dev); do_suspend = true; } + led_trigger_event(phydev->phy_act, LED_OFF); break; case PHY_RESUMING: if (AUTONEG_ENABLE == phydev->autoneg) { @@ -965,9 +975,12 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; + phy_trig_led_link(phydev, 1); + phy_trig_led_act(phydev); netif_carrier_on(phydev->attached_dev); } else { phydev->state = PHY_NOLINK; + phy_trig_led_link(phydev, 0); } phydev->adjust_link(phydev->attached_dev); } else { @@ -981,9 +994,12 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; + phy_trig_led_link(phydev, 1); + phy_trig_led_act(phydev); netif_carrier_on(phydev->attached_dev); } else { phydev->state = PHY_NOLINK; + phy_trig_led_link(phydev, 0); } phydev->adjust_link(phydev->attached_dev); } diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index e551f3a..fafcc3c 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -34,6 +34,7 @@ #include <linux/io.h> #include <linux/uaccess.h> #include <linux/of.h> +#include "phy_led.h" #include <asm/irq.h> @@ -1592,6 +1593,8 @@ static int phy_probe(struct device *dev) of_set_phy_supported(phydev); phydev->advertising = phydev->supported; + phy_led_init(phydev); + /* Set the state to READY by default */ phydev->state = PHY_READY; @@ -1608,6 +1611,7 @@ static int phy_remove(struct device *dev) struct phy_device *phydev = to_phy_device(dev); mutex_lock(&phydev->lock); + phy_led_cleanup(phydev); phydev->state = PHY_DOWN; mutex_unlock(&phydev->lock); diff --git a/drivers/net/phy/phy_led.c b/drivers/net/phy/phy_led.c new file mode 100644 index 0000000..240355a --- /dev/null +++ b/drivers/net/phy/phy_led.c @@ -0,0 +1,70 @@ +#include <linux/phy.h> +#include <linux/leds.h> + +static void phy_link_trig_activate(struct led_classdev *led) +{ + struct phy_device *phydev = led->trigger->data; + enum led_brightness value = LED_OFF; + + if (phydev && phydev->state != PHY_DOWN) + value = LED_FULL; + led_set_brightness(led, value); +} + +static void phy_act_trig_activate(struct led_classdev *led) +{ + struct phy_device *phydev = led->trigger->data; + unsigned long on = 0, off = 0; + + if (phydev && (phydev->state == PHY_RUNNING || + phydev->state == PHY_CHANGELINK)) + led_blink_set(led, &on, &off); +} + +void phy_led_init(struct phy_device *phydev) +{ + phydev->phy_link = kzalloc(sizeof(*phydev->phy_link), GFP_KERNEL); + if (!phydev->phy_link) + return; + + snprintf(phydev->phy_link_name, sizeof(phydev->phy_link_name), + "%x-eth-phy-link", phydev->drv->phy_id); + phydev->phy_link->name = phydev->phy_link_name; + if (led_trigger_register(phydev->phy_link)) { + kfree(phydev->phy_link); + phydev->phy_link = NULL; + } + phydev->phy_link->data = phydev; + phydev->phy_link->activate = phy_link_trig_activate; + + phydev->phy_act = kzalloc(sizeof(*phydev->phy_act), GFP_KERNEL); + if (!phydev->phy_act) { + led_trigger_unregister(phydev->phy_link); + kfree(phydev->phy_link); + return; + } + phydev->phy_act->data = phydev; + phydev->phy_act->activate = phy_act_trig_activate; + + snprintf(phydev->phy_act_name, sizeof(phydev->phy_act_name), + "%x-eth-phy-activity", phydev->drv->phy_id); + phydev->phy_act->name = phydev->phy_act_name; + if (led_trigger_register(phydev->phy_act)) { + kfree(phydev->phy_act); + phydev->phy_act = NULL; + return; + } +} + +void phy_led_cleanup(struct phy_device *phydev) +{ + if (phydev->phy_link) { + led_trigger_unregister(phydev->phy_link); + kfree(phydev->phy_link); + } + if (phydev->phy_act) { + led_trigger_unregister(phydev->phy_act); + kfree(phydev->phy_act); + } +} + diff --git a/drivers/net/phy/phy_led.h b/drivers/net/phy/phy_led.h new file mode 100644 index 0000000..ff5bd98 --- /dev/null +++ b/drivers/net/phy/phy_led.h @@ -0,0 +1,37 @@ +#include <linux/leds.h> + +#ifdef CONFIG_ETH_PHY_LED +static inline void phy_trig_led_act(struct phy_device *phydev) +{ + unsigned long on = 0, off = 0; + + led_trigger_blink(phydev->phy_act, &on, &off); +} + +static inline void phy_trig_led_link(struct phy_device *phydev, int on) +{ + enum led_brightness value = (on) ? LED_FULL : LED_OFF; + + led_trigger_event(phydev->phy_link, value); +} + +void phy_led_init(struct phy_device *phydev); +void phy_led_cleanup(struct phy_device *phydev); +#else +static inline void phy_trig_led_act(struct phy_device *phydev) +{ +} + +static inline void phy_trig_led_link(struct phy_device *phydev, int on) +{ +} + +static inline void phy_led_init(struct phy_device *phydev) +{ +} + +static inline void phy_led_cleanup(struct phy_device *phydev) +{ +} +#endif + diff --git a/include/linux/leds.h b/include/linux/leds.h index f203a8f..11ec574 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -233,6 +233,7 @@ struct led_trigger { const char *name; void (*activate)(struct led_classdev *led_cdev); void (*deactivate)(struct led_classdev *led_cdev); + void *data; /* LEDs under control by this trigger (for simple triggers) */ rwlock_t leddev_list_lock; diff --git a/include/linux/phy.h b/include/linux/phy.h index 2abd791..94fee77 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -25,6 +25,7 @@ #include <linux/timer.h> #include <linux/workqueue.h> #include <linux/mod_devicetable.h> +#include <linux/leds.h> #include <linux/atomic.h> @@ -60,6 +61,7 @@ #define PHY_HAS_INTERRUPT 0x00000001 #define PHY_HAS_MAGICANEG 0x00000002 #define PHY_IS_INTERNAL 0x00000004 +#define PHY_HAS_LED_CTRL 0x00000008 #define MDIO_DEVICE_IS_PHY 0x80000000 /* Interface Mode definitions */ @@ -424,6 +426,10 @@ struct phy_device { u8 mdix; void (*adjust_link)(struct net_device *dev); + + struct led_trigger *phy_link, *phy_act; + char phy_link_name[32], phy_act_name[32]; + }; #define to_phy_device(d) container_of(to_mdio_device(d), \ struct phy_device, mdio) -- 2.4.3