1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | |
4 | Broadcom B43 wireless driver |
5 | LED control |
6 | |
7 | Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>, |
8 | Copyright (c) 2005 Stefano Brivio <stefano.brivio@polimi.it> |
9 | Copyright (c) 2005-2007 Michael Buesch <m@bues.ch> |
10 | Copyright (c) 2005 Danny van Dyk <kugelfang@gentoo.org> |
11 | Copyright (c) 2005 Andreas Jaggi <andreas.jaggi@waterwave.ch> |
12 | |
13 | |
14 | */ |
15 | |
16 | #include "b43legacy.h" |
17 | #include "leds.h" |
18 | #include "rfkill.h" |
19 | |
20 | |
21 | static void b43legacy_led_turn_on(struct b43legacy_wldev *dev, u8 led_index, |
22 | bool activelow) |
23 | { |
24 | struct b43legacy_wl *wl = dev->wl; |
25 | unsigned long flags; |
26 | u16 ctl; |
27 | |
28 | spin_lock_irqsave(&wl->leds_lock, flags); |
29 | ctl = b43legacy_read16(dev, B43legacy_MMIO_GPIO_CONTROL); |
30 | if (activelow) |
31 | ctl &= ~(1 << led_index); |
32 | else |
33 | ctl |= (1 << led_index); |
34 | b43legacy_write16(dev, B43legacy_MMIO_GPIO_CONTROL, value: ctl); |
35 | spin_unlock_irqrestore(lock: &wl->leds_lock, flags); |
36 | } |
37 | |
38 | static void b43legacy_led_turn_off(struct b43legacy_wldev *dev, u8 led_index, |
39 | bool activelow) |
40 | { |
41 | struct b43legacy_wl *wl = dev->wl; |
42 | unsigned long flags; |
43 | u16 ctl; |
44 | |
45 | spin_lock_irqsave(&wl->leds_lock, flags); |
46 | ctl = b43legacy_read16(dev, B43legacy_MMIO_GPIO_CONTROL); |
47 | if (activelow) |
48 | ctl |= (1 << led_index); |
49 | else |
50 | ctl &= ~(1 << led_index); |
51 | b43legacy_write16(dev, B43legacy_MMIO_GPIO_CONTROL, value: ctl); |
52 | spin_unlock_irqrestore(lock: &wl->leds_lock, flags); |
53 | } |
54 | |
55 | /* Callback from the LED subsystem. */ |
56 | static void b43legacy_led_brightness_set(struct led_classdev *led_dev, |
57 | enum led_brightness brightness) |
58 | { |
59 | struct b43legacy_led *led = container_of(led_dev, struct b43legacy_led, |
60 | led_dev); |
61 | struct b43legacy_wldev *dev = led->dev; |
62 | bool radio_enabled; |
63 | |
64 | /* Checking the radio-enabled status here is slightly racy, |
65 | * but we want to avoid the locking overhead and we don't care |
66 | * whether the LED has the wrong state for a second. */ |
67 | radio_enabled = (dev->phy.radio_on && dev->radio_hw_enable); |
68 | |
69 | if (brightness == LED_OFF || !radio_enabled) |
70 | b43legacy_led_turn_off(dev, led_index: led->index, activelow: led->activelow); |
71 | else |
72 | b43legacy_led_turn_on(dev, led_index: led->index, activelow: led->activelow); |
73 | } |
74 | |
75 | static int b43legacy_register_led(struct b43legacy_wldev *dev, |
76 | struct b43legacy_led *led, |
77 | const char *name, |
78 | const char *default_trigger, |
79 | u8 led_index, bool activelow) |
80 | { |
81 | int err; |
82 | |
83 | b43legacy_led_turn_off(dev, led_index, activelow); |
84 | if (led->dev) |
85 | return -EEXIST; |
86 | if (!default_trigger) |
87 | return -EINVAL; |
88 | led->dev = dev; |
89 | led->index = led_index; |
90 | led->activelow = activelow; |
91 | strscpy(led->name, name, sizeof(led->name)); |
92 | |
93 | led->led_dev.name = led->name; |
94 | led->led_dev.default_trigger = default_trigger; |
95 | led->led_dev.brightness_set = b43legacy_led_brightness_set; |
96 | |
97 | err = led_classdev_register(parent: dev->dev->dev, led_cdev: &led->led_dev); |
98 | if (err) { |
99 | b43legacywarn(wl: dev->wl, fmt: "LEDs: Failed to register %s\n" , name); |
100 | led->dev = NULL; |
101 | return err; |
102 | } |
103 | return 0; |
104 | } |
105 | |
106 | static void b43legacy_unregister_led(struct b43legacy_led *led) |
107 | { |
108 | if (!led->dev) |
109 | return; |
110 | led_classdev_unregister(led_cdev: &led->led_dev); |
111 | b43legacy_led_turn_off(dev: led->dev, led_index: led->index, activelow: led->activelow); |
112 | led->dev = NULL; |
113 | } |
114 | |
115 | static void b43legacy_map_led(struct b43legacy_wldev *dev, |
116 | u8 led_index, |
117 | enum b43legacy_led_behaviour behaviour, |
118 | bool activelow) |
119 | { |
120 | struct ieee80211_hw *hw = dev->wl->hw; |
121 | char name[B43legacy_LED_MAX_NAME_LEN + 1]; |
122 | |
123 | /* Map the b43 specific LED behaviour value to the |
124 | * generic LED triggers. */ |
125 | switch (behaviour) { |
126 | case B43legacy_LED_INACTIVE: |
127 | break; |
128 | case B43legacy_LED_OFF: |
129 | b43legacy_led_turn_off(dev, led_index, activelow); |
130 | break; |
131 | case B43legacy_LED_ON: |
132 | b43legacy_led_turn_on(dev, led_index, activelow); |
133 | break; |
134 | case B43legacy_LED_ACTIVITY: |
135 | case B43legacy_LED_TRANSFER: |
136 | case B43legacy_LED_APTRANSFER: |
137 | snprintf(buf: name, size: sizeof(name), |
138 | fmt: "b43legacy-%s::tx" , wiphy_name(wiphy: hw->wiphy)); |
139 | b43legacy_register_led(dev, led: &dev->led_tx, name, |
140 | default_trigger: ieee80211_get_tx_led_name(hw), |
141 | led_index, activelow); |
142 | snprintf(buf: name, size: sizeof(name), |
143 | fmt: "b43legacy-%s::rx" , wiphy_name(wiphy: hw->wiphy)); |
144 | b43legacy_register_led(dev, led: &dev->led_rx, name, |
145 | default_trigger: ieee80211_get_rx_led_name(hw), |
146 | led_index, activelow); |
147 | break; |
148 | case B43legacy_LED_RADIO_ALL: |
149 | case B43legacy_LED_RADIO_A: |
150 | case B43legacy_LED_RADIO_B: |
151 | case B43legacy_LED_MODE_BG: |
152 | snprintf(buf: name, size: sizeof(name), |
153 | fmt: "b43legacy-%s::radio" , wiphy_name(wiphy: hw->wiphy)); |
154 | b43legacy_register_led(dev, led: &dev->led_radio, name, |
155 | default_trigger: ieee80211_get_radio_led_name(hw), |
156 | led_index, activelow); |
157 | /* Sync the RF-kill LED state with radio and switch states. */ |
158 | if (dev->phy.radio_on && b43legacy_is_hw_radio_enabled(dev)) |
159 | b43legacy_led_turn_on(dev, led_index, activelow); |
160 | break; |
161 | case B43legacy_LED_WEIRD: |
162 | case B43legacy_LED_ASSOC: |
163 | snprintf(buf: name, size: sizeof(name), |
164 | fmt: "b43legacy-%s::assoc" , wiphy_name(wiphy: hw->wiphy)); |
165 | b43legacy_register_led(dev, led: &dev->led_assoc, name, |
166 | default_trigger: ieee80211_get_assoc_led_name(hw), |
167 | led_index, activelow); |
168 | break; |
169 | default: |
170 | b43legacywarn(wl: dev->wl, fmt: "LEDs: Unknown behaviour 0x%02X\n" , |
171 | behaviour); |
172 | break; |
173 | } |
174 | } |
175 | |
176 | void b43legacy_leds_init(struct b43legacy_wldev *dev) |
177 | { |
178 | struct ssb_bus *bus = dev->dev->bus; |
179 | u8 sprom[4]; |
180 | int i; |
181 | enum b43legacy_led_behaviour behaviour; |
182 | bool activelow; |
183 | |
184 | sprom[0] = bus->sprom.gpio0; |
185 | sprom[1] = bus->sprom.gpio1; |
186 | sprom[2] = bus->sprom.gpio2; |
187 | sprom[3] = bus->sprom.gpio3; |
188 | |
189 | for (i = 0; i < 4; i++) { |
190 | if (sprom[i] == 0xFF) { |
191 | /* There is no LED information in the SPROM |
192 | * for this LED. Hardcode it here. */ |
193 | activelow = false; |
194 | switch (i) { |
195 | case 0: |
196 | behaviour = B43legacy_LED_ACTIVITY; |
197 | activelow = true; |
198 | if (bus->boardinfo.vendor == PCI_VENDOR_ID_COMPAQ) |
199 | behaviour = B43legacy_LED_RADIO_ALL; |
200 | break; |
201 | case 1: |
202 | behaviour = B43legacy_LED_RADIO_B; |
203 | if (bus->boardinfo.vendor == PCI_VENDOR_ID_ASUSTEK) |
204 | behaviour = B43legacy_LED_ASSOC; |
205 | break; |
206 | case 2: |
207 | behaviour = B43legacy_LED_RADIO_A; |
208 | break; |
209 | case 3: |
210 | behaviour = B43legacy_LED_OFF; |
211 | break; |
212 | default: |
213 | B43legacy_WARN_ON(1); |
214 | return; |
215 | } |
216 | } else { |
217 | behaviour = sprom[i] & B43legacy_LED_BEHAVIOUR; |
218 | activelow = !!(sprom[i] & B43legacy_LED_ACTIVELOW); |
219 | } |
220 | b43legacy_map_led(dev, led_index: i, behaviour, activelow); |
221 | } |
222 | } |
223 | |
224 | void b43legacy_leds_exit(struct b43legacy_wldev *dev) |
225 | { |
226 | b43legacy_unregister_led(led: &dev->led_tx); |
227 | b43legacy_unregister_led(led: &dev->led_rx); |
228 | b43legacy_unregister_led(led: &dev->led_assoc); |
229 | b43legacy_unregister_led(led: &dev->led_radio); |
230 | } |
231 | |