Commit a8446676 authored by David S. Miller's avatar David S. Miller

Merge branch 'phy-led-triggers'

Zach Brown says:

====================
Add support for led triggers on phy link state change

Fix skge driver that declared enum contants that conflicted with enum
constants in linux/leds.h

Create function that encapsulates actions taken during the adjust phy link step
of phy state changes.

Create function that provides list of speeds currently supported by the phy.

Add support for led triggers on phy link state changes by adding
a config option. When set the config option will create a set of led triggers
for each phy device. Users can use the led triggers to represent link state
changes on the phy.

v2:
 * New patch that creates phy_adjust_link function to encapsulate actions taken
   when adjusting phy link during phy state changes
 * led trigger speed strings changed to match existing phy speed strings
 * New function that maps speeds to led triggers
 * Replace magic constants with definitions when declaring trigger name
   buffer and number of triggers.
v3:
 * Changed LED_ON to LED_REG_ON in skge driver to avoid possible future
   conflict and improve consistency.
 * Dropped rtl8712 patch that was accepted separately.
v4:
 * tweaked commit message
v5
 * Changed commit message to explain relationship between the new triggers and
   leds driven by phys.
 * Added new patch that creates phy_supported_speeds function.
 * Moved phy_leds_triggers_register and phy_leds_triggers_unregister to
   phy_attach and phy_detach respectively. This change is so the
   phydev->supported field will be filled by the time the triggers are
   registered.
 * Changed hardcoded list of triggers to dynamic list determined by speeds
   return by phy_supported_speeds.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 5bb61cb5 2e0bc452
