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, ctl); 35 spin_unlock_irqrestore(&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, ctl); 52 spin_unlock_irqrestore(&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->activelow); 71 else 72 b43legacy_led_turn_on(dev, led->index, 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(dev->dev->dev, &led->led_dev); 98 if (err) { 99 b43legacywarn(dev->wl, "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->led_dev); 111 b43legacy_led_turn_off(led->dev, led->index, 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(name, sizeof(name), 138 "b43legacy-%s::tx", wiphy_name(hw->wiphy)); 139 b43legacy_register_led(dev, &dev->led_tx, name, 140 ieee80211_get_tx_led_name(hw), 141 led_index, activelow); 142 snprintf(name, sizeof(name), 143 "b43legacy-%s::rx", wiphy_name(hw->wiphy)); 144 b43legacy_register_led(dev, &dev->led_rx, name, 145 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(name, sizeof(name), 153 "b43legacy-%s::radio", wiphy_name(hw->wiphy)); 154 b43legacy_register_led(dev, &dev->led_radio, name, 155 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(name, sizeof(name), 164 "b43legacy-%s::assoc", wiphy_name(hw->wiphy)); 165 b43legacy_register_led(dev, &dev->led_assoc, name, 166 ieee80211_get_assoc_led_name(hw), 167 led_index, activelow); 168 break; 169 default: 170 b43legacywarn(dev->wl, "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, i, behaviour, activelow); 221 } 222 } 223 224 void b43legacy_leds_exit(struct b43legacy_wldev *dev) 225 { 226 b43legacy_unregister_led(&dev->led_tx); 227 b43legacy_unregister_led(&dev->led_rx); 228 b43legacy_unregister_led(&dev->led_assoc); 229 b43legacy_unregister_led(&dev->led_radio); 230 } 231