1618 lines
46 KiB
C
1618 lines
46 KiB
C
/*
|
|
* LIRC plugin for the STMicroelectronics IRDA devices
|
|
*
|
|
* Copyright (C) 2004-2008 STMicroelectronics
|
|
*
|
|
* June 2004: first implementation for a 2.4 Linux kernel
|
|
* Giuseppe Cavallaro <peppe.cavallaro@st.com>
|
|
* Marc 2005: review to support pure raw mode and to adapt to Linux 2.6
|
|
* Giuseppe Cavallaro <peppe.cavallaro@st.com>
|
|
* June 2005: Change to a MODE2 receive driver and made into a generic
|
|
* ST driver.
|
|
* Carl Shaw <carl.shaw@st.com>
|
|
* July 2005: fix STB7100 MODE2 implementation and improve performance
|
|
* of STm8000 version. <carl.shaw@st.com>
|
|
* Aug 2005: Added clock autoconfiguration support. Fixed module exit code.
|
|
* Added UHF support (kernel build only).
|
|
* Carl Shaw <carl.shaw@st.com>
|
|
* Sep 2005: Added first transmit support
|
|
* Added ability to set rxpolarity register
|
|
* Angelo Castello <angelo.castello@st.com>
|
|
* and Carl Shaw <carl.shaw@st.com>
|
|
* Oct 2005: Added 7100 transmit
|
|
* Added carrier width configuration
|
|
* Carl Shaw <carl.shaw@st.com>
|
|
* Sept 2006: Update:
|
|
* fix timing issues (bugzilla 764)
|
|
* Thomas Betker <thomas.betker@siemens.com>
|
|
* allocate PIO pins in driver
|
|
* update transmit
|
|
* improve fault handling on init
|
|
* Carl Shaw <carl.shaw@st.com>
|
|
* 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 <angelo.castello@st.com>
|
|
* 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 <angelo.castello@st.com>
|
|
* Dec 2007: Added device resource management support.
|
|
* Angelo Castello <angelo.castello@st.com>
|
|
* Mar 2008: Fix UHF support and general tidy up
|
|
* Carl Shaw <carl.shaw@st.com>
|
|
* 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 <angelo.castello@st.com>
|
|
* Apr 2008: Added SCD support
|
|
* Angelo Castello <angelo.castello@st.com>
|
|
* Carl Shaw <carl.shaw@st.com>
|
|
* Feb 2009: Added PM capability
|
|
* Angelo Castello <angelo.castello@st.com>
|
|
* Francesco Virlinzi <francesco.virlinzi@st.com>
|
|
* 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 <angelo.castello@st.com>
|
|
* 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 <angelo.castello@st.com>
|
|
* Apr 2010: Code review and optimizations to reduce the driver initialization
|
|
* time. All IRB capability are selectable from menuconfig.
|
|
* Angelo Castello <angelo.castello@st.com>
|
|
* Feb 2011: Fixed IR-RX handler routine to manage the input data overrun.
|
|
* Angelo Castello <angelo.castello@st.com>
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/io.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/time.h>
|
|
#include <linux/lirc.h>
|
|
#include <linux/stm/lirc.h>
|
|
#include <linux/stm/platform.h>
|
|
#include <linux/stm/clk.h>
|
|
#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 <carl.shaw@st.com>");
|
|
MODULE_AUTHOR("Angelo Castello <angelo.castello@st.com>");
|
|
MODULE_LICENSE("GPL");
|