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 "b43.h" |
17 | #include "leds.h" |
18 | #include "rfkill.h" |
19 | |
20 | |
21 | static void b43_led_turn_on(struct b43_wldev *dev, u8 led_index, |
22 | bool activelow) |
23 | { |
24 | u16 ctl; |
25 | |
26 | ctl = b43_read16(dev, B43_MMIO_GPIO_CONTROL); |
27 | if (activelow) |
28 | ctl &= ~(1 << led_index); |
29 | else |
30 | ctl |= (1 << led_index); |
31 | b43_write16(dev, B43_MMIO_GPIO_CONTROL, value: ctl); |
32 | } |
33 | |
34 | static void b43_led_turn_off(struct b43_wldev *dev, u8 led_index, |
35 | bool activelow) |
36 | { |
37 | u16 ctl; |
38 | |
39 | ctl = b43_read16(dev, B43_MMIO_GPIO_CONTROL); |
40 | if (activelow) |
41 | ctl |= (1 << led_index); |
42 | else |
43 | ctl &= ~(1 << led_index); |
44 | b43_write16(dev, B43_MMIO_GPIO_CONTROL, value: ctl); |
45 | } |
46 | |
47 | static void b43_led_update(struct b43_wldev *dev, |
48 | struct b43_led *led) |
49 | { |
50 | bool radio_enabled; |
51 | bool turn_on; |
52 | |
53 | if (!led->wl) |
54 | return; |
55 | |
56 | radio_enabled = (dev->phy.radio_on && dev->radio_hw_enable); |
57 | |
58 | /* The led->state read is racy, but we don't care. In case we raced |
59 | * with the brightness_set handler, we will be called again soon |
60 | * to fixup our state. */ |
61 | if (radio_enabled) |
62 | turn_on = atomic_read(v: &led->state) != LED_OFF; |
63 | else |
64 | turn_on = false; |
65 | if (turn_on == led->hw_state) |
66 | return; |
67 | led->hw_state = turn_on; |
68 | |
69 | if (turn_on) |
70 | b43_led_turn_on(dev, led_index: led->index, activelow: led->activelow); |
71 | else |
72 | b43_led_turn_off(dev, led_index: led->index, activelow: led->activelow); |
73 | } |
74 | |
75 | static void b43_leds_work(struct work_struct *work) |
76 | { |
77 | struct b43_leds *leds = container_of(work, struct b43_leds, work); |
78 | struct b43_wl *wl = container_of(leds, struct b43_wl, leds); |
79 | struct b43_wldev *dev; |
80 | |
81 | mutex_lock(&wl->mutex); |
82 | dev = wl->current_dev; |
83 | if (unlikely(!dev || b43_status(dev) < B43_STAT_STARTED)) |
84 | goto out_unlock; |
85 | |
86 | b43_led_update(dev, led: &wl->leds.led_tx); |
87 | b43_led_update(dev, led: &wl->leds.led_rx); |
88 | b43_led_update(dev, led: &wl->leds.led_radio); |
89 | b43_led_update(dev, led: &wl->leds.led_assoc); |
90 | |
91 | out_unlock: |
92 | mutex_unlock(lock: &wl->mutex); |
93 | } |
94 | |
95 | /* Callback from the LED subsystem. */ |
96 | static void b43_led_brightness_set(struct led_classdev *led_dev, |
97 | enum led_brightness brightness) |
98 | { |
99 | struct b43_led *led = container_of(led_dev, struct b43_led, led_dev); |
100 | struct b43_wl *wl = led->wl; |
101 | |
102 | if (likely(!wl->leds.stop)) { |
103 | atomic_set(v: &led->state, i: brightness); |
104 | ieee80211_queue_work(hw: wl->hw, work: &wl->leds.work); |
105 | } |
106 | } |
107 | |
108 | static int b43_register_led(struct b43_wldev *dev, struct b43_led *led, |
109 | const char *name, const char *default_trigger, |
110 | u8 led_index, bool activelow) |
111 | { |
112 | int err; |
113 | |
114 | if (led->wl) |
115 | return -EEXIST; |
116 | if (!default_trigger) |
117 | return -EINVAL; |
118 | led->wl = dev->wl; |
119 | led->index = led_index; |
120 | led->activelow = activelow; |
121 | strscpy(led->name, name, sizeof(led->name)); |
122 | atomic_set(v: &led->state, i: 0); |
123 | |
124 | led->led_dev.name = led->name; |
125 | led->led_dev.default_trigger = default_trigger; |
126 | led->led_dev.brightness_set = b43_led_brightness_set; |
127 | |
128 | err = led_classdev_register(parent: dev->dev->dev, led_cdev: &led->led_dev); |
129 | if (err) { |
130 | b43warn(wl: dev->wl, fmt: "LEDs: Failed to register %s\n" , name); |
131 | led->wl = NULL; |
132 | return err; |
133 | } |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static void b43_unregister_led(struct b43_led *led) |
139 | { |
140 | if (!led->wl) |
141 | return; |
142 | led_classdev_unregister(led_cdev: &led->led_dev); |
143 | led->wl = NULL; |
144 | } |
145 | |
146 | static void b43_map_led(struct b43_wldev *dev, |
147 | u8 led_index, |
148 | enum b43_led_behaviour behaviour, |
149 | bool activelow) |
150 | { |
151 | struct ieee80211_hw *hw = dev->wl->hw; |
152 | char name[B43_LED_MAX_NAME_LEN + 1]; |
153 | |
154 | /* Map the b43 specific LED behaviour value to the |
155 | * generic LED triggers. */ |
156 | switch (behaviour) { |
157 | case B43_LED_INACTIVE: |
158 | case B43_LED_OFF: |
159 | case B43_LED_ON: |
160 | break; |
161 | case B43_LED_ACTIVITY: |
162 | case B43_LED_TRANSFER: |
163 | case B43_LED_APTRANSFER: |
164 | snprintf(buf: name, size: sizeof(name), |
165 | fmt: "b43-%s::tx" , wiphy_name(wiphy: hw->wiphy)); |
166 | b43_register_led(dev, led: &dev->wl->leds.led_tx, name, |
167 | default_trigger: ieee80211_get_tx_led_name(hw), |
168 | led_index, activelow); |
169 | snprintf(buf: name, size: sizeof(name), |
170 | fmt: "b43-%s::rx" , wiphy_name(wiphy: hw->wiphy)); |
171 | b43_register_led(dev, led: &dev->wl->leds.led_rx, name, |
172 | default_trigger: ieee80211_get_rx_led_name(hw), |
173 | led_index, activelow); |
174 | break; |
175 | case B43_LED_RADIO_ALL: |
176 | case B43_LED_RADIO_A: |
177 | case B43_LED_RADIO_B: |
178 | case B43_LED_MODE_BG: |
179 | snprintf(buf: name, size: sizeof(name), |
180 | fmt: "b43-%s::radio" , wiphy_name(wiphy: hw->wiphy)); |
181 | b43_register_led(dev, led: &dev->wl->leds.led_radio, name, |
182 | default_trigger: ieee80211_get_radio_led_name(hw), |
183 | led_index, activelow); |
184 | break; |
185 | case B43_LED_WEIRD: |
186 | case B43_LED_ASSOC: |
187 | snprintf(buf: name, size: sizeof(name), |
188 | fmt: "b43-%s::assoc" , wiphy_name(wiphy: hw->wiphy)); |
189 | b43_register_led(dev, led: &dev->wl->leds.led_assoc, name, |
190 | default_trigger: ieee80211_get_assoc_led_name(hw), |
191 | led_index, activelow); |
192 | break; |
193 | default: |
194 | b43warn(wl: dev->wl, fmt: "LEDs: Unknown behaviour 0x%02X\n" , |
195 | behaviour); |
196 | break; |
197 | } |
198 | } |
199 | |
200 | static void b43_led_get_sprominfo(struct b43_wldev *dev, |
201 | unsigned int led_index, |
202 | enum b43_led_behaviour *behaviour, |
203 | bool *activelow) |
204 | { |
205 | u8 sprom[4]; |
206 | |
207 | sprom[0] = dev->dev->bus_sprom->gpio0; |
208 | sprom[1] = dev->dev->bus_sprom->gpio1; |
209 | sprom[2] = dev->dev->bus_sprom->gpio2; |
210 | sprom[3] = dev->dev->bus_sprom->gpio3; |
211 | |
212 | if ((sprom[0] & sprom[1] & sprom[2] & sprom[3]) == 0xff) { |
213 | /* There is no LED information in the SPROM |
214 | * for this LED. Hardcode it here. */ |
215 | *activelow = false; |
216 | switch (led_index) { |
217 | case 0: |
218 | *behaviour = B43_LED_ACTIVITY; |
219 | *activelow = true; |
220 | if (dev->dev->board_vendor == PCI_VENDOR_ID_COMPAQ) |
221 | *behaviour = B43_LED_RADIO_ALL; |
222 | break; |
223 | case 1: |
224 | *behaviour = B43_LED_RADIO_B; |
225 | if (dev->dev->board_vendor == PCI_VENDOR_ID_ASUSTEK) |
226 | *behaviour = B43_LED_ASSOC; |
227 | break; |
228 | case 2: |
229 | *behaviour = B43_LED_RADIO_A; |
230 | break; |
231 | case 3: |
232 | *behaviour = B43_LED_OFF; |
233 | break; |
234 | default: |
235 | *behaviour = B43_LED_OFF; |
236 | B43_WARN_ON(1); |
237 | return; |
238 | } |
239 | } else { |
240 | /* keep LED disabled if no mapping is defined */ |
241 | if (sprom[led_index] == 0xff) |
242 | *behaviour = B43_LED_OFF; |
243 | else |
244 | *behaviour = sprom[led_index] & B43_LED_BEHAVIOUR; |
245 | *activelow = !!(sprom[led_index] & B43_LED_ACTIVELOW); |
246 | } |
247 | } |
248 | |
249 | void b43_leds_init(struct b43_wldev *dev) |
250 | { |
251 | struct b43_led *led; |
252 | unsigned int i; |
253 | enum b43_led_behaviour behaviour; |
254 | bool activelow; |
255 | |
256 | /* Sync the RF-kill LED state (if we have one) with radio and switch states. */ |
257 | led = &dev->wl->leds.led_radio; |
258 | if (led->wl) { |
259 | if (dev->phy.radio_on && b43_is_hw_radio_enabled(dev)) { |
260 | b43_led_turn_on(dev, led_index: led->index, activelow: led->activelow); |
261 | led->hw_state = true; |
262 | atomic_set(v: &led->state, i: 1); |
263 | } else { |
264 | b43_led_turn_off(dev, led_index: led->index, activelow: led->activelow); |
265 | led->hw_state = false; |
266 | atomic_set(v: &led->state, i: 0); |
267 | } |
268 | } |
269 | |
270 | /* Initialize TX/RX/ASSOC leds */ |
271 | led = &dev->wl->leds.led_tx; |
272 | if (led->wl) { |
273 | b43_led_turn_off(dev, led_index: led->index, activelow: led->activelow); |
274 | led->hw_state = false; |
275 | atomic_set(v: &led->state, i: 0); |
276 | } |
277 | led = &dev->wl->leds.led_rx; |
278 | if (led->wl) { |
279 | b43_led_turn_off(dev, led_index: led->index, activelow: led->activelow); |
280 | led->hw_state = false; |
281 | atomic_set(v: &led->state, i: 0); |
282 | } |
283 | led = &dev->wl->leds.led_assoc; |
284 | if (led->wl) { |
285 | b43_led_turn_off(dev, led_index: led->index, activelow: led->activelow); |
286 | led->hw_state = false; |
287 | atomic_set(v: &led->state, i: 0); |
288 | } |
289 | |
290 | /* Initialize other LED states. */ |
291 | for (i = 0; i < B43_MAX_NR_LEDS; i++) { |
292 | b43_led_get_sprominfo(dev, led_index: i, behaviour: &behaviour, activelow: &activelow); |
293 | switch (behaviour) { |
294 | case B43_LED_OFF: |
295 | b43_led_turn_off(dev, led_index: i, activelow); |
296 | break; |
297 | case B43_LED_ON: |
298 | b43_led_turn_on(dev, led_index: i, activelow); |
299 | break; |
300 | default: |
301 | /* Leave others as-is. */ |
302 | break; |
303 | } |
304 | } |
305 | |
306 | dev->wl->leds.stop = 0; |
307 | } |
308 | |
309 | void b43_leds_exit(struct b43_wldev *dev) |
310 | { |
311 | struct b43_leds *leds = &dev->wl->leds; |
312 | |
313 | b43_led_turn_off(dev, led_index: leds->led_tx.index, activelow: leds->led_tx.activelow); |
314 | b43_led_turn_off(dev, led_index: leds->led_rx.index, activelow: leds->led_rx.activelow); |
315 | b43_led_turn_off(dev, led_index: leds->led_assoc.index, activelow: leds->led_assoc.activelow); |
316 | b43_led_turn_off(dev, led_index: leds->led_radio.index, activelow: leds->led_radio.activelow); |
317 | } |
318 | |
319 | void b43_leds_stop(struct b43_wldev *dev) |
320 | { |
321 | struct b43_leds *leds = &dev->wl->leds; |
322 | |
323 | leds->stop = 1; |
324 | cancel_work_sync(work: &leds->work); |
325 | } |
326 | |
327 | void b43_leds_register(struct b43_wldev *dev) |
328 | { |
329 | unsigned int i; |
330 | enum b43_led_behaviour behaviour; |
331 | bool activelow; |
332 | |
333 | INIT_WORK(&dev->wl->leds.work, b43_leds_work); |
334 | |
335 | /* Register the LEDs to the LED subsystem. */ |
336 | for (i = 0; i < B43_MAX_NR_LEDS; i++) { |
337 | b43_led_get_sprominfo(dev, led_index: i, behaviour: &behaviour, activelow: &activelow); |
338 | b43_map_led(dev, led_index: i, behaviour, activelow); |
339 | } |
340 | } |
341 | |
342 | void b43_leds_unregister(struct b43_wl *wl) |
343 | { |
344 | struct b43_leds *leds = &wl->leds; |
345 | |
346 | b43_unregister_led(led: &leds->led_tx); |
347 | b43_unregister_led(led: &leds->led_rx); |
348 | b43_unregister_led(led: &leds->led_assoc); |
349 | b43_unregister_led(led: &leds->led_radio); |
350 | } |
351 | |