summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/realtek/r8169_leds.c
blob: e10bee706bc691b8c32ec2410baa8d4279de69a0 (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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// SPDX-License-Identifier: GPL-2.0-only
/* r8169_leds.c: Realtek 8169/8168/8101/8125 ethernet driver.
 *
 * Copyright (c) 2023 Heiner Kallweit <hkallweit1@gmail.com>
 *
 * See MAINTAINERS file for support contact information.
 */

#include <linux/leds.h>
#include <linux/netdevice.h>
#include <uapi/linux/uleds.h>

#include "r8169.h"

#define RTL8168_LED_CTRL_OPTION2	BIT(15)
#define RTL8168_LED_CTRL_ACT		BIT(3)
#define RTL8168_LED_CTRL_LINK_1000	BIT(2)
#define RTL8168_LED_CTRL_LINK_100	BIT(1)
#define RTL8168_LED_CTRL_LINK_10	BIT(0)

#define RTL8125_LED_CTRL_ACT		BIT(9)
#define RTL8125_LED_CTRL_LINK_2500	BIT(5)
#define RTL8125_LED_CTRL_LINK_1000	BIT(3)
#define RTL8125_LED_CTRL_LINK_100	BIT(1)
#define RTL8125_LED_CTRL_LINK_10	BIT(0)

#define RTL8168_NUM_LEDS		3
#define RTL8125_NUM_LEDS		4

struct r8169_led_classdev {
	struct led_classdev led;
	struct net_device *ndev;
	int index;
};

#define lcdev_to_r8169_ldev(lcdev) container_of(lcdev, struct r8169_led_classdev, led)

static bool r8169_trigger_mode_is_valid(unsigned long flags)
{
	bool rx, tx;

	if (flags & BIT(TRIGGER_NETDEV_HALF_DUPLEX))
		return false;
	if (flags & BIT(TRIGGER_NETDEV_FULL_DUPLEX))
		return false;

	rx = flags & BIT(TRIGGER_NETDEV_RX);
	tx = flags & BIT(TRIGGER_NETDEV_TX);

	return rx == tx;
}

static int rtl8168_led_hw_control_is_supported(struct led_classdev *led_cdev,
					       unsigned long flags)
{
	struct r8169_led_classdev *ldev = lcdev_to_r8169_ldev(led_cdev);
	struct rtl8169_private *tp = netdev_priv(ldev->ndev);
	int shift = ldev->index * 4;

	if (!r8169_trigger_mode_is_valid(flags)) {
		/* Switch LED off to indicate that mode isn't supported */
		rtl8168_led_mod_ctrl(tp, 0x000f << shift, 0);
		return -EOPNOTSUPP;
	}

	return 0;
}

static int rtl8168_led_hw_control_set(struct led_classdev *led_cdev,
				      unsigned long flags)
{
	struct r8169_led_classdev *ldev = lcdev_to_r8169_ldev(led_cdev);
	struct rtl8169_private *tp = netdev_priv(ldev->ndev);
	int shift = ldev->index * 4;
	u16 mode = 0;

	if (flags & BIT(TRIGGER_NETDEV_LINK_10))
		mode |= RTL8168_LED_CTRL_LINK_10;
	if (flags & BIT(TRIGGER_NETDEV_LINK_100))
		mode |= RTL8168_LED_CTRL_LINK_100;
	if (flags & BIT(TRIGGER_NETDEV_LINK_1000))
		mode |= RTL8168_LED_CTRL_LINK_1000;
	if (flags & BIT(TRIGGER_NETDEV_TX))
		mode |= RTL8168_LED_CTRL_ACT;

	return rtl8168_led_mod_ctrl(tp, 0x000f << shift, mode << shift);
}

static int rtl8168_led_hw_control_get(struct led_classdev *led_cdev,
				      unsigned long *flags)
{
	struct r8169_led_classdev *ldev = lcdev_to_r8169_ldev(led_cdev);
	struct rtl8169_private *tp = netdev_priv(ldev->ndev);
	int shift = ldev->index * 4;
	int mode;

	mode = rtl8168_get_led_mode(tp);
	if (mode < 0)
		return mode;

	if (mode & RTL8168_LED_CTRL_OPTION2) {
		rtl8168_led_mod_ctrl(tp, RTL8168_LED_CTRL_OPTION2, 0);
		netdev_notice(ldev->ndev, "Deactivating unsupported Option2 LED mode\n");
	}

	mode = (mode >> shift) & 0x000f;

	if (mode & RTL8168_LED_CTRL_ACT)
		*flags |= BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX);

	if (mode & RTL8168_LED_CTRL_LINK_10)
		*flags |= BIT(TRIGGER_NETDEV_LINK_10);
	if (mode & RTL8168_LED_CTRL_LINK_100)
		*flags |= BIT(TRIGGER_NETDEV_LINK_100);
	if (mode & RTL8168_LED_CTRL_LINK_1000)
		*flags |= BIT(TRIGGER_NETDEV_LINK_1000);

	return 0;
}

static struct device *
	r8169_led_hw_control_get_device(struct led_classdev *led_cdev)
{
	struct r8169_led_classdev *ldev = lcdev_to_r8169_ldev(led_cdev);

	return &ldev->ndev->dev;
}

static void rtl8168_setup_ldev(struct r8169_led_classdev *ldev,
			       struct net_device *ndev, int index)
{
	struct rtl8169_private *tp = netdev_priv(ndev);
	struct led_classdev *led_cdev = &ldev->led;
	char led_name[LED_MAX_NAME_SIZE];

