--- a/drivers/net/wireless/ath/ath9k/ath9k.h +++ b/drivers/net/wireless/ath/ath9k/ath9k.h @@ -554,6 +554,9 @@ static inline int ath9k_dump_btcoex(stru void ath_init_leds(struct ath_softc *sc); void ath_deinit_leds(struct ath_softc *sc); void ath_fill_led_pin(struct ath_softc *sc); +int ath_create_gpio_led(struct ath_softc *sc, int gpio, const char *name, + const char *trigger, bool active_low); + #else static inline void ath_init_leds(struct ath_softc *sc) { @@ -692,6 +695,13 @@ void ath_ant_comb_scan(struct ath_softc #define PS_BEACON_SYNC BIT(4) #define PS_WAIT_FOR_ANI BIT(5) +struct ath_led { + struct list_head list; + struct ath_softc *sc; + const struct gpio_led *gpio; + struct led_classdev cdev; +}; + struct ath_softc { struct ieee80211_hw *hw; struct device *dev; @@ -731,9 +741,8 @@ struct ath_softc { struct ath_beacon beacon; #ifdef CPTCFG_MAC80211_LEDS - bool led_registered; - char led_name[32]; - struct led_classdev led_cdev; + const char *led_default_trigger; + struct list_head leds; #endif struct ath9k_hw_cal_data caldata; --- a/drivers/net/wireless/ath/ath9k/gpio.c +++ b/drivers/net/wireless/ath/ath9k/gpio.c @@ -24,40 +24,102 @@ static void ath_led_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) { - struct ath_softc *sc = container_of(led_cdev, struct ath_softc, led_cdev); - ath9k_hw_set_gpio(sc->sc_ah, sc->sc_ah->led_pin, (brightness == LED_OFF)); + struct ath_led *led = container_of(led_cdev, struct ath_led, cdev); + struct ath_softc *sc = led->sc; + + ath9k_ps_wakeup(sc); + ath9k_hw_set_gpio(sc->sc_ah, led->gpio->gpio, + (brightness != LED_OFF) ^ led->gpio->active_low); + ath9k_ps_restore(sc); +} + +static int ath_add_led(struct ath_softc *sc, struct ath_led *led) +{ + const struct gpio_led *gpio = led->gpio; + int ret; + + led->cdev.name = gpio->name; + led->cdev.default_trigger = gpio->default_trigger; + led->cdev.brightness_set = ath_led_brightness; + + ret = led_classdev_register(wiphy_dev(sc->hw->wiphy), &led->cdev); + if (ret < 0) + return ret; + + led->sc = sc; + list_add(&led->list, &sc->leds); + + /* Configure gpio for output */ + ath9k_hw_cfg_output(sc->sc_ah, gpio->gpio, + AR_GPIO_OUTPUT_MUX_AS_OUTPUT); + + /* LED off */ + ath9k_hw_set_gpio(sc->sc_ah, gpio->gpio, gpio->active_low); + + return 0; +} + +int ath_create_gpio_led(struct ath_softc *sc, int gpio_num, const char *name, + const char *trigger, bool active_low) +{ + struct ath_led *led; + struct gpio_led *gpio; + char *_name; + int ret; + + led = kzalloc(sizeof(*led) + sizeof(*gpio) + strlen(name) + 1, + GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->gpio = gpio = (struct gpio_led *) (led + 1); + _name = (char *) (led->gpio + 1); + + strcpy(_name, name); + gpio->name = _name; + gpio->gpio = gpio_num; + gpio->active_low = active_low; + gpio->default_trigger = trigger; + + ret = ath_add_led(sc, led); + if (unlikely(ret < 0)) + kfree(led); + + return ret; } void ath_deinit_leds(struct ath_softc *sc) { - if (!sc->led_registered) - return; + struct ath_led *led; - ath_led_brightness(&sc->led_cdev, LED_OFF); - led_classdev_unregister(&sc->led_cdev); + while (!list_empty(&sc->leds)) { + led = list_first_entry(&sc->leds, struct ath_led, list); + list_del(&led->list); + ath_led_brightness(&led->cdev, LED_OFF); + led_classdev_unregister(&led->cdev); + kfree(led); + } } void ath_init_leds(struct ath_softc *sc) { - int ret; + char led_name[32]; + const char *trigger; + + INIT_LIST_HEAD(&sc->leds); if (AR_SREV_9100(sc->sc_ah)) return; - if (!led_blink) - sc->led_cdev.default_trigger = - ieee80211_get_radio_led_name(sc->hw); - - snprintf(sc->led_name, sizeof(sc->led_name), - "ath9k-%s", wiphy_name(sc->hw->wiphy)); - sc->led_cdev.name = sc->led_name; - sc->led_cdev.brightness_set = ath_led_brightness; + snprintf(led_name, sizeof(led_name), "ath9k-%s", + wiphy_name(sc->hw->wiphy)); - ret = led_classdev_register(wiphy_dev(sc->hw->wiphy), &sc->led_cdev); - if (ret < 0) - return; + if (led_blink) + trigger = sc->led_default_trigger; + else + trigger = ieee80211_get_radio_led_name(sc->hw); - sc->led_registered = true; + ath_create_gpio_led(sc, sc->sc_ah->led_pin, led_name, trigger, 1); } void ath_fill_led_pin(struct ath_softc *sc) --- a/drivers/net/wireless/ath/ath9k/init.c +++ b/drivers/net/wireless/ath/ath9k/init.c @@ -806,7 +806,7 @@ int ath9k_init_device(u16 devid, struct #ifdef CPTCFG_MAC80211_LEDS /* must be initialized before ieee80211_register_hw */ - sc->led_cdev.default_trigger = ieee80211_create_tpt_led_trigger(sc->hw, + sc->led_default_trigger = ieee80211_create_tpt_led_trigger(sc->hw, IEEE80211_TPT_LEDTRIG_FL_RADIO, ath9k_tpt_blink, ARRAY_SIZE(ath9k_tpt_blink)); #endif --- a/drivers/net/wireless/ath/ath9k/debug.c +++ b/drivers/net/wireless/ath/ath9k/debug.c @@ -1577,6 +1577,61 @@ static const struct file_operations fops .llseek = default_llseek, }; +#ifdef CONFIG_MAC80211_LEDS + +static ssize_t write_file_gpio_led(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + char buf[32], *str, *name, *c; + ssize_t len; + unsigned int gpio; + bool active_low = false; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, ubuf, len)) + return -EFAULT; + + buf[len] = '\0'; + name = strchr(buf, ','); + if (!name) + return -EINVAL; + + *(name++) = 0; + if (!*name) + return -EINVAL; + + c = strchr(name, '\n'); + if (c) + *c = 0; + + str = buf; + if (*str == '!') { + str++; + active_low = true; + } + + if (kstrtouint(str, 0, &gpio) < 0) + return -EINVAL; + + if (gpio >= sc->sc_ah->caps.num_gpio_pins) + return -EINVAL; + + if (ath_create_gpio_led(sc, gpio, name, NULL, active_low) < 0) + return -EINVAL; + + return count; +} + +static const struct file_operations fops_gpio_led = { + .write = write_file_gpio_led, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +#endif + int ath9k_init_debug(struct ath_hw *ah) { @@ -1601,6 +1656,10 @@ int ath9k_init_debug(struct ath_hw *ah) &fops_eeprom); debugfs_create_file("chanbw", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy, sc, &fops_chanbw); +#ifdef CONFIG_MAC80211_LEDS + debugfs_create_file("gpio_led", S_IWUSR, + sc->debug.debugfs_phy, sc, &fops_gpio_led); +#endif debugfs_create_file("dma", S_IRUSR, sc->debug.debugfs_phy, sc, &fops_dma); debugfs_create_file("interrupt", S_IRUSR, sc->debug.debugfs_phy, sc,