15b2e303fSDavid Härdeman /* 25b2e303fSDavid Härdeman * winbond-cir.c - Driver for the Consumer IR functionality of Winbond 35b2e303fSDavid Härdeman * SuperI/O chips. 45b2e303fSDavid Härdeman * 55b2e303fSDavid Härdeman * Currently supports the Winbond WPCD376i chip (PNP id WEC1022), but 65b2e303fSDavid Härdeman * could probably support others (Winbond WEC102X, NatSemi, etc) 75b2e303fSDavid Härdeman * with minor modifications. 85b2e303fSDavid Härdeman * 9b87f2eddSDavid Härdeman * Original Author: David Härdeman <david@hardeman.nu> 1037b0b4e9SSean Young * Copyright (C) 2012 Sean Young <sean@mess.org> 11b87f2eddSDavid Härdeman * Copyright (C) 2009 - 2011 David Härdeman <david@hardeman.nu> 125b2e303fSDavid Härdeman * 135b2e303fSDavid Härdeman * Dedicated to my daughter Matilda, without whose loving attention this 145b2e303fSDavid Härdeman * driver would have been finished in half the time and with a fraction 155b2e303fSDavid Härdeman * of the bugs. 165b2e303fSDavid Härdeman * 175b2e303fSDavid Härdeman * Written using: 185b2e303fSDavid Härdeman * o Winbond WPCD376I datasheet helpfully provided by Jesse Barnes at Intel 195b2e303fSDavid Härdeman * o NatSemi PC87338/PC97338 datasheet (for the serial port stuff) 205b2e303fSDavid Härdeman * o DSDT dumps 215b2e303fSDavid Härdeman * 225b2e303fSDavid Härdeman * Supported features: 23c829f267SDavid Härdeman * o IR Receive 24c829f267SDavid Härdeman * o IR Transmit 255b2e303fSDavid Härdeman * o Wake-On-CIR functionality 2637b0b4e9SSean Young * o Carrier detection 275b2e303fSDavid Härdeman * 285b2e303fSDavid Härdeman * This program is free software; you can redistribute it and/or modify 295b2e303fSDavid Härdeman * it under the terms of the GNU General Public License as published by 305b2e303fSDavid Härdeman * the Free Software Foundation; either version 2 of the License, or 315b2e303fSDavid Härdeman * (at your option) any later version. 325b2e303fSDavid Härdeman * 335b2e303fSDavid Härdeman * This program is distributed in the hope that it will be useful, 345b2e303fSDavid Härdeman * but WITHOUT ANY WARRANTY; without even the implied warranty of 355b2e303fSDavid Härdeman * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 365b2e303fSDavid Härdeman * GNU General Public License for more details. 375b2e303fSDavid Härdeman * 385b2e303fSDavid Härdeman * You should have received a copy of the GNU General Public License 395b2e303fSDavid Härdeman * along with this program; if not, write to the Free Software 405b2e303fSDavid Härdeman * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 415b2e303fSDavid Härdeman */ 425b2e303fSDavid Härdeman 43d8a10ac9SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 44d8a10ac9SJoe Perches 455b2e303fSDavid Härdeman #include <linux/module.h> 465b2e303fSDavid Härdeman #include <linux/pnp.h> 475b2e303fSDavid Härdeman #include <linux/interrupt.h> 485b2e303fSDavid Härdeman #include <linux/timer.h> 495b2e303fSDavid Härdeman #include <linux/leds.h> 505b2e303fSDavid Härdeman #include <linux/spinlock.h> 515b2e303fSDavid Härdeman #include <linux/pci_ids.h> 525b2e303fSDavid Härdeman #include <linux/io.h> 535b2e303fSDavid Härdeman #include <linux/bitrev.h> 545b2e303fSDavid Härdeman #include <linux/slab.h> 55c829f267SDavid Härdeman #include <linux/wait.h> 56c829f267SDavid Härdeman #include <linux/sched.h> 576bda9644SMauro Carvalho Chehab #include <media/rc-core.h> 585b2e303fSDavid Härdeman 595b2e303fSDavid Härdeman #define DRVNAME "winbond-cir" 605b2e303fSDavid Härdeman 615b2e303fSDavid Härdeman /* CEIR Wake-Up Registers, relative to data->wbase */ 625b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_CTL 0x03 /* CEIR Receiver Control */ 635b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_STS 0x04 /* CEIR Receiver Status */ 645b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_EV_EN 0x05 /* CEIR Receiver Event Enable */ 655b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_CNTL 0x06 /* CEIR Receiver Counter Low */ 665b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_CNTH 0x07 /* CEIR Receiver Counter High */ 675b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_INDEX 0x08 /* CEIR Receiver Index */ 685b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_DATA 0x09 /* CEIR Receiver Data */ 695b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_CSL 0x0A /* CEIR Re. Compare Strlen */ 705b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_CFG1 0x0B /* CEIR Re. Configuration 1 */ 715b2e303fSDavid Härdeman #define WBCIR_REG_WCEIR_CFG2 0x0C /* CEIR Re. Configuration 2 */ 725b2e303fSDavid Härdeman 735b2e303fSDavid Härdeman /* CEIR Enhanced Functionality Registers, relative to data->ebase */ 745b2e303fSDavid Härdeman #define WBCIR_REG_ECEIR_CTS 0x00 /* Enhanced IR Control Status */ 755b2e303fSDavid Härdeman #define WBCIR_REG_ECEIR_CCTL 0x01 /* Infrared Counter Control */ 765b2e303fSDavid Härdeman #define WBCIR_REG_ECEIR_CNT_LO 0x02 /* Infrared Counter LSB */ 775b2e303fSDavid Härdeman #define WBCIR_REG_ECEIR_CNT_HI 0x03 /* Infrared Counter MSB */ 785b2e303fSDavid Härdeman #define WBCIR_REG_ECEIR_IREM 0x04 /* Infrared Emitter Status */ 795b2e303fSDavid Härdeman 805b2e303fSDavid Härdeman /* SP3 Banked Registers, relative to data->sbase */ 815b2e303fSDavid Härdeman #define WBCIR_REG_SP3_BSR 0x03 /* Bank Select, all banks */ 825b2e303fSDavid Härdeman /* Bank 0 */ 835b2e303fSDavid Härdeman #define WBCIR_REG_SP3_RXDATA 0x00 /* FIFO RX data (r) */ 845b2e303fSDavid Härdeman #define WBCIR_REG_SP3_TXDATA 0x00 /* FIFO TX data (w) */ 855b2e303fSDavid Härdeman #define WBCIR_REG_SP3_IER 0x01 /* Interrupt Enable */ 865b2e303fSDavid Härdeman #define WBCIR_REG_SP3_EIR 0x02 /* Event Identification (r) */ 875b2e303fSDavid Härdeman #define WBCIR_REG_SP3_FCR 0x02 /* FIFO Control (w) */ 885b2e303fSDavid Härdeman #define WBCIR_REG_SP3_MCR 0x04 /* Mode Control */ 895b2e303fSDavid Härdeman #define WBCIR_REG_SP3_LSR 0x05 /* Link Status */ 905b2e303fSDavid Härdeman #define WBCIR_REG_SP3_MSR 0x06 /* Modem Status */ 915b2e303fSDavid Härdeman #define WBCIR_REG_SP3_ASCR 0x07 /* Aux Status and Control */ 925b2e303fSDavid Härdeman /* Bank 2 */ 935b2e303fSDavid Härdeman #define WBCIR_REG_SP3_BGDL 0x00 /* Baud Divisor LSB */ 945b2e303fSDavid Härdeman #define WBCIR_REG_SP3_BGDH 0x01 /* Baud Divisor MSB */ 955b2e303fSDavid Härdeman #define WBCIR_REG_SP3_EXCR1 0x02 /* Extended Control 1 */ 965b2e303fSDavid Härdeman #define WBCIR_REG_SP3_EXCR2 0x04 /* Extended Control 2 */ 975b2e303fSDavid Härdeman #define WBCIR_REG_SP3_TXFLV 0x06 /* TX FIFO Level */ 985b2e303fSDavid Härdeman #define WBCIR_REG_SP3_RXFLV 0x07 /* RX FIFO Level */ 995b2e303fSDavid Härdeman /* Bank 3 */ 1005b2e303fSDavid Härdeman #define WBCIR_REG_SP3_MRID 0x00 /* Module Identification */ 1015b2e303fSDavid Härdeman #define WBCIR_REG_SP3_SH_LCR 0x01 /* LCR Shadow */ 1025b2e303fSDavid Härdeman #define WBCIR_REG_SP3_SH_FCR 0x02 /* FCR Shadow */ 1035b2e303fSDavid Härdeman /* Bank 4 */ 1045b2e303fSDavid Härdeman #define WBCIR_REG_SP3_IRCR1 0x02 /* Infrared Control 1 */ 1055b2e303fSDavid Härdeman /* Bank 5 */ 1065b2e303fSDavid Härdeman #define WBCIR_REG_SP3_IRCR2 0x04 /* Infrared Control 2 */ 1075b2e303fSDavid Härdeman /* Bank 6 */ 1085b2e303fSDavid Härdeman #define WBCIR_REG_SP3_IRCR3 0x00 /* Infrared Control 3 */ 1095b2e303fSDavid Härdeman #define WBCIR_REG_SP3_SIR_PW 0x02 /* SIR Pulse Width */ 1105b2e303fSDavid Härdeman /* Bank 7 */ 1115b2e303fSDavid Härdeman #define WBCIR_REG_SP3_IRRXDC 0x00 /* IR RX Demod Control */ 1125b2e303fSDavid Härdeman #define WBCIR_REG_SP3_IRTXMC 0x01 /* IR TX Mod Control */ 1135b2e303fSDavid Härdeman #define WBCIR_REG_SP3_RCCFG 0x02 /* CEIR Config */ 1145b2e303fSDavid Härdeman #define WBCIR_REG_SP3_IRCFG1 0x04 /* Infrared Config 1 */ 1155b2e303fSDavid Härdeman #define WBCIR_REG_SP3_IRCFG4 0x07 /* Infrared Config 4 */ 1165b2e303fSDavid Härdeman 1175b2e303fSDavid Härdeman /* 1185b2e303fSDavid Härdeman * Magic values follow 1195b2e303fSDavid Härdeman */ 1205b2e303fSDavid Härdeman 1215b2e303fSDavid Härdeman /* No interrupts for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ 1225b2e303fSDavid Härdeman #define WBCIR_IRQ_NONE 0x00 1235b2e303fSDavid Härdeman /* RX data bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ 1245b2e303fSDavid Härdeman #define WBCIR_IRQ_RX 0x01 125c829f267SDavid Härdeman /* TX data low bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ 126c829f267SDavid Härdeman #define WBCIR_IRQ_TX_LOW 0x02 1275b2e303fSDavid Härdeman /* Over/Under-flow bit for WBCIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ 1285b2e303fSDavid Härdeman #define WBCIR_IRQ_ERR 0x04 129c829f267SDavid Härdeman /* TX data empty bit for WBCEIR_REG_SP3_IER and WBCIR_REG_SP3_EIR */ 130c829f267SDavid Härdeman #define WBCIR_IRQ_TX_EMPTY 0x20 1315b2e303fSDavid Härdeman /* Led enable/disable bit for WBCIR_REG_ECEIR_CTS */ 1325b2e303fSDavid Härdeman #define WBCIR_LED_ENABLE 0x80 1335b2e303fSDavid Härdeman /* RX data available bit for WBCIR_REG_SP3_LSR */ 1345b2e303fSDavid Härdeman #define WBCIR_RX_AVAIL 0x01 135c829f267SDavid Härdeman /* RX data overrun error bit for WBCIR_REG_SP3_LSR */ 136c829f267SDavid Härdeman #define WBCIR_RX_OVERRUN 0x02 137c829f267SDavid Härdeman /* TX End-Of-Transmission bit for WBCIR_REG_SP3_ASCR */ 138c829f267SDavid Härdeman #define WBCIR_TX_EOT 0x04 1395b2e303fSDavid Härdeman /* RX disable bit for WBCIR_REG_SP3_ASCR */ 1405b2e303fSDavid Härdeman #define WBCIR_RX_DISABLE 0x20 141c829f267SDavid Härdeman /* TX data underrun error bit for WBCIR_REG_SP3_ASCR */ 142c829f267SDavid Härdeman #define WBCIR_TX_UNDERRUN 0x40 1435b2e303fSDavid Härdeman /* Extended mode enable bit for WBCIR_REG_SP3_EXCR1 */ 1445b2e303fSDavid Härdeman #define WBCIR_EXT_ENABLE 0x01 1455b2e303fSDavid Härdeman /* Select compare register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */ 1465b2e303fSDavid Härdeman #define WBCIR_REGSEL_COMPARE 0x10 1475b2e303fSDavid Härdeman /* Select mask register in WBCIR_REG_WCEIR_INDEX (bits 5 & 6) */ 1485b2e303fSDavid Härdeman #define WBCIR_REGSEL_MASK 0x20 1495b2e303fSDavid Härdeman /* Starting address of selected register in WBCIR_REG_WCEIR_INDEX */ 1505b2e303fSDavid Härdeman #define WBCIR_REG_ADDR0 0x00 15137b0b4e9SSean Young /* Enable carrier counter */ 15237b0b4e9SSean Young #define WBCIR_CNTR_EN 0x01 15337b0b4e9SSean Young /* Reset carrier counter */ 15437b0b4e9SSean Young #define WBCIR_CNTR_R 0x02 15537b0b4e9SSean Young /* Invert TX */ 15637b0b4e9SSean Young #define WBCIR_IRTX_INV 0x04 1576f2627c2SSean Young /* Receiver oversampling */ 1586f2627c2SSean Young #define WBCIR_RX_T_OV 0x40 1595b2e303fSDavid Härdeman 1605b2e303fSDavid Härdeman /* Valid banks for the SP3 UART */ 1615b2e303fSDavid Härdeman enum wbcir_bank { 1625b2e303fSDavid Härdeman WBCIR_BANK_0 = 0x00, 1635b2e303fSDavid Härdeman WBCIR_BANK_1 = 0x80, 1645b2e303fSDavid Härdeman WBCIR_BANK_2 = 0xE0, 1655b2e303fSDavid Härdeman WBCIR_BANK_3 = 0xE4, 1665b2e303fSDavid Härdeman WBCIR_BANK_4 = 0xE8, 1675b2e303fSDavid Härdeman WBCIR_BANK_5 = 0xEC, 1685b2e303fSDavid Härdeman WBCIR_BANK_6 = 0xF0, 1695b2e303fSDavid Härdeman WBCIR_BANK_7 = 0xF4, 1705b2e303fSDavid Härdeman }; 1715b2e303fSDavid Härdeman 1725b2e303fSDavid Härdeman /* Supported power-on IR Protocols */ 1735b2e303fSDavid Härdeman enum wbcir_protocol { 1745b2e303fSDavid Härdeman IR_PROTOCOL_RC5 = 0x0, 1755b2e303fSDavid Härdeman IR_PROTOCOL_NEC = 0x1, 1765b2e303fSDavid Härdeman IR_PROTOCOL_RC6 = 0x2, 1775b2e303fSDavid Härdeman }; 1785b2e303fSDavid Härdeman 179c829f267SDavid Härdeman /* Possible states for IR reception */ 180c829f267SDavid Härdeman enum wbcir_rxstate { 181c829f267SDavid Härdeman WBCIR_RXSTATE_INACTIVE = 0, 182c829f267SDavid Härdeman WBCIR_RXSTATE_ACTIVE, 183c829f267SDavid Härdeman WBCIR_RXSTATE_ERROR 184c829f267SDavid Härdeman }; 185c829f267SDavid Härdeman 186c829f267SDavid Härdeman /* Possible states for IR transmission */ 187c829f267SDavid Härdeman enum wbcir_txstate { 188c829f267SDavid Härdeman WBCIR_TXSTATE_INACTIVE = 0, 189c829f267SDavid Härdeman WBCIR_TXSTATE_ACTIVE, 190c829f267SDavid Härdeman WBCIR_TXSTATE_ERROR 191c829f267SDavid Härdeman }; 192c829f267SDavid Härdeman 1935b2e303fSDavid Härdeman /* Misc */ 194a66cd0b6SSean Young #define WBCIR_NAME "Winbond CIR" 1955b2e303fSDavid Härdeman #define WBCIR_ID_FAMILY 0xF1 /* Family ID for the WPCD376I */ 1965b2e303fSDavid Härdeman #define WBCIR_ID_CHIP 0x04 /* Chip ID for the WPCD376I */ 1975b2e303fSDavid Härdeman #define INVALID_SCANCODE 0x7FFFFFFF /* Invalid with all protos */ 1985b2e303fSDavid Härdeman #define WAKEUP_IOMEM_LEN 0x10 /* Wake-Up I/O Reg Len */ 1995b2e303fSDavid Härdeman #define EHFUNC_IOMEM_LEN 0x10 /* Enhanced Func I/O Reg Len */ 2005b2e303fSDavid Härdeman #define SP_IOMEM_LEN 0x08 /* Serial Port 3 (IR) Reg Len */ 2015b2e303fSDavid Härdeman 2025b2e303fSDavid Härdeman /* Per-device data */ 2035b2e303fSDavid Härdeman struct wbcir_data { 2045b2e303fSDavid Härdeman spinlock_t spinlock; 205c829f267SDavid Härdeman struct rc_dev *dev; 206c829f267SDavid Härdeman struct led_classdev led; 2075b2e303fSDavid Härdeman 2085b2e303fSDavid Härdeman unsigned long wbase; /* Wake-Up Baseaddr */ 2095b2e303fSDavid Härdeman unsigned long ebase; /* Enhanced Func. Baseaddr */ 2105b2e303fSDavid Härdeman unsigned long sbase; /* Serial Port Baseaddr */ 2115b2e303fSDavid Härdeman unsigned int irq; /* Serial Port IRQ */ 212c829f267SDavid Härdeman u8 irqmask; 2135b2e303fSDavid Härdeman 214c829f267SDavid Härdeman /* RX state */ 215c829f267SDavid Härdeman enum wbcir_rxstate rxstate; 21637b0b4e9SSean Young int carrier_report_enabled; 21737b0b4e9SSean Young u32 pulse_duration; 2185b2e303fSDavid Härdeman 219c829f267SDavid Härdeman /* TX state */ 220c829f267SDavid Härdeman enum wbcir_txstate txstate; 221c829f267SDavid Härdeman u32 txlen; 222c829f267SDavid Härdeman u32 txoff; 223c829f267SDavid Härdeman u32 *txbuf; 224c829f267SDavid Härdeman u8 txmask; 225c829f267SDavid Härdeman u32 txcarrier; 2265b2e303fSDavid Härdeman }; 2275b2e303fSDavid Härdeman 2285b2e303fSDavid Härdeman static enum wbcir_protocol protocol = IR_PROTOCOL_RC6; 2295b2e303fSDavid Härdeman module_param(protocol, uint, 0444); 2305b2e303fSDavid Härdeman MODULE_PARM_DESC(protocol, "IR protocol to use for the power-on command " 2315b2e303fSDavid Härdeman "(0 = RC5, 1 = NEC, 2 = RC6A, default)"); 2325b2e303fSDavid Härdeman 23390ab5ee9SRusty Russell static bool invert; /* default = 0 */ 2345b2e303fSDavid Härdeman module_param(invert, bool, 0444); 2355b2e303fSDavid Härdeman MODULE_PARM_DESC(invert, "Invert the signal from the IR receiver"); 2365b2e303fSDavid Härdeman 23790ab5ee9SRusty Russell static bool txandrx; /* default = 0 */ 238c829f267SDavid Härdeman module_param(txandrx, bool, 0444); 23957f4422fSAnton Blanchard MODULE_PARM_DESC(txandrx, "Allow simultaneous TX and RX"); 240c829f267SDavid Härdeman 2415b2e303fSDavid Härdeman static unsigned int wake_sc = 0x800F040C; 2425b2e303fSDavid Härdeman module_param(wake_sc, uint, 0644); 2435b2e303fSDavid Härdeman MODULE_PARM_DESC(wake_sc, "Scancode of the power-on IR command"); 2445b2e303fSDavid Härdeman 2455b2e303fSDavid Härdeman static unsigned int wake_rc6mode = 6; 2465b2e303fSDavid Härdeman module_param(wake_rc6mode, uint, 0644); 2475b2e303fSDavid Härdeman MODULE_PARM_DESC(wake_rc6mode, "RC6 mode for the power-on command " 2485b2e303fSDavid Härdeman "(0 = 0, 6 = 6A, default)"); 2495b2e303fSDavid Härdeman 2505b2e303fSDavid Härdeman 2515b2e303fSDavid Härdeman 2525b2e303fSDavid Härdeman /***************************************************************************** 2535b2e303fSDavid Härdeman * 2545b2e303fSDavid Härdeman * UTILITY FUNCTIONS 2555b2e303fSDavid Härdeman * 2565b2e303fSDavid Härdeman *****************************************************************************/ 2575b2e303fSDavid Härdeman 2585b2e303fSDavid Härdeman /* Caller needs to hold wbcir_lock */ 2595b2e303fSDavid Härdeman static void 2605b2e303fSDavid Härdeman wbcir_set_bits(unsigned long addr, u8 bits, u8 mask) 2615b2e303fSDavid Härdeman { 2625b2e303fSDavid Härdeman u8 val; 2635b2e303fSDavid Härdeman 2645b2e303fSDavid Härdeman val = inb(addr); 2655b2e303fSDavid Härdeman val = ((val & ~mask) | (bits & mask)); 2665b2e303fSDavid Härdeman outb(val, addr); 2675b2e303fSDavid Härdeman } 2685b2e303fSDavid Härdeman 2695b2e303fSDavid Härdeman /* Selects the register bank for the serial port */ 2705b2e303fSDavid Härdeman static inline void 2715b2e303fSDavid Härdeman wbcir_select_bank(struct wbcir_data *data, enum wbcir_bank bank) 2725b2e303fSDavid Härdeman { 2735b2e303fSDavid Härdeman outb(bank, data->sbase + WBCIR_REG_SP3_BSR); 2745b2e303fSDavid Härdeman } 2755b2e303fSDavid Härdeman 276c829f267SDavid Härdeman static inline void 277c829f267SDavid Härdeman wbcir_set_irqmask(struct wbcir_data *data, u8 irqmask) 278c829f267SDavid Härdeman { 279c829f267SDavid Härdeman if (data->irqmask == irqmask) 280c829f267SDavid Härdeman return; 281c829f267SDavid Härdeman 282c829f267SDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_0); 283c829f267SDavid Härdeman outb(irqmask, data->sbase + WBCIR_REG_SP3_IER); 284c829f267SDavid Härdeman data->irqmask = irqmask; 285c829f267SDavid Härdeman } 286c829f267SDavid Härdeman 2875b2e303fSDavid Härdeman static enum led_brightness 2885b2e303fSDavid Härdeman wbcir_led_brightness_get(struct led_classdev *led_cdev) 2895b2e303fSDavid Härdeman { 2905b2e303fSDavid Härdeman struct wbcir_data *data = container_of(led_cdev, 2915b2e303fSDavid Härdeman struct wbcir_data, 2925b2e303fSDavid Härdeman led); 2935b2e303fSDavid Härdeman 2945b2e303fSDavid Härdeman if (inb(data->ebase + WBCIR_REG_ECEIR_CTS) & WBCIR_LED_ENABLE) 2955b2e303fSDavid Härdeman return LED_FULL; 2965b2e303fSDavid Härdeman else 2975b2e303fSDavid Härdeman return LED_OFF; 2985b2e303fSDavid Härdeman } 2995b2e303fSDavid Härdeman 3005b2e303fSDavid Härdeman static void 3015b2e303fSDavid Härdeman wbcir_led_brightness_set(struct led_classdev *led_cdev, 3025b2e303fSDavid Härdeman enum led_brightness brightness) 3035b2e303fSDavid Härdeman { 3045b2e303fSDavid Härdeman struct wbcir_data *data = container_of(led_cdev, 3055b2e303fSDavid Härdeman struct wbcir_data, 3065b2e303fSDavid Härdeman led); 3075b2e303fSDavid Härdeman 3085b2e303fSDavid Härdeman wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS, 3095b2e303fSDavid Härdeman brightness == LED_OFF ? 0x00 : WBCIR_LED_ENABLE, 3105b2e303fSDavid Härdeman WBCIR_LED_ENABLE); 3115b2e303fSDavid Härdeman } 3125b2e303fSDavid Härdeman 3135b2e303fSDavid Härdeman /* Manchester encodes bits to RC6 message cells (see wbcir_shutdown) */ 3145b2e303fSDavid Härdeman static u8 3155b2e303fSDavid Härdeman wbcir_to_rc6cells(u8 val) 3165b2e303fSDavid Härdeman { 3175b2e303fSDavid Härdeman u8 coded = 0x00; 3185b2e303fSDavid Härdeman int i; 3195b2e303fSDavid Härdeman 3205b2e303fSDavid Härdeman val &= 0x0F; 3215b2e303fSDavid Härdeman for (i = 0; i < 4; i++) { 3225b2e303fSDavid Härdeman if (val & 0x01) 3235b2e303fSDavid Härdeman coded |= 0x02 << (i * 2); 3245b2e303fSDavid Härdeman else 3255b2e303fSDavid Härdeman coded |= 0x01 << (i * 2); 3265b2e303fSDavid Härdeman val >>= 1; 3275b2e303fSDavid Härdeman } 3285b2e303fSDavid Härdeman 3295b2e303fSDavid Härdeman return coded; 3305b2e303fSDavid Härdeman } 3315b2e303fSDavid Härdeman 3325b2e303fSDavid Härdeman /***************************************************************************** 3335b2e303fSDavid Härdeman * 3345b2e303fSDavid Härdeman * INTERRUPT FUNCTIONS 3355b2e303fSDavid Härdeman * 3365b2e303fSDavid Härdeman *****************************************************************************/ 3375b2e303fSDavid Härdeman 338c829f267SDavid Härdeman static void 33937b0b4e9SSean Young wbcir_carrier_report(struct wbcir_data *data) 34037b0b4e9SSean Young { 34137b0b4e9SSean Young unsigned counter = inb(data->ebase + WBCIR_REG_ECEIR_CNT_LO) | 34237b0b4e9SSean Young inb(data->ebase + WBCIR_REG_ECEIR_CNT_HI) << 8; 34337b0b4e9SSean Young 34437b0b4e9SSean Young if (counter > 0 && counter < 0xffff) { 34537b0b4e9SSean Young DEFINE_IR_RAW_EVENT(ev); 34637b0b4e9SSean Young 34737b0b4e9SSean Young ev.carrier_report = 1; 34837b0b4e9SSean Young ev.carrier = DIV_ROUND_CLOSEST(counter * 1000000u, 34937b0b4e9SSean Young data->pulse_duration); 35037b0b4e9SSean Young 35137b0b4e9SSean Young ir_raw_event_store(data->dev, &ev); 35237b0b4e9SSean Young } 35337b0b4e9SSean Young 35437b0b4e9SSean Young /* reset and restart the counter */ 35537b0b4e9SSean Young data->pulse_duration = 0; 35637b0b4e9SSean Young wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CCTL, WBCIR_CNTR_R, 35737b0b4e9SSean Young WBCIR_CNTR_EN | WBCIR_CNTR_R); 35837b0b4e9SSean Young wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CCTL, WBCIR_CNTR_EN, 35937b0b4e9SSean Young WBCIR_CNTR_EN | WBCIR_CNTR_R); 36037b0b4e9SSean Young } 36137b0b4e9SSean Young 36237b0b4e9SSean Young static void 363488ebc48SDavid Härdeman wbcir_idle_rx(struct rc_dev *dev, bool idle) 3645b2e303fSDavid Härdeman { 365488ebc48SDavid Härdeman struct wbcir_data *data = dev->priv; 3665b2e303fSDavid Härdeman 3671ac7fdeeSSean Young if (!idle && data->rxstate == WBCIR_RXSTATE_INACTIVE) 368c829f267SDavid Härdeman data->rxstate = WBCIR_RXSTATE_ACTIVE; 3695b2e303fSDavid Härdeman 370e5eda7faSSean Young if (idle && data->rxstate != WBCIR_RXSTATE_INACTIVE) { 371e5eda7faSSean Young data->rxstate = WBCIR_RXSTATE_INACTIVE; 37237b0b4e9SSean Young 37337b0b4e9SSean Young if (data->carrier_report_enabled) 37437b0b4e9SSean Young wbcir_carrier_report(data); 37537b0b4e9SSean Young 376488ebc48SDavid Härdeman /* Tell hardware to go idle by setting RXINACTIVE */ 377488ebc48SDavid Härdeman outb(WBCIR_RX_DISABLE, data->sbase + WBCIR_REG_SP3_ASCR); 378488ebc48SDavid Härdeman } 379e5eda7faSSean Young } 380488ebc48SDavid Härdeman 381488ebc48SDavid Härdeman static void 382488ebc48SDavid Härdeman wbcir_irq_rx(struct wbcir_data *data, struct pnp_dev *device) 383488ebc48SDavid Härdeman { 384488ebc48SDavid Härdeman u8 irdata; 385488ebc48SDavid Härdeman DEFINE_IR_RAW_EVENT(rawir); 38637b0b4e9SSean Young unsigned duration; 387488ebc48SDavid Härdeman 3885b2e303fSDavid Härdeman /* Since RXHDLEV is set, at least 8 bytes are in the FIFO */ 389488ebc48SDavid Härdeman while (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_AVAIL) { 390488ebc48SDavid Härdeman irdata = inb(data->sbase + WBCIR_REG_SP3_RXDATA); 391c829f267SDavid Härdeman if (data->rxstate == WBCIR_RXSTATE_ERROR) 3925b2e303fSDavid Härdeman continue; 39337b0b4e9SSean Young 3946f2627c2SSean Young duration = ((irdata & 0x7F) + 1) * 3956f2627c2SSean Young (data->carrier_report_enabled ? 2 : 10); 396488ebc48SDavid Härdeman rawir.pulse = irdata & 0x80 ? false : true; 39737b0b4e9SSean Young rawir.duration = US_TO_NS(duration); 39837b0b4e9SSean Young 39937b0b4e9SSean Young if (rawir.pulse) 40037b0b4e9SSean Young data->pulse_duration += duration; 40137b0b4e9SSean Young 402488ebc48SDavid Härdeman ir_raw_event_store_with_filter(data->dev, &rawir); 4035b2e303fSDavid Härdeman } 4045b2e303fSDavid Härdeman 4055b2e303fSDavid Härdeman ir_raw_event_handle(data->dev); 406c829f267SDavid Härdeman } 4075b2e303fSDavid Härdeman 408c829f267SDavid Härdeman static void 409c829f267SDavid Härdeman wbcir_irq_tx(struct wbcir_data *data) 410c829f267SDavid Härdeman { 411c829f267SDavid Härdeman unsigned int space; 412c829f267SDavid Härdeman unsigned int used; 413c829f267SDavid Härdeman u8 bytes[16]; 414c829f267SDavid Härdeman u8 byte; 415c829f267SDavid Härdeman 416c829f267SDavid Härdeman if (!data->txbuf) 417c829f267SDavid Härdeman return; 418c829f267SDavid Härdeman 419c829f267SDavid Härdeman switch (data->txstate) { 420c829f267SDavid Härdeman case WBCIR_TXSTATE_INACTIVE: 421c829f267SDavid Härdeman /* TX FIFO empty */ 422c829f267SDavid Härdeman space = 16; 423c829f267SDavid Härdeman break; 424c829f267SDavid Härdeman case WBCIR_TXSTATE_ACTIVE: 425c829f267SDavid Härdeman /* TX FIFO low (3 bytes or less) */ 426c829f267SDavid Härdeman space = 13; 427c829f267SDavid Härdeman break; 428c829f267SDavid Härdeman case WBCIR_TXSTATE_ERROR: 429c829f267SDavid Härdeman space = 0; 430c829f267SDavid Härdeman break; 431c829f267SDavid Härdeman default: 432c829f267SDavid Härdeman return; 433c829f267SDavid Härdeman } 434c829f267SDavid Härdeman 435c829f267SDavid Härdeman /* 436c829f267SDavid Härdeman * TX data is run-length coded in bytes: YXXXXXXX 437c829f267SDavid Härdeman * Y = space (1) or pulse (0) 438c829f267SDavid Härdeman * X = duration, encoded as (X + 1) * 10us (i.e 10 to 1280 us) 439c829f267SDavid Härdeman */ 440c829f267SDavid Härdeman for (used = 0; used < space && data->txoff != data->txlen; used++) { 441c829f267SDavid Härdeman if (data->txbuf[data->txoff] == 0) { 442c829f267SDavid Härdeman data->txoff++; 443c829f267SDavid Härdeman continue; 444c829f267SDavid Härdeman } 445c829f267SDavid Härdeman byte = min((u32)0x80, data->txbuf[data->txoff]); 446c829f267SDavid Härdeman data->txbuf[data->txoff] -= byte; 447c829f267SDavid Härdeman byte--; 448c829f267SDavid Härdeman byte |= (data->txoff % 2 ? 0x80 : 0x00); /* pulse/space */ 449c829f267SDavid Härdeman bytes[used] = byte; 450c829f267SDavid Härdeman } 451c829f267SDavid Härdeman 452c829f267SDavid Härdeman while (data->txbuf[data->txoff] == 0 && data->txoff != data->txlen) 453c829f267SDavid Härdeman data->txoff++; 454c829f267SDavid Härdeman 455c829f267SDavid Härdeman if (used == 0) { 456c829f267SDavid Härdeman /* Finished */ 457c829f267SDavid Härdeman if (data->txstate == WBCIR_TXSTATE_ERROR) 458c829f267SDavid Härdeman /* Clear TX underrun bit */ 459c829f267SDavid Härdeman outb(WBCIR_TX_UNDERRUN, data->sbase + WBCIR_REG_SP3_ASCR); 460c829f267SDavid Härdeman wbcir_set_irqmask(data, WBCIR_IRQ_RX | WBCIR_IRQ_ERR); 4617bfb5dc1SDavid Härdeman kfree(data->txbuf); 4627bfb5dc1SDavid Härdeman data->txbuf = NULL; 4637bfb5dc1SDavid Härdeman data->txstate = WBCIR_TXSTATE_INACTIVE; 464c829f267SDavid Härdeman } else if (data->txoff == data->txlen) { 465c829f267SDavid Härdeman /* At the end of transmission, tell the hw before last byte */ 466c829f267SDavid Härdeman outsb(data->sbase + WBCIR_REG_SP3_TXDATA, bytes, used - 1); 467c829f267SDavid Härdeman outb(WBCIR_TX_EOT, data->sbase + WBCIR_REG_SP3_ASCR); 468c829f267SDavid Härdeman outb(bytes[used - 1], data->sbase + WBCIR_REG_SP3_TXDATA); 469c829f267SDavid Härdeman wbcir_set_irqmask(data, WBCIR_IRQ_RX | WBCIR_IRQ_ERR | 470c829f267SDavid Härdeman WBCIR_IRQ_TX_EMPTY); 471c829f267SDavid Härdeman } else { 472c829f267SDavid Härdeman /* More data to follow... */ 473c829f267SDavid Härdeman outsb(data->sbase + WBCIR_REG_SP3_RXDATA, bytes, used); 474c829f267SDavid Härdeman if (data->txstate == WBCIR_TXSTATE_INACTIVE) { 475c829f267SDavid Härdeman wbcir_set_irqmask(data, WBCIR_IRQ_RX | WBCIR_IRQ_ERR | 476c829f267SDavid Härdeman WBCIR_IRQ_TX_LOW); 477c829f267SDavid Härdeman data->txstate = WBCIR_TXSTATE_ACTIVE; 478c829f267SDavid Härdeman } 479c829f267SDavid Härdeman } 480c829f267SDavid Härdeman } 481c829f267SDavid Härdeman 482c829f267SDavid Härdeman static irqreturn_t 483c829f267SDavid Härdeman wbcir_irq_handler(int irqno, void *cookie) 484c829f267SDavid Härdeman { 485c829f267SDavid Härdeman struct pnp_dev *device = cookie; 486c829f267SDavid Härdeman struct wbcir_data *data = pnp_get_drvdata(device); 487c829f267SDavid Härdeman unsigned long flags; 488c829f267SDavid Härdeman u8 status; 489c829f267SDavid Härdeman 490c829f267SDavid Härdeman spin_lock_irqsave(&data->spinlock, flags); 491c829f267SDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_0); 492c829f267SDavid Härdeman status = inb(data->sbase + WBCIR_REG_SP3_EIR); 493c829f267SDavid Härdeman status &= data->irqmask; 494c829f267SDavid Härdeman 495c829f267SDavid Härdeman if (!status) { 496c829f267SDavid Härdeman spin_unlock_irqrestore(&data->spinlock, flags); 497c829f267SDavid Härdeman return IRQ_NONE; 498c829f267SDavid Härdeman } 499c829f267SDavid Härdeman 500c829f267SDavid Härdeman if (status & WBCIR_IRQ_ERR) { 501c829f267SDavid Härdeman /* RX overflow? (read clears bit) */ 502c829f267SDavid Härdeman if (inb(data->sbase + WBCIR_REG_SP3_LSR) & WBCIR_RX_OVERRUN) { 503c829f267SDavid Härdeman data->rxstate = WBCIR_RXSTATE_ERROR; 504c829f267SDavid Härdeman ir_raw_event_reset(data->dev); 505c829f267SDavid Härdeman } 506c829f267SDavid Härdeman 507c829f267SDavid Härdeman /* TX underflow? */ 508c829f267SDavid Härdeman if (inb(data->sbase + WBCIR_REG_SP3_ASCR) & WBCIR_TX_UNDERRUN) 509c829f267SDavid Härdeman data->txstate = WBCIR_TXSTATE_ERROR; 510c829f267SDavid Härdeman } 511c829f267SDavid Härdeman 512c829f267SDavid Härdeman if (status & WBCIR_IRQ_RX) 513c829f267SDavid Härdeman wbcir_irq_rx(data, device); 514c829f267SDavid Härdeman 515c829f267SDavid Härdeman if (status & (WBCIR_IRQ_TX_LOW | WBCIR_IRQ_TX_EMPTY)) 516c829f267SDavid Härdeman wbcir_irq_tx(data); 517c829f267SDavid Härdeman 5185b2e303fSDavid Härdeman spin_unlock_irqrestore(&data->spinlock, flags); 5195b2e303fSDavid Härdeman return IRQ_HANDLED; 5205b2e303fSDavid Härdeman } 5215b2e303fSDavid Härdeman 522c829f267SDavid Härdeman /***************************************************************************** 523c829f267SDavid Härdeman * 524c829f267SDavid Härdeman * RC-CORE INTERFACE FUNCTIONS 525c829f267SDavid Härdeman * 526c829f267SDavid Härdeman *****************************************************************************/ 5275b2e303fSDavid Härdeman 528c829f267SDavid Härdeman static int 52937b0b4e9SSean Young wbcir_set_carrier_report(struct rc_dev *dev, int enable) 53037b0b4e9SSean Young { 53137b0b4e9SSean Young struct wbcir_data *data = dev->priv; 53237b0b4e9SSean Young unsigned long flags; 53337b0b4e9SSean Young 53437b0b4e9SSean Young spin_lock_irqsave(&data->spinlock, flags); 53537b0b4e9SSean Young 53637b0b4e9SSean Young if (data->carrier_report_enabled == enable) { 53737b0b4e9SSean Young spin_unlock_irqrestore(&data->spinlock, flags); 53837b0b4e9SSean Young return 0; 53937b0b4e9SSean Young } 54037b0b4e9SSean Young 54137b0b4e9SSean Young data->pulse_duration = 0; 54237b0b4e9SSean Young wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CCTL, WBCIR_CNTR_R, 54337b0b4e9SSean Young WBCIR_CNTR_EN | WBCIR_CNTR_R); 54437b0b4e9SSean Young 54537b0b4e9SSean Young if (enable && data->dev->idle) 54637b0b4e9SSean Young wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CCTL, 54737b0b4e9SSean Young WBCIR_CNTR_EN, WBCIR_CNTR_EN | WBCIR_CNTR_R); 54837b0b4e9SSean Young 5496f2627c2SSean Young /* Set a higher sampling resolution if carrier reports are enabled */ 5506f2627c2SSean Young wbcir_select_bank(data, WBCIR_BANK_2); 5516f2627c2SSean Young data->dev->rx_resolution = US_TO_NS(enable ? 2 : 10); 5526f2627c2SSean Young outb(enable ? 0x03 : 0x0f, data->sbase + WBCIR_REG_SP3_BGDL); 5536f2627c2SSean Young outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH); 5546f2627c2SSean Young 5556f2627c2SSean Young /* Enable oversampling if carrier reports are enabled */ 5566f2627c2SSean Young wbcir_select_bank(data, WBCIR_BANK_7); 5576f2627c2SSean Young wbcir_set_bits(data->sbase + WBCIR_REG_SP3_RCCFG, 5586f2627c2SSean Young enable ? WBCIR_RX_T_OV : 0, WBCIR_RX_T_OV); 5596f2627c2SSean Young 56037b0b4e9SSean Young data->carrier_report_enabled = enable; 56137b0b4e9SSean Young spin_unlock_irqrestore(&data->spinlock, flags); 56237b0b4e9SSean Young 56337b0b4e9SSean Young return 0; 56437b0b4e9SSean Young } 56537b0b4e9SSean Young 56637b0b4e9SSean Young static int 567c829f267SDavid Härdeman wbcir_txcarrier(struct rc_dev *dev, u32 carrier) 568c829f267SDavid Härdeman { 569c829f267SDavid Härdeman struct wbcir_data *data = dev->priv; 570c829f267SDavid Härdeman unsigned long flags; 571c829f267SDavid Härdeman u8 val; 572c829f267SDavid Härdeman u32 freq; 573c829f267SDavid Härdeman 574c829f267SDavid Härdeman freq = DIV_ROUND_CLOSEST(carrier, 1000); 575c829f267SDavid Härdeman if (freq < 30 || freq > 60) 576c829f267SDavid Härdeman return -EINVAL; 577c829f267SDavid Härdeman 578c829f267SDavid Härdeman switch (freq) { 579c829f267SDavid Härdeman case 58: 580c829f267SDavid Härdeman case 59: 581c829f267SDavid Härdeman case 60: 582c829f267SDavid Härdeman val = freq - 58; 583c829f267SDavid Härdeman freq *= 1000; 584c829f267SDavid Härdeman break; 585c829f267SDavid Härdeman case 57: 586c829f267SDavid Härdeman val = freq - 27; 587c829f267SDavid Härdeman freq = 56900; 588c829f267SDavid Härdeman break; 589c829f267SDavid Härdeman default: 590c829f267SDavid Härdeman val = freq - 27; 591c829f267SDavid Härdeman freq *= 1000; 592c829f267SDavid Härdeman break; 593c829f267SDavid Härdeman } 594c829f267SDavid Härdeman 595c829f267SDavid Härdeman spin_lock_irqsave(&data->spinlock, flags); 596c829f267SDavid Härdeman if (data->txstate != WBCIR_TXSTATE_INACTIVE) { 597c829f267SDavid Härdeman spin_unlock_irqrestore(&data->spinlock, flags); 598c829f267SDavid Härdeman return -EBUSY; 599c829f267SDavid Härdeman } 600c829f267SDavid Härdeman 601c829f267SDavid Härdeman if (data->txcarrier != freq) { 602c829f267SDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_7); 603c829f267SDavid Härdeman wbcir_set_bits(data->sbase + WBCIR_REG_SP3_IRTXMC, val, 0x1F); 604c829f267SDavid Härdeman data->txcarrier = freq; 605c829f267SDavid Härdeman } 606c829f267SDavid Härdeman 607c829f267SDavid Härdeman spin_unlock_irqrestore(&data->spinlock, flags); 608c829f267SDavid Härdeman return 0; 609c829f267SDavid Härdeman } 610c829f267SDavid Härdeman 611c829f267SDavid Härdeman static int 612c829f267SDavid Härdeman wbcir_txmask(struct rc_dev *dev, u32 mask) 613c829f267SDavid Härdeman { 614c829f267SDavid Härdeman struct wbcir_data *data = dev->priv; 615c829f267SDavid Härdeman unsigned long flags; 616c829f267SDavid Härdeman u8 val; 617c829f267SDavid Härdeman 618c829f267SDavid Härdeman /* Four outputs, only one output can be enabled at a time */ 619c829f267SDavid Härdeman switch (mask) { 620c829f267SDavid Härdeman case 0x1: 621c829f267SDavid Härdeman val = 0x0; 622c829f267SDavid Härdeman break; 623c829f267SDavid Härdeman case 0x2: 624c829f267SDavid Härdeman val = 0x1; 625c829f267SDavid Härdeman break; 626c829f267SDavid Härdeman case 0x4: 627c829f267SDavid Härdeman val = 0x2; 628c829f267SDavid Härdeman break; 629c829f267SDavid Härdeman case 0x8: 630c829f267SDavid Härdeman val = 0x3; 631c829f267SDavid Härdeman break; 632c829f267SDavid Härdeman default: 633c829f267SDavid Härdeman return -EINVAL; 634c829f267SDavid Härdeman } 635c829f267SDavid Härdeman 636c829f267SDavid Härdeman spin_lock_irqsave(&data->spinlock, flags); 637c829f267SDavid Härdeman if (data->txstate != WBCIR_TXSTATE_INACTIVE) { 638c829f267SDavid Härdeman spin_unlock_irqrestore(&data->spinlock, flags); 639c829f267SDavid Härdeman return -EBUSY; 640c829f267SDavid Härdeman } 641c829f267SDavid Härdeman 642c829f267SDavid Härdeman if (data->txmask != mask) { 643c829f267SDavid Härdeman wbcir_set_bits(data->ebase + WBCIR_REG_ECEIR_CTS, val, 0x0c); 644c829f267SDavid Härdeman data->txmask = mask; 645c829f267SDavid Härdeman } 646c829f267SDavid Härdeman 647c829f267SDavid Härdeman spin_unlock_irqrestore(&data->spinlock, flags); 648c829f267SDavid Härdeman return 0; 649c829f267SDavid Härdeman } 650c829f267SDavid Härdeman 651c829f267SDavid Härdeman static int 6527bfb5dc1SDavid Härdeman wbcir_tx(struct rc_dev *dev, unsigned *b, unsigned count) 653c829f267SDavid Härdeman { 654c829f267SDavid Härdeman struct wbcir_data *data = dev->priv; 6557bfb5dc1SDavid Härdeman unsigned *buf; 656c829f267SDavid Härdeman unsigned i; 657c829f267SDavid Härdeman unsigned long flags; 658c829f267SDavid Härdeman 6597bfb5dc1SDavid Härdeman buf = kmalloc(count * sizeof(*b), GFP_KERNEL); 6607bfb5dc1SDavid Härdeman if (!buf) 6617bfb5dc1SDavid Härdeman return -ENOMEM; 6627bfb5dc1SDavid Härdeman 6637bfb5dc1SDavid Härdeman /* Convert values to multiples of 10us */ 6647bfb5dc1SDavid Härdeman for (i = 0; i < count; i++) 6657bfb5dc1SDavid Härdeman buf[i] = DIV_ROUND_CLOSEST(b[i], 10); 6667bfb5dc1SDavid Härdeman 667c829f267SDavid Härdeman /* Not sure if this is possible, but better safe than sorry */ 668c829f267SDavid Härdeman spin_lock_irqsave(&data->spinlock, flags); 669c829f267SDavid Härdeman if (data->txstate != WBCIR_TXSTATE_INACTIVE) { 670c829f267SDavid Härdeman spin_unlock_irqrestore(&data->spinlock, flags); 6717bfb5dc1SDavid Härdeman kfree(buf); 672c829f267SDavid Härdeman return -EBUSY; 673c829f267SDavid Härdeman } 674c829f267SDavid Härdeman 675c829f267SDavid Härdeman /* Fill the TX fifo once, the irq handler will do the rest */ 676c829f267SDavid Härdeman data->txbuf = buf; 677c829f267SDavid Härdeman data->txlen = count; 678c829f267SDavid Härdeman data->txoff = 0; 679c829f267SDavid Härdeman wbcir_irq_tx(data); 680c829f267SDavid Härdeman 681c829f267SDavid Härdeman /* We're done */ 682c829f267SDavid Härdeman spin_unlock_irqrestore(&data->spinlock, flags); 683c829f267SDavid Härdeman return count; 684c829f267SDavid Härdeman } 6855b2e303fSDavid Härdeman 6865b2e303fSDavid Härdeman /***************************************************************************** 6875b2e303fSDavid Härdeman * 6885b2e303fSDavid Härdeman * SETUP/INIT/SUSPEND/RESUME FUNCTIONS 6895b2e303fSDavid Härdeman * 6905b2e303fSDavid Härdeman *****************************************************************************/ 6915b2e303fSDavid Härdeman 6925b2e303fSDavid Härdeman static void 6935b2e303fSDavid Härdeman wbcir_shutdown(struct pnp_dev *device) 6945b2e303fSDavid Härdeman { 6955b2e303fSDavid Härdeman struct device *dev = &device->dev; 6965b2e303fSDavid Härdeman struct wbcir_data *data = pnp_get_drvdata(device); 69767cdd42eSDavid Härdeman bool do_wake = true; 6985b2e303fSDavid Härdeman u8 match[11]; 6995b2e303fSDavid Härdeman u8 mask[11]; 7005b2e303fSDavid Härdeman u8 rc6_csl = 0; 7015b2e303fSDavid Härdeman int i; 7025b2e303fSDavid Härdeman 7035b2e303fSDavid Härdeman memset(match, 0, sizeof(match)); 7045b2e303fSDavid Härdeman memset(mask, 0, sizeof(mask)); 7055b2e303fSDavid Härdeman 7065b2e303fSDavid Härdeman if (wake_sc == INVALID_SCANCODE || !device_may_wakeup(dev)) { 70767cdd42eSDavid Härdeman do_wake = false; 7085b2e303fSDavid Härdeman goto finish; 7095b2e303fSDavid Härdeman } 7105b2e303fSDavid Härdeman 7115b2e303fSDavid Härdeman switch (protocol) { 7125b2e303fSDavid Härdeman case IR_PROTOCOL_RC5: 7135b2e303fSDavid Härdeman if (wake_sc > 0xFFF) { 71467cdd42eSDavid Härdeman do_wake = false; 7155b2e303fSDavid Härdeman dev_err(dev, "RC5 - Invalid wake scancode\n"); 7165b2e303fSDavid Härdeman break; 7175b2e303fSDavid Härdeman } 7185b2e303fSDavid Härdeman 7195b2e303fSDavid Härdeman /* Mask = 13 bits, ex toggle */ 7205b2e303fSDavid Härdeman mask[0] = 0xFF; 7215b2e303fSDavid Härdeman mask[1] = 0x17; 7225b2e303fSDavid Härdeman 7235b2e303fSDavid Härdeman match[0] = (wake_sc & 0x003F); /* 6 command bits */ 7245b2e303fSDavid Härdeman match[0] |= (wake_sc & 0x0180) >> 1; /* 2 address bits */ 7255b2e303fSDavid Härdeman match[1] = (wake_sc & 0x0E00) >> 9; /* 3 address bits */ 7265b2e303fSDavid Härdeman if (!(wake_sc & 0x0040)) /* 2nd start bit */ 7275b2e303fSDavid Härdeman match[1] |= 0x10; 7285b2e303fSDavid Härdeman 7295b2e303fSDavid Härdeman break; 7305b2e303fSDavid Härdeman 7315b2e303fSDavid Härdeman case IR_PROTOCOL_NEC: 7325b2e303fSDavid Härdeman if (wake_sc > 0xFFFFFF) { 73367cdd42eSDavid Härdeman do_wake = false; 7345b2e303fSDavid Härdeman dev_err(dev, "NEC - Invalid wake scancode\n"); 7355b2e303fSDavid Härdeman break; 7365b2e303fSDavid Härdeman } 7375b2e303fSDavid Härdeman 7385b2e303fSDavid Härdeman mask[0] = mask[1] = mask[2] = mask[3] = 0xFF; 7395b2e303fSDavid Härdeman 7405b2e303fSDavid Härdeman match[1] = bitrev8((wake_sc & 0xFF)); 7415b2e303fSDavid Härdeman match[0] = ~match[1]; 7425b2e303fSDavid Härdeman 7435b2e303fSDavid Härdeman match[3] = bitrev8((wake_sc & 0xFF00) >> 8); 7445b2e303fSDavid Härdeman if (wake_sc > 0xFFFF) 7455b2e303fSDavid Härdeman match[2] = bitrev8((wake_sc & 0xFF0000) >> 16); 7465b2e303fSDavid Härdeman else 7475b2e303fSDavid Härdeman match[2] = ~match[3]; 7485b2e303fSDavid Härdeman 7495b2e303fSDavid Härdeman break; 7505b2e303fSDavid Härdeman 7515b2e303fSDavid Härdeman case IR_PROTOCOL_RC6: 7525b2e303fSDavid Härdeman 7535b2e303fSDavid Härdeman if (wake_rc6mode == 0) { 7545b2e303fSDavid Härdeman if (wake_sc > 0xFFFF) { 75567cdd42eSDavid Härdeman do_wake = false; 7565b2e303fSDavid Härdeman dev_err(dev, "RC6 - Invalid wake scancode\n"); 7575b2e303fSDavid Härdeman break; 7585b2e303fSDavid Härdeman } 7595b2e303fSDavid Härdeman 7605b2e303fSDavid Härdeman /* Command */ 7615b2e303fSDavid Härdeman match[0] = wbcir_to_rc6cells(wake_sc >> 0); 7625b2e303fSDavid Härdeman mask[0] = 0xFF; 7635b2e303fSDavid Härdeman match[1] = wbcir_to_rc6cells(wake_sc >> 4); 7645b2e303fSDavid Härdeman mask[1] = 0xFF; 7655b2e303fSDavid Härdeman 7665b2e303fSDavid Härdeman /* Address */ 7675b2e303fSDavid Härdeman match[2] = wbcir_to_rc6cells(wake_sc >> 8); 7685b2e303fSDavid Härdeman mask[2] = 0xFF; 7695b2e303fSDavid Härdeman match[3] = wbcir_to_rc6cells(wake_sc >> 12); 7705b2e303fSDavid Härdeman mask[3] = 0xFF; 7715b2e303fSDavid Härdeman 7725b2e303fSDavid Härdeman /* Header */ 7735b2e303fSDavid Härdeman match[4] = 0x50; /* mode1 = mode0 = 0, ignore toggle */ 7745b2e303fSDavid Härdeman mask[4] = 0xF0; 7755b2e303fSDavid Härdeman match[5] = 0x09; /* start bit = 1, mode2 = 0 */ 7765b2e303fSDavid Härdeman mask[5] = 0x0F; 7775b2e303fSDavid Härdeman 7785b2e303fSDavid Härdeman rc6_csl = 44; 7795b2e303fSDavid Härdeman 7805b2e303fSDavid Härdeman } else if (wake_rc6mode == 6) { 7815b2e303fSDavid Härdeman i = 0; 7825b2e303fSDavid Härdeman 7835b2e303fSDavid Härdeman /* Command */ 7845b2e303fSDavid Härdeman match[i] = wbcir_to_rc6cells(wake_sc >> 0); 7855b2e303fSDavid Härdeman mask[i++] = 0xFF; 7865b2e303fSDavid Härdeman match[i] = wbcir_to_rc6cells(wake_sc >> 4); 7875b2e303fSDavid Härdeman mask[i++] = 0xFF; 7885b2e303fSDavid Härdeman 7895b2e303fSDavid Härdeman /* Address + Toggle */ 7905b2e303fSDavid Härdeman match[i] = wbcir_to_rc6cells(wake_sc >> 8); 7915b2e303fSDavid Härdeman mask[i++] = 0xFF; 7925b2e303fSDavid Härdeman match[i] = wbcir_to_rc6cells(wake_sc >> 12); 7935b2e303fSDavid Härdeman mask[i++] = 0x3F; 7945b2e303fSDavid Härdeman 7955b2e303fSDavid Härdeman /* Customer bits 7 - 0 */ 7965b2e303fSDavid Härdeman match[i] = wbcir_to_rc6cells(wake_sc >> 16); 7975b2e303fSDavid Härdeman mask[i++] = 0xFF; 7985b2e303fSDavid Härdeman match[i] = wbcir_to_rc6cells(wake_sc >> 20); 7995b2e303fSDavid Härdeman mask[i++] = 0xFF; 8005b2e303fSDavid Härdeman 8015b2e303fSDavid Härdeman if (wake_sc & 0x80000000) { 8025b2e303fSDavid Härdeman /* Customer range bit and bits 15 - 8 */ 8035b2e303fSDavid Härdeman match[i] = wbcir_to_rc6cells(wake_sc >> 24); 8045b2e303fSDavid Härdeman mask[i++] = 0xFF; 8055b2e303fSDavid Härdeman match[i] = wbcir_to_rc6cells(wake_sc >> 28); 8065b2e303fSDavid Härdeman mask[i++] = 0xFF; 8075b2e303fSDavid Härdeman rc6_csl = 76; 8085b2e303fSDavid Härdeman } else if (wake_sc <= 0x007FFFFF) { 8095b2e303fSDavid Härdeman rc6_csl = 60; 8105b2e303fSDavid Härdeman } else { 81167cdd42eSDavid Härdeman do_wake = false; 8125b2e303fSDavid Härdeman dev_err(dev, "RC6 - Invalid wake scancode\n"); 8135b2e303fSDavid Härdeman break; 8145b2e303fSDavid Härdeman } 8155b2e303fSDavid Härdeman 8165b2e303fSDavid Härdeman /* Header */ 8175b2e303fSDavid Härdeman match[i] = 0x93; /* mode1 = mode0 = 1, submode = 0 */ 8185b2e303fSDavid Härdeman mask[i++] = 0xFF; 8195b2e303fSDavid Härdeman match[i] = 0x0A; /* start bit = 1, mode2 = 1 */ 8205b2e303fSDavid Härdeman mask[i++] = 0x0F; 8215b2e303fSDavid Härdeman 8225b2e303fSDavid Härdeman } else { 82367cdd42eSDavid Härdeman do_wake = false; 8245b2e303fSDavid Härdeman dev_err(dev, "RC6 - Invalid wake mode\n"); 8255b2e303fSDavid Härdeman } 8265b2e303fSDavid Härdeman 8275b2e303fSDavid Härdeman break; 8285b2e303fSDavid Härdeman 8295b2e303fSDavid Härdeman default: 83067cdd42eSDavid Härdeman do_wake = false; 8315b2e303fSDavid Härdeman break; 8325b2e303fSDavid Härdeman } 8335b2e303fSDavid Härdeman 8345b2e303fSDavid Härdeman finish: 8355b2e303fSDavid Härdeman if (do_wake) { 8365b2e303fSDavid Härdeman /* Set compare and compare mask */ 8375b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX, 8385b2e303fSDavid Härdeman WBCIR_REGSEL_COMPARE | WBCIR_REG_ADDR0, 8395b2e303fSDavid Härdeman 0x3F); 8405b2e303fSDavid Härdeman outsb(data->wbase + WBCIR_REG_WCEIR_DATA, match, 11); 8415b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_INDEX, 8425b2e303fSDavid Härdeman WBCIR_REGSEL_MASK | WBCIR_REG_ADDR0, 8435b2e303fSDavid Härdeman 0x3F); 8445b2e303fSDavid Härdeman outsb(data->wbase + WBCIR_REG_WCEIR_DATA, mask, 11); 8455b2e303fSDavid Härdeman 8465b2e303fSDavid Härdeman /* RC6 Compare String Len */ 8475b2e303fSDavid Härdeman outb(rc6_csl, data->wbase + WBCIR_REG_WCEIR_CSL); 8485b2e303fSDavid Härdeman 8495b2e303fSDavid Härdeman /* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */ 8505b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17); 8515b2e303fSDavid Härdeman 8525b2e303fSDavid Härdeman /* Clear BUFF_EN, Clear END_EN, Set MATCH_EN */ 8535b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x01, 0x07); 8545b2e303fSDavid Härdeman 8555b2e303fSDavid Härdeman /* Set CEIR_EN */ 8565b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x01, 0x01); 8575b2e303fSDavid Härdeman 8585b2e303fSDavid Härdeman } else { 8595b2e303fSDavid Härdeman /* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */ 8605b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07); 8615b2e303fSDavid Härdeman 8625b2e303fSDavid Härdeman /* Clear CEIR_EN */ 8635b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01); 8645b2e303fSDavid Härdeman } 8655b2e303fSDavid Härdeman 8665b2e303fSDavid Härdeman /* 8675b2e303fSDavid Härdeman * ACPI will set the HW disable bit for SP3 which means that the 8685b2e303fSDavid Härdeman * output signals are left in an undefined state which may cause 8695b2e303fSDavid Härdeman * spurious interrupts which we need to ignore until the hardware 8705b2e303fSDavid Härdeman * is reinitialized. 8715b2e303fSDavid Härdeman */ 872c829f267SDavid Härdeman wbcir_set_irqmask(data, WBCIR_IRQ_NONE); 8735b2e303fSDavid Härdeman disable_irq(data->irq); 8745b2e303fSDavid Härdeman } 8755b2e303fSDavid Härdeman 8765b2e303fSDavid Härdeman static int 8775b2e303fSDavid Härdeman wbcir_suspend(struct pnp_dev *device, pm_message_t state) 8785b2e303fSDavid Härdeman { 8791ac7fdeeSSean Young struct wbcir_data *data = pnp_get_drvdata(device); 8801ac7fdeeSSean Young led_classdev_suspend(&data->led); 8815b2e303fSDavid Härdeman wbcir_shutdown(device); 8825b2e303fSDavid Härdeman return 0; 8835b2e303fSDavid Härdeman } 8845b2e303fSDavid Härdeman 8855b2e303fSDavid Härdeman static void 8865b2e303fSDavid Härdeman wbcir_init_hw(struct wbcir_data *data) 8875b2e303fSDavid Härdeman { 8885b2e303fSDavid Härdeman u8 tmp; 8895b2e303fSDavid Härdeman 8905b2e303fSDavid Härdeman /* Disable interrupts */ 891c829f267SDavid Härdeman wbcir_set_irqmask(data, WBCIR_IRQ_NONE); 8925b2e303fSDavid Härdeman 8935b2e303fSDavid Härdeman /* Set PROT_SEL, RX_INV, Clear CEIR_EN (needed for the led) */ 8945b2e303fSDavid Härdeman tmp = protocol << 4; 8955b2e303fSDavid Härdeman if (invert) 8965b2e303fSDavid Härdeman tmp |= 0x08; 8975b2e303fSDavid Härdeman outb(tmp, data->wbase + WBCIR_REG_WCEIR_CTL); 8985b2e303fSDavid Härdeman 8995b2e303fSDavid Härdeman /* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */ 9005b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17); 9015b2e303fSDavid Härdeman 9025b2e303fSDavid Härdeman /* Clear BUFF_EN, Clear END_EN, Clear MATCH_EN */ 9035b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07); 9045b2e303fSDavid Härdeman 9055b2e303fSDavid Härdeman /* Set RC5 cell time to correspond to 36 kHz */ 9065b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CFG1, 0x4A, 0x7F); 9075b2e303fSDavid Härdeman 9085b2e303fSDavid Härdeman /* Set IRTX_INV */ 9095b2e303fSDavid Härdeman if (invert) 91037b0b4e9SSean Young outb(WBCIR_IRTX_INV, data->ebase + WBCIR_REG_ECEIR_CCTL); 9115b2e303fSDavid Härdeman else 9125b2e303fSDavid Härdeman outb(0x00, data->ebase + WBCIR_REG_ECEIR_CCTL); 9135b2e303fSDavid Härdeman 9145b2e303fSDavid Härdeman /* 915c829f267SDavid Härdeman * Clear IR LED, set SP3 clock to 24Mhz, set TX mask to IRTX1, 9165b2e303fSDavid Härdeman * set SP3_IRRX_SW to binary 01, helpfully not documented 9175b2e303fSDavid Härdeman */ 9185b2e303fSDavid Härdeman outb(0x10, data->ebase + WBCIR_REG_ECEIR_CTS); 919c829f267SDavid Härdeman data->txmask = 0x1; 9205b2e303fSDavid Härdeman 9215b2e303fSDavid Härdeman /* Enable extended mode */ 9225b2e303fSDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_2); 9235b2e303fSDavid Härdeman outb(WBCIR_EXT_ENABLE, data->sbase + WBCIR_REG_SP3_EXCR1); 9245b2e303fSDavid Härdeman 9255b2e303fSDavid Härdeman /* 9265b2e303fSDavid Härdeman * Configure baud generator, IR data will be sampled at 9275b2e303fSDavid Härdeman * a bitrate of: (24Mhz * prescaler) / (divisor * 16). 9285b2e303fSDavid Härdeman * 9295b2e303fSDavid Härdeman * The ECIR registers include a flag to change the 9305b2e303fSDavid Härdeman * 24Mhz clock freq to 48Mhz. 9315b2e303fSDavid Härdeman * 9325b2e303fSDavid Härdeman * It's not documented in the specs, but fifo levels 9335b2e303fSDavid Härdeman * other than 16 seems to be unsupported. 9345b2e303fSDavid Härdeman */ 9355b2e303fSDavid Härdeman 9365b2e303fSDavid Härdeman /* prescaler 1.0, tx/rx fifo lvl 16 */ 9375b2e303fSDavid Härdeman outb(0x30, data->sbase + WBCIR_REG_SP3_EXCR2); 9385b2e303fSDavid Härdeman 9396f2627c2SSean Young /* Set baud divisor to sample every 10 us */ 9406f2627c2SSean Young outb(0x0f, data->sbase + WBCIR_REG_SP3_BGDL); 9415b2e303fSDavid Härdeman outb(0x00, data->sbase + WBCIR_REG_SP3_BGDH); 9425b2e303fSDavid Härdeman 9435b2e303fSDavid Härdeman /* Set CEIR mode */ 9445b2e303fSDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_0); 9455b2e303fSDavid Härdeman outb(0xC0, data->sbase + WBCIR_REG_SP3_MCR); 9465b2e303fSDavid Härdeman inb(data->sbase + WBCIR_REG_SP3_LSR); /* Clear LSR */ 9475b2e303fSDavid Härdeman inb(data->sbase + WBCIR_REG_SP3_MSR); /* Clear MSR */ 9485b2e303fSDavid Härdeman 9496f2627c2SSean Young /* Disable RX demod, enable run-length enc/dec, set freq span */ 9505b2e303fSDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_7); 9516f2627c2SSean Young outb(0x90, data->sbase + WBCIR_REG_SP3_RCCFG); 9525b2e303fSDavid Härdeman 9535b2e303fSDavid Härdeman /* Disable timer */ 9545b2e303fSDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_4); 9555b2e303fSDavid Härdeman outb(0x00, data->sbase + WBCIR_REG_SP3_IRCR1); 9565b2e303fSDavid Härdeman 957c829f267SDavid Härdeman /* Disable MSR interrupt, clear AUX_IRX, mask RX during TX? */ 9585b2e303fSDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_5); 959c829f267SDavid Härdeman outb(txandrx ? 0x03 : 0x02, data->sbase + WBCIR_REG_SP3_IRCR2); 9605b2e303fSDavid Härdeman 9615b2e303fSDavid Härdeman /* Disable CRC */ 9625b2e303fSDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_6); 9635b2e303fSDavid Härdeman outb(0x20, data->sbase + WBCIR_REG_SP3_IRCR3); 9645b2e303fSDavid Härdeman 965c829f267SDavid Härdeman /* Set RX demodulation freq, not really used */ 9665b2e303fSDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_7); 9675b2e303fSDavid Härdeman outb(0xF2, data->sbase + WBCIR_REG_SP3_IRRXDC); 968c829f267SDavid Härdeman 969c829f267SDavid Härdeman /* Set TX modulation, 36kHz, 7us pulse width */ 9705b2e303fSDavid Härdeman outb(0x69, data->sbase + WBCIR_REG_SP3_IRTXMC); 971c829f267SDavid Härdeman data->txcarrier = 36000; 9725b2e303fSDavid Härdeman 9735b2e303fSDavid Härdeman /* Set invert and pin direction */ 9745b2e303fSDavid Härdeman if (invert) 9755b2e303fSDavid Härdeman outb(0x10, data->sbase + WBCIR_REG_SP3_IRCFG4); 9765b2e303fSDavid Härdeman else 9775b2e303fSDavid Härdeman outb(0x00, data->sbase + WBCIR_REG_SP3_IRCFG4); 9785b2e303fSDavid Härdeman 9795b2e303fSDavid Härdeman /* Set FIFO thresholds (RX = 8, TX = 3), reset RX/TX */ 9805b2e303fSDavid Härdeman wbcir_select_bank(data, WBCIR_BANK_0); 9815b2e303fSDavid Härdeman outb(0x97, data->sbase + WBCIR_REG_SP3_FCR); 9825b2e303fSDavid Härdeman 9835b2e303fSDavid Härdeman /* Clear AUX status bits */ 9845b2e303fSDavid Härdeman outb(0xE0, data->sbase + WBCIR_REG_SP3_ASCR); 9855b2e303fSDavid Härdeman 986c829f267SDavid Härdeman /* Clear RX state */ 987c829f267SDavid Härdeman data->rxstate = WBCIR_RXSTATE_INACTIVE; 9885b2e303fSDavid Härdeman ir_raw_event_reset(data->dev); 989e5eda7faSSean Young ir_raw_event_set_idle(data->dev, true); 9905b2e303fSDavid Härdeman 9917bfb5dc1SDavid Härdeman /* Clear TX state */ 992c829f267SDavid Härdeman if (data->txstate == WBCIR_TXSTATE_ACTIVE) { 9937bfb5dc1SDavid Härdeman kfree(data->txbuf); 9947bfb5dc1SDavid Härdeman data->txbuf = NULL; 9957bfb5dc1SDavid Härdeman data->txstate = WBCIR_TXSTATE_INACTIVE; 996c829f267SDavid Härdeman } 997c829f267SDavid Härdeman 9985b2e303fSDavid Härdeman /* Enable interrupts */ 999c829f267SDavid Härdeman wbcir_set_irqmask(data, WBCIR_IRQ_RX | WBCIR_IRQ_ERR); 10005b2e303fSDavid Härdeman } 10015b2e303fSDavid Härdeman 10025b2e303fSDavid Härdeman static int 10035b2e303fSDavid Härdeman wbcir_resume(struct pnp_dev *device) 10045b2e303fSDavid Härdeman { 10055b2e303fSDavid Härdeman struct wbcir_data *data = pnp_get_drvdata(device); 10065b2e303fSDavid Härdeman 10075b2e303fSDavid Härdeman wbcir_init_hw(data); 10085b2e303fSDavid Härdeman enable_irq(data->irq); 10091ac7fdeeSSean Young led_classdev_resume(&data->led); 10105b2e303fSDavid Härdeman 10115b2e303fSDavid Härdeman return 0; 10125b2e303fSDavid Härdeman } 10135b2e303fSDavid Härdeman 10144c62e976SGreg Kroah-Hartman static int 10155b2e303fSDavid Härdeman wbcir_probe(struct pnp_dev *device, const struct pnp_device_id *dev_id) 10165b2e303fSDavid Härdeman { 10175b2e303fSDavid Härdeman struct device *dev = &device->dev; 10185b2e303fSDavid Härdeman struct wbcir_data *data; 10195b2e303fSDavid Härdeman int err; 10205b2e303fSDavid Härdeman 10215b2e303fSDavid Härdeman if (!(pnp_port_len(device, 0) == EHFUNC_IOMEM_LEN && 10225b2e303fSDavid Härdeman pnp_port_len(device, 1) == WAKEUP_IOMEM_LEN && 10235b2e303fSDavid Härdeman pnp_port_len(device, 2) == SP_IOMEM_LEN)) { 10245b2e303fSDavid Härdeman dev_err(dev, "Invalid resources\n"); 10255b2e303fSDavid Härdeman return -ENODEV; 10265b2e303fSDavid Härdeman } 10275b2e303fSDavid Härdeman 10285b2e303fSDavid Härdeman data = kzalloc(sizeof(*data), GFP_KERNEL); 10295b2e303fSDavid Härdeman if (!data) { 10305b2e303fSDavid Härdeman err = -ENOMEM; 10315b2e303fSDavid Härdeman goto exit; 10325b2e303fSDavid Härdeman } 10335b2e303fSDavid Härdeman 10345b2e303fSDavid Härdeman pnp_set_drvdata(device, data); 10355b2e303fSDavid Härdeman 10365b2e303fSDavid Härdeman spin_lock_init(&data->spinlock); 10375b2e303fSDavid Härdeman data->ebase = pnp_port_start(device, 0); 10385b2e303fSDavid Härdeman data->wbase = pnp_port_start(device, 1); 10395b2e303fSDavid Härdeman data->sbase = pnp_port_start(device, 2); 10405b2e303fSDavid Härdeman data->irq = pnp_irq(device, 0); 10415b2e303fSDavid Härdeman 10425b2e303fSDavid Härdeman if (data->wbase == 0 || data->ebase == 0 || 10435b2e303fSDavid Härdeman data->sbase == 0 || data->irq == 0) { 10445b2e303fSDavid Härdeman err = -ENODEV; 10455b2e303fSDavid Härdeman dev_err(dev, "Invalid resources\n"); 10465b2e303fSDavid Härdeman goto exit_free_data; 10475b2e303fSDavid Härdeman } 10485b2e303fSDavid Härdeman 10495b2e303fSDavid Härdeman dev_dbg(&device->dev, "Found device " 10505b2e303fSDavid Härdeman "(w: 0x%lX, e: 0x%lX, s: 0x%lX, i: %u)\n", 10515b2e303fSDavid Härdeman data->wbase, data->ebase, data->sbase, data->irq); 10525b2e303fSDavid Härdeman 10535b2e303fSDavid Härdeman data->led.name = "cir::activity"; 10541ac7fdeeSSean Young data->led.default_trigger = "rc-feedback"; 10555b2e303fSDavid Härdeman data->led.brightness_set = wbcir_led_brightness_set; 10565b2e303fSDavid Härdeman data->led.brightness_get = wbcir_led_brightness_get; 10575b2e303fSDavid Härdeman err = led_classdev_register(&device->dev, &data->led); 10585b2e303fSDavid Härdeman if (err) 10591ac7fdeeSSean Young goto exit_free_data; 10605b2e303fSDavid Härdeman 10615b2e303fSDavid Härdeman data->dev = rc_allocate_device(); 10625b2e303fSDavid Härdeman if (!data->dev) { 10635b2e303fSDavid Härdeman err = -ENOMEM; 10645b2e303fSDavid Härdeman goto exit_unregister_led; 10655b2e303fSDavid Härdeman } 10665b2e303fSDavid Härdeman 1067d9b78695SDavid Härdeman data->dev->driver_type = RC_DRIVER_IR_RAW; 1068a66cd0b6SSean Young data->dev->driver_name = DRVNAME; 10695b2e303fSDavid Härdeman data->dev->input_name = WBCIR_NAME; 10705b2e303fSDavid Härdeman data->dev->input_phys = "wbcir/cir0"; 10715b2e303fSDavid Härdeman data->dev->input_id.bustype = BUS_HOST; 10725b2e303fSDavid Härdeman data->dev->input_id.vendor = PCI_VENDOR_ID_WINBOND; 10735b2e303fSDavid Härdeman data->dev->input_id.product = WBCIR_ID_FAMILY; 10745b2e303fSDavid Härdeman data->dev->input_id.version = WBCIR_ID_CHIP; 1075c829f267SDavid Härdeman data->dev->map_name = RC_MAP_RC6_MCE; 1076488ebc48SDavid Härdeman data->dev->s_idle = wbcir_idle_rx; 107737b0b4e9SSean Young data->dev->s_carrier_report = wbcir_set_carrier_report; 1078c829f267SDavid Härdeman data->dev->s_tx_mask = wbcir_txmask; 1079c829f267SDavid Härdeman data->dev->s_tx_carrier = wbcir_txcarrier; 1080c829f267SDavid Härdeman data->dev->tx_ir = wbcir_tx; 10815b2e303fSDavid Härdeman data->dev->priv = data; 10825b2e303fSDavid Härdeman data->dev->dev.parent = &device->dev; 10838299d628SAnton Blanchard data->dev->timeout = MS_TO_NS(100); 1084c496e716SSean Young data->dev->rx_resolution = US_TO_NS(2); 1085c003ab1bSDavid Härdeman data->dev->allowed_protos = RC_BIT_ALL; 10865b2e303fSDavid Härdeman 10879fa35204SMatthijs Kooijman err = rc_register_device(data->dev); 10889fa35204SMatthijs Kooijman if (err) 10899fa35204SMatthijs Kooijman goto exit_free_rc; 10909fa35204SMatthijs Kooijman 10919ef449c6SLuis Henriques if (!request_region(data->wbase, WAKEUP_IOMEM_LEN, DRVNAME)) { 10929ef449c6SLuis Henriques dev_err(dev, "Region 0x%lx-0x%lx already in use!\n", 10939ef449c6SLuis Henriques data->wbase, data->wbase + WAKEUP_IOMEM_LEN - 1); 10949ef449c6SLuis Henriques err = -EBUSY; 10959fa35204SMatthijs Kooijman goto exit_unregister_device; 10969ef449c6SLuis Henriques } 10979ef449c6SLuis Henriques 10989ef449c6SLuis Henriques if (!request_region(data->ebase, EHFUNC_IOMEM_LEN, DRVNAME)) { 10999ef449c6SLuis Henriques dev_err(dev, "Region 0x%lx-0x%lx already in use!\n", 11009ef449c6SLuis Henriques data->ebase, data->ebase + EHFUNC_IOMEM_LEN - 1); 11019ef449c6SLuis Henriques err = -EBUSY; 11029ef449c6SLuis Henriques goto exit_release_wbase; 11039ef449c6SLuis Henriques } 11049ef449c6SLuis Henriques 11059ef449c6SLuis Henriques if (!request_region(data->sbase, SP_IOMEM_LEN, DRVNAME)) { 11069ef449c6SLuis Henriques dev_err(dev, "Region 0x%lx-0x%lx already in use!\n", 11079ef449c6SLuis Henriques data->sbase, data->sbase + SP_IOMEM_LEN - 1); 11089ef449c6SLuis Henriques err = -EBUSY; 11099ef449c6SLuis Henriques goto exit_release_ebase; 11109ef449c6SLuis Henriques } 11119ef449c6SLuis Henriques 11129ef449c6SLuis Henriques err = request_irq(data->irq, wbcir_irq_handler, 1113b9e9f02aSMichael Opdenacker 0, DRVNAME, device); 11149ef449c6SLuis Henriques if (err) { 11159ef449c6SLuis Henriques dev_err(dev, "Failed to claim IRQ %u\n", data->irq); 11169ef449c6SLuis Henriques err = -EBUSY; 11179ef449c6SLuis Henriques goto exit_release_sbase; 11189ef449c6SLuis Henriques } 11199ef449c6SLuis Henriques 11205b2e303fSDavid Härdeman device_init_wakeup(&device->dev, 1); 11215b2e303fSDavid Härdeman 11225b2e303fSDavid Härdeman wbcir_init_hw(data); 11235b2e303fSDavid Härdeman 11245b2e303fSDavid Härdeman return 0; 11255b2e303fSDavid Härdeman 11265b2e303fSDavid Härdeman exit_release_sbase: 11275b2e303fSDavid Härdeman release_region(data->sbase, SP_IOMEM_LEN); 11285b2e303fSDavid Härdeman exit_release_ebase: 11295b2e303fSDavid Härdeman release_region(data->ebase, EHFUNC_IOMEM_LEN); 11305b2e303fSDavid Härdeman exit_release_wbase: 11315b2e303fSDavid Härdeman release_region(data->wbase, WAKEUP_IOMEM_LEN); 11329fa35204SMatthijs Kooijman exit_unregister_device: 11339fa35204SMatthijs Kooijman rc_unregister_device(data->dev); 11344ec16da7SWei Yongjun data->dev = NULL; 11359ef449c6SLuis Henriques exit_free_rc: 11369ef449c6SLuis Henriques rc_free_device(data->dev); 11379ef449c6SLuis Henriques exit_unregister_led: 11389ef449c6SLuis Henriques led_classdev_unregister(&data->led); 11395b2e303fSDavid Härdeman exit_free_data: 11405b2e303fSDavid Härdeman kfree(data); 11415b2e303fSDavid Härdeman pnp_set_drvdata(device, NULL); 11425b2e303fSDavid Härdeman exit: 11435b2e303fSDavid Härdeman return err; 11445b2e303fSDavid Härdeman } 11455b2e303fSDavid Härdeman 11464c62e976SGreg Kroah-Hartman static void 11475b2e303fSDavid Härdeman wbcir_remove(struct pnp_dev *device) 11485b2e303fSDavid Härdeman { 11495b2e303fSDavid Härdeman struct wbcir_data *data = pnp_get_drvdata(device); 11505b2e303fSDavid Härdeman 11515b2e303fSDavid Härdeman /* Disable interrupts */ 1152c829f267SDavid Härdeman wbcir_set_irqmask(data, WBCIR_IRQ_NONE); 11535b2e303fSDavid Härdeman free_irq(data->irq, device); 11545b2e303fSDavid Härdeman 11555b2e303fSDavid Härdeman /* Clear status bits NEC_REP, BUFF, MSG_END, MATCH */ 11565b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_STS, 0x17, 0x17); 11575b2e303fSDavid Härdeman 11585b2e303fSDavid Härdeman /* Clear CEIR_EN */ 11595b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_CTL, 0x00, 0x01); 11605b2e303fSDavid Härdeman 11615b2e303fSDavid Härdeman /* Clear BUFF_EN, END_EN, MATCH_EN */ 11625b2e303fSDavid Härdeman wbcir_set_bits(data->wbase + WBCIR_REG_WCEIR_EV_EN, 0x00, 0x07); 11635b2e303fSDavid Härdeman 11645b2e303fSDavid Härdeman rc_unregister_device(data->dev); 11655b2e303fSDavid Härdeman 11665b2e303fSDavid Härdeman led_classdev_unregister(&data->led); 11675b2e303fSDavid Härdeman 11685b2e303fSDavid Härdeman /* This is ok since &data->led isn't actually used */ 11695b2e303fSDavid Härdeman wbcir_led_brightness_set(&data->led, LED_OFF); 11705b2e303fSDavid Härdeman 11715b2e303fSDavid Härdeman release_region(data->wbase, WAKEUP_IOMEM_LEN); 11725b2e303fSDavid Härdeman release_region(data->ebase, EHFUNC_IOMEM_LEN); 11735b2e303fSDavid Härdeman release_region(data->sbase, SP_IOMEM_LEN); 11745b2e303fSDavid Härdeman 11755b2e303fSDavid Härdeman kfree(data); 11765b2e303fSDavid Härdeman 11775b2e303fSDavid Härdeman pnp_set_drvdata(device, NULL); 11785b2e303fSDavid Härdeman } 11795b2e303fSDavid Härdeman 11805b2e303fSDavid Härdeman static const struct pnp_device_id wbcir_ids[] = { 11815b2e303fSDavid Härdeman { "WEC1022", 0 }, 11825b2e303fSDavid Härdeman { "", 0 } 11835b2e303fSDavid Härdeman }; 11845b2e303fSDavid Härdeman MODULE_DEVICE_TABLE(pnp, wbcir_ids); 11855b2e303fSDavid Härdeman 11865b2e303fSDavid Härdeman static struct pnp_driver wbcir_driver = { 11875b2e303fSDavid Härdeman .name = WBCIR_NAME, 11885b2e303fSDavid Härdeman .id_table = wbcir_ids, 11895b2e303fSDavid Härdeman .probe = wbcir_probe, 11904c62e976SGreg Kroah-Hartman .remove = wbcir_remove, 11915b2e303fSDavid Härdeman .suspend = wbcir_suspend, 11925b2e303fSDavid Härdeman .resume = wbcir_resume, 11935b2e303fSDavid Härdeman .shutdown = wbcir_shutdown 11945b2e303fSDavid Härdeman }; 11955b2e303fSDavid Härdeman 11965b2e303fSDavid Härdeman static int __init 11975b2e303fSDavid Härdeman wbcir_init(void) 11985b2e303fSDavid Härdeman { 11995b2e303fSDavid Härdeman int ret; 12005b2e303fSDavid Härdeman 12015b2e303fSDavid Härdeman switch (protocol) { 12025b2e303fSDavid Härdeman case IR_PROTOCOL_RC5: 12035b2e303fSDavid Härdeman case IR_PROTOCOL_NEC: 12045b2e303fSDavid Härdeman case IR_PROTOCOL_RC6: 12055b2e303fSDavid Härdeman break; 12065b2e303fSDavid Härdeman default: 1207d8a10ac9SJoe Perches pr_err("Invalid power-on protocol\n"); 12085b2e303fSDavid Härdeman } 12095b2e303fSDavid Härdeman 12105b2e303fSDavid Härdeman ret = pnp_register_driver(&wbcir_driver); 12115b2e303fSDavid Härdeman if (ret) 1212d8a10ac9SJoe Perches pr_err("Unable to register driver\n"); 12135b2e303fSDavid Härdeman 12145b2e303fSDavid Härdeman return ret; 12155b2e303fSDavid Härdeman } 12165b2e303fSDavid Härdeman 12175b2e303fSDavid Härdeman static void __exit 12185b2e303fSDavid Härdeman wbcir_exit(void) 12195b2e303fSDavid Härdeman { 12205b2e303fSDavid Härdeman pnp_unregister_driver(&wbcir_driver); 12215b2e303fSDavid Härdeman } 12225b2e303fSDavid Härdeman 12235b2e303fSDavid Härdeman module_init(wbcir_init); 12245b2e303fSDavid Härdeman module_exit(wbcir_exit); 12255b2e303fSDavid Härdeman 1226d36b6910SAl Viro MODULE_AUTHOR("David Härdeman <david@hardeman.nu>"); 12275b2e303fSDavid Härdeman MODULE_DESCRIPTION("Winbond SuperI/O Consumer IR Driver"); 12285b2e303fSDavid Härdeman MODULE_LICENSE("GPL"); 1229