1ca47d344SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2423e3ce3SKalle Valo /*
3423e3ce3SKalle Valo 
4423e3ce3SKalle Valo   Broadcom B43 wireless driver
5423e3ce3SKalle Valo   LED control
6423e3ce3SKalle Valo 
7423e3ce3SKalle Valo   Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>,
8423e3ce3SKalle Valo   Copyright (c) 2005 Stefano Brivio <stefano.brivio@polimi.it>
9423e3ce3SKalle Valo   Copyright (c) 2005-2007 Michael Buesch <m@bues.ch>
10423e3ce3SKalle Valo   Copyright (c) 2005 Danny van Dyk <kugelfang@gentoo.org>
11423e3ce3SKalle Valo   Copyright (c) 2005 Andreas Jaggi <andreas.jaggi@waterwave.ch>
12423e3ce3SKalle Valo 
13423e3ce3SKalle Valo 
14423e3ce3SKalle Valo */
15423e3ce3SKalle Valo 
16423e3ce3SKalle Valo #include "b43legacy.h"
17423e3ce3SKalle Valo #include "leds.h"
18423e3ce3SKalle Valo #include "rfkill.h"
19423e3ce3SKalle Valo 
20423e3ce3SKalle Valo 
b43legacy_led_turn_on(struct b43legacy_wldev * dev,u8 led_index,bool activelow)21423e3ce3SKalle Valo static void b43legacy_led_turn_on(struct b43legacy_wldev *dev, u8 led_index,
22423e3ce3SKalle Valo 			    bool activelow)
23423e3ce3SKalle Valo {
24423e3ce3SKalle Valo 	struct b43legacy_wl *wl = dev->wl;
25423e3ce3SKalle Valo 	unsigned long flags;
26423e3ce3SKalle Valo 	u16 ctl;
27423e3ce3SKalle Valo 
28423e3ce3SKalle Valo 	spin_lock_irqsave(&wl->leds_lock, flags);
29423e3ce3SKalle Valo 	ctl = b43legacy_read16(dev, B43legacy_MMIO_GPIO_CONTROL);
30423e3ce3SKalle Valo 	if (activelow)
31423e3ce3SKalle Valo 		ctl &= ~(1 << led_index);
32423e3ce3SKalle Valo 	else
33423e3ce3SKalle Valo 		ctl |= (1 << led_index);
34423e3ce3SKalle Valo 	b43legacy_write16(dev, B43legacy_MMIO_GPIO_CONTROL, ctl);
35423e3ce3SKalle Valo 	spin_unlock_irqrestore(&wl->leds_lock, flags);
36423e3ce3SKalle Valo }
37423e3ce3SKalle Valo 
b43legacy_led_turn_off(struct b43legacy_wldev * dev,u8 led_index,bool activelow)38423e3ce3SKalle Valo static void b43legacy_led_turn_off(struct b43legacy_wldev *dev, u8 led_index,
39423e3ce3SKalle Valo 			     bool activelow)
40423e3ce3SKalle Valo {
41423e3ce3SKalle Valo 	struct b43legacy_wl *wl = dev->wl;
42423e3ce3SKalle Valo 	unsigned long flags;
43423e3ce3SKalle Valo 	u16 ctl;
44423e3ce3SKalle Valo 
45423e3ce3SKalle Valo 	spin_lock_irqsave(&wl->leds_lock, flags);
46423e3ce3SKalle Valo 	ctl = b43legacy_read16(dev, B43legacy_MMIO_GPIO_CONTROL);
47423e3ce3SKalle Valo 	if (activelow)
48423e3ce3SKalle Valo 		ctl |= (1 << led_index);
49423e3ce3SKalle Valo 	else
50423e3ce3SKalle Valo 		ctl &= ~(1 << led_index);
51423e3ce3SKalle Valo 	b43legacy_write16(dev, B43legacy_MMIO_GPIO_CONTROL, ctl);
52423e3ce3SKalle Valo 	spin_unlock_irqrestore(&wl->leds_lock, flags);
53423e3ce3SKalle Valo }
54423e3ce3SKalle Valo 
55423e3ce3SKalle Valo /* Callback from the LED subsystem. */
b43legacy_led_brightness_set(struct led_classdev * led_dev,enum led_brightness brightness)56423e3ce3SKalle Valo static void b43legacy_led_brightness_set(struct led_classdev *led_dev,
57423e3ce3SKalle Valo 				   enum led_brightness brightness)
58423e3ce3SKalle Valo {
59423e3ce3SKalle Valo 	struct b43legacy_led *led = container_of(led_dev, struct b43legacy_led,
60423e3ce3SKalle Valo 				    led_dev);
61423e3ce3SKalle Valo 	struct b43legacy_wldev *dev = led->dev;
62423e3ce3SKalle Valo 	bool radio_enabled;
63423e3ce3SKalle Valo 
64423e3ce3SKalle Valo 	/* Checking the radio-enabled status here is slightly racy,
65423e3ce3SKalle Valo 	 * but we want to avoid the locking overhead and we don't care
66423e3ce3SKalle Valo 	 * whether the LED has the wrong state for a second. */
67423e3ce3SKalle Valo 	radio_enabled = (dev->phy.radio_on && dev->radio_hw_enable);
68423e3ce3SKalle Valo 
69423e3ce3SKalle Valo 	if (brightness == LED_OFF || !radio_enabled)
70423e3ce3SKalle Valo 		b43legacy_led_turn_off(dev, led->index, led->activelow);
71423e3ce3SKalle Valo 	else
72423e3ce3SKalle Valo 		b43legacy_led_turn_on(dev, led->index, led->activelow);
73423e3ce3SKalle Valo }
74423e3ce3SKalle Valo 
b43legacy_register_led(struct b43legacy_wldev * dev,struct b43legacy_led * led,const char * name,const char * default_trigger,u8 led_index,bool activelow)75423e3ce3SKalle Valo static int b43legacy_register_led(struct b43legacy_wldev *dev,
76423e3ce3SKalle Valo 				  struct b43legacy_led *led,
77423e3ce3SKalle Valo 				  const char *name,
78423e3ce3SKalle Valo 				  const char *default_trigger,
79423e3ce3SKalle Valo 				  u8 led_index, bool activelow)
80423e3ce3SKalle Valo {
81423e3ce3SKalle Valo 	int err;
82423e3ce3SKalle Valo 
83423e3ce3SKalle Valo 	b43legacy_led_turn_off(dev, led_index, activelow);
84423e3ce3SKalle Valo 	if (led->dev)
85423e3ce3SKalle Valo 		return -EEXIST;
86423e3ce3SKalle Valo 	if (!default_trigger)
87423e3ce3SKalle Valo 		return -EINVAL;
88423e3ce3SKalle Valo 	led->dev = dev;
89423e3ce3SKalle Valo 	led->index = led_index;
90423e3ce3SKalle Valo 	led->activelow = activelow;
91*bf99f11dSWolfram Sang 	strscpy(led->name, name, sizeof(led->name));
92423e3ce3SKalle Valo 
93423e3ce3SKalle Valo 	led->led_dev.name = led->name;
94423e3ce3SKalle Valo 	led->led_dev.default_trigger = default_trigger;
95423e3ce3SKalle Valo 	led->led_dev.brightness_set = b43legacy_led_brightness_set;
96423e3ce3SKalle Valo 
97423e3ce3SKalle Valo 	err = led_classdev_register(dev->dev->dev, &led->led_dev);
98423e3ce3SKalle Valo 	if (err) {
99423e3ce3SKalle Valo 		b43legacywarn(dev->wl, "LEDs: Failed to register %s\n", name);
100423e3ce3SKalle Valo 		led->dev = NULL;
101423e3ce3SKalle Valo 		return err;
102423e3ce3SKalle Valo 	}
103423e3ce3SKalle Valo 	return 0;
104423e3ce3SKalle Valo }
105423e3ce3SKalle Valo 
b43legacy_unregister_led(struct b43legacy_led * led)106423e3ce3SKalle Valo static void b43legacy_unregister_led(struct b43legacy_led *led)
107423e3ce3SKalle Valo {
108423e3ce3SKalle Valo 	if (!led->dev)
109423e3ce3SKalle Valo 		return;
110423e3ce3SKalle Valo 	led_classdev_unregister(&led->led_dev);
111423e3ce3SKalle Valo 	b43legacy_led_turn_off(led->dev, led->index, led->activelow);
112423e3ce3SKalle Valo 	led->dev = NULL;
113423e3ce3SKalle Valo }
114423e3ce3SKalle Valo 
b43legacy_map_led(struct b43legacy_wldev * dev,u8 led_index,enum b43legacy_led_behaviour behaviour,bool activelow)115423e3ce3SKalle Valo static void b43legacy_map_led(struct b43legacy_wldev *dev,
116423e3ce3SKalle Valo 			u8 led_index,
117423e3ce3SKalle Valo 			enum b43legacy_led_behaviour behaviour,
118423e3ce3SKalle Valo 			bool activelow)
119423e3ce3SKalle Valo {
120423e3ce3SKalle Valo 	struct ieee80211_hw *hw = dev->wl->hw;
121423e3ce3SKalle Valo 	char name[B43legacy_LED_MAX_NAME_LEN + 1];
122423e3ce3SKalle Valo 
123423e3ce3SKalle Valo 	/* Map the b43 specific LED behaviour value to the
124423e3ce3SKalle Valo 	 * generic LED triggers. */
125423e3ce3SKalle Valo 	switch (behaviour) {
126423e3ce3SKalle Valo 	case B43legacy_LED_INACTIVE:
127423e3ce3SKalle Valo 		break;
128423e3ce3SKalle Valo 	case B43legacy_LED_OFF:
129423e3ce3SKalle Valo 		b43legacy_led_turn_off(dev, led_index, activelow);
130423e3ce3SKalle Valo 		break;
131423e3ce3SKalle Valo 	case B43legacy_LED_ON:
132423e3ce3SKalle Valo 		b43legacy_led_turn_on(dev, led_index, activelow);
133423e3ce3SKalle Valo 		break;
134423e3ce3SKalle Valo 	case B43legacy_LED_ACTIVITY:
135423e3ce3SKalle Valo 	case B43legacy_LED_TRANSFER:
136423e3ce3SKalle Valo 	case B43legacy_LED_APTRANSFER:
137423e3ce3SKalle Valo 		snprintf(name, sizeof(name),
138423e3ce3SKalle Valo 			 "b43legacy-%s::tx", wiphy_name(hw->wiphy));
139423e3ce3SKalle Valo 		b43legacy_register_led(dev, &dev->led_tx, name,
140423e3ce3SKalle Valo 				 ieee80211_get_tx_led_name(hw),
141423e3ce3SKalle Valo 				 led_index, activelow);
142423e3ce3SKalle Valo 		snprintf(name, sizeof(name),
143423e3ce3SKalle Valo 			 "b43legacy-%s::rx", wiphy_name(hw->wiphy));
144423e3ce3SKalle Valo 		b43legacy_register_led(dev, &dev->led_rx, name,
145423e3ce3SKalle Valo 				 ieee80211_get_rx_led_name(hw),
146423e3ce3SKalle Valo 				 led_index, activelow);
147423e3ce3SKalle Valo 		break;
148423e3ce3SKalle Valo 	case B43legacy_LED_RADIO_ALL:
149423e3ce3SKalle Valo 	case B43legacy_LED_RADIO_A:
150423e3ce3SKalle Valo 	case B43legacy_LED_RADIO_B:
151423e3ce3SKalle Valo 	case B43legacy_LED_MODE_BG:
152423e3ce3SKalle Valo 		snprintf(name, sizeof(name),
153423e3ce3SKalle Valo 			 "b43legacy-%s::radio", wiphy_name(hw->wiphy));
154423e3ce3SKalle Valo 		b43legacy_register_led(dev, &dev->led_radio, name,
155423e3ce3SKalle Valo 				 ieee80211_get_radio_led_name(hw),
156423e3ce3SKalle Valo 				 led_index, activelow);
157423e3ce3SKalle Valo 		/* Sync the RF-kill LED state with radio and switch states. */
158423e3ce3SKalle Valo 		if (dev->phy.radio_on && b43legacy_is_hw_radio_enabled(dev))
159423e3ce3SKalle Valo 			b43legacy_led_turn_on(dev, led_index, activelow);
160423e3ce3SKalle Valo 		break;
161423e3ce3SKalle Valo 	case B43legacy_LED_WEIRD:
162423e3ce3SKalle Valo 	case B43legacy_LED_ASSOC:
163423e3ce3SKalle Valo 		snprintf(name, sizeof(name),
164423e3ce3SKalle Valo 			 "b43legacy-%s::assoc", wiphy_name(hw->wiphy));
165423e3ce3SKalle Valo 		b43legacy_register_led(dev, &dev->led_assoc, name,
166423e3ce3SKalle Valo 				 ieee80211_get_assoc_led_name(hw),
167423e3ce3SKalle Valo 				 led_index, activelow);
168423e3ce3SKalle Valo 		break;
169423e3ce3SKalle Valo 	default:
170423e3ce3SKalle Valo 		b43legacywarn(dev->wl, "LEDs: Unknown behaviour 0x%02X\n",
171423e3ce3SKalle Valo 			behaviour);
172423e3ce3SKalle Valo 		break;
173423e3ce3SKalle Valo 	}
174423e3ce3SKalle Valo }
175423e3ce3SKalle Valo 
b43legacy_leds_init(struct b43legacy_wldev * dev)176423e3ce3SKalle Valo void b43legacy_leds_init(struct b43legacy_wldev *dev)
177423e3ce3SKalle Valo {
178423e3ce3SKalle Valo 	struct ssb_bus *bus = dev->dev->bus;
179423e3ce3SKalle Valo 	u8 sprom[4];
180423e3ce3SKalle Valo 	int i;
181423e3ce3SKalle Valo 	enum b43legacy_led_behaviour behaviour;
182423e3ce3SKalle Valo 	bool activelow;
183423e3ce3SKalle Valo 
184423e3ce3SKalle Valo 	sprom[0] = bus->sprom.gpio0;
185423e3ce3SKalle Valo 	sprom[1] = bus->sprom.gpio1;
186423e3ce3SKalle Valo 	sprom[2] = bus->sprom.gpio2;
187423e3ce3SKalle Valo 	sprom[3] = bus->sprom.gpio3;
188423e3ce3SKalle Valo 
189423e3ce3SKalle Valo 	for (i = 0; i < 4; i++) {
190423e3ce3SKalle Valo 		if (sprom[i] == 0xFF) {
191423e3ce3SKalle Valo 			/* There is no LED information in the SPROM
192423e3ce3SKalle Valo 			 * for this LED. Hardcode it here. */
193423e3ce3SKalle Valo 			activelow = false;
194423e3ce3SKalle Valo 			switch (i) {
195423e3ce3SKalle Valo 			case 0:
196423e3ce3SKalle Valo 				behaviour = B43legacy_LED_ACTIVITY;
197423e3ce3SKalle Valo 				activelow = true;
198423e3ce3SKalle Valo 				if (bus->boardinfo.vendor == PCI_VENDOR_ID_COMPAQ)
199423e3ce3SKalle Valo 					behaviour = B43legacy_LED_RADIO_ALL;
200423e3ce3SKalle Valo 				break;
201423e3ce3SKalle Valo 			case 1:
202423e3ce3SKalle Valo 				behaviour = B43legacy_LED_RADIO_B;
203423e3ce3SKalle Valo 				if (bus->boardinfo.vendor == PCI_VENDOR_ID_ASUSTEK)
204423e3ce3SKalle Valo 					behaviour = B43legacy_LED_ASSOC;
205423e3ce3SKalle Valo 				break;
206423e3ce3SKalle Valo 			case 2:
207423e3ce3SKalle Valo 				behaviour = B43legacy_LED_RADIO_A;
208423e3ce3SKalle Valo 				break;
209423e3ce3SKalle Valo 			case 3:
210423e3ce3SKalle Valo 				behaviour = B43legacy_LED_OFF;
211423e3ce3SKalle Valo 				break;
212423e3ce3SKalle Valo 			default:
213423e3ce3SKalle Valo 				B43legacy_WARN_ON(1);
214423e3ce3SKalle Valo 				return;
215423e3ce3SKalle Valo 			}
216423e3ce3SKalle Valo 		} else {
217423e3ce3SKalle Valo 			behaviour = sprom[i] & B43legacy_LED_BEHAVIOUR;
218423e3ce3SKalle Valo 			activelow = !!(sprom[i] & B43legacy_LED_ACTIVELOW);
219423e3ce3SKalle Valo 		}
220423e3ce3SKalle Valo 		b43legacy_map_led(dev, i, behaviour, activelow);
221423e3ce3SKalle Valo 	}
222423e3ce3SKalle Valo }
223423e3ce3SKalle Valo 
b43legacy_leds_exit(struct b43legacy_wldev * dev)224423e3ce3SKalle Valo void b43legacy_leds_exit(struct b43legacy_wldev *dev)
225423e3ce3SKalle Valo {
226423e3ce3SKalle Valo 	b43legacy_unregister_led(&dev->led_tx);
227423e3ce3SKalle Valo 	b43legacy_unregister_led(&dev->led_rx);
228423e3ce3SKalle Valo 	b43legacy_unregister_led(&dev->led_assoc);
229423e3ce3SKalle Valo 	b43legacy_unregister_led(&dev->led_radio);
230423e3ce3SKalle Valo }
231