Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2e0bc452 authored by Zach Brown's avatar Zach Brown Committed by David S. Miller
Browse files

net: phy: leds: add support for led triggers on phy link state change



Create an option CONFIG_LED_TRIGGER_PHY (default n), which will create a
set of led triggers for each instantiated PHY device. There is one LED
trigger per link-speed, per-phy.
The triggers are registered during phy_attach and unregistered during
phy_detach.

This allows for a user to configure their system to allow a set of LEDs
not controlled by the phy to represent link state changes on the phy.
LEDS controlled by the phy are unaffected.

For example, we have a board where some of the leds in the
RJ45 socket are controlled by the phy, but others are not. Using the
triggers provided by this patch the leds not controlled by the phy can
be configured to show the current speed of the ethernet connection. The
leds controlled by the phy are unaffected.

Signed-off-by: default avatarJosh Cartwright <josh.cartwright@ni.com>
Signed-off-by: default avatarNathan Sullivan <nathan.sullivan@ni.com>
Signed-off-by: default avatarZach Brown <zach.brown@ni.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 1f9127ca
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -15,6 +15,19 @@ if PHYLIB
config SWPHY
	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"

config MDIO_BCM_IPROC
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

libphy-y			:= phy.o phy_device.o mdio_bus.o mdio_device.o
libphy-$(CONFIG_SWPHY)		+= swphy.o
libphy-$(CONFIG_LED_TRIGGER_PHY)	+= phy_led_triggers.o

obj-$(CONFIG_PHYLIB)		+= libphy.o

+1 −0
Original line number Diff line number Diff line
@@ -946,6 +946,7 @@ 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);
}

/**
+5 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@
#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/phy.h>
#include <linux/phy_led_triggers.h>
#include <linux/mdio.h>
#include <linux/io.h>
#include <linux/uaccess.h>
@@ -916,6 +917,8 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
	else
		phy_resume(phydev);

	phy_led_triggers_register(phydev);

	return err;

error:
@@ -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
	 * a use-after-free bug by reading the underlying bus first.
+136 −0
Original line number Diff line number Diff line
/* 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);
Loading