| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* | 
 |  *  winbond-cir.c - Driver for the Consumer IR functionality of Winbond | 
 |  *                  SuperI/O chips. | 
 |  * | 
 |  *  Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but | 
 |  *  could probably support others (Winbond WEC102X, NatSemi, etc) | 
 |  *  with minor modifications. | 
 |  * | 
 |  *  Original Author: David Härdeman <david@hardeman.nu> | 
 |  *     Copyright (C) 2012 Sean Young <sean@mess.org> | 
 |  *     Copyright (C) 2009 - 2011 David Härdeman <david@hardeman.nu> | 
 |  * | 
 |  *  Dedicated to my daughter Matilda, without whose loving attention this | 
 |  *  driver would have been finished in half the time and with a fraction | 
 |  *  of the bugs. | 
 |  * | 
 |  *  Written using: | 
 |  *    o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel | 
 |  *    o NatSemi PC87338/PC97338 datasheet (for the serial port stuff) | 
 |  *    o DSDT dumps | 
 |  * | 
 |  *  Supported features: | 
 |  *    o IR Receive | 
 |  *    o IR Transmit | 
 |  *    o Wake-On-CIR functionality | 
 |  *    o Carrier detection | 
 |  */ | 
 |  | 
 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 
 |  | 
 | #include <linux/module.h> | 
 | #include <linux/pnp.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/timer.h> | 
 | #include <linux/leds.h> | 
 | #include <linux/spinlock.h> | 
 | #include <linux/pci_ids.h> | 
 | #include <linux/io.h> | 
 | #include <linux/bitrev.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/wait.h> | 
 | #include <linux/sched.h> | 
 | #include <media/rc-core.h> | 
 |  | 
 | #define DRVNAME "winbond-cir" | 
 |  | 
 | /* CEIR Wake-Up Registers, relative to data->wbase                      */ | 
 | #define WBCIR_REG_WCEIR_CTL	0x03 /* CEIR Receiver Control		*/ | 
 | #define WBCIR_REG_WCEIR_STS	0x04 /* CEIR Receiver Status		*/ | 
 | #define WBCIR_REG_WCEIR_EV_EN	0x05 /* CEIR Receiver Event Enable	*/ | 
 | #define WBCIR_REG_WCEIR_CNTL	0x06 /* CEIR Receiver Counter Low	*/ | 
 | #define WBCIR_REG_WCEIR_CNTH	0x07 /* CEIR Receiver Counter High	*/ | 
 | #define WBCIR_REG_WCEIR_INDEX	0x08 /* CEIR Receiver Index		*/ | 
 | #define WBCIR_REG_WCEIR_DATA	0x09 /* CEIR Receiver Data		*/ | 
 | #define WBCIR_REG_WCEIR_CSL	0x0A /* CEIR Re. Compare Strlen		*/ | 
 | #define WBCIR_REG_WCEIR_CFG1	0x0B /* CEIR Re. Configuration 1	*/ | 
 | #define WBCIR_REG_WCEIR_CFG2	0x0C /* CEIR Re. Configuration 2	*/ | 
 |  | 
 | /* CEIR Enhanced Functionality Registers, relative to data->ebase       */ | 
 | #define WBCIR_REG_ECEIR_CTS	0x00 /* Enhanced IR Control Status	*/ | 
 | #define WBCIR_REG_ECEIR_CCTL	0x01 /* Infrared Counter Control	*/ | 
 | #define WBCIR_REG_ECEIR_CNT_LO	0x02 /* Infrared Counter LSB		*/ | 
 | #define WBCIR_REG_ECEIR_CNT_HI	0x03 /* Infrared Counter MSB		*/ | 
 | #define WBCIR_REG_ECEIR_IREM	0x04 /* Infrared Emitter Status		*/ | 
 |  | 
 | /* SP3 Banked Registers, relative to data->sbase                        */ | 
 | #define WBCIR_REG_SP3_BSR	0x03 /* Bank Select, all banks		*/ | 
 | 				      /* Bank 0				*/ | 
 | #define WBCIR_REG_SP3_RXDATA	0x00 /* FIFO RX data (r)		*/ | 
 | #define WBCIR_REG_SP3_TXDATA	0x00 /* FIFO TX data (w)		*/ | 
 | #define WBCIR_REG_SP3_IER	0x01 /* Interrupt Enable		*/ | 
 | #define WBCIR_REG_SP3_EIR	0x02 /* Event Identification (r)	*/ | 
 | #define WBCIR_REG_SP3_FCR	0x02 /* FIFO Control (w)		*/ | 
 | #define WBCIR_REG_SP3_MCR	0x04 /* Mode Control			*/ | 
 | #define WBCIR_REG_SP3_LSR	0x05 /* Link Status			*/ | 
 | #define WBCIR_REG_SP3_MSR	0x06 /* Modem Status			*/ | 
 | #define WBCIR_REG_SP3_ASCR	0x07 /* Aux Status and Control		*/ | 
 | 				      /* Bank 2				*/ | 
 | #define WBCIR_REG_SP3_BGDL	0x00 /* Baud Divisor LSB		*/ | 
 | #define WBCIR_REG_SP3_BGDH	0x01 /* Baud Divisor MSB		*/ | 
 | #define WBCIR_REG_SP3_EXCR1	0x02 /* Extended Control 1		*/ | 
 | #define WBCIR_REG_SP3_EXCR2	0x04 /* Extended Control 2		*/ | 
 | #define WBCIR_REG_SP3_TXFLV	0x06 /* TX FIFO Level			*/ | 
 | #define WBCIR_REG_SP3_RXFLV	0x07 /* RX FIFO Level			*/ | 
 | 				      /* Bank 3				*/ | 
 | #define WBCIR_REG_SP3_MRID	0x00 /* Module Identification		*/ | 
 | #define WBCIR_REG_SP3_SH_LCR	0x01 /* LCR Shadow			*/ | 
 | #define WBCIR_REG_SP3_SH_FCR	0x02 /* FCR Shadow			*/ | 
 | 				      /* Bank 4				*/ | 
 | #define WBCIR_REG_SP3_IRCR1	0x02 /* Infrared Control 1		*/ | 
 | 				      /* Bank 5				*/ | 
 | #define WBCIR_REG_SP3_IRCR2	0x04 /* Infrared Control 2		*/ | 
 | 				      /* Bank 6				*/ | 
 | #define WBCIR_REG_SP3_IRCR3	0x00 /* Infrared Control 3		*/ | 
 | #define WBCIR_REG_SP3_SIR_PW	0x02 /* SIR Pulse Width			*/ | 
 | 				      /* Bank 7				*/ | 
 | #define WBCIR_REG_SP3_IRRXDC	0x00 /* IR RX Demod Control		*/ | 
 | #define WBCIR_REG_SP3_IRTXMC	0x01 /* IR TX Mod Control		*/ | 
 | #define WBCIR_REG_SP3_RCCFG	0x02 /* CEIR Config			*/ | 
 | #define WBCIR_REG_SP3_IRCFG1	0x04 /* Infrared Config 1		*/ | 
 | #define WBCIR_REG_SP3_IRCFG4	0x07 /* Infrared Config 4		*/ | 
 |  | 
 | /* | 
 |  * Magic values follow | 
 |  */ | 
 |  | 
 | /* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ | 
 | #define WBCIR_IRQ_NONE		0x00 | 
 | /* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ | 
 | #define WBCIR_IRQ_RX		0x01 | 
 | /* TX data low bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ | 
 | #define WBCIR_IRQ_TX_LOW	0x02 | 
 | /* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ | 
 | #define WBCIR_IRQ_ERR		0x04 | 
 | /* TX data empty bit for WBCEIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ | 
 | #define WBCIR_IRQ_TX_EMPTY	0x20 | 
 | /* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */ | 
 | #define WBCIR_LED_ENABLE	0x80 | 
 | /* RX data available bit for WBCIR_REG_SP3_LSR */ | 
 | #define WBCIR_RX_AVAIL		0x01 | 
 | /* RX data overrun error bit for WBCIR_REG_SP3_LSR */ | 
 | #define WBCIR_RX_OVERRUN	0x02 | 
 | /* TX End-Of-Transmission bit for WBCIR_REG_SP3_ASCR */ | 
 | #define WBCIR_TX_EOT		0x04 | 
 | /* RX disable bit for WBCIR_REG_SP3_ASCR */ | 
 | #define WBCIR_RX_DISABLE	0x20 | 
 | /* TX data underrun error bit for WBCIR_REG_SP3_ASCR */ | 
 | #define WBCIR_TX_UNDERRUN	0x40 | 
 | /* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */ | 
 | #define WBCIR_EXT_ENABLE	0x01 | 
 | /* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */ | 
 | #define WBCIR_REGSEL_COMPARE	0x10 | 
 | /* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */ | 
 | #define WBCIR_REGSEL_MASK	0x20 | 
 | /* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */ | 
 | #define WBCIR_REG_ADDR0		0x00 | 
 | /* Enable carrier counter */ | 
 | #define WBCIR_CNTR_EN		0x01 | 
 | /* Reset carrier counter */ | 
 | #define WBCIR_CNTR_R		0x02 | 
 | /* Invert TX */ | 
 | #define WBCIR_IRTX_INV		0x04 | 
 | /* Receiver oversampling */ | 
 | #define WBCIR_RX_T_OV		0x40 | 
 |  | 
 | /* Valid banks for the SP3 UART */ | 
 | enum wbcir_bank { | 
 | 	WBCIR_BANK_0          = 0x00, | 
 | 	WBCIR_BANK_1          = 0x80, | 
 | 	WBCIR_BANK_2          = 0xE0, | 
 | 	WBCIR_BANK_3          = 0xE4, | 
 | 	WBCIR_BANK_4          = 0xE8, | 
 | 	WBCIR_BANK_5          = 0xEC, | 
 | 	WBCIR_BANK_6          = 0xF0, | 
 | 	WBCIR_BANK_7          = 0xF4, | 
 | }; | 
 |  | 
 | /* Supported power-on IR Protocols */ | 
 | enum wbcir_protocol { | 
 | 	IR_PROTOCOL_RC5          = 0x0, | 
 | 	IR_PROTOCOL_NEC          = 0x1, | 
 | 	IR_PROTOCOL_RC6          = 0x2, | 
 | }; | 
 |  | 
 | /* Possible states for IR reception */ | 
 | enum wbcir_rxstate { | 
 | 	WBCIR_RXSTATE_INACTIVE = 0, | 
 | 	WBCIR_RXSTATE_ACTIVE, | 
 | 	WBCIR_RXSTATE_ERROR | 
 | }; | 
 |  | 
 | /* Possible states for IR transmission */ | 
 | enum wbcir_txstate { | 
 | 	WBCIR_TXSTATE_INACTIVE = 0, | 
 | 	WBCIR_TXSTATE_ACTIVE, | 
 | 	WBCIR_TXSTATE_ERROR | 
 | }; | 
 |  | 
 | /* Misc */ | 
 | #define WBCIR_NAME	"Winbond CIR" | 
 | #define WBCIR_ID_FAMILY          0xF1 /* Family ID for the WPCD376I	*/ | 
 | #define	WBCIR_ID_CHIP            0x04 /* Chip ID for the WPCD376I	*/ | 
 | #define WAKEUP_IOMEM_LEN         0x10 /* Wake-Up I/O Reg Len		*/ | 
 | #define EHFUNC_IOMEM_LEN         0x10 /* Enhanced Func I/O Reg Len	*/ | 
 | #define SP_IOMEM_LEN             0x08 /* Serial Port 3 (IR) Reg Len	*/ | 
 |  | 
 | /* Per-device data */ | 
 | struct wbcir_data { | 
 | 	spinlock_t spinlock; | 
 | 	struct rc_dev *dev; | 
 | 	struct led_classdev led; | 
 |  | 
 | 	unsigned long wbase;        /* Wake-Up Baseaddr		*/ | 
 | 	unsigned long ebase;        /* Enhanced Func. Baseaddr	*/ | 
 | 	unsigned long sbase;        /* Serial Port Baseaddr	*/ | 
 | 	unsigned int  irq;          /* Serial Port IRQ		*/ | 
 | 	u8 irqmask; | 
 |  | 
 | 	/* RX state */ | 
 | 	enum wbcir_rxstate rxstate; | 
 | 	int carrier_report_enabled; | 
 | 	u32 pulse_duration; | 
 |  | 
 | 	/* TX state */ | 
 | 	enum wbcir_txstate txstate; | 
 | 	u32 txlen; | 
 | 	u32 txoff; | 
 | 	u32 *txbuf; | 
 | 	u8 txmask; | 
 | 	u32 txcarrier; | 
 | }; | 
 |  | 
 | static bool invert; /* default = 0 */ | 
 | module_param(invert, bool, 0444); | 
 | MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver"); | 
 |  | 
 | static bool txandrx; /* default = 0 */ | 
 | module_param(txandrx, bool, 0444); | 
 | MODULE_PARM_DESC(txandrx, "Allow simultaneous TX and RX"); | 
 |  | 
 |  | 
 | /***************************************************************************** | 
 |  * | 
 |  * UTILITY FUNCTIONS | 
 |  * | 
 |  *****************************************************************************/ | 
 |  | 
 | /* Caller needs to hold wbcir_lock */ | 
 | static void | 
 | wbcir_set_bits(unsigned long addr, u8 bits, u8 mask) | 
 | { | 
 | 	u8 val; | 
 |  | 
 | 	val = inb(addr); | 
 | 	val = ((val & ~mask) | (bits & mask)); | 
 | 	outb(val, addr); | 
 | } | 
 |  | 
 | /* Selects the register bank for the serial port */ | 
 | static inline void | 
 | wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank) | 
 | { | 
 | 	outb(bank, data->sbase + WBCIR_REG_SP3_BSR); | 
 | } | 
 |  | 
 | static inline void | 
 | wbcir_set_irqmask(struct wbcir_data *data, u8 irqmask) | 
 | { | 
 | 	if (data->irqmask == irqmask) | 
 | 		return; | 
 |  | 
 | 	wbcir_select_bank(data, WBCIR_BANK_0); | 
 | 	outb(irqmask, data->sbase + WBCIR_REG_SP3_IER); | 
 | 	data->irqmask = irqmask; | 
 | } | 
 |  | 
 | static enum led_brightness | 
 | wbcir_led_brightness_get(struct led_classdev *led_cdev) | 
 | { | 
 | 	struct wbcir_data *data = container_of(led_cdev, | 
 | 					       struct wbcir_data, | 
 | 					       led); | 
 |  | 
 | 	if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE) | 
 | 		return LED_FULL; | 
 | 	else | 
 | 		return LED_OFF; | 
 | } | 
 |  | 
 | static void | 
 | wbcir_led_brightness_set(struct led_classdev *led_cdev, | 
 | 			 enum led_brightness brightness) | 
 | { | 
 | 	struct wbcir_data *data = container_of(led_cdev, | 
 | 					       struct wbcir_data, | 
 | 					       led); | 
 |  | 
 | 	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS, | 
 | 		       brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE, | 
 | 		       WBCIR_LED_ENABLE); | 
 | } | 
 |  | 
 | /* Manchester encodes bits to RC6 message cells (see wbcir_shutdown) */ | 
 | static u8 | 
 | wbcir_to_rc6cells(u8 val) | 
 | { | 
 | 	u8 coded = 0x00; | 
 | 	int i; | 
 |  | 
 | 	val &= 0x0F; | 
 | 	for (i = 0; i < 4; i++) { | 
 | 		if (val & 0x01) | 
 | 			coded |= 0x02 << (i * 2); | 
 | 		else | 
 | 			coded |= 0x01 << (i * 2); | 
 | 		val >>= 1; | 
 | 	} | 
 |  | 
 | 	return coded; | 
 | } | 
 |  | 
 | /***************************************************************************** | 
 |  * | 
 |  * INTERRUPT FUNCTIONS | 
 |  * | 
 |  *****************************************************************************/ | 
 |  | 
 | static void | 
 | wbcir_carrier_report(struct wbcir_data *data) | 
 | { | 
 | 	unsigned counter = inb(data->ebase + WBCIR_REG_ECEIR_CNT_LO) | | 
 | 			inb(data->ebase + WBCIR_REG_ECEIR_CNT_HI) << 8; | 
 |  | 
 | 	if (counter > 0 && counter < 0xffff) { | 
 | 		struct ir_raw_event ev = { | 
 | 			.carrier_report = 1, | 
 | 			.carrier = DIV_ROUND_CLOSEST(counter * 1000000u, | 
 | 						data->pulse_duration) | 
 | 		}; | 
 |  | 
 | 		ir_raw_event_store(data->dev, &ev); | 
 | 	} | 
 |  | 
 | 	/* reset and restart the counter */ | 
 | 	data->pulse_duration = 0; | 
 | 	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CCTL, WBCIR_CNTR_R, | 
 | 						WBCIR_CNTR_EN | WBCIR_CNTR_R); | 
 | 	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CCTL, WBCIR_CNTR_EN, | 
 | 						WBCIR_CNTR_EN | WBCIR_CNTR_R); | 
 | } | 
 |  | 
 | static void | 
 | wbcir_idle_rx(struct rc_dev *dev, bool idle) | 
 | { | 
 | 	struct wbcir_data *data = dev->priv; | 
 |  | 
 | 	if (!idle && data->rxstate == WBCIR_RXSTATE_INACTIVE) | 
 | 		data->rxstate = WBCIR_RXSTATE_ACTIVE; | 
 |  | 
 | 	if (idle && data->rxstate != WBCIR_RXSTATE_INACTIVE) { | 
 | 		data->rxstate = WBCIR_RXSTATE_INACTIVE; | 
 |  | 
 | 		if (data->carrier_report_enabled) | 
 | 			wbcir_carrier_report(data); | 
 |  | 
 | 		/* Tell hardware to go idle by setting RXINACTIVE */ | 
 | 		outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR); | 
 | 	} | 
 | } | 
 |  | 
 | static void | 
 | wbcir_irq_rx(struct wbcir_data *data, struct pnp_dev *device) | 
 | { | 
 | 	u8 irdata; | 
 | 	struct ir_raw_event rawir = {}; | 
 |  | 
 | 	/* Since RXHDLEV is set, at least 8 bytes are in the FIFO */ | 
 | 	while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL) { | 
 | 		irdata = inb(data->sbase + WBCIR_REG_SP3_RXDATA); | 
 | 		if (data->rxstate == WBCIR_RXSTATE_ERROR) | 
 | 			continue; | 
 |  | 
 | 		rawir.duration = ((irdata & 0x7F) + 1) * | 
 | 			(data->carrier_report_enabled ? 2 : 10); | 
 | 		rawir.pulse = irdata & 0x80 ? false : true; | 
 |  | 
 | 		if (rawir.pulse) | 
 | 			data->pulse_duration += rawir.duration; | 
 |  | 
 | 		ir_raw_event_store_with_filter(data->dev, &rawir); | 
 | 	} | 
 |  | 
 | 	ir_raw_event_handle(data->dev); | 
 | } | 
 |  | 
 | static void | 
 | wbcir_irq_tx(struct wbcir_data *data) | 
 | { | 
 | 	unsigned int space; | 
 | 	unsigned int used; | 
 | 	u8 bytes[16]; | 
 | 	u8 byte; | 
 |  | 
 | 	if (!data->txbuf) | 
 | 		return; | 
 |  | 
 | 	switch (data->txstate) { | 
 | 	case WBCIR_TXSTATE_INACTIVE: | 
 | 		/* TX FIFO empty */ | 
 | 		space = 16; | 
 | 		break; | 
 | 	case WBCIR_TXSTATE_ACTIVE: | 
 | 		/* TX FIFO low (3 bytes or less) */ | 
 | 		space = 13; | 
 | 		break; | 
 | 	case WBCIR_TXSTATE_ERROR: | 
 | 		space = 0; | 
 | 		break; | 
 | 	default: | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * TX data is run-length coded in bytes: YXXXXXXX | 
 | 	 * Y = space (1) or pulse (0) | 
 | 	 * X = duration, encoded as (X + 1) * 10us (i.e 10 to 1280 us) | 
 | 	 */ | 
 | 	for (used = 0; used < space && data->txoff != data->txlen; used++) { | 
 | 		if (data->txbuf[data->txoff] == 0) { | 
 | 			data->txoff++; | 
 | 			continue; | 
 | 		} | 
 | 		byte = min((u32)0x80, data->txbuf[data->txoff]); | 
 | 		data->txbuf[data->txoff] -= byte; | 
 | 		byte--; | 
 | 		byte |= (data->txoff % 2 ? 0x80 : 0x00); /* pulse/space */ | 
 | 		bytes[used] = byte; | 
 | 	} | 
 |  | 
 | 	while (data->txoff != data->txlen && data->txbuf[data->txoff] == 0) | 
 | 		data->txoff++; | 
 |  | 
 | 	if (used == 0) { | 
 | 		/* Finished */ | 
 | 		if (data->txstate == WBCIR_TXSTATE_ERROR) | 
 | 			/* Clear TX underrun bit */ | 
 | 			outb(WBCIR_TX_UNDERRUN, data->sbase + WBCIR_REG_SP3_ASCR); | 
 | 		wbcir_set_irqmask(data, WBCIR_IRQ_RX | WBCIR_IRQ_ERR); | 
 | 		kfree(data->txbuf); | 
 | 		data->txbuf = NULL; | 
 | 		data->txstate = WBCIR_TXSTATE_INACTIVE; | 
 | 	} else if (data->txoff == data->txlen) { | 
 | 		/* At the end of transmission, tell the hw before last byte */ | 
 | 		outsb(data->sbase + WBCIR_REG_SP3_TXDATA, bytes, used - 1); | 
 | 		outb(WBCIR_TX_EOT, data->sbase + WBCIR_REG_SP3_ASCR); | 
 | 		outb(bytes[used - 1], data->sbase + WBCIR_REG_SP3_TXDATA); | 
 | 		wbcir_set_irqmask(data, WBCIR_IRQ_RX | WBCIR_IRQ_ERR | | 
 | 				  WBCIR_IRQ_TX_EMPTY); | 
 | 	} else { | 
 | 		/* More data to follow... */ | 
 | 		outsb(data->sbase + WBCIR_REG_SP3_RXDATA, bytes, used); | 
 | 		if (data->txstate == WBCIR_TXSTATE_INACTIVE) { | 
 | 			wbcir_set_irqmask(data, WBCIR_IRQ_RX | WBCIR_IRQ_ERR | | 
 | 					  WBCIR_IRQ_TX_LOW); | 
 | 			data->txstate = WBCIR_TXSTATE_ACTIVE; | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | static irqreturn_t | 
 | wbcir_irq_handler(int irqno, void *cookie) | 
 | { | 
 | 	struct pnp_dev *device = cookie; | 
 | 	struct wbcir_data *data = pnp_get_drvdata(device); | 
 | 	unsigned long flags; | 
 | 	u8 status; | 
 |  | 
 | 	spin_lock_irqsave(&data->spinlock, flags); | 
 | 	wbcir_select_bank(data, WBCIR_BANK_0); | 
 | 	status = inb(data->sbase + WBCIR_REG_SP3_EIR); | 
 | 	status &= data->irqmask; | 
 |  | 
 | 	if (!status) { | 
 | 		spin_unlock_irqrestore(&data->spinlock, flags); | 
 | 		return IRQ_NONE; | 
 | 	} | 
 |  | 
 | 	if (status & WBCIR_IRQ_ERR) { | 
 | 		/* RX overflow? (read clears bit) */ | 
 | 		if (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_OVERRUN) { | 
 | 			data->rxstate = WBCIR_RXSTATE_ERROR; | 
 | 			ir_raw_event_reset(data->dev); | 
 | 		} | 
 |  | 
 | 		/* TX underflow? */ | 
 | 		if (inb(data->sbase + WBCIR_REG_SP3_ASCR) & WBCIR_TX_UNDERRUN) | 
 | 			data->txstate = WBCIR_TXSTATE_ERROR; | 
 | 	} | 
 |  | 
 | 	if (status & WBCIR_IRQ_RX) | 
 | 		wbcir_irq_rx(data, device); | 
 |  | 
 | 	if (status & (WBCIR_IRQ_TX_LOW | WBCIR_IRQ_TX_EMPTY)) | 
 | 		wbcir_irq_tx(data); | 
 |  | 
 | 	spin_unlock_irqrestore(&data->spinlock, flags); | 
 | 	return IRQ_HANDLED; | 
 | } | 
 |  | 
 | /***************************************************************************** | 
 |  * | 
 |  * RC-CORE INTERFACE FUNCTIONS | 
 |  * | 
 |  *****************************************************************************/ | 
 |  | 
 | static int | 
 | wbcir_set_carrier_report(struct rc_dev *dev, int enable) | 
 | { | 
 | 	struct wbcir_data *data = dev->priv; | 
 | 	unsigned long flags; | 
 |  | 
 | 	spin_lock_irqsave(&data->spinlock, flags); | 
 |  | 
 | 	if (data->carrier_report_enabled == enable) { | 
 | 		spin_unlock_irqrestore(&data->spinlock, flags); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	data->pulse_duration = 0; | 
 | 	wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CCTL, WBCIR_CNTR_R, | 
 | 						WBCIR_CNTR_EN | WBCIR_CNTR_R); | 
 |  | 
 | 	if (enable && data->dev->idle) | 
 | 		wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CCTL, | 
 | 				WBCIR_CNTR_EN, WBCIR_CNTR_EN | WBCIR_CNTR_R); | 
 |  | 
 | 	/* Set a higher sampling resolution if carrier reports are enabled */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_2); | 
 | 	data->dev->rx_resolution = enable ? 2 : 10; | 
 | 	outb(enable ? 0x03 : 0x0f, data->sbase + WBCIR_REG_SP3_BGDL); | 
 | 	outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH); | 
 |  | 
 | 	/* Enable oversampling if carrier reports are enabled */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_7); | 
 | 	wbcir_set_bits(data->sbase + WBCIR_REG_SP3_RCCFG, | 
 | 				enable ? WBCIR_RX_T_OV : 0, WBCIR_RX_T_OV); | 
 |  | 
 | 	data->carrier_report_enabled = enable; | 
 | 	spin_unlock_irqrestore(&data->spinlock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | wbcir_txcarrier(struct rc_dev *dev, u32 carrier) | 
 | { | 
 | 	struct wbcir_data *data = dev->priv; | 
 | 	unsigned long flags; | 
 | 	u8 val; | 
 | 	u32 freq; | 
 |  | 
 | 	freq = DIV_ROUND_CLOSEST(carrier, 1000); | 
 | 	if (freq < 30 || freq > 60) | 
 | 		return -EINVAL; | 
 |  | 
 | 	switch (freq) { | 
 | 	case 58: | 
 | 	case 59: | 
 | 	case 60: | 
 | 		val = freq - 58; | 
 | 		freq *= 1000; | 
 | 		break; | 
 | 	case 57: | 
 | 		val = freq - 27; | 
 | 		freq = 56900; | 
 | 		break; | 
 | 	default: | 
 | 		val = freq - 27; | 
 | 		freq *= 1000; | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	spin_lock_irqsave(&data->spinlock, flags); | 
 | 	if (data->txstate != WBCIR_TXSTATE_INACTIVE) { | 
 | 		spin_unlock_irqrestore(&data->spinlock, flags); | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	if (data->txcarrier != freq) { | 
 | 		wbcir_select_bank(data, WBCIR_BANK_7); | 
 | 		wbcir_set_bits(data->sbase + WBCIR_REG_SP3_IRTXMC, val, 0x1F); | 
 | 		data->txcarrier = freq; | 
 | 	} | 
 |  | 
 | 	spin_unlock_irqrestore(&data->spinlock, flags); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | wbcir_txmask(struct rc_dev *dev, u32 mask) | 
 | { | 
 | 	struct wbcir_data *data = dev->priv; | 
 | 	unsigned long flags; | 
 | 	u8 val; | 
 |  | 
 | 	/* return the number of transmitters */ | 
 | 	if (mask > 15) | 
 | 		return 4; | 
 |  | 
 | 	/* Four outputs, only one output can be enabled at a time */ | 
 | 	switch (mask) { | 
 | 	case 0x1: | 
 | 		val = 0x0; | 
 | 		break; | 
 | 	case 0x2: | 
 | 		val = 0x1; | 
 | 		break; | 
 | 	case 0x4: | 
 | 		val = 0x2; | 
 | 		break; | 
 | 	case 0x8: | 
 | 		val = 0x3; | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	spin_lock_irqsave(&data->spinlock, flags); | 
 | 	if (data->txstate != WBCIR_TXSTATE_INACTIVE) { | 
 | 		spin_unlock_irqrestore(&data->spinlock, flags); | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	if (data->txmask != mask) { | 
 | 		wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS, val, 0x0c); | 
 | 		data->txmask = mask; | 
 | 	} | 
 |  | 
 | 	spin_unlock_irqrestore(&data->spinlock, flags); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | wbcir_tx(struct rc_dev *dev, unsigned *b, unsigned count) | 
 | { | 
 | 	struct wbcir_data *data = dev->priv; | 
 | 	unsigned *buf; | 
 | 	unsigned i; | 
 | 	unsigned long flags; | 
 |  | 
 | 	buf = kmalloc_array(count, sizeof(*b), GFP_KERNEL); | 
 | 	if (!buf) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	/* Convert values to multiples of 10us */ | 
 | 	for (i = 0; i < count; i++) | 
 | 		buf[i] = DIV_ROUND_CLOSEST(b[i], 10); | 
 |  | 
 | 	/* Not sure if this is possible, but better safe than sorry */ | 
 | 	spin_lock_irqsave(&data->spinlock, flags); | 
 | 	if (data->txstate != WBCIR_TXSTATE_INACTIVE) { | 
 | 		spin_unlock_irqrestore(&data->spinlock, flags); | 
 | 		kfree(buf); | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	/* Fill the TX fifo once, the irq handler will do the rest */ | 
 | 	data->txbuf = buf; | 
 | 	data->txlen = count; | 
 | 	data->txoff = 0; | 
 | 	wbcir_irq_tx(data); | 
 |  | 
 | 	/* We're done */ | 
 | 	spin_unlock_irqrestore(&data->spinlock, flags); | 
 | 	return count; | 
 | } | 
 |  | 
 | /***************************************************************************** | 
 |  * | 
 |  * SETUP/INIT/SUSPEND/RESUME FUNCTIONS | 
 |  * | 
 |  *****************************************************************************/ | 
 |  | 
 | static void | 
 | wbcir_shutdown(struct pnp_dev *device) | 
 | { | 
 | 	struct device *dev = &device->dev; | 
 | 	struct wbcir_data *data = pnp_get_drvdata(device); | 
 | 	struct rc_dev *rc = data->dev; | 
 | 	bool do_wake = true; | 
 | 	u8 match[11]; | 
 | 	u8 mask[11]; | 
 | 	u8 rc6_csl = 0; | 
 | 	u8 proto; | 
 | 	u32 wake_sc = rc->scancode_wakeup_filter.data; | 
 | 	u32 mask_sc = rc->scancode_wakeup_filter.mask; | 
 | 	int i; | 
 |  | 
 | 	memset(match, 0, sizeof(match)); | 
 | 	memset(mask, 0, sizeof(mask)); | 
 |  | 
 | 	if (!mask_sc || !device_may_wakeup(dev)) { | 
 | 		do_wake = false; | 
 | 		goto finish; | 
 | 	} | 
 |  | 
 | 	switch (rc->wakeup_protocol) { | 
 | 	case RC_PROTO_RC5: | 
 | 		/* Mask = 13 bits, ex toggle */ | 
 | 		mask[0]  = (mask_sc & 0x003f); | 
 | 		mask[0] |= (mask_sc & 0x0300) >> 2; | 
 | 		mask[1]  = (mask_sc & 0x1c00) >> 10; | 
 | 		if (mask_sc & 0x0040)		      /* 2nd start bit  */ | 
 | 			match[1] |= 0x10; | 
 |  | 
 | 		match[0]  = (wake_sc & 0x003F);       /* 6 command bits */ | 
 | 		match[0] |= (wake_sc & 0x0300) >> 2;  /* 2 address bits */ | 
 | 		match[1]  = (wake_sc & 0x1c00) >> 10; /* 3 address bits */ | 
 | 		if (!(wake_sc & 0x0040))	      /* 2nd start bit  */ | 
 | 			match[1] |= 0x10; | 
 |  | 
 | 		proto = IR_PROTOCOL_RC5; | 
 | 		break; | 
 |  | 
 | 	case RC_PROTO_NEC: | 
 | 		mask[1] = bitrev8(mask_sc); | 
 | 		mask[0] = mask[1]; | 
 | 		mask[3] = bitrev8(mask_sc >> 8); | 
 | 		mask[2] = mask[3]; | 
 |  | 
 | 		match[1] = bitrev8(wake_sc); | 
 | 		match[0] = ~match[1]; | 
 | 		match[3] = bitrev8(wake_sc >> 8); | 
 | 		match[2] = ~match[3]; | 
 |  | 
 | 		proto = IR_PROTOCOL_NEC; | 
 | 		break; | 
 |  | 
 | 	case RC_PROTO_NECX: | 
 | 		mask[1] = bitrev8(mask_sc); | 
 | 		mask[0] = mask[1]; | 
 | 		mask[2] = bitrev8(mask_sc >> 8); | 
 | 		mask[3] = bitrev8(mask_sc >> 16); | 
 |  | 
 | 		match[1] = bitrev8(wake_sc); | 
 | 		match[0] = ~match[1]; | 
 | 		match[2] = bitrev8(wake_sc >> 8); | 
 | 		match[3] = bitrev8(wake_sc >> 16); | 
 |  | 
 | 		proto = IR_PROTOCOL_NEC; | 
 | 		break; | 
 |  | 
 | 	case RC_PROTO_NEC32: | 
 | 		mask[0] = bitrev8(mask_sc); | 
 | 		mask[1] = bitrev8(mask_sc >> 8); | 
 | 		mask[2] = bitrev8(mask_sc >> 16); | 
 | 		mask[3] = bitrev8(mask_sc >> 24); | 
 |  | 
 | 		match[0] = bitrev8(wake_sc); | 
 | 		match[1] = bitrev8(wake_sc >> 8); | 
 | 		match[2] = bitrev8(wake_sc >> 16); | 
 | 		match[3] = bitrev8(wake_sc >> 24); | 
 |  | 
 | 		proto = IR_PROTOCOL_NEC; | 
 | 		break; | 
 |  | 
 | 	case RC_PROTO_RC6_0: | 
 | 		/* Command */ | 
 | 		match[0] = wbcir_to_rc6cells(wake_sc >> 0); | 
 | 		mask[0]  = wbcir_to_rc6cells(mask_sc >> 0); | 
 | 		match[1] = wbcir_to_rc6cells(wake_sc >> 4); | 
 | 		mask[1]  = wbcir_to_rc6cells(mask_sc >> 4); | 
 |  | 
 | 		/* Address */ | 
 | 		match[2] = wbcir_to_rc6cells(wake_sc >>  8); | 
 | 		mask[2]  = wbcir_to_rc6cells(mask_sc >>  8); | 
 | 		match[3] = wbcir_to_rc6cells(wake_sc >> 12); | 
 | 		mask[3]  = wbcir_to_rc6cells(mask_sc >> 12); | 
 |  | 
 | 		/* Header */ | 
 | 		match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */ | 
 | 		mask[4]  = 0xF0; | 
 | 		match[5] = 0x09; /* start bit = 1, mode2 = 0 */ | 
 | 		mask[5]  = 0x0F; | 
 |  | 
 | 		rc6_csl = 44; | 
 | 		proto = IR_PROTOCOL_RC6; | 
 | 		break; | 
 |  | 
 | 	case RC_PROTO_RC6_6A_24: | 
 | 	case RC_PROTO_RC6_6A_32: | 
 | 	case RC_PROTO_RC6_MCE: | 
 | 		i = 0; | 
 |  | 
 | 		/* Command */ | 
 | 		match[i]  = wbcir_to_rc6cells(wake_sc >>  0); | 
 | 		mask[i++] = wbcir_to_rc6cells(mask_sc >>  0); | 
 | 		match[i]  = wbcir_to_rc6cells(wake_sc >>  4); | 
 | 		mask[i++] = wbcir_to_rc6cells(mask_sc >>  4); | 
 |  | 
 | 		/* Address + Toggle */ | 
 | 		match[i]  = wbcir_to_rc6cells(wake_sc >>  8); | 
 | 		mask[i++] = wbcir_to_rc6cells(mask_sc >>  8); | 
 | 		match[i]  = wbcir_to_rc6cells(wake_sc >> 12); | 
 | 		mask[i++] = wbcir_to_rc6cells(mask_sc >> 12); | 
 |  | 
 | 		/* Customer bits 7 - 0 */ | 
 | 		match[i]  = wbcir_to_rc6cells(wake_sc >> 16); | 
 | 		mask[i++] = wbcir_to_rc6cells(mask_sc >> 16); | 
 |  | 
 | 		if (rc->wakeup_protocol == RC_PROTO_RC6_6A_20) { | 
 | 			rc6_csl = 52; | 
 | 		} else { | 
 | 			match[i]  = wbcir_to_rc6cells(wake_sc >> 20); | 
 | 			mask[i++] = wbcir_to_rc6cells(mask_sc >> 20); | 
 |  | 
 | 			if (rc->wakeup_protocol == RC_PROTO_RC6_6A_24) { | 
 | 				rc6_csl = 60; | 
 | 			} else { | 
 | 				/* Customer range bit and bits 15 - 8 */ | 
 | 				match[i]  = wbcir_to_rc6cells(wake_sc >> 24); | 
 | 				mask[i++] = wbcir_to_rc6cells(mask_sc >> 24); | 
 | 				match[i]  = wbcir_to_rc6cells(wake_sc >> 28); | 
 | 				mask[i++] = wbcir_to_rc6cells(mask_sc >> 28); | 
 | 				rc6_csl = 76; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		/* Header */ | 
 | 		match[i]  = 0x93; /* mode1 = mode0 = 1, submode = 0 */ | 
 | 		mask[i++] = 0xFF; | 
 | 		match[i]  = 0x0A; /* start bit = 1, mode2 = 1 */ | 
 | 		mask[i++] = 0x0F; | 
 | 		proto = IR_PROTOCOL_RC6; | 
 | 		break; | 
 | 	default: | 
 | 		do_wake = false; | 
 | 		break; | 
 | 	} | 
 |  | 
 | finish: | 
 | 	if (do_wake) { | 
 | 		/* Set compare and compare mask */ | 
 | 		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX, | 
 | 			       WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0, | 
 | 			       0x3F); | 
 | 		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11); | 
 | 		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX, | 
 | 			       WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0, | 
 | 			       0x3F); | 
 | 		outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11); | 
 |  | 
 | 		/* RC6 Compare String Len */ | 
 | 		outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL); | 
 |  | 
 | 		/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */ | 
 | 		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17); | 
 |  | 
 | 		/* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */ | 
 | 		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07); | 
 |  | 
 | 		/* Set CEIR_EN */ | 
 | 		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, | 
 | 			       (proto << 4) | 0x01, 0x31); | 
 |  | 
 | 	} else { | 
 | 		/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */ | 
 | 		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07); | 
 |  | 
 | 		/* Clear CEIR_EN */ | 
 | 		wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01); | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * ACPI will set the HW disable bit for SP3 which means that the | 
 | 	 * output signals are left in an undefined state which may cause | 
 | 	 * spurious interrupts which we need to ignore until the hardware | 
 | 	 * is reinitialized. | 
 | 	 */ | 
 | 	wbcir_set_irqmask(data, WBCIR_IRQ_NONE); | 
 | 	disable_irq(data->irq); | 
 | } | 
 |  | 
 | /* | 
 |  * Wakeup handling is done on shutdown. | 
 |  */ | 
 | static int | 
 | wbcir_set_wakeup_filter(struct rc_dev *rc, struct rc_scancode_filter *filter) | 
 | { | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | wbcir_suspend(struct pnp_dev *device, pm_message_t state) | 
 | { | 
 | 	struct wbcir_data *data = pnp_get_drvdata(device); | 
 | 	led_classdev_suspend(&data->led); | 
 | 	wbcir_shutdown(device); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void | 
 | wbcir_init_hw(struct wbcir_data *data) | 
 | { | 
 | 	/* Disable interrupts */ | 
 | 	wbcir_set_irqmask(data, WBCIR_IRQ_NONE); | 
 |  | 
 | 	/* Set RX_INV, Clear CEIR_EN (needed for the led) */ | 
 | 	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, invert ? 8 : 0, 0x09); | 
 |  | 
 | 	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */ | 
 | 	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17); | 
 |  | 
 | 	/* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */ | 
 | 	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07); | 
 |  | 
 | 	/* Set RC5 cell time to correspond to 36 kHz */ | 
 | 	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F); | 
 |  | 
 | 	/* Set IRTX_INV */ | 
 | 	if (invert) | 
 | 		outb(WBCIR_IRTX_INV, data->ebase + WBCIR_REG_ECEIR_CCTL); | 
 | 	else | 
 | 		outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL); | 
 |  | 
 | 	/* | 
 | 	 * Clear IR LED, set SP3 clock to 24Mhz, set TX mask to IRTX1, | 
 | 	 * set SP3_IRRX_SW to binary 01, helpfully not documented | 
 | 	 */ | 
 | 	outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS); | 
 | 	data->txmask = 0x1; | 
 |  | 
 | 	/* Enable extended mode */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_2); | 
 | 	outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1); | 
 |  | 
 | 	/* | 
 | 	 * Configure baud generator, IR data will be sampled at | 
 | 	 * a bitrate of: (24Mhz * prescaler) / (divisor * 16). | 
 | 	 * | 
 | 	 * The ECIR registers include a flag to change the | 
 | 	 * 24Mhz clock freq to 48Mhz. | 
 | 	 * | 
 | 	 * It's not documented in the specs, but fifo levels | 
 | 	 * other than 16 seems to be unsupported. | 
 | 	 */ | 
 |  | 
 | 	/* prescaler 1.0, tx/rx fifo lvl 16 */ | 
 | 	outb(0x30, data->sbase + WBCIR_REG_SP3_EXCR2); | 
 |  | 
 | 	/* Set baud divisor to sample every 10 us */ | 
 | 	outb(0x0f, data->sbase + WBCIR_REG_SP3_BGDL); | 
 | 	outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH); | 
 |  | 
 | 	/* Set CEIR mode */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_0); | 
 | 	outb(0xC0, data->sbase + WBCIR_REG_SP3_MCR); | 
 | 	inb(data->sbase + WBCIR_REG_SP3_LSR); /* Clear LSR */ | 
 | 	inb(data->sbase + WBCIR_REG_SP3_MSR); /* Clear MSR */ | 
 |  | 
 | 	/* Disable RX demod, enable run-length enc/dec, set freq span */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_7); | 
 | 	outb(0x90, data->sbase + WBCIR_REG_SP3_RCCFG); | 
 |  | 
 | 	/* Disable timer */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_4); | 
 | 	outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR1); | 
 |  | 
 | 	/* Disable MSR interrupt, clear AUX_IRX, mask RX during TX? */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_5); | 
 | 	outb(txandrx ? 0x03 : 0x02, data->sbase + WBCIR_REG_SP3_IRCR2); | 
 |  | 
 | 	/* Disable CRC */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_6); | 
 | 	outb(0x20, data->sbase + WBCIR_REG_SP3_IRCR3); | 
 |  | 
 | 	/* Set RX demodulation freq, not really used */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_7); | 
 | 	outb(0xF2, data->sbase + WBCIR_REG_SP3_IRRXDC); | 
 |  | 
 | 	/* Set TX modulation, 36kHz, 7us pulse width */ | 
 | 	outb(0x69, data->sbase + WBCIR_REG_SP3_IRTXMC); | 
 | 	data->txcarrier = 36000; | 
 |  | 
 | 	/* Set invert and pin direction */ | 
 | 	if (invert) | 
 | 		outb(0x10, data->sbase + WBCIR_REG_SP3_IRCFG4); | 
 | 	else | 
 | 		outb(0x00, data->sbase + WBCIR_REG_SP3_IRCFG4); | 
 |  | 
 | 	/* Set FIFO thresholds (RX = 8, TX = 3), reset RX/TX */ | 
 | 	wbcir_select_bank(data, WBCIR_BANK_0); | 
 | 	outb(0x97, data->sbase + WBCIR_REG_SP3_FCR); | 
 |  | 
 | 	/* Clear AUX status bits */ | 
 | 	outb(0xE0, data->sbase + WBCIR_REG_SP3_ASCR); | 
 |  | 
 | 	/* Clear RX state */ | 
 | 	data->rxstate = WBCIR_RXSTATE_INACTIVE; | 
 | 	wbcir_idle_rx(data->dev, true); | 
 |  | 
 | 	/* Clear TX state */ | 
 | 	if (data->txstate == WBCIR_TXSTATE_ACTIVE) { | 
 | 		kfree(data->txbuf); | 
 | 		data->txbuf = NULL; | 
 | 		data->txstate = WBCIR_TXSTATE_INACTIVE; | 
 | 	} | 
 |  | 
 | 	/* Enable interrupts */ | 
 | 	wbcir_set_irqmask(data, WBCIR_IRQ_RX | WBCIR_IRQ_ERR); | 
 | } | 
 |  | 
 | static int | 
 | wbcir_resume(struct pnp_dev *device) | 
 | { | 
 | 	struct wbcir_data *data = pnp_get_drvdata(device); | 
 |  | 
 | 	wbcir_init_hw(data); | 
 | 	ir_raw_event_reset(data->dev); | 
 | 	enable_irq(data->irq); | 
 | 	led_classdev_resume(&data->led); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id) | 
 | { | 
 | 	struct device *dev = &device->dev; | 
 | 	struct wbcir_data *data; | 
 | 	int err; | 
 |  | 
 | 	if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN && | 
 | 	      pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN && | 
 | 	      pnp_port_len(device, 2) == SP_IOMEM_LEN)) { | 
 | 		dev_err(dev, "Invalid resources\n"); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	data = kzalloc(sizeof(*data), GFP_KERNEL); | 
 | 	if (!data) { | 
 | 		err = -ENOMEM; | 
 | 		goto exit; | 
 | 	} | 
 |  | 
 | 	pnp_set_drvdata(device, data); | 
 |  | 
 | 	spin_lock_init(&data->spinlock); | 
 | 	data->ebase = pnp_port_start(device, 0); | 
 | 	data->wbase = pnp_port_start(device, 1); | 
 | 	data->sbase = pnp_port_start(device, 2); | 
 | 	data->irq = pnp_irq(device, 0); | 
 |  | 
 | 	if (data->wbase == 0 || data->ebase == 0 || | 
 | 	    data->sbase == 0 || data->irq == -1) { | 
 | 		err = -ENODEV; | 
 | 		dev_err(dev, "Invalid resources\n"); | 
 | 		goto exit_free_data; | 
 | 	} | 
 |  | 
 | 	dev_dbg(&device->dev, "Found device (w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n", | 
 | 		data->wbase, data->ebase, data->sbase, data->irq); | 
 |  | 
 | 	data->led.name = "cir::activity"; | 
 | 	data->led.default_trigger = "rc-feedback"; | 
 | 	data->led.brightness_set = wbcir_led_brightness_set; | 
 | 	data->led.brightness_get = wbcir_led_brightness_get; | 
 | 	err = led_classdev_register(&device->dev, &data->led); | 
 | 	if (err) | 
 | 		goto exit_free_data; | 
 |  | 
 | 	data->dev = rc_allocate_device(RC_DRIVER_IR_RAW); | 
 | 	if (!data->dev) { | 
 | 		err = -ENOMEM; | 
 | 		goto exit_unregister_led; | 
 | 	} | 
 |  | 
 | 	data->dev->driver_name = DRVNAME; | 
 | 	data->dev->device_name = WBCIR_NAME; | 
 | 	data->dev->input_phys = "wbcir/cir0"; | 
 | 	data->dev->input_id.bustype = BUS_HOST; | 
 | 	data->dev->input_id.vendor = PCI_VENDOR_ID_WINBOND; | 
 | 	data->dev->input_id.product = WBCIR_ID_FAMILY; | 
 | 	data->dev->input_id.version = WBCIR_ID_CHIP; | 
 | 	data->dev->map_name = RC_MAP_RC6_MCE; | 
 | 	data->dev->s_idle = wbcir_idle_rx; | 
 | 	data->dev->s_carrier_report = wbcir_set_carrier_report; | 
 | 	data->dev->s_tx_mask = wbcir_txmask; | 
 | 	data->dev->s_tx_carrier = wbcir_txcarrier; | 
 | 	data->dev->tx_ir = wbcir_tx; | 
 | 	data->dev->priv = data; | 
 | 	data->dev->dev.parent = &device->dev; | 
 | 	data->dev->min_timeout = 1; | 
 | 	data->dev->timeout = IR_DEFAULT_TIMEOUT; | 
 | 	data->dev->max_timeout = 10 * IR_DEFAULT_TIMEOUT; | 
 | 	data->dev->rx_resolution = 2; | 
 | 	data->dev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; | 
 | 	data->dev->allowed_wakeup_protocols = RC_PROTO_BIT_NEC | | 
 | 		RC_PROTO_BIT_NECX | RC_PROTO_BIT_NEC32 | RC_PROTO_BIT_RC5 | | 
 | 		RC_PROTO_BIT_RC6_0 | RC_PROTO_BIT_RC6_6A_20 | | 
 | 		RC_PROTO_BIT_RC6_6A_24 | RC_PROTO_BIT_RC6_6A_32 | | 
 | 		RC_PROTO_BIT_RC6_MCE; | 
 | 	data->dev->wakeup_protocol = RC_PROTO_RC6_MCE; | 
 | 	data->dev->scancode_wakeup_filter.data = 0x800f040c; | 
 | 	data->dev->scancode_wakeup_filter.mask = 0xffff7fff; | 
 | 	data->dev->s_wakeup_filter = wbcir_set_wakeup_filter; | 
 |  | 
 | 	err = rc_register_device(data->dev); | 
 | 	if (err) | 
 | 		goto exit_free_rc; | 
 |  | 
 | 	if (!request_region(data->wbase, WAKEUP_IOMEM_LEN, DRVNAME)) { | 
 | 		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n", | 
 | 			data->wbase, data->wbase + WAKEUP_IOMEM_LEN - 1); | 
 | 		err = -EBUSY; | 
 | 		goto exit_unregister_device; | 
 | 	} | 
 |  | 
 | 	if (!request_region(data->ebase, EHFUNC_IOMEM_LEN, DRVNAME)) { | 
 | 		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n", | 
 | 			data->ebase, data->ebase + EHFUNC_IOMEM_LEN - 1); | 
 | 		err = -EBUSY; | 
 | 		goto exit_release_wbase; | 
 | 	} | 
 |  | 
 | 	if (!request_region(data->sbase, SP_IOMEM_LEN, DRVNAME)) { | 
 | 		dev_err(dev, "Region 0x%lx-0x%lx already in use!\n", | 
 | 			data->sbase, data->sbase + SP_IOMEM_LEN - 1); | 
 | 		err = -EBUSY; | 
 | 		goto exit_release_ebase; | 
 | 	} | 
 |  | 
 | 	err = request_irq(data->irq, wbcir_irq_handler, | 
 | 			  0, DRVNAME, device); | 
 | 	if (err) { | 
 | 		dev_err(dev, "Failed to claim IRQ %u\n", data->irq); | 
 | 		err = -EBUSY; | 
 | 		goto exit_release_sbase; | 
 | 	} | 
 |  | 
 | 	device_init_wakeup(&device->dev, 1); | 
 |  | 
 | 	wbcir_init_hw(data); | 
 |  | 
 | 	return 0; | 
 |  | 
 | exit_release_sbase: | 
 | 	release_region(data->sbase, SP_IOMEM_LEN); | 
 | exit_release_ebase: | 
 | 	release_region(data->ebase, EHFUNC_IOMEM_LEN); | 
 | exit_release_wbase: | 
 | 	release_region(data->wbase, WAKEUP_IOMEM_LEN); | 
 | exit_unregister_device: | 
 | 	rc_unregister_device(data->dev); | 
 | 	data->dev = NULL; | 
 | exit_free_rc: | 
 | 	rc_free_device(data->dev); | 
 | exit_unregister_led: | 
 | 	led_classdev_unregister(&data->led); | 
 | exit_free_data: | 
 | 	kfree(data); | 
 | 	pnp_set_drvdata(device, NULL); | 
 | exit: | 
 | 	return err; | 
 | } | 
 |  | 
 | static void | 
 | wbcir_remove(struct pnp_dev *device) | 
 | { | 
 | 	struct wbcir_data *data = pnp_get_drvdata(device); | 
 |  | 
 | 	/* Disable interrupts */ | 
 | 	wbcir_set_irqmask(data, WBCIR_IRQ_NONE); | 
 | 	free_irq(data->irq, device); | 
 |  | 
 | 	/* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */ | 
 | 	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17); | 
 |  | 
 | 	/* Clear CEIR_EN */ | 
 | 	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01); | 
 |  | 
 | 	/* Clear BUFF_EN, END_EN, MATCH_EN */ | 
 | 	wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07); | 
 |  | 
 | 	rc_unregister_device(data->dev); | 
 |  | 
 | 	led_classdev_unregister(&data->led); | 
 |  | 
 | 	/* This is ok since &data->led isn't actually used */ | 
 | 	wbcir_led_brightness_set(&data->led, LED_OFF); | 
 |  | 
 | 	release_region(data->wbase, WAKEUP_IOMEM_LEN); | 
 | 	release_region(data->ebase, EHFUNC_IOMEM_LEN); | 
 | 	release_region(data->sbase, SP_IOMEM_LEN); | 
 |  | 
 | 	kfree(data); | 
 |  | 
 | 	pnp_set_drvdata(device, NULL); | 
 | } | 
 |  | 
 | static const struct pnp_device_id wbcir_ids[] = { | 
 | 	{ "WEC1022", 0 }, | 
 | 	{ "", 0 } | 
 | }; | 
 | MODULE_DEVICE_TABLE(pnp, wbcir_ids); | 
 |  | 
 | static struct pnp_driver wbcir_driver = { | 
 | 	.name     = DRVNAME, | 
 | 	.id_table = wbcir_ids, | 
 | 	.probe    = wbcir_probe, | 
 | 	.remove   = wbcir_remove, | 
 | 	.suspend  = wbcir_suspend, | 
 | 	.resume   = wbcir_resume, | 
 | 	.shutdown = wbcir_shutdown | 
 | }; | 
 |  | 
 | static int __init | 
 | wbcir_init(void) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = pnp_register_driver(&wbcir_driver); | 
 | 	if (ret) | 
 | 		pr_err("Unable to register driver\n"); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void __exit | 
 | wbcir_exit(void) | 
 | { | 
 | 	pnp_unregister_driver(&wbcir_driver); | 
 | } | 
 |  | 
 | module_init(wbcir_init); | 
 | module_exit(wbcir_exit); | 
 |  | 
 | MODULE_AUTHOR("David Härdeman <david@hardeman.nu>"); | 
 | MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver"); | 
 | MODULE_LICENSE("GPL"); |