	ldev->ndev = ndev;
	ldev->index = index;

	r8169_get_led_name(tp, index, led_name, LED_MAX_NAME_SIZE);
	led_cdev->name = led_name;
	led_cdev->hw_control_trigger = "netdev";
	led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;
	led_cdev->hw_control_is_supported = rtl8168_led_hw_control_is_supported;
	led_cdev->hw_control_set = rtl8168_led_hw_control_set;
	led_cdev->hw_control_get = rtl8168_led_hw_control_get;
	led_cdev->hw_control_get_device = r8169_led_hw_control_get_device;

	/* ignore errors */
	led_classdev_register(&ndev->dev, led_cdev);
}

struct r8169_led_classdev *rtl8168_init_leds(struct net_device *ndev)
{
	struct r8169_led_classdev *leds;
	int i;

	leds = kcalloc(RTL8168_NUM_LEDS + 1, sizeof(*leds), GFP_KERNEL);
	if (!leds)
		return NULL;

	for (i = 0; i < RTL8168_NUM_LEDS; i++)
		rtl8168_setup_ldev(leds + i, ndev, i);

	return leds;
}

static int rtl8125_led_hw_control_is_supported(struct led_classdev *led_cdev,
					       unsigned long flags)
{
	struct r8169_led_classdev *ldev = lcdev_to_r8169_ldev(led_cdev);
	struct rtl8169_private *tp = netdev_priv(ldev->ndev);

	if (!r8169_trigger_mode_is_valid(flags)) {
		/* Switch LED off to indicate that mode isn't supported */
		rtl8125_set_led_mode(tp, ldev->index, 0);
		return -EOPNOTSUPP;
	}

	return 0;
}

static int rtl8125_led_hw_control_set(struct led_classdev *led_cdev,
				      unsigned long flags)
{
	struct r8169_led_classdev *ldev = lcdev_to_r8169_ldev(led_cdev);
	struct rtl8169_private *tp = netdev_priv(ldev->ndev);
	u16 mode = 0;

	if (flags & BIT(TRIGGER_NETDEV_LINK_10))
		mode |= RTL8125_LED_CTRL_LINK_10;
	if (flags & BIT(TRIGGER_NETDEV_LINK_100))
		mode |= RTL8125_LED_CTRL_LINK_100;
	if (flags & BIT(TRIGGER_NETDEV_LINK_1000))
		mode |= RTL8125_LED_CTRL_LINK_1000;
	if (flags & BIT(TRIGGER_NETDEV_LINK_2500))
		mode |= RTL8125_LED_CTRL_LINK_2500;
	if (flags & (BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX)))
		mode |= RTL8125_LED_CTRL_ACT;

	return rtl8125_set_led_mode(tp, ldev->index, mode);
}

static int rtl8125_led_hw_control_get(struct led_classdev *led_cdev,
				      unsigned long *flags)
{
	struct r8169_led_classdev *ldev = lcdev_to_r8169_ldev(led_cdev);
	struct rtl8169_private *tp = netdev_priv(ldev->ndev);
	int mode;

	mode = rtl8125_get_led_mode(tp, ldev->index);
	if (mode < 0)
		return mode;

	if (mode & RTL8125_LED_CTRL_LINK_10)
		*flags |= BIT(TRIGGER_NETDEV_LINK_10);
	if (mode & RTL8125_LED_CTRL_LINK_100)
		*flags |= BIT(TRIGGER_NETDEV_LINK_100);
	if (mode & RTL8125_LED_CTRL_LINK_1000)
		*flags |= BIT(TRIGGER_NETDEV_LINK_1000);
	if (mode & RTL8125_LED_CTRL_LINK_2500)
		*flags |= BIT(TRIGGER_NETDEV_LINK_2500);
	if (mode & RTL8125_LED_CTRL_ACT)
		*flags |= BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX);

	return 0;
}

static void rtl8125_setup_led_ldev(struct r8169_led_classdev *ldev,
				   struct net_device *ndev, int index)
{
	struct rtl8169_private *tp = netdev_priv(ndev);
	struct led_classdev *led_cdev = &ldev->led;
	char led_name[LED_MAX_NAME_SIZE];

	ldev->ndev = ndev;
	ldev->index = index;

	r8169_get_led_name(tp, index, led_name, LED_MAX_NAME_SIZE);
	led_cdev->name = led_name;
	led_cdev->hw_control_trigger = "netdev";
	led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;
	led_cdev->hw_control_is_supported = rtl8125_led_hw_control_is_supported;
	led_cdev->hw_control_set = rtl8125_led_hw_control_set;
	led_cdev->hw_control_get = rtl8125_led_hw_control_get;
	led_cdev->hw_control_get_device = r8169_led_hw_control_get_device;

	/* ignore errors */
	led_classdev_register(&ndev->dev, led_cdev);
}

struct r8169_led_classdev *rtl8125_init_leds(struct net_device *ndev)
{
	struct r8169_led_classdev *leds;
	int i;

	leds = kcalloc(RTL8125_NUM_LEDS + 1, sizeof(*leds), GFP_KERNEL);
	if (!leds)
		return NULL;

	for (i = 0; i < RTL8125_NUM_LEDS; i++)
		rtl8125_setup_led_ldev(leds + i, ndev, i);

	return leds;
}

void r8169_remove_leds(struct r8169_led_classdev *leds)
{
	if (!leds)
		return;

	for (struct r8169_led_classdev *l = leds; l->ndev; l++)
		led_classdev_unregister(&l->led);

	kfree(leds);
}