/* * Linux LED driver for RTL8187 * * Copyright 2009 Larry Finger <Larry.Finger@lwfinger.net> * * Based on the LED handling in the r8187 driver, which is: * Copyright (c) Realtek Semiconductor Corp. All rights reserved. * * Thanks to Realtek for their support! * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #ifdef CONFIG_RTL8187_LEDS #include <net/mac80211.h> #include <linux/usb.h> #include <linux/eeprom_93cx6.h> #include "rtl8187.h" #include "leds.h" static void led_turn_on(struct work_struct *work) { /* As this routine does read/write operations on the hardware, it must * be run from a work queue. */ u8 reg; struct rtl8187_priv *priv = container_of(work, struct rtl8187_priv, led_on.work); struct rtl8187_led *led = &priv->led_tx; /* Don't change the LED, when the device is down. */ if (!priv->vif || priv->vif->type == NL80211_IFTYPE_UNSPECIFIED) return ; /* Skip if the LED is not registered. */ if (!led->dev) return; mutex_lock(&priv->conf_mutex); switch (led->ledpin) { case LED_PIN_GPIO0: rtl818x_iowrite8(priv, &priv->map->GPIO0, 0x01); rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0x00); break; case LED_PIN_LED0: reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) & ~(1 << 4); rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); break; case LED_PIN_LED1: reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) & ~(1 << 5); rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); break; case LED_PIN_HW: default: break; } mutex_unlock(&priv->conf_mutex); } static void led_turn_off(struct work_struct *work) { /* As this routine does read/write operations on the hardware, it must * be run from a work queue. */ u8 reg; struct rtl8187_priv *priv = container_of(work, struct rtl8187_priv, led_off.work); struct rtl8187_led *led = &priv->led_tx; /* Don't change the LED, when the device is down. */ if (!priv->vif || priv->vif->type == NL80211_IFTYPE_UNSPECIFIED) return ; /* Skip if the LED is not registered. */ if (!led->dev) return; mutex_lock(&priv->conf_mutex); switch (led->ledpin) { case LED_PIN_GPIO0: rtl818x_iowrite8(priv, &priv->map->GPIO0, 0x01); rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0x01); break; case LED_PIN_LED0: reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) | (1 << 4); rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); break; case LED_PIN_LED1: reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) | (1 << 5); rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); break; case LED_PIN_HW: default: break; } mutex_unlock(&priv->conf_mutex); } /* Callback from the LED subsystem. */ static void rtl8187_led_brightness_set(struct led_classdev *led_dev, enum led_brightness brightness) { struct rtl8187_led *led = container_of(led_dev, struct rtl8187_led, led_dev); struct ieee80211_hw *hw = led->dev; struct rtl8187_priv *priv; static bool radio_on; if (!hw) return; priv = hw->priv; if (led->is_radio) { if (brightness == LED_FULL) { ieee80211_queue_delayed_work(hw, &priv->led_on, 0); radio_on = true; } else if (radio_on) { radio_on = false; cancel_delayed_work(&priv->led_on); ieee80211_queue_delayed_work(hw, &priv->led_off, 0); } } else if (radio_on) { if (brightness == LED_OFF) { ieee80211_queue_delayed_work(hw, &priv->led_off, 0); /* The LED is off for 1/20 sec - it just blinks. */ ieee80211_queue_delayed_work(hw, &priv->led_on, HZ / 20); } else ieee80211_queue_delayed_work(hw, &priv->led_on, 0); } } static int rtl8187_register_led(struct ieee80211_hw *dev, struct rtl8187_led *led, const char *name, const char *default_trigger, u8 ledpin, bool is_radio) { int err; struct rtl8187_priv *priv = dev->priv; if (led->dev) return -EEXIST; if (!default_trigger) return -EINVAL; led->dev = dev; led->ledpin = ledpin; led->is_radio = is_radio; strncpy(led->name, name, sizeof(led->name)); led->led_dev.name = led->name; led->led_dev.default_trigger = default_trigger; led->led_dev.brightness_set = rtl8187_led_brightness_set; err = led_classdev_register(&priv->udev->dev, &led->led_dev); if (err) { printk(KERN_INFO "LEDs: Failed to register %s\n", name); led->dev = NULL; return err; } return 0; } static void rtl8187_unregister_led(struct rtl8187_led *led) { struct ieee80211_hw *hw = led->dev; struct rtl8187_priv *priv = hw->priv; led_classdev_unregister(&led->led_dev); flush_delayed_work(&priv->led_off); led->dev = NULL; } void rtl8187_leds_init(struct ieee80211_hw *dev, u16 custid) { struct rtl8187_priv *priv = dev->priv; char name[RTL8187_LED_MAX_NAME_LEN + 1]; u8 ledpin; int err; /* According to the vendor driver, the LED operation depends on the * customer ID encoded in the EEPROM */ printk(KERN_INFO "rtl8187: Customer ID is 0x%02X\n", custid); switch (custid) { case EEPROM_CID_RSVD0: case EEPROM_CID_RSVD1: case EEPROM_CID_SERCOMM_PS: case EEPROM_CID_QMI: case EEPROM_CID_DELL: case EEPROM_CID_TOSHIBA: ledpin = LED_PIN_GPIO0; break; case EEPROM_CID_ALPHA0: ledpin = LED_PIN_LED0; break; case EEPROM_CID_HW: ledpin = LED_PIN_HW; break; default: ledpin = LED_PIN_GPIO0; } INIT_DELAYED_WORK(&priv->led_on, led_turn_on); INIT_DELAYED_WORK(&priv->led_off, led_turn_off); snprintf(name, sizeof(name), "rtl8187-%s::radio", wiphy_name(dev->wiphy)); err = rtl8187_register_led(dev, &priv->led_radio, name, ieee80211_get_radio_led_name(dev), ledpin, true); if (err) return; snprintf(name, sizeof(name), "rtl8187-%s::tx", wiphy_name(dev->wiphy)); err = rtl8187_register_led(dev, &priv->led_tx, name, ieee80211_get_tx_led_name(dev), ledpin, false); if (err) goto err_tx; snprintf(name, sizeof(name), "rtl8187-%s::rx", wiphy_name(dev->wiphy)); err = rtl8187_register_led(dev, &priv->led_rx, name, ieee80211_get_rx_led_name(dev), ledpin, false); if (!err) return; /* registration of RX LED failed - unregister */ rtl8187_unregister_led(&priv->led_tx); err_tx: rtl8187_unregister_led(&priv->led_radio); } void rtl8187_leds_exit(struct ieee80211_hw *dev) { struct rtl8187_priv *priv = dev->priv; rtl8187_unregister_led(&priv->led_radio); rtl8187_unregister_led(&priv->led_rx); rtl8187_unregister_led(&priv->led_tx); cancel_delayed_work_sync(&priv->led_off); cancel_delayed_work_sync(&priv->led_on); } #endif /* def CONFIG_RTL8187_LEDS */