162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci Broadcom B43 wireless driver 562306a36Sopenharmony_ci LED control 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>, 862306a36Sopenharmony_ci Copyright (c) 2005 Stefano Brivio <stefano.brivio@polimi.it> 962306a36Sopenharmony_ci Copyright (c) 2005-2007 Michael Buesch <m@bues.ch> 1062306a36Sopenharmony_ci Copyright (c) 2005 Danny van Dyk <kugelfang@gentoo.org> 1162306a36Sopenharmony_ci Copyright (c) 2005 Andreas Jaggi <andreas.jaggi@waterwave.ch> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci*/ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "b43legacy.h" 1762306a36Sopenharmony_ci#include "leds.h" 1862306a36Sopenharmony_ci#include "rfkill.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic void b43legacy_led_turn_on(struct b43legacy_wldev *dev, u8 led_index, 2262306a36Sopenharmony_ci bool activelow) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci struct b43legacy_wl *wl = dev->wl; 2562306a36Sopenharmony_ci unsigned long flags; 2662306a36Sopenharmony_ci u16 ctl; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci spin_lock_irqsave(&wl->leds_lock, flags); 2962306a36Sopenharmony_ci ctl = b43legacy_read16(dev, B43legacy_MMIO_GPIO_CONTROL); 3062306a36Sopenharmony_ci if (activelow) 3162306a36Sopenharmony_ci ctl &= ~(1 << led_index); 3262306a36Sopenharmony_ci else 3362306a36Sopenharmony_ci ctl |= (1 << led_index); 3462306a36Sopenharmony_ci b43legacy_write16(dev, B43legacy_MMIO_GPIO_CONTROL, ctl); 3562306a36Sopenharmony_ci spin_unlock_irqrestore(&wl->leds_lock, flags); 3662306a36Sopenharmony_ci} 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic void b43legacy_led_turn_off(struct b43legacy_wldev *dev, u8 led_index, 3962306a36Sopenharmony_ci bool activelow) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci struct b43legacy_wl *wl = dev->wl; 4262306a36Sopenharmony_ci unsigned long flags; 4362306a36Sopenharmony_ci u16 ctl; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci spin_lock_irqsave(&wl->leds_lock, flags); 4662306a36Sopenharmony_ci ctl = b43legacy_read16(dev, B43legacy_MMIO_GPIO_CONTROL); 4762306a36Sopenharmony_ci if (activelow) 4862306a36Sopenharmony_ci ctl |= (1 << led_index); 4962306a36Sopenharmony_ci else 5062306a36Sopenharmony_ci ctl &= ~(1 << led_index); 5162306a36Sopenharmony_ci b43legacy_write16(dev, B43legacy_MMIO_GPIO_CONTROL, ctl); 5262306a36Sopenharmony_ci spin_unlock_irqrestore(&wl->leds_lock, flags); 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci/* Callback from the LED subsystem. */ 5662306a36Sopenharmony_cistatic void b43legacy_led_brightness_set(struct led_classdev *led_dev, 5762306a36Sopenharmony_ci enum led_brightness brightness) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci struct b43legacy_led *led = container_of(led_dev, struct b43legacy_led, 6062306a36Sopenharmony_ci led_dev); 6162306a36Sopenharmony_ci struct b43legacy_wldev *dev = led->dev; 6262306a36Sopenharmony_ci bool radio_enabled; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci /* Checking the radio-enabled status here is slightly racy, 6562306a36Sopenharmony_ci * but we want to avoid the locking overhead and we don't care 6662306a36Sopenharmony_ci * whether the LED has the wrong state for a second. */ 6762306a36Sopenharmony_ci radio_enabled = (dev->phy.radio_on && dev->radio_hw_enable); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (brightness == LED_OFF || !radio_enabled) 7062306a36Sopenharmony_ci b43legacy_led_turn_off(dev, led->index, led->activelow); 7162306a36Sopenharmony_ci else 7262306a36Sopenharmony_ci b43legacy_led_turn_on(dev, led->index, led->activelow); 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic int b43legacy_register_led(struct b43legacy_wldev *dev, 7662306a36Sopenharmony_ci struct b43legacy_led *led, 7762306a36Sopenharmony_ci const char *name, 7862306a36Sopenharmony_ci const char *default_trigger, 7962306a36Sopenharmony_ci u8 led_index, bool activelow) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci int err; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci b43legacy_led_turn_off(dev, led_index, activelow); 8462306a36Sopenharmony_ci if (led->dev) 8562306a36Sopenharmony_ci return -EEXIST; 8662306a36Sopenharmony_ci if (!default_trigger) 8762306a36Sopenharmony_ci return -EINVAL; 8862306a36Sopenharmony_ci led->dev = dev; 8962306a36Sopenharmony_ci led->index = led_index; 9062306a36Sopenharmony_ci led->activelow = activelow; 9162306a36Sopenharmony_ci strscpy(led->name, name, sizeof(led->name)); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci led->led_dev.name = led->name; 9462306a36Sopenharmony_ci led->led_dev.default_trigger = default_trigger; 9562306a36Sopenharmony_ci led->led_dev.brightness_set = b43legacy_led_brightness_set; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci err = led_classdev_register(dev->dev->dev, &led->led_dev); 9862306a36Sopenharmony_ci if (err) { 9962306a36Sopenharmony_ci b43legacywarn(dev->wl, "LEDs: Failed to register %s\n", name); 10062306a36Sopenharmony_ci led->dev = NULL; 10162306a36Sopenharmony_ci return err; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci return 0; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic void b43legacy_unregister_led(struct b43legacy_led *led) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci if (!led->dev) 10962306a36Sopenharmony_ci return; 11062306a36Sopenharmony_ci led_classdev_unregister(&led->led_dev); 11162306a36Sopenharmony_ci b43legacy_led_turn_off(led->dev, led->index, led->activelow); 11262306a36Sopenharmony_ci led->dev = NULL; 11362306a36Sopenharmony_ci} 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_cistatic void b43legacy_map_led(struct b43legacy_wldev *dev, 11662306a36Sopenharmony_ci u8 led_index, 11762306a36Sopenharmony_ci enum b43legacy_led_behaviour behaviour, 11862306a36Sopenharmony_ci bool activelow) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci struct ieee80211_hw *hw = dev->wl->hw; 12162306a36Sopenharmony_ci char name[B43legacy_LED_MAX_NAME_LEN + 1]; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci /* Map the b43 specific LED behaviour value to the 12462306a36Sopenharmony_ci * generic LED triggers. */ 12562306a36Sopenharmony_ci switch (behaviour) { 12662306a36Sopenharmony_ci case B43legacy_LED_INACTIVE: 12762306a36Sopenharmony_ci break; 12862306a36Sopenharmony_ci case B43legacy_LED_OFF: 12962306a36Sopenharmony_ci b43legacy_led_turn_off(dev, led_index, activelow); 13062306a36Sopenharmony_ci break; 13162306a36Sopenharmony_ci case B43legacy_LED_ON: 13262306a36Sopenharmony_ci b43legacy_led_turn_on(dev, led_index, activelow); 13362306a36Sopenharmony_ci break; 13462306a36Sopenharmony_ci case B43legacy_LED_ACTIVITY: 13562306a36Sopenharmony_ci case B43legacy_LED_TRANSFER: 13662306a36Sopenharmony_ci case B43legacy_LED_APTRANSFER: 13762306a36Sopenharmony_ci snprintf(name, sizeof(name), 13862306a36Sopenharmony_ci "b43legacy-%s::tx", wiphy_name(hw->wiphy)); 13962306a36Sopenharmony_ci b43legacy_register_led(dev, &dev->led_tx, name, 14062306a36Sopenharmony_ci ieee80211_get_tx_led_name(hw), 14162306a36Sopenharmony_ci led_index, activelow); 14262306a36Sopenharmony_ci snprintf(name, sizeof(name), 14362306a36Sopenharmony_ci "b43legacy-%s::rx", wiphy_name(hw->wiphy)); 14462306a36Sopenharmony_ci b43legacy_register_led(dev, &dev->led_rx, name, 14562306a36Sopenharmony_ci ieee80211_get_rx_led_name(hw), 14662306a36Sopenharmony_ci led_index, activelow); 14762306a36Sopenharmony_ci break; 14862306a36Sopenharmony_ci case B43legacy_LED_RADIO_ALL: 14962306a36Sopenharmony_ci case B43legacy_LED_RADIO_A: 15062306a36Sopenharmony_ci case B43legacy_LED_RADIO_B: 15162306a36Sopenharmony_ci case B43legacy_LED_MODE_BG: 15262306a36Sopenharmony_ci snprintf(name, sizeof(name), 15362306a36Sopenharmony_ci "b43legacy-%s::radio", wiphy_name(hw->wiphy)); 15462306a36Sopenharmony_ci b43legacy_register_led(dev, &dev->led_radio, name, 15562306a36Sopenharmony_ci ieee80211_get_radio_led_name(hw), 15662306a36Sopenharmony_ci led_index, activelow); 15762306a36Sopenharmony_ci /* Sync the RF-kill LED state with radio and switch states. */ 15862306a36Sopenharmony_ci if (dev->phy.radio_on && b43legacy_is_hw_radio_enabled(dev)) 15962306a36Sopenharmony_ci b43legacy_led_turn_on(dev, led_index, activelow); 16062306a36Sopenharmony_ci break; 16162306a36Sopenharmony_ci case B43legacy_LED_WEIRD: 16262306a36Sopenharmony_ci case B43legacy_LED_ASSOC: 16362306a36Sopenharmony_ci snprintf(name, sizeof(name), 16462306a36Sopenharmony_ci "b43legacy-%s::assoc", wiphy_name(hw->wiphy)); 16562306a36Sopenharmony_ci b43legacy_register_led(dev, &dev->led_assoc, name, 16662306a36Sopenharmony_ci ieee80211_get_assoc_led_name(hw), 16762306a36Sopenharmony_ci led_index, activelow); 16862306a36Sopenharmony_ci break; 16962306a36Sopenharmony_ci default: 17062306a36Sopenharmony_ci b43legacywarn(dev->wl, "LEDs: Unknown behaviour 0x%02X\n", 17162306a36Sopenharmony_ci behaviour); 17262306a36Sopenharmony_ci break; 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_civoid b43legacy_leds_init(struct b43legacy_wldev *dev) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct ssb_bus *bus = dev->dev->bus; 17962306a36Sopenharmony_ci u8 sprom[4]; 18062306a36Sopenharmony_ci int i; 18162306a36Sopenharmony_ci enum b43legacy_led_behaviour behaviour; 18262306a36Sopenharmony_ci bool activelow; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci sprom[0] = bus->sprom.gpio0; 18562306a36Sopenharmony_ci sprom[1] = bus->sprom.gpio1; 18662306a36Sopenharmony_ci sprom[2] = bus->sprom.gpio2; 18762306a36Sopenharmony_ci sprom[3] = bus->sprom.gpio3; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci for (i = 0; i < 4; i++) { 19062306a36Sopenharmony_ci if (sprom[i] == 0xFF) { 19162306a36Sopenharmony_ci /* There is no LED information in the SPROM 19262306a36Sopenharmony_ci * for this LED. Hardcode it here. */ 19362306a36Sopenharmony_ci activelow = false; 19462306a36Sopenharmony_ci switch (i) { 19562306a36Sopenharmony_ci case 0: 19662306a36Sopenharmony_ci behaviour = B43legacy_LED_ACTIVITY; 19762306a36Sopenharmony_ci activelow = true; 19862306a36Sopenharmony_ci if (bus->boardinfo.vendor == PCI_VENDOR_ID_COMPAQ) 19962306a36Sopenharmony_ci behaviour = B43legacy_LED_RADIO_ALL; 20062306a36Sopenharmony_ci break; 20162306a36Sopenharmony_ci case 1: 20262306a36Sopenharmony_ci behaviour = B43legacy_LED_RADIO_B; 20362306a36Sopenharmony_ci if (bus->boardinfo.vendor == PCI_VENDOR_ID_ASUSTEK) 20462306a36Sopenharmony_ci behaviour = B43legacy_LED_ASSOC; 20562306a36Sopenharmony_ci break; 20662306a36Sopenharmony_ci case 2: 20762306a36Sopenharmony_ci behaviour = B43legacy_LED_RADIO_A; 20862306a36Sopenharmony_ci break; 20962306a36Sopenharmony_ci case 3: 21062306a36Sopenharmony_ci behaviour = B43legacy_LED_OFF; 21162306a36Sopenharmony_ci break; 21262306a36Sopenharmony_ci default: 21362306a36Sopenharmony_ci B43legacy_WARN_ON(1); 21462306a36Sopenharmony_ci return; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci } else { 21762306a36Sopenharmony_ci behaviour = sprom[i] & B43legacy_LED_BEHAVIOUR; 21862306a36Sopenharmony_ci activelow = !!(sprom[i] & B43legacy_LED_ACTIVELOW); 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci b43legacy_map_led(dev, i, behaviour, activelow); 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_civoid b43legacy_leds_exit(struct b43legacy_wldev *dev) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci b43legacy_unregister_led(&dev->led_tx); 22762306a36Sopenharmony_ci b43legacy_unregister_led(&dev->led_rx); 22862306a36Sopenharmony_ci b43legacy_unregister_led(&dev->led_assoc); 22962306a36Sopenharmony_ci b43legacy_unregister_led(&dev->led_radio); 23062306a36Sopenharmony_ci} 231