summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/realtek/r8169_leds.c
blob: 78078eca3c434be66c6b50fb9efa059786a257aa (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
// 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 RTL8168_NUM_LEDS		3

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 */
	devm_led_classdev_register(&ndev->dev, led_cdev);
}

void rtl8168_init_leds(struct net_device *ndev)
{
	/* bind resource mgmt to netdev */
	struct device *dev = &ndev->dev;
	struct r8169_led_classdev *leds;
	int i;

	leds = devm_kcalloc(dev, RTL8168_NUM_LEDS, sizeof(*leds), GFP_KERNEL);
	if (!leds)
		return;

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