| /* | 
 |  | 
 |   Broadcom BCM43xx wireless driver | 
 |  | 
 |   Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>, | 
 |                      Stefano Brivio <st3@riseup.net> | 
 |                      Michael Buesch <mbuesch@freenet.de> | 
 |                      Danny van Dyk <kugelfang@gentoo.org> | 
 |                      Andreas Jaggi <andreas.jaggi@waterwave.ch> | 
 |  | 
 |   This program is free software; you can redistribute it and/or modify | 
 |   it under the terms of the GNU General Public License as published by | 
 |   the Free Software Foundation; either version 2 of the License, or | 
 |   (at your option) any later version. | 
 |  | 
 |   This program is distributed in the hope that it will be useful, | 
 |   but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |   GNU General Public License for more details. | 
 |  | 
 |   You should have received a copy of the GNU General Public License | 
 |   along with this program; see the file COPYING.  If not, write to | 
 |   the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, | 
 |   Boston, MA 02110-1301, USA. | 
 |  | 
 | */ | 
 |  | 
 | #include "bcm43xx_leds.h" | 
 | #include "bcm43xx.h" | 
 |  | 
 | #include <asm/bitops.h> | 
 |  | 
 |  | 
 | static void bcm43xx_led_changestate(struct bcm43xx_led *led) | 
 | { | 
 | 	struct bcm43xx_private *bcm = led->bcm; | 
 | 	const int index = bcm43xx_led_index(led); | 
 | 	const u16 mask = (1 << index); | 
 | 	u16 ledctl; | 
 |  | 
 | 	assert(index >= 0 && index < BCM43xx_NR_LEDS); | 
 | 	assert(led->blink_interval); | 
 | 	ledctl = bcm43xx_read16(bcm, BCM43xx_MMIO_GPIO_CONTROL); | 
 | 	ledctl = (ledctl & mask) ? (ledctl & ~mask) : (ledctl | mask); | 
 | 	bcm43xx_write16(bcm, BCM43xx_MMIO_GPIO_CONTROL, ledctl); | 
 | } | 
 |  | 
 | static void bcm43xx_led_blink(unsigned long d) | 
 | { | 
 | 	struct bcm43xx_led *led = (struct bcm43xx_led *)d; | 
 | 	struct bcm43xx_private *bcm = led->bcm; | 
 | 	unsigned long flags; | 
 |  | 
 | 	spin_lock_irqsave(&bcm->leds_lock, flags); | 
 | 	if (led->blink_interval) { | 
 | 		bcm43xx_led_changestate(led); | 
 | 		mod_timer(&led->blink_timer, jiffies + led->blink_interval); | 
 | 	} | 
 | 	spin_unlock_irqrestore(&bcm->leds_lock, flags); | 
 | } | 
 |  | 
 | static void bcm43xx_led_blink_start(struct bcm43xx_led *led, | 
 | 				    unsigned long interval) | 
 | { | 
 | 	if (led->blink_interval) | 
 | 		return; | 
 | 	led->blink_interval = interval; | 
 | 	bcm43xx_led_changestate(led); | 
 | 	led->blink_timer.expires = jiffies + interval; | 
 | 	add_timer(&led->blink_timer); | 
 | } | 
 |  | 
 | static void bcm43xx_led_blink_stop(struct bcm43xx_led *led, int sync) | 
 | { | 
 | 	struct bcm43xx_private *bcm = led->bcm; | 
 | 	const int index = bcm43xx_led_index(led); | 
 | 	u16 ledctl; | 
 |  | 
 | 	if (!led->blink_interval) | 
 | 		return; | 
 | 	if (unlikely(sync)) | 
 | 		del_timer_sync(&led->blink_timer); | 
 | 	else | 
 | 		del_timer(&led->blink_timer); | 
 | 	led->blink_interval = 0; | 
 |  | 
 | 	/* Make sure the LED is turned off. */ | 
 | 	assert(index >= 0 && index < BCM43xx_NR_LEDS); | 
 | 	ledctl = bcm43xx_read16(bcm, BCM43xx_MMIO_GPIO_CONTROL); | 
 | 	if (led->activelow) | 
 | 		ledctl |= (1 << index); | 
 | 	else | 
 | 		ledctl &= ~(1 << index); | 
 | 	bcm43xx_write16(bcm, BCM43xx_MMIO_GPIO_CONTROL, ledctl); | 
 | } | 
 |  | 
 | static void bcm43xx_led_init_hardcoded(struct bcm43xx_private *bcm, | 
 | 				       struct bcm43xx_led *led, | 
 | 				       int led_index) | 
 | { | 
 | 	/* This function is called, if the behaviour (and activelow) | 
 | 	 * information for a LED is missing in the SPROM. | 
 | 	 * We hardcode the behaviour values for various devices here. | 
 | 	 * Note that the BCM43xx_LED_TEST_XXX behaviour values can | 
 | 	 * be used to figure out which led is mapped to which index. | 
 | 	 */ | 
 |  | 
 | 	switch (led_index) { | 
 | 	case 0: | 
 | 		led->behaviour = BCM43xx_LED_ACTIVITY; | 
 | 		if (bcm->board_vendor == PCI_VENDOR_ID_COMPAQ) | 
 | 			led->behaviour = BCM43xx_LED_RADIO_ALL; | 
 | 		break; | 
 | 	case 1: | 
 | 		led->behaviour = BCM43xx_LED_RADIO_B; | 
 | 		if (bcm->board_vendor == PCI_VENDOR_ID_ASUSTEK) | 
 | 			led->behaviour = BCM43xx_LED_ASSOC; | 
 | 		break; | 
 | 	case 2: | 
 | 		led->behaviour = BCM43xx_LED_RADIO_A; | 
 | 		break; | 
 | 	case 3: | 
 | 		led->behaviour = BCM43xx_LED_OFF; | 
 | 		break; | 
 | 	default: | 
 | 		assert(0); | 
 | 	} | 
 | } | 
 |  | 
 | int bcm43xx_leds_init(struct bcm43xx_private *bcm) | 
 | { | 
 | 	struct bcm43xx_led *led; | 
 | 	u8 sprom[4]; | 
 | 	int i; | 
 |  | 
 | 	sprom[0] = bcm->sprom.wl0gpio0; | 
 | 	sprom[1] = bcm->sprom.wl0gpio1; | 
 | 	sprom[2] = bcm->sprom.wl0gpio2; | 
 | 	sprom[3] = bcm->sprom.wl0gpio3; | 
 |  | 
 | 	for (i = 0; i < BCM43xx_NR_LEDS; i++) { | 
 | 		led = &(bcm->leds[i]); | 
 | 		led->bcm = bcm; | 
 | 		setup_timer(&led->blink_timer, | 
 | 			    bcm43xx_led_blink, | 
 | 			    (unsigned long)led); | 
 |  | 
 | 		if (sprom[i] == 0xFF) { | 
 | 			bcm43xx_led_init_hardcoded(bcm, led, i); | 
 | 		} else { | 
 | 			led->behaviour = sprom[i] & BCM43xx_LED_BEHAVIOUR; | 
 | 			led->activelow = !!(sprom[i] & BCM43xx_LED_ACTIVELOW); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | void bcm43xx_leds_exit(struct bcm43xx_private *bcm) | 
 | { | 
 | 	struct bcm43xx_led *led; | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < BCM43xx_NR_LEDS; i++) { | 
 | 		led = &(bcm->leds[i]); | 
 | 		bcm43xx_led_blink_stop(led, 1); | 
 | 	} | 
 | 	bcm43xx_leds_switch_all(bcm, 0); | 
 | } | 
 |  | 
 | void bcm43xx_leds_update(struct bcm43xx_private *bcm, int activity) | 
 | { | 
 | 	struct bcm43xx_led *led; | 
 | 	struct bcm43xx_radioinfo *radio = bcm43xx_current_radio(bcm); | 
 | 	struct bcm43xx_phyinfo *phy = bcm43xx_current_phy(bcm); | 
 | 	const int transferring = (jiffies - bcm->stats.last_tx) < BCM43xx_LED_XFER_THRES; | 
 | 	int i, turn_on; | 
 | 	unsigned long interval = 0; | 
 | 	u16 ledctl; | 
 | 	unsigned long flags; | 
 |  | 
 | 	spin_lock_irqsave(&bcm->leds_lock, flags); | 
 | 	ledctl = bcm43xx_read16(bcm, BCM43xx_MMIO_GPIO_CONTROL); | 
 | 	for (i = 0; i < BCM43xx_NR_LEDS; i++) { | 
 | 		led = &(bcm->leds[i]); | 
 |  | 
 | 		turn_on = 0; | 
 | 		switch (led->behaviour) { | 
 | 		case BCM43xx_LED_INACTIVE: | 
 | 			continue; | 
 | 		case BCM43xx_LED_OFF: | 
 | 		case BCM43xx_LED_BCM4303_3: | 
 | 			break; | 
 | 		case BCM43xx_LED_ON: | 
 | 			turn_on = 1; | 
 | 			break; | 
 | 		case BCM43xx_LED_ACTIVITY: | 
 | 		case BCM43xx_LED_BCM4303_0: | 
 | 			turn_on = activity; | 
 | 			break; | 
 | 		case BCM43xx_LED_RADIO_ALL: | 
 | 			turn_on = radio->enabled; | 
 | 			break; | 
 | 		case BCM43xx_LED_RADIO_A: | 
 | 		case BCM43xx_LED_BCM4303_2: | 
 | 			turn_on = (radio->enabled && phy->type == BCM43xx_PHYTYPE_A); | 
 | 			break; | 
 | 		case BCM43xx_LED_RADIO_B: | 
 | 		case BCM43xx_LED_BCM4303_1: | 
 | 			turn_on = (radio->enabled && | 
 | 				   (phy->type == BCM43xx_PHYTYPE_B || | 
 | 				    phy->type == BCM43xx_PHYTYPE_G)); | 
 | 			break; | 
 | 		case BCM43xx_LED_MODE_BG: | 
 | 			if (phy->type == BCM43xx_PHYTYPE_G && | 
 | 			    1/*FIXME: using G rates.*/) | 
 | 				turn_on = 1; | 
 | 			break; | 
 | 		case BCM43xx_LED_TRANSFER: | 
 | 			if (transferring) | 
 | 				bcm43xx_led_blink_start(led, BCM43xx_LEDBLINK_MEDIUM); | 
 | 			else | 
 | 				bcm43xx_led_blink_stop(led, 0); | 
 | 			continue; | 
 | 		case BCM43xx_LED_APTRANSFER: | 
 | 			if (bcm->ieee->iw_mode == IW_MODE_MASTER) { | 
 | 				if (transferring) { | 
 | 					interval = BCM43xx_LEDBLINK_FAST; | 
 | 					turn_on = 1; | 
 | 				} | 
 | 			} else { | 
 | 				turn_on = 1; | 
 | 				if (0/*TODO: not assoc*/) | 
 | 					interval = BCM43xx_LEDBLINK_SLOW; | 
 | 				else if (transferring) | 
 | 					interval = BCM43xx_LEDBLINK_FAST; | 
 | 				else | 
 | 					turn_on = 0; | 
 | 			} | 
 | 			if (turn_on) | 
 | 				bcm43xx_led_blink_start(led, interval); | 
 | 			else | 
 | 				bcm43xx_led_blink_stop(led, 0); | 
 | 			continue; | 
 | 		case BCM43xx_LED_WEIRD: | 
 | 			//TODO | 
 | 			break; | 
 | 		case BCM43xx_LED_ASSOC: | 
 | 			if (bcm->softmac->associnfo.associated) | 
 | 				turn_on = 1; | 
 | 			break; | 
 | #ifdef CONFIG_BCM43XX_DEBUG | 
 | 		case BCM43xx_LED_TEST_BLINKSLOW: | 
 | 			bcm43xx_led_blink_start(led, BCM43xx_LEDBLINK_SLOW); | 
 | 			continue; | 
 | 		case BCM43xx_LED_TEST_BLINKMEDIUM: | 
 | 			bcm43xx_led_blink_start(led, BCM43xx_LEDBLINK_MEDIUM); | 
 | 			continue; | 
 | 		case BCM43xx_LED_TEST_BLINKFAST: | 
 | 			bcm43xx_led_blink_start(led, BCM43xx_LEDBLINK_FAST); | 
 | 			continue; | 
 | #endif /* CONFIG_BCM43XX_DEBUG */ | 
 | 		default: | 
 | 			dprintkl(KERN_INFO PFX "Bad value in leds_update," | 
 | 				" led->behaviour: 0x%x\n", led->behaviour); | 
 | 		}; | 
 |  | 
 | 		if (led->activelow) | 
 | 			turn_on = !turn_on; | 
 | 		if (turn_on) | 
 | 			ledctl |= (1 << i); | 
 | 		else | 
 | 			ledctl &= ~(1 << i); | 
 | 	} | 
 | 	bcm43xx_write16(bcm, BCM43xx_MMIO_GPIO_CONTROL, ledctl); | 
 | 	spin_unlock_irqrestore(&bcm->leds_lock, flags); | 
 | } | 
 |  | 
 | void bcm43xx_leds_switch_all(struct bcm43xx_private *bcm, int on) | 
 | { | 
 | 	struct bcm43xx_led *led; | 
 | 	u16 ledctl; | 
 | 	int i; | 
 | 	int bit_on; | 
 | 	unsigned long flags; | 
 |  | 
 | 	spin_lock_irqsave(&bcm->leds_lock, flags); | 
 | 	ledctl = bcm43xx_read16(bcm, BCM43xx_MMIO_GPIO_CONTROL); | 
 | 	for (i = 0; i < BCM43xx_NR_LEDS; i++) { | 
 | 		led = &(bcm->leds[i]); | 
 | 		if (led->behaviour == BCM43xx_LED_INACTIVE) | 
 | 			continue; | 
 | 		if (on) | 
 | 			bit_on = led->activelow ? 0 : 1; | 
 | 		else | 
 | 			bit_on = led->activelow ? 1 : 0; | 
 | 		if (bit_on) | 
 | 			ledctl |= (1 << i); | 
 | 		else | 
 | 			ledctl &= ~(1 << i); | 
 | 	} | 
 | 	bcm43xx_write16(bcm, BCM43xx_MMIO_GPIO_CONTROL, ledctl); | 
 | 	spin_unlock_irqrestore(&bcm->leds_lock, flags); | 
 | } |