...@@ -1048,7 +1048,7 @@ static const char *skge_pause(enum pause_status status) ...@@ -1048,7 +1048,7 @@ static const char *skge_pause(enum pause_status status)
static void skge_link_up(struct skge_port *skge) static void skge_link_up(struct skge_port *skge)
{ {
skge_write8(skge->hw, SK_REG(skge->port, LNK_LED_REG), skge_write8(skge->hw, SK_REG(skge->port, LNK_LED_REG),
LED_BLK_OFF|LED_SYNC_OFF|LED_ON); LED_BLK_OFF|LED_SYNC_OFF|LED_REG_ON);
netif_carrier_on(skge->netdev); netif_carrier_on(skge->netdev);
netif_wake_queue(skge->netdev); netif_wake_queue(skge->netdev);
...@@ -1062,7 +1062,7 @@ static void skge_link_up(struct skge_port *skge) ...@@ -1062,7 +1062,7 @@ static void skge_link_up(struct skge_port *skge)
static void skge_link_down(struct skge_port *skge) static void skge_link_down(struct skge_port *skge)
{ {
skge_write8(skge->hw, SK_REG(skge->port, LNK_LED_REG), LED_OFF); skge_write8(skge->hw, SK_REG(skge->port, LNK_LED_REG), LED_REG_OFF);
netif_carrier_off(skge->netdev); netif_carrier_off(skge->netdev);
netif_stop_queue(skge->netdev); netif_stop_queue(skge->netdev);
...@@ -2668,7 +2668,7 @@ static int skge_down(struct net_device *dev) ...@@ -2668,7 +2668,7 @@ static int skge_down(struct net_device *dev)
if (hw->ports == 1) if (hw->ports == 1)
free_irq(hw->pdev->irq, hw); free_irq(hw->pdev->irq, hw);
skge_write8(skge->hw, SK_REG(skge->port, LNK_LED_REG), LED_OFF); skge_write8(skge->hw, SK_REG(skge->port, LNK_LED_REG), LED_REG_OFF);
if (is_genesis(hw)) if (is_genesis(hw))
genesis_stop(skge); genesis_stop(skge);
else else
......
...@@ -662,8 +662,8 @@ enum { ...@@ -662,8 +662,8 @@ enum {
LED_BLK_OFF = 1<<4, /* Link LED Blinking Off */ LED_BLK_OFF = 1<<4, /* Link LED Blinking Off */
LED_SYNC_ON = 1<<3, /* Use Sync Wire to switch LED */ LED_SYNC_ON = 1<<3, /* Use Sync Wire to switch LED */
LED_SYNC_OFF = 1<<2, /* Disable Sync Wire Input */ LED_SYNC_OFF = 1<<2, /* Disable Sync Wire Input */
LED_ON = 1<<1, /* switch LED on */ LED_REG_ON = 1<<1, /* switch LED on */
LED_OFF = 1<<0, /* switch LED off */ LED_REG_OFF = 1<<0, /* switch LED off */
}; };
/* Receive GMAC FIFO (YUKON) */ /* Receive GMAC FIFO (YUKON) */
......
...@@ -15,6 +15,19 @@ if PHYLIB ...@@ -15,6 +15,19 @@ if PHYLIB
config SWPHY config SWPHY
bool bool
config LED_TRIGGER_PHY
bool "Support LED triggers for tracking link state"
depends on LEDS_TRIGGERS
---help---
Adds support for a set of LED trigger events per-PHY. Link
state change will trigger the events, for consumption by an
LED class driver. There are triggers for each link speed currently
supported by the phy, and are of the form:
<mii bus id>:<phy>:<speed>
Where speed is in the form:
<Speed in megabits>Mbps or <Speed in gigabits>Gbps
comment "MDIO bus device drivers" comment "MDIO bus device drivers"
config MDIO_BCM_IPROC config MDIO_BCM_IPROC
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o
libphy-$(CONFIG_SWPHY) += swphy.o libphy-$(CONFIG_SWPHY) += swphy.o
libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.o
obj-$(CONFIG_PHYLIB) += libphy.o obj-$(CONFIG_PHYLIB) += libphy.o
......
...@@ -260,6 +260,41 @@ static inline unsigned int phy_find_valid(unsigned int idx, u32 features) ...@@ -260,6 +260,41 @@ static inline unsigned int phy_find_valid(unsigned int idx, u32 features)
return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
} }
/**
* phy_supported_speeds - return all speeds currently supported by a phy device
* @phy: The phy device to return supported speeds of.
* @speeds: buffer to store supported speeds in.
* @size: size of speeds buffer.
*
* Description: Returns the number of supported speeds, and fills the speeds
* buffer with the supported speeds. If speeds buffer is too small to contain
* all currently supported speeds, will return as many speeds as can fit.
*/
unsigned int phy_supported_speeds(struct phy_device *phy,
unsigned int *speeds,
unsigned int size)
{
unsigned int count = 0;
unsigned int idx = 0;
while (idx < MAX_NUM_SETTINGS && count < size) {
idx = phy_find_valid(idx, phy->supported);
if (!(settings[idx].setting & phy->supported))
break;
/* Assumes settings are grouped by speed */
if ((count == 0) ||
(speeds[count - 1] != settings[idx].speed)) {
speeds[count] = settings[idx].speed;
count++;
}
idx++;
}
return count;
}
/** /**
* phy_check_valid - check if there is a valid PHY setting which matches * phy_check_valid - check if there is a valid PHY setting which matches
* speed, duplex, and feature mask * speed, duplex, and feature mask
...@@ -908,6 +943,12 @@ void phy_start(struct phy_device *phydev) ...@@ -908,6 +943,12 @@ void phy_start(struct phy_device *phydev)
} }
EXPORT_SYMBOL(phy_start); EXPORT_SYMBOL(phy_start);
static void phy_adjust_link(struct phy_device *phydev)
{
phydev->adjust_link(phydev->attached_dev);
phy_led_trigger_change_speed(phydev);
}
/** /**
* phy_state_machine - Handle the state machine * phy_state_machine - Handle the state machine
* @work: work_struct that describes the work to be done * @work: work_struct that describes the work to be done
...@@ -950,7 +991,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -950,7 +991,7 @@ void phy_state_machine(struct work_struct *work)
if (!phydev->link) { if (!phydev->link) {
phydev->state = PHY_NOLINK; phydev->state = PHY_NOLINK;
netif_carrier_off(phydev->attached_dev); netif_carrier_off(phydev->attached_dev);
phydev->adjust_link(phydev->attached_dev); phy_adjust_link(phydev);
break; break;
} }
...@@ -963,7 +1004,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -963,7 +1004,7 @@ void phy_state_machine(struct work_struct *work)
if (err > 0) { if (err > 0) {
phydev->state = PHY_RUNNING; phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev); netif_carrier_on(phydev->attached_dev);
phydev->adjust_link(phydev->attached_dev); phy_adjust_link(phydev);
} else if (0 == phydev->link_timeout--) } else if (0 == phydev->link_timeout--)
needs_aneg = true; needs_aneg = true;
...@@ -990,7 +1031,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -990,7 +1031,7 @@ void phy_state_machine(struct work_struct *work)
} }
phydev->state = PHY_RUNNING; phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev); netif_carrier_on(phydev->attached_dev);
phydev->adjust_link(phydev->attached_dev); phy_adjust_link(phydev);
} }
break; break;
case PHY_FORCING: case PHY_FORCING:
...@@ -1006,7 +1047,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -1006,7 +1047,7 @@ void phy_state_machine(struct work_struct *work)
needs_aneg = true; needs_aneg = true;
} }
phydev->adjust_link(phydev->attached_dev); phy_adjust_link(phydev);
break; break;
case PHY_RUNNING: case PHY_RUNNING:
/* Only register a CHANGE if we are polling and link changed /* Only register a CHANGE if we are polling and link changed
...@@ -1035,7 +1076,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -1035,7 +1076,7 @@ void phy_state_machine(struct work_struct *work)
netif_carrier_off(phydev->attached_dev); netif_carrier_off(phydev->attached_dev);
} }
phydev->adjust_link(phydev->attached_dev); phy_adjust_link(phydev);
if (phy_interrupt_is_valid(phydev)) if (phy_interrupt_is_valid(phydev))
err = phy_config_interrupt(phydev, err = phy_config_interrupt(phydev,
...@@ -1045,7 +1086,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -1045,7 +1086,7 @@ void phy_state_machine(struct work_struct *work)
if (phydev->link) { if (phydev->link) {
phydev->link = 0; phydev->link = 0;
netif_carrier_off(phydev->attached_dev); netif_carrier_off(phydev->attached_dev);
phydev->adjust_link(phydev->attached_dev); phy_adjust_link(phydev);
do_suspend = true; do_suspend = true;
} }
break; break;
...@@ -1069,7 +1110,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -1069,7 +1110,7 @@ void phy_state_machine(struct work_struct *work)
} else { } else {
phydev->state = PHY_NOLINK; phydev->state = PHY_NOLINK;
} }
phydev->adjust_link(phydev->attached_dev); phy_adjust_link(phydev);
} else { } else {
phydev->state = PHY_AN; phydev->state = PHY_AN;
phydev->link_timeout = PHY_AN_TIMEOUT; phydev->link_timeout = PHY_AN_TIMEOUT;
...@@ -1085,7 +1126,7 @@ void phy_state_machine(struct work_struct *work) ...@@ -1085,7 +1126,7 @@ void phy_state_machine(struct work_struct *work)
} else { } else {
phydev->state = PHY_NOLINK; phydev->state = PHY_NOLINK;
} }
phydev->adjust_link(phydev->attached_dev); phy_adjust_link(phydev);
} }
break; break;
} }
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <linux/mii.h> #include <linux/mii.h>
#include <linux/ethtool.h> #include <linux/ethtool.h>
#include <linux/phy.h> #include <linux/phy.h>
#include <linux/phy_led_triggers.h>
#include <linux/mdio.h> #include <linux/mdio.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
...@@ -916,6 +917,8 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, ...@@ -916,6 +917,8 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
else else
phy_resume(phydev); phy_resume(phydev);
phy_led_triggers_register(phydev);
return err; return err;
error: error:
...@@ -989,6 +992,8 @@ void phy_detach(struct phy_device *phydev) ...@@ -989,6 +992,8 @@ void phy_detach(struct phy_device *phydev)
} }
} }
phy_led_triggers_unregister(phydev);
/* /*
* The phydev might go away on the put_device() below, so avoid * The phydev might go away on the put_device() below, so avoid
* a use-after-free bug by reading the underlying bus first. * a use-after-free bug by reading the underlying bus first.
......
/* Copyright (C) 2016 National Instruments Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/leds.h>
#include <linux/phy.h>
#include <linux/netdevice.h>
static struct phy_led_trigger *phy_speed_to_led_trigger(struct phy_device *phy,
unsigned int speed)
{
unsigned int i;
for (i = 0; i < phy->phy_num_led_triggers; i++) {
if (phy->phy_led_triggers[i].speed == speed)
return &phy->phy_led_triggers[i];
}
return NULL;
}
void phy_led_trigger_change_speed(struct phy_device *phy)
{
struct phy_led_trigger *plt;
if (!phy->link)
goto out_change_speed;
if (phy->speed == 0)
return;
plt = phy_speed_to_led_trigger(phy, phy->speed);
if (!plt) {
netdev_alert(phy->attached_dev,
"No phy led trigger registered for speed(%d)\n",
phy->speed);
goto out_change_speed;
}
if (plt != phy->last_triggered) {
led_trigger_event(&phy->last_triggered->trigger, LED_OFF);
led_trigger_event(&plt->trigger, LED_FULL);
phy->last_triggered = plt;
}
return;
out_change_speed:
if (phy->last_triggered) {
led_trigger_event(&phy->last_triggered->trigger,
LED_OFF);
phy->last_triggered = NULL;
}
}
EXPORT_SYMBOL_GPL(phy_led_trigger_change_speed);
static int phy_led_trigger_register(struct phy_device *phy,
struct phy_led_trigger *plt,
unsigned int speed)
{
char name_suffix[PHY_LED_TRIGGER_SPEED_SUFFIX_SIZE];
plt->speed = speed;
if (speed < SPEED_1000)
snprintf(name_suffix, sizeof(name_suffix), "%dMbps", speed);
else if (speed == SPEED_2500)
snprintf(name_suffix, sizeof(name_suffix), "2.5Gbps");
else
snprintf(name_suffix, sizeof(name_suffix), "%dGbps",
DIV_ROUND_CLOSEST(speed, 1000));
snprintf(plt->name, sizeof(plt->name), PHY_ID_FMT ":%s",
phy->mdio.bus->id, phy->mdio.addr, name_suffix);
plt->trigger.name = plt->name;
return led_trigger_register(&plt->trigger);
}
static void phy_led_trigger_unregister(struct phy_led_trigger *plt)
{
led_trigger_unregister(&plt->trigger);
}
int phy_led_triggers_register(struct phy_device *phy)
{
int i, err;
unsigned int speeds[50];
phy->phy_num_led_triggers = phy_supported_speeds(phy, speeds,
ARRAY_SIZE(speeds));
if (!phy->phy_num_led_triggers)
return 0;
phy->phy_led_triggers = devm_kzalloc(&phy->mdio.dev,
sizeof(struct phy_led_trigger) *
phy->phy_num_led_triggers,
GFP_KERNEL);
if (!phy->phy_led_triggers)
return -ENOMEM;
for (i = 0; i < phy->phy_num_led_triggers; i++) {
err = phy_led_trigger_register(phy, &phy->phy_led_triggers[i],
speeds[i]);
if (err)
goto out_unreg;
}
phy->last_triggered = NULL;
phy_led_trigger_change_speed(phy);
return 0;
out_unreg:
while (i--)
phy_led_trigger_unregister(&phy->phy_led_triggers[i]);
devm_kfree(&phy->mdio.dev, phy->phy_led_triggers);
return err;
}
EXPORT_SYMBOL_GPL(phy_led_triggers_register);
void phy_led_triggers_unregister(struct phy_device *phy)
{
int i;
for (i = 0; i < phy->phy_num_led_triggers; i++)
phy_led_trigger_unregister(&phy->phy_led_triggers[i]);
devm_kfree(&phy->mdio.dev, phy->phy_led_triggers);
}
EXPORT_SYMBOL_GPL(phy_led_triggers_unregister);
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <linux/mod_devicetable.h> #include <linux/mod_devicetable.h>
#include <linux/phy_led_triggers.h>
#include <linux/atomic.h> #include <linux/atomic.h>
...@@ -84,6 +85,21 @@ typedef enum { ...@@ -84,6 +85,21 @@ typedef enum {
PHY_INTERFACE_MODE_MAX, PHY_INTERFACE_MODE_MAX,
} phy_interface_t; } phy_interface_t;
/**
* phy_supported_speeds - return all speeds currently supported by a phy device
* @phy: The phy device to return supported speeds of.
* @speeds: buffer to store supported speeds in.
* @size: size of speeds buffer.
*
* Description: Returns the number of supported speeds, and
* fills the speeds * buffer with the supported speeds. If speeds buffer is
* too small to contain * all currently supported speeds, will return as
* many speeds as can fit.
*/
unsigned int phy_supported_speeds(struct phy_device *phy,
unsigned int *speeds,
unsigned int size);
/** /**
* It maps 'enum phy_interface_t' found in include/linux/phy.h * It maps 'enum phy_interface_t' found in include/linux/phy.h
* into the device tree binding of 'phy-mode', so that Ethernet * into the device tree binding of 'phy-mode', so that Ethernet
...@@ -405,6 +421,12 @@ struct phy_device { ...@@ -405,6 +421,12 @@ struct phy_device {
int link_timeout; int link_timeout;
#ifdef CONFIG_LED_TRIGGER_PHY
struct phy_led_trigger *phy_led_triggers;
unsigned int phy_num_led_triggers;
struct phy_led_trigger *last_triggered;
#endif
/* /*
* Interrupt number for this PHY * Interrupt number for this PHY
* -1 means no interrupt * -1 means no interrupt
......
/* Copyright (C) 2016 National Instruments Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __PHY_LED_TRIGGERS
#define __PHY_LED_TRIGGERS
struct phy_device;
#ifdef CONFIG_LED_TRIGGER_PHY
#include <linux/leds.h>
#define PHY_LED_TRIGGER_SPEED_SUFFIX_SIZE 10
#define PHY_MII_BUS_ID_SIZE (20 - 3)
#define PHY_LINK_LED_TRIGGER_NAME_SIZE (PHY_MII_BUS_ID_SIZE + \
FIELD_SIZEOF(struct mdio_device, addr)+\
PHY_LED_TRIGGER_SPEED_SUFFIX_SIZE)
struct phy_led_trigger {
struct led_trigger trigger;
char name[PHY_LINK_LED_TRIGGER_NAME_SIZE];
unsigned int speed;
};
extern int phy_led_triggers_register(struct phy_device *phy);
extern void phy_led_triggers_unregister(struct phy_device *phy);
extern void phy_led_trigger_change_speed(struct phy_device *phy);
#else
static inline int phy_led_triggers_register(struct phy_device *phy)
{
return 0;
}
static inline void phy_led_triggers_unregister(struct phy_device *phy) { }
static inline void phy_led_trigger_change_speed(struct phy_device *phy) { }
#endif
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment