/*
 * 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");