/* * LIRC plugin for the STMicroelectronics IRDA devices * * Copyright (C) 2004-2008 STMicroelectronics * * June 2004: first implementation for a 2.4 Linux kernel * Giuseppe Cavallaro * Marc 2005: review to support pure raw mode and to adapt to Linux 2.6 * Giuseppe Cavallaro * June 2005: Change to a MODE2 receive driver and made into a generic * ST driver. * Carl Shaw * July 2005: fix STB7100 MODE2 implementation and improve performance * of STm8000 version. * Aug 2005: Added clock autoconfiguration support. Fixed module exit code. * Added UHF support (kernel build only). * Carl Shaw * Sep 2005: Added first transmit support * Added ability to set rxpolarity register * Angelo Castello * and Carl Shaw * Oct 2005: Added 7100 transmit * Added carrier width configuration * Carl Shaw * Sept 2006: Update: * fix timing issues (bugzilla 764) * Thomas Betker * allocate PIO pins in driver * update transmit * improve fault handling on init * Carl Shaw * Oct 2007: Added both lirc-0.8.2 common interface and integrated out IRB driver * to be working for linux-2.6.23-rc7. Removed old platform support... * Sti5528 STb8000. Added new IR rx intq mechanism to reduce the amount * intq needed to identify one button. Fix TX transmission loop setting up * correctly the irq clean register. * Angelo Castello * Nov 2007: Moved here all the platform * dependences leaving clear of this task the common interface. (lirc_dev.c) * Code cleaning and optimization. * Angelo Castello * Dec 2007: Added device resource management support. * Angelo Castello * Mar 2008: Fix UHF support and general tidy up * Carl Shaw * Mar 2008: Fix insmod/rmmod actions. Added new PIO allocate mechanism * based on platform PIOs dependencies values (LIRC_PIO_ON, * LIRC_IR_RX, LIRC_UHF_RX so on ) * Angelo Castello * Apr 2008: Added SCD support * Angelo Castello * Carl Shaw * Feb 2009: Added PM capability * Angelo Castello * Francesco Virlinzi * Jul 2009: ported for kernel 2.6.30 from lirc-0.8.5 project. * Updated code to manage SCD values incoming from kconfig. * Default SCD code is for the Futarque RC. * Angelo Castello * Apr 2010: Fixed the following SCD issues: * Q1: to manage rightly border-line symbols * Q2: to add others constrains for safe initialization. * Q3: to manage potential contiguous Pulse/Space when the SCD * programming is not aligned to leading/trailing edge * Q4: to rebuild rightly the incoming signals train filtered by SCD * when detected from SCD_CODE or from SCD_ALT_CODE. * Angelo Castello * Apr 2010: Code review and optimizations to reduce the driver initialization * time. All IRB capability are selectable from menuconfig. * Angelo Castello * Feb 2011: Fixed IR-RX handler routine to manage the input data overrun. * Angelo Castello */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lirc_dev.h" #define LIRC_STM_NAME "lirc-stm" /* General debugging */ #ifdef CONFIG_LIRC_STM_DEBUG #define DPRINTK(fmt, args...) \ pr_debug(LIRC_STM_NAME ": %s: " fmt, __func__ , ## args) #else #define DPRINTK(fmt, args...) #endif /* * LiRC data */ static int ir_or_uhf_offset; static void *irb_base_address; /* IR block register base address */ struct lirc_stm_plugin_data_s { int open_count; struct stm_plat_lirc_data *p_lirc_d; }; static struct lirc_stm_plugin_data_s pd; /* IR data config */ static struct clk *lirc_sys_clock; /* LIRC subsytem symbol buffer. managed only via common lirc routines * user process read symbols from here */ struct lirc_buffer lirc_stm_rbuf; /* * IRB IR/UHF common configurations */ #define IRB_CM_REG(x) (irb_base_address + x) #define IRB_RX_RATE_COMMON IRB_CM_REG(0x64) /* sample freq divisor*/ #define IRB_RX_CLOCK_SEL IRB_CM_REG(0x70) /* clock select */ #define IRB_RX_CLOCK_SEL_STATUS IRB_CM_REG(0x74) /* clock status */ #define IRB_RX_NOISE_SUPP_WIDTH IRB_CM_REG(0x9C) #define RX_CLEAR_IRQ(x) writel((x), IRB_RX_INT_CLEAR) #define RX_WORDS_IN_FIFO_OR_OVERRUN() (readl(IRB_RX_STATUS) & 0xff04) #define LIRC_STM_MINOR 0 #define LIRC_STM_MAX_SYMBOLS 100 #define LIRC_STM_BUFSIZE (LIRC_STM_MAX_SYMBOLS*sizeof(lirc_t)) /* Bit settings */ #define LIRC_STM_IS_OVERRUN 0x04 #define LIRC_STM_CLEAR_IRQ 0x38 #define LIRC_STM_CLEAR_OVERRUN 0x04 /* IRQ set: Enable full FIFO 1 -> bit 3; * Enable overrun IRQ 1 -> bit 2; * Enable last symbol IRQ 1 -> bit 1: * Enable RX interrupt 1 -> bit 0; */ #define LIRC_STM_ENABLE_IRQ 0x0f /* * IRB IR/UHF receiver configurations */ #define IRB_RX_REG(x) (irb_base_address + x + ir_or_uhf_offset) #define IRB_RX_ON IRB_RX_REG(0x40) /* pulse time capture */ #define IRB_RX_SYS IRB_RX_REG(0X44) /* sym period capture */ #define IRB_RX_INT_EN IRB_RX_REG(0x48) /* IRQ enable (R/W) */ #define IRB_RX_INT_STATUS IRB_RX_REG(0x4C) /* IRQ status (R/W) */ #define IRB_RX_EN IRB_RX_REG(0x50) /* Receive enablei */ #define IRB_MAX_SYM_PERIOD IRB_RX_REG(0x54) /* max sym value */ #define IRB_RX_INT_CLEAR IRB_RX_REG(0x58) /* overrun status */ #define IRB_RX_STATUS IRB_RX_REG(0x6C) /* receive status */ #define IRB_RX_NOISE_SUPPR IRB_RX_REG(0x5C) /* noise suppression */ #define IRB_RX_POLARITY_INV IRB_RX_REG(0x68) /* polarity inverter */ /* RX graphical example to better understand the difference between ST IR block * output and standard definition used by LIRC (and most of the world!) * * mark mark * |-IRB_RX_ON-| |-IRB_RX_ON-| * ___ ___ ___ ___ ___ ___ _ * | | | | | | | | | | | | | * | | | | | | space 0 | | | | | | space 1 | * _____| |__| |__| |____________________________| |__| |__| |_____________| * * |--------------- IRB_RX_SYS -------------|------ IRB_RX_SYS -------| * * |------------- encoding bit 0 -----------|---- encoding bit 1 -----| * * ST hardware returns mark (IRB_RX_ON) and total symbol time (IRB_RX_SYS), so * convert to standard mark/space we have to calculate space=(IRB_RX_SYS - mark) * The mark time represents the amount of time the carrier (usually 36-40kHz) * is detected. * * TX is the same but with opposite calculation. * * The above examples shows Pulse Width Modulation encoding where bit 0 is * represented by space>mark. */ struct lirc_stm_rx_data_s { /* timing fine control */ int symbol_mult; int symbol_div; int pulse_mult; int pulse_div; /* data configuration */ unsigned int sampling_freq_div; lirc_t *rbuf; unsigned int off_rbuf; unsigned int sumUs; int error; struct timeval sync; }; static struct lirc_stm_rx_data_s rx; /* RX data config */ /* * IRB UHF-SCD filter configuration */ #ifdef CONFIG_LIRC_STM_UHF_SCD #define IRB_SCD_CFG IRB_CM_REG(0x200) /* config */ #define IRB_SCD_STA IRB_CM_REG(0x204) /* status */ #define IRB_SCD_CODE IRB_CM_REG(0x208) /* normal code */ #define IRB_SCD_CODE_LEN IRB_CM_REG(0x20c) /* num code symbols */ #define IRB_SCD_SYMB_MIN_TIME IRB_CM_REG(0x210) /* min symbol time */ #define IRB_SCD_SYMB_MAX_TIME IRB_CM_REG(0x214) /* max symbol time */ #define IRB_SCD_SYMB_NOM_TIME IRB_CM_REG(0x218) /* nom symbol time */ #define IRB_SCD_PRESCALAR IRB_CM_REG(0x21c) /* prescalar */ #define IRB_SCD_INT_EN IRB_CM_REG(0x220) /* interrupt enable */ #define IRB_SCD_INT_CLR IRB_CM_REG(0x224) /* interrupt clear */ #define IRB_SCD_INT_STA IRB_CM_REG(0x22c) /* interrupt status */ #define IRB_SCD_NOISE_RECOV IRB_CM_REG(0x228) /* noise recovery */ #define IRB_SCD_ALT_CODE IRB_CM_REG(0x230) /* alternative code */ /* Start Code Detect (SCD) graphical example to understand how to configure * propely the code, code length and nominal time values based on Remote Control * signals train example. * * __________________ ________ _______ ___ ___ ___ * | | | | | | | | | | | | * | | | | | | | | | | | | * _____| |________| |______| |__| |__| |__| |___...... * * |---- 1000us ----|- 429 -|- 521 -|- 500-|.... * |-- 500 -|- 500 -|- 500 -|- 500 -| units in us for SCD code. * |-- 1 -|- 1 -|- 0 -|- 1 -| SCD code Ob1101. * * The nominal symbol duration is 500us, code length 4 and code Ob1101. */ #define LIRC_STM_SCD_MAX_SYMBOLS 32 #define LIRC_STM_SCD_BUFSIZE ((LIRC_STM_SCD_MAX_SYMBOLS/2+1) * \ sizeof(lirc_t)) #define LIRC_STM_SCD_TOLERANCE 25 /* rx.scd_flags fields: * scd normal 1 -> bit 0 * scd altenative 1 -> bit 1 * scd normal = alternative 1 -> bit 2 * scd enabled 1 -> bit 3 */ #define SCD_NORMAL 0x01 #define SCD_ALTERNATIVE 0x02 #define SCD_NOR_EQ_ALT 0x04 #define SCD_ENABLED 0x08 #define SCD_ALT_MASK (SCD_NORMAL|SCD_ALTERNATIVE|SCD_ENABLED) static struct lirc_scd_s scd_s = { .code = CONFIG_LIRC_STM_UHF_SCD_CODE, .alt_code = CONFIG_LIRC_STM_UHF_SCD_ALTCODE, .nomtime = CONFIG_LIRC_STM_UHF_SCD_NTIME, .noiserecov = 0, }; /* SCD support */ struct lirc_stm_scd_data_s { struct lirc_scd_s value; int flags; lirc_t *prefix_code; lirc_t *prefix_altcode; unsigned int off_prefix_code; unsigned int off_prefix_altcode; }; static struct lirc_stm_scd_data_s scd; /* SCD data config */ #endif /* CONFIG_LIRC_STM_UHF_SCD */ /* * IRB TX transmitter configuration */ #ifdef CONFIG_LIRC_STM_TX #define IRB_TX_REG(x) (irb_base_address + x) /* TX ... */ #define IRB_TX_PRESCALAR IRB_TX_REG(0x00) /* clock prescalar */ #define IRB_TX_SUBCARRIER IRB_TX_REG(0x04) /* subcarrier freq */ #define IRB_TX_SYMPERIOD IRB_TX_REG(0x08) /* symbol period */ #define IRB_TX_ONTIME IRB_TX_REG(0x0c) /* symbol pulse time */ #define IRB_TX_INT_ENABLE IRB_TX_REG(0x10) /* irq enable */ #define IRB_TX_INT_STATUS IRB_TX_REG(0x14) /* irq status */ #define IRB_TX_ENABLE IRB_TX_REG(0x18) /* enable */ #define IRB_TX_INT_CLEAR IRB_TX_REG(0x1c) /* interrupt clear */ #define IRB_TX_SUBCARRIER_WIDTH IRB_TX_REG(0x20) /* subcarrier freq */ #define IRB_TX_STATUS IRB_TX_REG(0x24) /* status */ #define TX_INT_PENDING 0x01 #define TX_INT_UNDERRUN 0x02 #define TX_FIFO_DEPTH 7 #define TX_FIFO_USED ((readl(IRB_TX_STATUS) >> 8) & 0x07) #define TX_CARRIER_FREQ 38000 /* 38KHz */ struct lirc_stm_tx_data_s { wait_queue_head_t waitq; /* timing fine control */ unsigned int mult; unsigned int div; /* transmit buffer */ lirc_t *wbuf; unsigned int off_wbuf; }; static struct lirc_stm_tx_data_s tx; /* TX data config */ static inline unsigned int lirc_stm_time_to_cycles(unsigned int microsecondtime) { /* convert a microsecond time to the nearest number of subcarrier clock * cycles */ microsecondtime *= tx.mult; microsecondtime /= tx.div; return microsecondtime * TX_CARRIER_FREQ / 1000000; } static ssize_t lirc_stm_write(struct file *file, const char *buf, size_t n, loff_t *ppos) { int i; size_t rdn = n / sizeof(size_t); unsigned int symbol, mark; int fifosyms; if (!pd.p_lirc_d->txenabled) { pr_err(LIRC_STM_NAME ": write operation unsupported.\n"); return -ENOTSUPP; } if (n % sizeof(lirc_t)) return -EINVAL; if (tx.off_wbuf != 0 && (file->f_flags & O_NONBLOCK)) return -EAGAIN; /* Wait for transmit to become free... */ if (wait_event_interruptible(tx.waitq, tx.off_wbuf == 0)) return -ERESTARTSYS; /* Prevent against buffer overflow... */ if (rdn > LIRC_STM_MAX_SYMBOLS) rdn = LIRC_STM_MAX_SYMBOLS; n -= rdn * sizeof(size_t); if (copy_from_user((char *)tx.wbuf, buf, rdn * sizeof(size_t))) return -EFAULT; if (n == 0) tx.wbuf[rdn - 1] = 0xFFFF; /* load the first words into the FIFO */ fifosyms = rdn; if (fifosyms > TX_FIFO_DEPTH) fifosyms = TX_FIFO_DEPTH; for (i = 0; i < fifosyms; i++) { mark = tx.wbuf[(i * 2)]; symbol = mark + tx.wbuf[(i * 2) + 1]; DPRINTK("TX raw m %d s %d ", mark, symbol); mark = lirc_stm_time_to_cycles(mark) + 1; symbol = lirc_stm_time_to_cycles(symbol) + 2; DPRINTK("cal m %d s %d\n", mark, symbol); tx.off_wbuf++; writel(mark, IRB_TX_ONTIME); writel(symbol, IRB_TX_SYMPERIOD); } /* enable the transmit */ writel(0x07, IRB_TX_INT_ENABLE); writel(0x01, IRB_TX_ENABLE); DPRINTK("TX enabled\n"); return n; } static void lirc_stm_tx_calc_clocks(unsigned int clockfreq, unsigned int subwidthpercent) { /* We know the system base clock and the required IR carrier frequency * We now want a divisor of the system base clock that gives the * nearest integer multiple of the carrier frequency */ const unsigned int clkratio = clockfreq / TX_CARRIER_FREQ; unsigned int scalar, n; int delta; unsigned int diffbest = clockfreq, nbest = 0, scalarbest = 0; unsigned int nmin = clkratio / 255; if ((nmin & 0x01) == 1) nmin++; for (n = nmin; n < clkratio; n += 2) { scalar = clkratio / n; if ((scalar & 0x01) == 0 && scalar != 0) { delta = clockfreq - (scalar * TX_CARRIER_FREQ * n); if (delta < 0) delta *= -1; if (delta < diffbest) { /* better set of parameters ? */ diffbest = delta; nbest = n; scalarbest = scalar; } if (delta == 0) /* an exact multiple */ break; } } scalarbest /= 2; nbest *= 2; DPRINTK("TX clock scalar = %d\n", scalarbest); DPRINTK("TX subcarrier scalar = %d\n", nbest); /* Set the registers now */ writel(scalarbest, IRB_TX_PRESCALAR); writel(nbest, IRB_TX_SUBCARRIER); writel(nbest * subwidthpercent / 100, IRB_TX_SUBCARRIER_WIDTH); /* Now calculate timing to subcarrier cycles factors which compensate * for any remaining difference between our clock ratios and real times * in microseconds */ if (diffbest == 0) { /* no adjustment required - our clock is running at the required * speed */ tx.mult = 1; tx.div = 1; } else { /* adjustment is required */ delta = scalarbest * TX_CARRIER_FREQ * nbest; tx.mult = delta / (clockfreq / 10000); if (delta < clockfreq) {/* our clock is running too fast */ DPRINTK("clock running slow at %d\n", delta); tx.div = tx.mult; tx.mult = 10000; } else { /* our clock is running too slow */ DPRINTK("clock running fast at %d\n", delta); tx.div = 10000; } } DPRINTK("TX fine adjustment mult = %d\n", tx.mult); DPRINTK("TX fine adjustment div = %d\n", tx.div); init_waitqueue_head(&tx.waitq); } static void lirc_stm_tx_interrupt(int irq, void *dev_id) { unsigned int symbol, mark, done = 0; unsigned int tx_irq_status = readl(IRB_TX_INT_STATUS); if ((tx_irq_status & TX_INT_PENDING) != TX_INT_PENDING) return; while (done == 0) { if (unlikely((readl(IRB_TX_INT_STATUS) & TX_INT_UNDERRUN) == TX_INT_UNDERRUN)) { /* There has been an underrun - clear flag, switch * off transmitter and signal possible exit */ pr_err(LIRC_STM_NAME ": transmit underrun!\n"); writel(0x02, IRB_TX_INT_CLEAR); writel(0x00, IRB_TX_INT_ENABLE); writel(0x00, IRB_TX_ENABLE); done = 1; DPRINTK("disabled TX\n"); wake_up_interruptible(&tx.waitq); } else { int fifoslots = TX_FIFO_USED; while (fifoslots < TX_FIFO_DEPTH) { mark = tx.wbuf[(tx.off_wbuf * 2)]; symbol = mark + tx.wbuf[(tx.off_wbuf * 2) + 1]; DPRINTK("TX raw m %d s %d ", mark, symbol); mark = lirc_stm_time_to_cycles(mark) + 1; symbol = lirc_stm_time_to_cycles(symbol) + 2; DPRINTK("cal m %d s %d\n", mark, symbol); if ((tx.wbuf[(tx.off_wbuf * 2)] == 0xFFFF) || (tx.wbuf[(tx.off_wbuf * 2) + 1] == 0xFFFF)) { /* Dump out last symbol */ writel(mark * 2, IRB_TX_SYMPERIOD); writel(mark, IRB_TX_ONTIME); DPRINTK("TX end m %d s %d\n", mark, mark * 2); /* flush transmit fifo */ while (TX_FIFO_USED != 0) { }; writel(0, IRB_TX_SYMPERIOD); writel(0, IRB_TX_ONTIME); /* spin until TX fifo empty */ while (TX_FIFO_USED != 0) { }; /* disable tx interrupts and * transmitter */ writel(0x07, IRB_TX_INT_CLEAR); writel(0x00, IRB_TX_INT_ENABLE); writel(0x00, IRB_TX_ENABLE); DPRINTK("TX disabled\n"); tx.off_wbuf = 0; fifoslots = 999; done = 1; } else { writel(symbol, IRB_TX_SYMPERIOD); writel(mark, IRB_TX_ONTIME); DPRINTK("Nm %d s %d\n", mark, symbol); tx.off_wbuf++; fifoslots = TX_FIFO_USED; } } } } } #define lirc_stm_tx_kzalloc() { \ tx.wbuf = (lirc_t *) devm_kzalloc(dev, \ LIRC_STM_BUFSIZE, \ GFP_KERNEL); \ if (!tx.wbuf) \ return -ENOMEM; \ } #else #define lirc_stm_write NULL #define lirc_stm_tx_kzalloc() #define lirc_stm_tx_interrupt(irq, dev_id) #define lirc_stm_tx_calc_clocks(clockfreq, subwidthpercent) #endif /* CONFIG_LIRC_STM_TX */ #ifdef CONFIG_LIRC_STM_UHF_SCD static void lirc_stm_scd_prefix_symbols(void) { lirc_t *lscd; unsigned int *offscd; if (!scd.flags) return; DPRINTK("SCD_STA(0x%x)\n", scd.flags & (SCD_ALTERNATIVE | SCD_NORMAL)); /* Here need to take care the following SCD constrain: * If there is only one start code to be detected, the registers for * normal and alternative start codes has been programmed with * identical values. The low significate bits of scd_flags means: * 1 : SCD_ENABLED * 1 : SCD_NOR_EQ_ALT * 1 : SCD_ALTERNATIVE * 1 : SCD_NORMAL * 11xx : normal and alternative are the same due to constrain. * 1001 : normal detected * 1011 : alternative detected */ if ((scd.flags & SCD_NOR_EQ_ALT) || (scd.flags ^ SCD_ALT_MASK)) { /* normal code detected */ lscd = scd.prefix_code; offscd = &scd.off_prefix_code; } else { /* alternative code detected */ lscd = scd.prefix_altcode; offscd = &scd.off_prefix_altcode; } /* manage potential contiguous Pulse/Space when the SCD programming * is not aligned to leading/trailing edge */ if ((lscd[*offscd - 1] & PULSE_BIT) == (rx.rbuf[0] & PULSE_BIT)) { rx.rbuf[0] += (lscd[*offscd - 1] & ~PULSE_BIT); (*offscd)--; } lirc_buffer_write_n(&lirc_stm_rbuf, (unsigned char *)lscd, *offscd); return; } static int lirc_stm_scd_set(char enable) { if (!scd.flags) return -1; if (enable) { writel(0x01, IRB_SCD_INT_EN); writel(0x01, IRB_SCD_INT_CLR); writel(0x01, IRB_SCD_CFG); } else { writel(0x00, IRB_SCD_INT_EN); writel(0x00, IRB_SCD_CFG); } DPRINTK("SCD %s\n", (enable ? "enabled" : "disabled")); return 0; } static int lirc_stm_scd_config(unsigned long clk) { unsigned int nrec, ival, scwidth; unsigned int prescalar, tolerance; unsigned int space, mark; int i, j, k; lirc_t *lscd; unsigned int *offscd; unsigned int code, codelen, alt_codelen, curr_codelen; struct lirc_scd_s last_scd = scd.value; if (!pd.p_lirc_d->rxuhfmode) { pr_err(LIRC_STM_NAME ": SCD not available in IR-RX mode. Not armed\n"); return -ENOTSUPP; } scd.value = scd_s; scd.flags = 0; scd.off_prefix_code = 0; scd.off_prefix_altcode = 0; codelen = fls(scd.value.code); if ((scd.value.code == 0) || (codelen > LIRC_STM_SCD_MAX_SYMBOLS) || (codelen < 1)) { pr_err(LIRC_STM_NAME ": SCD invalid start code. Not armed\n"); scd.value = last_scd; return -EINVAL; } alt_codelen = fls(scd.value.alt_code); if (alt_codelen > 0) { if ((scd.value.alt_code == 0) || (alt_codelen > LIRC_STM_SCD_MAX_SYMBOLS) || (alt_codelen < codelen) || (scd.value.code == (scd.value.alt_code >> (alt_codelen - codelen)))) { pr_err(LIRC_STM_NAME ": SCD invalid alternative code. Not armed\n"); alt_codelen = 0; } } /* SCD disable */ writel(0x00, IRB_SCD_CFG); /* Configure pre-scalar clock first to give 1MHz sampling */ prescalar = clk / 1000000; writel(prescalar, IRB_SCD_PRESCALAR); /* pre-loading of not filtered SCD codes and * preparing data for tolerance calculation. */ lscd = scd.prefix_code; offscd = &scd.off_prefix_code; code = scd.value.code; curr_codelen = codelen; for (k = 0; k < 2; k++) { space = 0; mark = 0; j = 0; for (i = (curr_codelen - 1); i >= 0; i--) { j = 1 << (i - 1); if (code & (1 << i)) { mark += scd.value.nomtime; if (!(code & j) || (i == 0)) { lscd[*offscd] = mark | PULSE_BIT; DPRINTK("SCD mark rscd[%d](%d)\n", *offscd, lscd[*offscd] & ~PULSE_BIT); (*offscd)++; mark = 0; } } else { space += scd.value.nomtime; if ((code & j) || (i == 0)) { lscd[*offscd] = space; DPRINTK("SCD space rscd[%d](%d)\n", *offscd, lscd[*offscd]); (*offscd)++; space = 0; } } } lscd = scd.prefix_altcode; offscd = &scd.off_prefix_altcode; code = scd.value.alt_code; curr_codelen = alt_codelen; } /* normaly 20% of nomtine is enough as tolerance */ tolerance = scd.value.nomtime * LIRC_STM_SCD_TOLERANCE / 100; DPRINTK("SCD prescalar %d nominal %d tolerance %d\n", prescalar, scd.value.nomtime, tolerance); /* Sanity check to garantee all hw constrains must be satisfied */ if ((tolerance > ((scd.value.nomtime >> 1) - scd.value.nomtime)) || (scd.value.nomtime < ((scd.value.nomtime >> 1) + tolerance))) { tolerance = scd.value.nomtime * LIRC_STM_SCD_TOLERANCE / 100; DPRINTK("SCD tolerance out of range. default %d\n", tolerance); } if (tolerance < 4) { tolerance = scd.value.nomtime * LIRC_STM_SCD_TOLERANCE / 100; DPRINTK("SCD tolerance too close. default %d\n", tolerance); } /* Program in scd codes and lengths */ writel(scd.value.code, IRB_SCD_CODE); /* Some cuts of chips have broken SCD, so check... */ i = readl(IRB_SCD_CODE); if (i != scd.value.code) { pr_err(LIRC_STM_NAME ": SCD hardware fault. Broken silicon?\n"); pr_err(LIRC_STM_NAME ": SCD wrote code 0x%08x read 0x%08x. Not armed\n", scd.value.code, i); scd.value = last_scd; return -ENODEV; } /* Program scd min time, max time and nominal time */ writel(scd.value.nomtime - tolerance, IRB_SCD_SYMB_MIN_TIME); writel(scd.value.nomtime, IRB_SCD_SYMB_NOM_TIME); writel(scd.value.nomtime + tolerance, IRB_SCD_SYMB_MAX_TIME); /* Program in noise recovery (if required) */ if (scd.value.noiserecov) { nrec = 1 | (1 << 16); /* primary and alt code enable */ i = 1 << (codelen - 1); ival = scd.value.code & i; if (ival) nrec |= 2; scwidth = 0; while (i > 0 && ((scd.value.code & i) == ival)) { scwidth++; i >>= 1; } nrec |= (scwidth << 8); i = 1 << (alt_codelen - 1); ival = scd.value.alt_code & i; if (ival) nrec |= 1 << 17; scwidth = 0; while (i > 0 && ((scd.value.alt_code & i) == ival)) { scwidth++; i >>= 1; } nrec |= (scwidth << 24); DPRINTK("SCD noise recovery 0x%08x\n", nrec); writel(nrec, IRB_SCD_NOISE_RECOV); } /* Set supported flag */ pr_info(LIRC_STM_NAME ": SCD normal code 0x%x codelen 0x%x nomtime 0x%x armed\n", scd.value.code, codelen, scd.value.nomtime); if (alt_codelen > 0) { pr_info(LIRC_STM_NAME ": SCD alternative code 0x%x codelen 0x%x armed\n", scd.value.alt_code, alt_codelen); writel(scd.value.alt_code, IRB_SCD_ALT_CODE); } else { writel(scd.value.code, IRB_SCD_ALT_CODE); alt_codelen = codelen; scd.flags |= SCD_NOR_EQ_ALT; } /* SCD codelen range [00001,...,11111,00000] where 00000 = 32 symbols */ writel(((alt_codelen & 0x1f) << 8) | (codelen & 0x1f), IRB_SCD_CODE_LEN); scd.flags |= SCD_ENABLED; return 0; } #define lirc_stm_scd_kzalloc() { \ scd.prefix_code = (lirc_t *) devm_kzalloc(dev, \ LIRC_STM_SCD_BUFSIZE, \ GFP_KERNEL); \ if (!scd.prefix_code) \ return -ENOMEM; \ \ scd.prefix_altcode = (lirc_t *) devm_kzalloc(dev, \ LIRC_STM_SCD_BUFSIZE, \ GFP_KERNEL); \ if (!scd.prefix_altcode) \ return -ENOMEM; \ } #define lirc_stm_scd_reactivate() { \ if (scd.flags && lastSymbol) { \ /* reset the SCD flags */ \ scd.flags &= (SCD_ENABLED | SCD_NOR_EQ_ALT); \ writel(0x01, IRB_SCD_INT_EN); \ writel(0x01, IRB_SCD_CFG); \ } \ } #define lirc_stm_scd_set_flags() { \ if (scd.flags) { \ /* SCD status catched only the first time */ \ if (!(scd.flags & SCD_NORMAL)) { \ scd.flags |= readb(IRB_SCD_STA); \ writel(0x01, IRB_SCD_INT_CLR); \ writel(0x00, IRB_SCD_INT_EN); \ } \ } \ } #define lirc_stm_scd_cases() { \ case LIRC_SCD_CONFIGURE: \ if (copy_from_user(&scd_s, (unsigned long *)arg,\ sizeof(scd_s))) \ return -EFAULT; \ retval = lirc_stm_scd_config( \ clk_get_rate(lirc_sys_clock)); \ break; \ case LIRC_SCD_ENABLE: \ case LIRC_SCD_DISABLE: \ retval = lirc_stm_scd_set( \ cmd == LIRC_SCD_ENABLE); \ break; \ case LIRC_SCD_STATUS: \ retval = put_user(scd.flags & SCD_ENABLED, \ (unsigned long *)arg); \ break; \ case LIRC_SCD_GET_VALUE: \ if (copy_to_user((unsigned long *)arg, \ &scd.value, \ sizeof(scd_s))) \ return -EFAULT; \ break; \ } #define lirc_stm_scd_restart() { \ writel(0x02, IRB_SCD_CFG); \ writel(0x01, IRB_SCD_CFG); \ } #else #define lirc_stm_scd_prefix_symbols() #define lirc_stm_scd_set(enable) #define lirc_stm_scd_config(clk) #define lirc_stm_scd_reactivate() #define lirc_stm_scd_set_flags() #define lirc_stm_scd_restart() #define lirc_stm_scd_kzalloc() #define lirc_stm_scd_cases() { \ case LIRC_SCD_CONFIGURE: case LIRC_SCD_ENABLE: \ case LIRC_SCD_DISABLE: case LIRC_SCD_STATUS: \ msg = "LIRC_SCD_xxx"; \ goto _not_supported; \ } #endif /* CONFIG_LIRC_STM_UHF_SCD */ static inline void lirc_stm_rx_reset_data(void) { rx.error = 0; rx.off_rbuf = 0; rx.sumUs = 0; memset(rx.rbuf, 0, LIRC_STM_BUFSIZE); } static void lirc_stm_rx_interrupt(int irq, void *dev_id) { unsigned int symbol, mark = 0; int lastSymbol = 0, clear_irq = 1; lirc_stm_scd_set_flags(); while (RX_WORDS_IN_FIFO_OR_OVERRUN()) { /* discard the entire collection in case of errors! */ if (unlikely(readl(IRB_RX_INT_STATUS) & LIRC_STM_IS_OVERRUN)) { pr_info(LIRC_STM_NAME ": IR RX overrun\n"); writel(LIRC_STM_CLEAR_OVERRUN, IRB_RX_INT_CLEAR); rx.error = 1; } /* get the symbol times from FIFO */ symbol = (readl(IRB_RX_SYS)); mark = (readl(IRB_RX_ON)); if (clear_irq) { /* Clear the interrupt * and leave only the overrun irq enabled */ RX_CLEAR_IRQ(LIRC_STM_CLEAR_IRQ); writel(0x07, IRB_RX_INT_EN); clear_irq = 0; } if (rx.off_rbuf >= LIRC_STM_MAX_SYMBOLS) { pr_info(LIRC_STM_NAME ": IR too many symbols (max %d)\n", LIRC_STM_MAX_SYMBOLS); rx.error = 1; } /* now handle the data depending on error condition */ if (rx.error) { /* Try again */ lirc_stm_rx_reset_data(); continue; } if (symbol == 0xFFFF) lastSymbol = 1; else lastSymbol = 0; /* A sequence seems to start with a constant time symbol (1us) * pulse and symbol time length, both of 1us. We ignore this. */ if ((mark > 2) && (symbol > 1)) { /* Make fine adjustments to timings */ symbol -= mark; /* to get space timing */ symbol *= rx.symbol_mult; symbol /= rx.symbol_div; mark *= rx.pulse_mult; mark /= rx.pulse_div; /* The ST hardware returns the pulse time and the * period, which is the pulse time + space time, so * we need to subtract the pulse time from the period * to get the space time. * For a pulse in LIRC MODE2, we need to set the * PULSE_BIT ON */ rx.rbuf[(rx.off_rbuf * 2)] = mark | PULSE_BIT; rx.rbuf[(rx.off_rbuf * 2) + 1] = symbol; rx.sumUs += mark + symbol; rx.off_rbuf++; if (lastSymbol) { /* move the entire collection into user * buffer if enough space, drop otherwise * (perhaps too crude a recovery?) * We need to have enough space to move SCD * symbols yet (max 32). */ if (likely(lirc_buffer_available (&lirc_stm_rbuf) >= ((2 * rx.off_rbuf) + 32))) { struct timeval now; lirc_t syncSpace; DPRINTK("W symbols = %d\n", rx.off_rbuf); /* Calculate and write the leading * space. All spaces and pulses * together sum up to the * microseconds elapsed since we * sent the previous block of data */ do_gettimeofday(&now); if (now.tv_sec - rx.sync.tv_sec < 0) syncSpace = 0; else if (now.tv_sec - rx.sync.tv_sec > PULSE_MASK / 1000000) syncSpace = PULSE_MASK; else { syncSpace = (now.tv_sec - rx.sync.tv_sec) * 1000000 + (now.tv_usec - rx.sync.tv_usec); syncSpace -= (rx.sumUs - rx.rbuf[((rx.off_rbuf - 1) * 2) + 1]); if (syncSpace < 0) syncSpace = 0; else if (syncSpace > PULSE_MASK) syncSpace = PULSE_MASK; } _lirc_buffer_write_1 (&lirc_stm_rbuf, (unsigned char *) &syncSpace); rx.sync = now; /* Now write the SCD filtered-out * pulse / space pairs */ lirc_stm_scd_prefix_symbols(); /* Now write the pulse / space pairs * EXCEPT FOR THE LAST SPACE * The last space value should be * 0xFFFF to denote a timeout */ lirc_buffer_write_n (&lirc_stm_rbuf, (unsigned char *)rx.rbuf, (2 * rx.off_rbuf) - 1); wake_up_interruptible (&lirc_stm_rbuf.wait_poll); } else pr_err(LIRC_STM_NAME ": not enough space " "in user buffer\n"); lirc_stm_rx_reset_data(); } } } /* while */ RX_CLEAR_IRQ(LIRC_STM_CLEAR_IRQ | 0x02); writel(LIRC_STM_ENABLE_IRQ, IRB_RX_INT_EN); lirc_stm_scd_reactivate(); } static irqreturn_t lirc_stm_interrupt(int irq, void *dev_id) { lirc_stm_tx_interrupt(irq, dev_id); lirc_stm_rx_interrupt(irq, dev_id); return IRQ_HANDLED; } static int lirc_stm_open_inc(void *data) { struct lirc_stm_plugin_data_s *lpd = (struct lirc_stm_plugin_data_s *)data; DPRINTK("entering open\n"); if (lpd->open_count++ == 0) { unsigned long flags; DPRINTK("plugin enabled\n"); local_irq_save(flags); /* enable interrupts and receiver */ writel(LIRC_STM_ENABLE_IRQ, IRB_RX_INT_EN); writel(0x01, IRB_RX_EN); lirc_stm_rx_reset_data(); local_irq_restore(flags); lirc_stm_scd_set(1); } else DPRINTK("plugin already open\n"); return 0; } static void lirc_stm_rx_flush(void) { lirc_stm_scd_set(0); /* Disable receiver */ writel(0x00, IRB_RX_EN); /* Disable interrupt */ writel(0x20, IRB_RX_INT_EN); /* clean the buffer */ lirc_stm_rx_reset_data(); } /* ** Called by lirc_dev as a last action on a real close */ static void lirc_stm_close_dec(void *data) { struct lirc_stm_plugin_data_s *lpd = (struct lirc_stm_plugin_data_s *)data; DPRINTK("entering close\n"); /* The last close disable the receiver */ if (--lpd->open_count == 0) lirc_stm_rx_flush(); } static int lirc_stm_ioctl(struct inode *node, struct file *filep, unsigned int cmd, unsigned long arg) { int retval = 0; unsigned long value = 0; char *msg = ""; switch (cmd) { case LIRC_GET_FEATURES: /* * Our driver can receive in mode2 and send in pulse mode. * TODO: We can generate our own carrier freq * (LIRC_CAN_SET_SEND_CARRIER) and also change duty * cycle (LIRC_CAN_SET_SEND_DUTY_CYCLE) */ DPRINTK("LIRC_GET_FEATURES return REC_MODE2|SEND_PULSE\n"); retval = put_user(LIRC_CAN_REC_MODE2 | LIRC_CAN_SEND_PULSE, (unsigned long *)arg); break; case LIRC_GET_REC_MODE: DPRINTK("LIRC_GET_REC_MODE return LIRC_MODE_MODE2\n"); retval = put_user(LIRC_MODE_MODE2, (unsigned long *)arg); break; case LIRC_SET_REC_MODE: retval = get_user(value, (unsigned long *)arg); DPRINTK("LIRC_SET_REC_MODE to 0x%lx\n", value); if (value != LIRC_MODE_MODE2) retval = -ENOSYS; break; case LIRC_GET_SEND_MODE: DPRINTK("LIRC_GET_SEND_MODE return LIRC_MODE_PULSE\n"); retval = put_user(LIRC_MODE_PULSE, (unsigned long *)arg); break; case LIRC_SET_SEND_MODE: retval = get_user(value, (unsigned long *)arg); DPRINTK("LIRC_SET_SEND_MODE to 0x%lx\n", value); /* only LIRC_MODE_PULSE supported */ if (value != LIRC_MODE_PULSE) return (-ENOSYS); break; lirc_stm_scd_cases(); case LIRC_GET_REC_RESOLUTION: msg = "LIRC_GET_REC_RESOLUTION"; goto _not_supported; case LIRC_GET_REC_CARRIER: msg = "LIRC_GET_REC_CARRIER"; goto _not_supported; case LIRC_SET_REC_CARRIER: msg = "LIRC_SET_REC_CARRIER"; goto _not_supported; case LIRC_GET_SEND_CARRIER: msg = "LIRC_GET_SEND_CARRIER"; goto _not_supported; case LIRC_SET_SEND_CARRIER: msg = "LIRC_SET_SEND_CARRIER"; goto _not_supported; case LIRC_GET_REC_DUTY_CYCLE: msg = "LIRC_GET_REC_DUTY_CYCLE"; goto _not_supported; case LIRC_SET_REC_DUTY_CYCLE: msg = "LIRC_SET_REC_DUTY_CYCLE"; goto _not_supported; case LIRC_GET_SEND_DUTY_CYCLE: msg = "LIRC_GET_SEND_DUTY_CYCLE"; goto _not_supported; case LIRC_SET_SEND_DUTY_CYCLE: msg = "LIRC_SET_SEND_DUTY_CYCLE"; goto _not_supported; case LIRC_GET_LENGTH: msg = "LIRC_GET_LENGTH"; goto _not_supported; default: msg = "???"; _not_supported: DPRINTK("command %s (0x%x) not supported\n", msg, cmd); retval = -ENOIOCTLCMD; } return retval; } static void lirc_stm_rx_calc_clocks(struct platform_device *pdev, unsigned long baseclock) { struct stm_plat_lirc_data *lirc_pdata = NULL; unsigned int rx_max_symbol_per; lirc_pdata = pdev->dev.platform_data; if (lirc_pdata->irbclkdiv == 0) { /* Auto-calculate clock divisor */ int freqdiff; rx.sampling_freq_div = baseclock / 10000000; /* Work out the timing adjustment factors */ freqdiff = baseclock - (rx.sampling_freq_div * 10000000); /* freqdiff contains the difference between our clock and a * true 10 MHz clock which the IR block wants */ if (freqdiff == 0) { /* no adjustment required - our clock is running at the * required speed */ rx.symbol_mult = 1; rx.pulse_mult = 1; rx.symbol_div = 1; rx.pulse_div = 1; } else { /* adjustment is required */ rx.symbol_mult = baseclock / (10000 * rx.sampling_freq_div); if (freqdiff > 0) { /* our clock is running too fast */ rx.pulse_mult = 1000; rx.pulse_div = rx.symbol_mult; rx.symbol_mult = rx.pulse_mult; rx.symbol_div = rx.pulse_div; } else { /* our clock is running too slow */ rx.symbol_div = 1000; rx.pulse_mult = rx.symbol_mult; rx.pulse_div = 1000; } } } else { rx.sampling_freq_div = (lirc_pdata->irbclkdiv); rx.symbol_mult = (lirc_pdata->irbperiodmult); rx.symbol_div = (lirc_pdata->irbperioddiv); } writel(rx.sampling_freq_div, IRB_RX_RATE_COMMON); DPRINTK("IR base clock is %lu\n", baseclock); DPRINTK("IR clock divisor is %d\n", rx.sampling_freq_div); DPRINTK("IR clock divisor readlack is %d\n", readl(IRB_RX_RATE_COMMON)); DPRINTK("IR period mult factor is %d\n", rx.symbol_mult); DPRINTK("IR period divisor factor is %d\n", rx.symbol_div); DPRINTK("IR pulse mult factor is %d\n", rx.pulse_mult); DPRINTK("IR pulse divisor factor is %d\n", rx.pulse_div); /* maximum symbol period. * Symbol periods longer than this will generate * an interrupt and terminate a command */ if ((lirc_pdata->irbrxmaxperiod) != 0) rx_max_symbol_per = (lirc_pdata->irbrxmaxperiod) * rx.symbol_mult / rx.symbol_div; else rx_max_symbol_per = 0; DPRINTK("RX Maximum symbol period register 0x%x\n", rx_max_symbol_per); writel(rx_max_symbol_per, IRB_MAX_SYM_PERIOD); } static int lirc_stm_hardware_init(struct platform_device *pdev) { struct stm_plat_lirc_data *lirc_pdata; unsigned int scwidth; int baseclock; /* set up the hardware version dependent setup parameters */ lirc_pdata = pdev->dev.platform_data; /* Set the polarity inversion bit to the correct state */ writel(lirc_pdata->rxpolarity, IRB_RX_POLARITY_INV); /* Get or calculate the clock and timing adjustment values. * We can auto-calculate these in some cases */ baseclock = clk_get_rate(lirc_sys_clock) / lirc_pdata->sysclkdiv; lirc_stm_rx_calc_clocks(pdev, baseclock); /* Set up the transmit timings */ if (lirc_pdata->subcarrwidth != 0) scwidth = lirc_pdata->subcarrwidth; else scwidth = 50; if (scwidth > 100) scwidth = 50; DPRINTK("subcarrier width set to %d %%\n", scwidth); lirc_stm_tx_calc_clocks(baseclock, scwidth); lirc_stm_scd_config(baseclock); return 0; } static int lirc_stm_remove(struct platform_device *pdev) { DPRINTK("lirc_stm_remove called\n"); clk_disable(lirc_sys_clock); return 0; } static int lirc_stm_probe(struct platform_device *pdev) { int ret = -EINVAL; struct device *dev = &pdev->dev; struct resource *res; int irb_irq, irb_irq_wup; irb_irq = irb_irq_wup = 0; if (pdev->name == NULL) { pr_err(LIRC_STM_NAME ": probe failed. Check kernel SoC config.\n"); return -ENODEV; } lirc_sys_clock = clk_get(NULL, "comms_clk"); if (!lirc_sys_clock) { pr_err(LIRC_STM_NAME " system clock not found\n"); return -ENODEV; } clk_enable(lirc_sys_clock); pr_info(LIRC_STM_NAME ": probe found data for platform device %s\n", pdev->name); pd.p_lirc_d = pdev->dev.platform_data; irb_irq = platform_get_irq(pdev, 0); if (irb_irq < 0) { pr_err(LIRC_STM_NAME ": IRQ configuration not found\n"); return -ENODEV; } if (devm_request_irq(dev, irb_irq, lirc_stm_interrupt, IRQF_DISABLED, LIRC_STM_NAME, (void *)&pd) < 0) { pr_err(LIRC_STM_NAME ": IRQ %d register failed\n", irb_irq); return -EIO; } /* Enable wakeup interrupt if any */ irb_irq_wup = platform_get_irq(pdev, 1); if (irb_irq_wup >= 0) { if (devm_request_irq (dev, irb_irq_wup, lirc_stm_interrupt, IRQF_DISABLED, LIRC_STM_NAME, (void *)&pd) < 0) { pr_err(LIRC_STM_NAME ": wakeup IRQ %d register failed\n", irb_irq_wup); return -EIO; } disable_irq(irb_irq_wup); enable_irq_wake(irb_irq_wup); pr_info(LIRC_STM_NAME ": the driver has wakeup IRQ %d\n", irb_irq_wup); } /* Configure for ir or uhf. rxuhfmode==1 is UHF */ if (pd.p_lirc_d->rxuhfmode) ir_or_uhf_offset = 0x40; else ir_or_uhf_offset = 0x00; /* Hardware IR block setup - the PIO ports should already be set up * in the board-dependent configuration. We need to remap the * IR registers into kernel space - we do this in one chunk */ if ((rx.rbuf = (lirc_t *) devm_kzalloc(dev, LIRC_STM_BUFSIZE, GFP_KERNEL)) == NULL) return -ENOMEM; lirc_stm_scd_kzalloc(); lirc_stm_tx_kzalloc(); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { pr_err(LIRC_STM_NAME ": IO MEM not found\n"); return -ENODEV; } if (!devm_request_mem_region(dev, res->start, res->end - res->start, LIRC_STM_NAME)) { pr_err(LIRC_STM_NAME ": request_mem_region failed\n"); return -EBUSY; } irb_base_address = devm_ioremap_nocache(dev, res->start, res->end - res->start); if (irb_base_address == NULL) { pr_err(LIRC_STM_NAME ": ioremap failed\n"); ret = -ENOMEM; } else { DPRINTK("ioremapped register block at 0x%x\n", res->start); DPRINTK("ioremapped to 0x%x\n", (unsigned int)irb_base_address); pr_info(LIRC_STM_NAME ": STM LIRC plugin using IRQ %d" " in %s mode\n", irb_irq, pd.p_lirc_d->rxuhfmode ? "UHF" : "IR"); if (!devm_stm_pad_claim(dev, pd.p_lirc_d->pads, LIRC_STM_NAME)) { pr_err(LIRC_STM_NAME ": Failed to claim " "pads!\n"); return -EIO; } /* enable signal detection */ ret = lirc_stm_hardware_init(pdev); device_set_wakeup_capable(&pdev->dev, 1); /* by default the device is on */ pm_runtime_set_active(&pdev->dev); pm_suspend_ignore_children(&pdev->dev, 1); pm_runtime_enable(&pdev->dev); } return ret; } #ifdef CONFIG_PM static void lirc_stm_rx_restore(void) { lirc_stm_scd_set(1); /* enable interrupts and receiver */ writel(LIRC_STM_ENABLE_IRQ, IRB_RX_INT_EN); writel(0x01, IRB_RX_EN); /* clean the buffer */ lirc_stm_rx_reset_data(); } static int lirc_stm_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct stm_plat_lirc_data *lirc_pdata = NULL; lirc_pdata = pdev->dev.platform_data; if (device_may_wakeup(&pdev->dev)) { /* need for the resuming phase */ lirc_stm_rx_flush(); lirc_stm_rx_calc_clocks(pdev, clk_get_rate(lirc_sys_clock)); lirc_stm_scd_config(clk_get_rate(lirc_sys_clock)); lirc_stm_rx_restore(); lirc_stm_scd_restart(); } else clk_disable(lirc_sys_clock); return 0; } static int lirc_stm_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); if (!device_may_wakeup(&pdev->dev)) clk_enable(lirc_sys_clock); lirc_stm_hardware_init(pdev); lirc_stm_rx_restore(); lirc_stm_scd_restart(); return 0; } static int lirc_stm_freeze(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct stm_plat_lirc_data *lirc_pdata = NULL; lirc_pdata = pdev->dev.platform_data; /* disable IR RX/TX interrupts plus clear status */ writel(0x00, IRB_RX_EN); writel(0xff, IRB_RX_INT_CLEAR); /* disabling LIRC irq request */ /* flush LIRC plugin data */ lirc_stm_rx_reset_data(); clk_disable(lirc_sys_clock); return 0; } static int lirc_stm_restore(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); clk_enable(lirc_sys_clock); lirc_stm_hardware_init(pdev); /* there was I really open device ? */ if (pd.open_count > 0) { /* enable interrupts and receiver */ writel(LIRC_STM_ENABLE_IRQ, IRB_RX_INT_EN); writel(0x01, IRB_RX_EN); } return 0; } static struct dev_pm_ops stm_lirc_pm_ops = { .suspend = lirc_stm_suspend, /* on standby/memstandby */ .resume = lirc_stm_resume, /* resume from standby/memstandby */ .freeze = lirc_stm_freeze, /* hibernation */ .restore = lirc_stm_restore, /* resume from hibernation */ .runtime_suspend = lirc_stm_suspend, .runtime_resume = lirc_stm_resume, }; #endif static struct platform_driver lirc_device_driver = { .driver = { .name = LIRC_STM_NAME, .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &stm_lirc_pm_ops, #endif }, .probe = lirc_stm_probe, .remove = lirc_stm_remove, }; static struct file_operations lirc_stm_fops = { .write = lirc_stm_write, .ioctl = lirc_stm_ioctl, }; static struct lirc_driver lirc_stm_driver = { .name = LIRC_STM_NAME, .minor = LIRC_STM_MINOR, .code_length = 1, .sample_rate = 0, /* plugin can receive raw pulse and space timings for each symbol */ .features = LIRC_CAN_REC_MODE2, /* plugin private data */ .data = (void *)&pd, /* buffer handled by upper layer */ .add_to_buf = NULL, #ifndef LIRC_REMOVE_DURING_EXPORT .get_queue = NULL, #endif .set_use_inc = lirc_stm_open_inc, .set_use_dec = lirc_stm_close_dec, .fops = &lirc_stm_fops, .rbuf = &lirc_stm_rbuf, .owner = THIS_MODULE, }; static int __init lirc_stm_init(void) { DPRINTK("initializing the IR receiver...\n"); if (platform_driver_register(&lirc_device_driver)) { pr_err(LIRC_STM_NAME ": platform driver register failed\n"); goto out_err; } if (!pd.p_lirc_d) { pr_err(LIRC_STM_NAME ": missed out hardware probing." "Check kernel SoC config.\n"); goto out_err; } /* inform the top level driver that we use our own user buffer */ if (lirc_buffer_init(&lirc_stm_rbuf, sizeof(lirc_t), (2 * LIRC_STM_MAX_SYMBOLS))) { pr_err(LIRC_STM_NAME ": buffer init failed\n"); platform_driver_unregister(&lirc_device_driver); goto out_err; } request_module("lirc_dev"); if (lirc_register_driver(&lirc_stm_driver) < 0) { pr_err(LIRC_STM_NAME ": driver registration failed\n"); lirc_buffer_free(&lirc_stm_rbuf); platform_driver_unregister(&lirc_device_driver); goto out_err; } pr_info("STMicroelectronics LIRC driver initialized.\n"); return 0; out_err: pr_err("STMicroelectronics LIRC driver not initialized.\n"); return -EINVAL; } void __exit lirc_stm_release(void) { DPRINTK("removing STM lirc driver\n"); /* unregister the driver */ lirc_stm_rx_flush(); platform_driver_unregister(&lirc_device_driver); /* unplug the lirc stm driver */ if (lirc_unregister_driver(LIRC_STM_MINOR) < 0) pr_err(LIRC_STM_NAME ": driver unregister failed\n"); /* free buffer */ lirc_buffer_free(&lirc_stm_rbuf); pr_info("STMicroelectronics LIRC driver removed\n"); } module_init(lirc_stm_init); module_exit(lirc_stm_release); MODULE_DESCRIPTION ("Linux InfraRed receiver driver for STMicroelectronics platforms"); MODULE_AUTHOR("Carl Shaw "); MODULE_AUTHOR("Angelo Castello "); MODULE_LICENSE("GPL");