summaryrefslogtreecommitdiffstats
path: root/drivers/net/phy/phy_led_triggers.c
blob: 94ca42e630bbead0c4fcae0b2ef6c8b19296bb69 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/* 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/phy_led_triggers.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) {
		err = -ENOMEM;
		goto out_clear;
	}

	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);
out_clear:
	phy->phy_num_led_triggers = 0;
	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]);
}
EXPORT_SYMBOL_GPL(phy_led_triggers_unregister);