diff --git a/drivers/staging/media/lirc/Kconfig b/drivers/staging/media/lirc/Kconfig index 526ec0f..8d7971d 100644 --- a/drivers/staging/media/lirc/Kconfig +++ b/drivers/staging/media/lirc/Kconfig @@ -69,6 +69,21 @@ config LIRC_TTUSBIR help Driver for the Technotrend USB IR Receiver +config LIRC_GPIO + tristate "LIRC GPIO receiver/transmitter" + depends on LIRC && GPIOLIB + help + Driver for GPIOLIB enabled gpio chip. Module setup for and tested + on Allwinner A10 Gpio SOC. + change/redefine LIRC_GPIO_ID_STRING to the correct gpiochip label + +config LIRC_SUNXI_RAW + tristate "LIRC for sunxi CIR interface" + depends on LIRC + help + Driver for Allwinner's native CIR interface, passing + data (almost) unfiltered to LIRC device + config LIRC_ZILOG tristate "Zilog/Hauppauge IR Transmitter" depends on LIRC && I2C diff --git a/drivers/staging/media/lirc/Makefile b/drivers/staging/media/lirc/Makefile index d76b0fa..65af5e6 100644 --- a/drivers/staging/media/lirc/Makefile +++ b/drivers/staging/media/lirc/Makefile @@ -11,4 +11,6 @@ obj-$(CONFIG_LIRC_SASEM) += lirc_sasem.o obj-$(CONFIG_LIRC_SERIAL) += lirc_serial.o obj-$(CONFIG_LIRC_SIR) += lirc_sir.o obj-$(CONFIG_LIRC_TTUSBIR) += lirc_ttusbir.o +obj-$(CONFIG_LIRC_GPIO) += lirc_gpio.o +obj-$(CONFIG_LIRC_SUNXI_RAW) += sunxi-lirc.o obj-$(CONFIG_LIRC_ZILOG) += lirc_zilog.o diff --git a/drivers/staging/media/lirc/lirc_gpio.c b/drivers/staging/media/lirc/lirc_gpio.c new file mode 100644 index 0000000..10f5129 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_gpio.c @@ -0,0 +1,968 @@ +/* + * lirc_gpio.c + * + * lirc_gpio - Device driver that records pulse- and pause-lengths + * (space-lengths) (just like the lirc_serial driver does) + * between GPIO interrupt events. Tested on a Cubieboard with Allwinner A10 + * However, everything relies on the gpiolib.c module, so there is a good + * chance it will also run on other platforms. + * Lots of code has been taken from the lirc_rpi module, who in turn took a + * lot of code from the lirc_serial module, + * so I would like say thanks to the authors. + * + * Copyright (C) 2013 Matthias H��lling , + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include <../drivers/gpio/gpio-sunxi.h> + +#define LIRC_DRIVER_NAME "lirc_gpio" +/* this may have to be adapted for different platforms */ +#define LIRC_GPIO_ID_STRING "A1X_GPIO" +#define RBUF_LEN 256 +#define LIRC_TRANSMITTER_LATENCY 256 + +#ifndef MAX_UDELAY_MS +#define MAX_UDELAY_US 5000 +#else +#define MAX_UDELAY_US (MAX_UDELAY_MS*1000) +#endif + +#define RX_OFFSET_GPIOCHIP gpio_in_pin - gpiochip->base +#define TX_OFFSET_GPIOCHIP gpio_out_pin - gpiochip->base + +#define dprintk(fmt, args...) \ +do { \ +if (debug) \ +printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \ +fmt, ## args); \ +} while (0) + +/* module parameters */ + +/* set the default GPIO input pins, 3 channels */ +static int gpio_in_pin = 0; +/* -1 = auto, 0 = active high, 1 = active low */ +static int sense = -1; +static struct timeval lasttv = { 0, 0 }; +static spinlock_t lock; + +/* set the default GPIO output pin, 4 channels */ +static int gpio_out_pin = 0; +/* enable debugging messages */ +static int debug; +/* use softcarrier by default */ +static int softcarrier = 1; +/* 0 = do not invert output, 1 = invert output */ +static int invert = 0; + +/* is the device open, so interrupt must be changed if pins are changed */ +static int device_open = 0; + +struct gpio_chip *gpiochip=NULL; +struct irq_chip *irqchip=NULL; +struct irq_data *irqdata=NULL; + +/* forward declarations */ +static long send_pulse(unsigned long length); +static void send_space(long length); +static void lirc_gpio_exit(void); + +static struct platform_device *lirc_gpio_dev; +static struct lirc_buffer rbuf; + +/* initialized/set in init_timing_params() */ +static unsigned int freq = 38000; +static unsigned int duty_cycle = 50; +static unsigned long period; +static unsigned long pulse_width; +static unsigned long space_width; + + +/* stuff for TX pin */ + +static void safe_udelay(unsigned long usecs) +{ + while (usecs > MAX_UDELAY_US) { + udelay(MAX_UDELAY_US); + usecs -= MAX_UDELAY_US; + } + udelay(usecs); +} + +static int init_timing_params(unsigned int new_duty_cycle, + unsigned int new_freq) +{ + /* + * period, pulse/space width are kept with 8 binary places - + * IE multiplied by 256. + */ + if (256 * 1000000L / new_freq * new_duty_cycle / 100 <= + LIRC_TRANSMITTER_LATENCY) + return -EINVAL; + if (256 * 1000000L / new_freq * (100 - new_duty_cycle) / 100 <= + LIRC_TRANSMITTER_LATENCY) + return -EINVAL; + duty_cycle = new_duty_cycle; + freq = new_freq; + period = 256 * 1000000L / freq; + pulse_width = period * duty_cycle / 100; + space_width = period - pulse_width; + /*printk(KERN_INFO "in init_timing_params, freq=%d pulse=%ld, " + "space=%ld\n", freq, pulse_width, space_width); */ + return 0; +} + +static long send_pulse_softcarrier(unsigned long length) +{ + int flag; + unsigned long actual, target, d; + + length <<= 8; + + actual = 0; target = 0; flag = 0; + while (actual < length) { + if (flag) { + gpiochip->set(gpiochip, TX_OFFSET_GPIOCHIP, invert); + target += space_width; + } else { + gpiochip->set(gpiochip, TX_OFFSET_GPIOCHIP, !invert); + target += pulse_width; + } + d = (target - actual - + LIRC_TRANSMITTER_LATENCY + 128) >> 8; + /* + * Note - we've checked in ioctl that the pulse/space + * widths are big enough so that d is > 0 + */ + udelay(d); + actual += (d << 8) + LIRC_TRANSMITTER_LATENCY; + flag = !flag; + } + return (actual-length) >> 8; +} + +static long send_pulse(unsigned long length) +{ + if (length <= 0) + return 0; + + if (softcarrier) { + return send_pulse_softcarrier(length); + } else { + gpiochip->set(gpiochip, TX_OFFSET_GPIOCHIP, !invert); + safe_udelay(length); + return 0; + } +} + + +static void send_space(long length) +{ + gpiochip->set(gpiochip, TX_OFFSET_GPIOCHIP, invert); + if (length <= 0) + return; + safe_udelay(length); +} + +/* end of TX stuff */ + + + +/* RX stuff: Handle interrupt and write vals to lirc buffer */ + +static void rbwrite(int l) +{ + if (lirc_buffer_full(&rbuf)) { + /* no new signals will be accepted */ + dprintk("Buffer overrun\n"); + return; + } + lirc_buffer_write(&rbuf, (void *)&l); +} + +static void frbwrite(int l) +{ + /* simple noise filter */ + static int pulse, space; + static unsigned int ptr; + + if (ptr > 0 && (l & PULSE_BIT)) { + pulse += l & PULSE_MASK; + if (pulse > 250) { + rbwrite(space); + rbwrite(pulse | PULSE_BIT); + ptr = 0; + pulse = 0; + } + return; + } + if (!(l & PULSE_BIT)) { + if (ptr == 0) { + if (l > 20000) { + space = l; + ptr++; + return; + } + } else { + if (l > 20000) { + space += pulse; + if (space > PULSE_MASK) + space = PULSE_MASK; + space += l; + if (space > PULSE_MASK) + space = PULSE_MASK; + pulse = 0; + return; + } + rbwrite(space); + rbwrite(pulse | PULSE_BIT); + ptr = 0; + pulse = 0; + } + } + rbwrite(l); +} + +static irqreturn_t irq_handler(int i, void *blah, struct pt_regs *regs) +{ + struct timeval tv; + long deltv; + int data; + int signal; + + /* use the GPIO signal level */ + signal = gpiochip->get(gpiochip, RX_OFFSET_GPIOCHIP); + + /* unmask the irq */ + irqchip->irq_unmask(irqdata); + + if (sense != -1) { + /* get current time */ + do_gettimeofday(&tv); + + /* calc time since last interrupt in microseconds */ + deltv = tv.tv_sec-lasttv.tv_sec; + if (tv.tv_sec < lasttv.tv_sec || + (tv.tv_sec == lasttv.tv_sec && + tv.tv_usec < lasttv.tv_usec)) { + printk(KERN_WARNING LIRC_DRIVER_NAME + ": AIEEEE: your clock just jumped backwards\n"); + printk(KERN_WARNING LIRC_DRIVER_NAME + ": %d %d %lx %lx %lx %lx\n", signal, sense, + tv.tv_sec, lasttv.tv_sec, + tv.tv_usec, lasttv.tv_usec); + data = PULSE_MASK; + } else if (deltv > 15) { + data = PULSE_MASK; /* really long time */ + if (!(signal^sense)) { + /* sanity check */ + printk(KERN_WARNING LIRC_DRIVER_NAME + ": AIEEEE: %d %d %lx %lx %lx %lx\n", + signal, sense, tv.tv_sec, lasttv.tv_sec, + tv.tv_usec, lasttv.tv_usec); + /* + * detecting pulse while this + * MUST be a space! + */ + sense = sense ? 0 : 1; + } + } else { + data = (int) (deltv*1000000 + + (tv.tv_usec - lasttv.tv_usec)); + } + frbwrite(signal^sense ? data : (data|PULSE_BIT)); + lasttv = tv; + wake_up_interruptible(&rbuf.wait_poll); + } + + return IRQ_HANDLED; +} + +/* end of rx stuff */ + + +/* setup pins, rx, tx, interrupts, active low/high.... */ + +static void set_sense(void) +{ + int i,nlow, nhigh; + if (gpio_in_pin==0) { + return; // no rx + } + if (sense == -1) { + /* wait 1/10 sec for the power supply */ + msleep(100); + + /* + * probe 9 times every 0.04s, collect "votes" for + * active high/low + */ + nlow = 0; + nhigh = 0; + for (i = 0; i < 9; i++) { + if (gpiochip->get(gpiochip, RX_OFFSET_GPIOCHIP)) + nlow++; + else + nhigh++; + msleep(40); + } + sense = (nlow >= nhigh ? 1 : 0); + printk(KERN_INFO LIRC_DRIVER_NAME + ": auto-detected active %s receiver on GPIO pin %d\n", + sense ? "low" : "high", gpio_in_pin); + } else { + printk(KERN_INFO LIRC_DRIVER_NAME + ": manually/previously detected using active %s receiver on GPIO pin %d\n", + sense ? "low" : "high", gpio_in_pin); + } + +} + + + +static int setup_tx(int new_out_pin) +{ + int ret; + user_gpio_set_t* pinstate; + struct sunxi_gpio_chip* sgpio = container_of(gpiochip,struct sunxi_gpio_chip,chip); + dprintk("addresses: gpiochip: %lx, sgpio: %lx",(unsigned long int) gpiochip,(unsigned long int) sgpio); + if (gpio_out_pin==new_out_pin) + return 0; //do not set up, pin not changed + + if (gpio_out_pin!=0) { //we had tx pin setup. Free it so others can use it! + dprintk(": trying to free old out pin index %d: %s\n",gpio_out_pin,gpiochip->names[TX_OFFSET_GPIOCHIP]); + gpio_free(gpio_out_pin); + } + gpio_out_pin=new_out_pin; + if (gpio_out_pin==0) { + return 0; // do not set up, TX disabled + } + dprintk(": trying to claim new out pin index %d: %s\n",gpio_out_pin,gpiochip->names[TX_OFFSET_GPIOCHIP]); + ret = gpio_request(gpio_out_pin,LIRC_DRIVER_NAME "ir/out"); + if (ret){ + printk(KERN_ALERT LIRC_DRIVER_NAME + ": cant claim gpio pin %d with code %d\n", gpio_out_pin,ret); + ret = -ENODEV; + goto exit_disable_tx; + } + gpiochip->direction_output(gpiochip, TX_OFFSET_GPIOCHIP, 1); + gpiochip->set(gpiochip, TX_OFFSET_GPIOCHIP, invert); + pinstate=kzalloc(sizeof(user_gpio_set_t),GFP_KERNEL); + + //dprintk("pin:address %lx, pin_name: %s, pin handler: %d",(unsigned long int) &(sgpio->data[TX_OFFSET_GPIOCHIP]), + // sgpio->data[TX_OFFSET_GPIOCHIP].pin_name,sgpio->data[TX_OFFSET_GPIOCHIP].gpio_handler); + ret=gpio_get_one_pin_status(sgpio->data[TX_OFFSET_GPIOCHIP].gpio_handler, pinstate, sgpio->data[TX_OFFSET_GPIOCHIP].pin_name, true); + if(pinstate && !ret) { + pr_info("Maximum load on '%s' is %d mA\n", gpiochip->names[TX_OFFSET_GPIOCHIP], 10+10*pinstate->drv_level); + kfree(pinstate); + } + else + printk(KERN_ALERT LIRC_DRIVER_NAME ": something might have gone wrong, return from pin status query: %d",ret); + return 0; // successfully set up + +exit_disable_tx: + // cannot claim new pin + gpio_out_pin = 0; // disable tx + return ret; +} + +static int setup_rx(int new_in_pin) +{ + int ret,irq; + if (gpio_in_pin==new_in_pin) + return 0; //do not set up, pin not changed + + if (gpio_in_pin!=0) { //we had rx pin setup. Free it so others can use it! + dprintk(": trying to free old in pin index %d: %s\n",gpio_in_pin,gpiochip->names[RX_OFFSET_GPIOCHIP]); + gpio_free(gpio_in_pin); + irqchip=NULL; + irqdata=NULL; + } + gpio_in_pin=new_in_pin; + if (gpio_in_pin==0) { + return 0; // do not set up, RX disabled + } + dprintk(": trying to claim new in pin index %d: %s\n",gpio_in_pin,gpiochip->names[RX_OFFSET_GPIOCHIP]); + ret = gpio_request(gpio_in_pin, LIRC_DRIVER_NAME " ir/in"); + if (ret) { + printk(KERN_ALERT LIRC_DRIVER_NAME + ": cant claim gpio pin %d with code %d\n", gpio_in_pin,ret); + ret = -ENODEV; + goto exit_disable_rx; + } + + gpiochip->direction_input(gpiochip, RX_OFFSET_GPIOCHIP); + /* try to setup interrupt data */ + irq = gpiochip->to_irq(gpiochip, RX_OFFSET_GPIOCHIP); + dprintk("to_irq %d for pin %d\n", irq, gpio_in_pin); + irqdata = irq_get_irq_data(irq); + + if (irqdata && irqdata->chip) { + irqchip = irqdata->chip; + } else { + ret = -ENODEV; + goto exit_gpio_free_in_pin; + } + + set_sense(); + + return 0; //successfully set up + +exit_gpio_free_in_pin: + // interrupt set up failed, so free pin + gpio_free(gpio_in_pin); +exit_disable_rx: + // could not claim new pin + gpio_in_pin=0; // disable rx + return ret; + +} + +/* end of pin setup */ + +/* get right gpio chip */ + +static int is_right_chip(struct gpio_chip *chip, const void *data) +{ + if (strcmp(data, chip->label) == 0) + return 1; + return 0; +} + + +static int set_gpiochip(void) +{ + gpiochip = gpiochip_find(LIRC_GPIO_ID_STRING, is_right_chip); + + if (!gpiochip) + return -ENODEV; + + return 0; + +} + +/* end of find gpio chip */ + + + +/* lirc device stuff */ + +/* called when the character device is opened + timing params initialized and interrupts activated */ +static int set_use_inc(void *data) +{ + int result; + unsigned long flags; + + init_timing_params(duty_cycle, freq); + /* initialize pulse/space widths */ + + //initialize timestamp, would not be needed if no RX + do_gettimeofday(&lasttv); + device_open++; + + if (gpio_in_pin!=0) { // entered if RX used + /* try to set all interrupts to same handler, should work */ + result = request_irq(gpiochip->to_irq(gpiochip, RX_OFFSET_GPIOCHIP), + (irq_handler_t) irq_handler, 0, + LIRC_DRIVER_NAME, (void*) 0); + + switch (result) { + case -EBUSY: + printk(KERN_ERR LIRC_DRIVER_NAME + ": IRQ %d is busy\n", + gpiochip->to_irq(gpiochip, RX_OFFSET_GPIOCHIP)); + return -EBUSY; + case -EINVAL: + printk(KERN_ERR LIRC_DRIVER_NAME + ": Bad irq number or handler\n"); + return -EINVAL; + default: + dprintk("Interrupt %d obtained\n", + gpiochip->to_irq(gpiochip, RX_OFFSET_GPIOCHIP)); + break; + }; + spin_lock_irqsave(&lock, flags); + + /* GPIO Pin Falling/Rising Edge Detect Enable */ + irqchip->irq_set_type(irqdata, + IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING); + + /* unmask the irq for active channel, only */ + irqchip->irq_unmask(irqdata); + spin_unlock_irqrestore(&lock, flags); + + } + + return 0; +} + +/* called when character device is closed */ +static void set_use_dec(void *data) +{ + unsigned long flags; + device_open--; + + if(!irqchip) + return; + + /* GPIO Pin Falling/Rising Edge Detect Disable */ + spin_lock_irqsave(&lock, flags); + irqchip->irq_set_type(irqdata, 0); + irqchip->irq_mask(irqdata); + spin_unlock_irqrestore(&lock, flags); + + free_irq(gpiochip->to_irq(gpiochip, RX_OFFSET_GPIOCHIP), (void *) 0); + device_open--; + dprintk("freed IRQ %d\n", gpiochip->to_irq(gpiochip, RX_OFFSET_GPIOCHIP)); + +} + +/* lirc to tx */ +static ssize_t lirc_write(struct file *file, const char *buf, + size_t n, loff_t *ppos) +{ + int i, count; + unsigned long flags; + + long delta = 0; + int *wbuf; + if (!gpio_out_pin) { + return -ENODEV; + } + count = n / sizeof(int); + if (n % sizeof(int) || count % 2 == 0) + return -EINVAL; + wbuf = memdup_user(buf, n); + if (IS_ERR(wbuf)) + return PTR_ERR(wbuf); + spin_lock_irqsave(&lock, flags); + dprintk("lirc_write called, offset %d",TX_OFFSET_GPIOCHIP); + for (i = 0; i < count; i++) { + if (i%2) + send_space(wbuf[i] - delta); + else + delta = send_pulse(wbuf[i]); + } + gpiochip->set(gpiochip, TX_OFFSET_GPIOCHIP, invert); + spin_unlock_irqrestore(&lock, flags); + if (count>11) { + dprintk("lirc_write sent %d pulses: no10: %d, no11: %d\n",count,wbuf[10],wbuf[11]); + } + kfree(wbuf); + return n; +} + +/* interpret lirc commands */ +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + int result; + __u32 value; + + switch (cmd) { + case LIRC_GET_SEND_MODE: + return -ENOIOCTLCMD; + break; + + case LIRC_SET_SEND_MODE: + result = get_user(value, (__u32 *) arg); + if (result) + return result; + /* only LIRC_MODE_PULSE supported */ + if (value != LIRC_MODE_PULSE) + return -ENOSYS; + dprintk("Sending stuff on lirc"); + break; + + case LIRC_GET_LENGTH: + return -ENOSYS; + break; + + case LIRC_SET_SEND_DUTY_CYCLE: + result = get_user(value, (__u32 *) arg); + if (result) + return result; + if (value <= 0 || value > 100) + return -EINVAL; + dprintk("SET_SEND_DUTY_CYCLE to %d \n", value); + return init_timing_params(value, freq); + break; + + case LIRC_SET_SEND_CARRIER: + result = get_user(value, (__u32 *) arg); + if (result) + return result; + if (value > 500000 || value < 20000) + return -EINVAL; + dprintk("SET_SEND_CARRIER to %d \n",value); + return init_timing_params(duty_cycle, value); + break; + + default: + return lirc_dev_fop_ioctl(filep, cmd, arg); + } + return 0; +} + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .write = lirc_write, + .unlocked_ioctl = lirc_ioctl, + .read = lirc_dev_fop_read, // this and the rest is default + .poll = lirc_dev_fop_poll, + .open = lirc_dev_fop_open, + .release = lirc_dev_fop_close, + .llseek = no_llseek, +}; + +static struct lirc_driver driver = { + .name = LIRC_DRIVER_NAME, + .minor = -1, // assing automatically + .code_length = 1, + .sample_rate = 0, + .data = NULL, + .add_to_buf = NULL, + .rbuf = &rbuf, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .dev = NULL, + .owner = THIS_MODULE, +}; + +/* end of lirc device/driver stuff */ + +/* now comes THIS driver, above is lirc */ +static struct platform_driver lirc_gpio_driver = { + .driver = { + .name = LIRC_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + + + +/* stuff for sysfs*/ + +static DEFINE_MUTEX(sysfs_lock); + + + +static ssize_t lirc_txpin_show(struct class *class, struct class_attribute *attr, char *buf) +{ + ssize_t status; + mutex_lock(&sysfs_lock); + status = sprintf(buf,"%d\n",gpio_out_pin); + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t lirc_txpin_store(struct class *class, struct class_attribute *attr, const char* buf, size_t size) +{ + int new_pin; + ssize_t status; + mutex_lock(&sysfs_lock); + sscanf(buf,"%d",&new_pin); + status = setup_tx(new_pin) ? : size; + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t lirc_rxpin_show(struct class *class, struct class_attribute *attr, char *buf) +{ + ssize_t status; + mutex_lock(&sysfs_lock); + status = sprintf(buf,"%d\n",gpio_in_pin); + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t lirc_rxpin_store(struct class *class, struct class_attribute *attr, const char* buf, size_t size) +{ + int new_pin; + ssize_t status; + mutex_lock(&sysfs_lock); + sscanf(buf,"%d",&new_pin); + status = setup_rx(new_pin) ? : size; + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t lirc_softcarrier_show(struct class *class, struct class_attribute *attr, char *buf) +{ + ssize_t status; + mutex_lock(&sysfs_lock); + status = sprintf(buf,"%d\n",softcarrier); + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t lirc_softcarrier_store(struct class *class, struct class_attribute *attr, const char* buf, size_t size) +{ + int try_value; + ssize_t status=size; + mutex_lock(&sysfs_lock); + sscanf(buf,"%d",&try_value); + if ((try_value==0) || (try_value==1)) { + softcarrier=try_value; + } + else + status = -EINVAL; + mutex_unlock(&sysfs_lock); + return status; +} +static ssize_t lirc_invert_show(struct class *class, struct class_attribute *attr, char *buf) +{ + ssize_t status; + mutex_lock(&sysfs_lock); + status = sprintf(buf,"%d\n",invert); + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t lirc_invert_store(struct class *class, struct class_attribute *attr, const char* buf, size_t size) +{ + int try_value; + ssize_t status=size; + mutex_lock(&sysfs_lock); + sscanf(buf,"%d",&try_value); + if ((try_value==0) || (try_value==1)) { + invert=try_value; + } + else + status = -EINVAL; + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t lirc_sense_show(struct class *class, struct class_attribute *attr, char *buf) +{ + ssize_t status; + mutex_lock(&sysfs_lock); + status = sprintf(buf,"%d\n",sense); + mutex_unlock(&sysfs_lock); + return status; +} + +static ssize_t lirc_sense_store(struct class *class, struct class_attribute *attr, const char* buf, size_t size) +{ + int try_value; + ssize_t status=size; + mutex_lock(&sysfs_lock); + sscanf(buf,"%d",&try_value); + mutex_unlock(&sysfs_lock); + if ((try_value<2) && (try_value>-2)) { + sense=try_value; + } + else + return -EINVAL; + + if (gpio_in_pin>0) { + set_sense(); + } + return status; +} + +/* I don't think we need another device, so just put it in the class directory + * All we need is a way to access some global parameters of this module */ + +static struct class_attribute lirc_gpio_attrs[] = { + __ATTR(tx_gpio_pin, 0644, lirc_txpin_show, lirc_txpin_store), + __ATTR(rx_gpio_pin, 0644, lirc_rxpin_show, lirc_rxpin_store), + __ATTR(lirc_softcarrier, 0644, lirc_softcarrier_show, lirc_softcarrier_store), + __ATTR(lirc_invert, 0644, lirc_invert_show, lirc_invert_store), + __ATTR(lirc_sense, 0644, lirc_sense_show, lirc_sense_store), + __ATTR_NULL, +}; +static struct class lirc_gpio_class = { + .name = "lirc_gpio", + .owner = THIS_MODULE, + .class_attrs = lirc_gpio_attrs, +}; + +/* end of sysfs stuff */ + +/* initialize / free THIS driver and device and a lirc buffer*/ + +static int __init lirc_gpio_init(void) +{ + int result; + + /* Init read buffer. */ + result = lirc_buffer_init(&rbuf, sizeof(int), RBUF_LEN); + if (result < 0) + return -ENOMEM; + + result = platform_driver_register(&lirc_gpio_driver); + if (result) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": lirc register returned %d\n", result); + goto exit_buffer_free; + } + + lirc_gpio_dev = platform_device_alloc(LIRC_DRIVER_NAME, 0); + if (!lirc_gpio_dev) { + result = -ENOMEM; + goto exit_driver_unregister; + } + + result = platform_device_add(lirc_gpio_dev); + if (result) + goto exit_device_put; + + return 0; + +exit_device_put: + platform_device_put(lirc_gpio_dev); + +exit_driver_unregister: + platform_driver_unregister(&lirc_gpio_driver); + +exit_buffer_free: + lirc_buffer_free(&rbuf); + + return result; +} + +static void lirc_gpio_exit(void) +{ + setup_tx(0); // frees gpio_out_pin if set + setup_rx(0); // dito + platform_device_unregister(lirc_gpio_dev); + platform_driver_unregister(&lirc_gpio_driver); + lirc_buffer_free(&rbuf); +} + +/* end of stuff for THIS driver/device registration */ +/* ignorance of unset pins in setup routines tolerate call if nothing is set up */ + +/* master init */ + +static int __init lirc_gpio_init_module(void) +{ + int result,temp_in_pin,temp_out_pin; + + result = lirc_gpio_init(); + if (result) + return result; + // 'driver' is the lirc driver + driver.features = LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SET_SEND_CARRIER | + LIRC_CAN_SEND_PULSE | + LIRC_CAN_REC_MODE2; + + driver.dev = &lirc_gpio_dev->dev; // link THIS platform device to lirc driver + driver.minor = lirc_register_driver(&driver); + + if (driver.minor < 0) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": device registration failed with %d\n", result); + result = -EIO; + goto exit_lirc; + } + + printk(KERN_INFO LIRC_DRIVER_NAME ": driver registered!\n"); + + result = set_gpiochip(); + if (result < 0) + goto exit_lirc; + /* some hacking to get pins initialized on first used */ + /* setup_tx/rx will not do anything if pins would not change */ + temp_out_pin = gpio_out_pin; gpio_out_pin = 0; + result = setup_tx(temp_out_pin); + if (result < 0) + goto exit_lirc; + /* dito for rx */ + temp_in_pin = gpio_in_pin; gpio_in_pin = 0; + result = setup_rx(temp_in_pin); + if (result < 0) + goto exit_lirc; + if (device_open) { // this is unlikely, but well... + result = set_use_inc((void*) 0); + if (result<0) { + goto exit_lirc; + } + } + + result=class_register(&lirc_gpio_class); + if (result) { + goto exit_lirc; + } + + + return 0; + +exit_lirc: + /* failed attempt to setup_tx/rx sets pin to 0. */ + /* next call with arg 0 will then not do anything -> only one exit routine */ + lirc_gpio_exit(); + + return result; +} + +static void __exit lirc_gpio_exit_module(void) +{ + + lirc_gpio_exit(); + class_unregister(&lirc_gpio_class); + + lirc_unregister_driver(driver.minor); + printk(KERN_INFO LIRC_DRIVER_NAME ": cleaned up module\n"); +} + +module_init(lirc_gpio_init_module); +module_exit(lirc_gpio_exit_module); + +MODULE_DESCRIPTION("Infra-red receiver and blaster driver for GPIO-Lib."); +MODULE_DESCRIPTION("Parameters can be set/changed in /sys/class/lirc_gpio"); +MODULE_DESCRIPTION("If RX Pin is changed, previous value of sense is taken"); +MODULE_DESCRIPTION("You may want to write -1 to lirc_sense to force new auto-detection"); +MODULE_AUTHOR("Matthias Hoelling , + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define LIRC_DRIVER_NAME "sunxi_lirc" +#define RBUF_LEN 256 + +static struct platform_device *lirc_sunxi_dev; + +static struct clk *apb_ir_clk; +static struct clk *ir_clk; +static u32 ir_gpio_hdle; + +#define SYS_CLK_CFG_EN + +#define SYS_GPIO_CFG_EN +/* #define DEBUG_IR */ +#define PRINT_SUSPEND_INFO + +#define dprintk(fmt, args...) \ +do { \ +if (debug) \ +printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \ +fmt, ## args); \ +} while (0) + +/* Registers */ +#define IR_REG(x) (x) +#define IR0_BASE (0xf1c21800) +#define IR1_BASE (0xf1c21c00) +#define IR_BASE IR0_BASE +#define IR_IRQNO (SW_INT_IRQNO_IR0) + +/* CCM register */ +#define CCM_BASE 0xf1c20000 +/* PIO register */ +#define PI_BASE 0xf1c20800 + +#define IR_CTRL_REG IR_REG(0x00) /* IR Control */ +#define IR_RXCFG_REG IR_REG(0x10) /* Rx Config */ +#define IR_RXDAT_REG IR_REG(0x20) /* Rx Data */ +#define IR_RXINTE_REG IR_REG(0x2c) /* Rx Interrupt Enable */ +#define IR_RXINTS_REG IR_REG(0x30) /* Rx Interrupt Status */ +#define IR_SPLCFG_REG IR_REG(0x34) /* IR Sample Config */ + + +/* Bit Definition of IR_RXINTS_REG Register */ +#define IR_RXINTS_RXOF (0x1 << 0) /* Rx FIFO Overflow */ +#define IR_RXINTS_RXPE (0x1 << 1) /* Rx Packet End */ +#define IR_RXINTS_RXDA (0x1 << 4) /* Rx FIFO Data Available */ + +/* Helper */ +#define PULSE_BIT_SHIFTER (17) /* from 0x80 to PULSE_BIT */ +#define SAMPLES_TO_US(us) (( ((unsigned long) us) * 1000000UL)/46875UL) + +#ifdef CONFIG_ARCH_SUN5I +#define IR_FIFO_SIZE (64) /* 64Bytes */ +#else +#define IR_FIFO_SIZE (16) /* 16Bytes */ +#endif +/* Frequency of Sample Clock = 46875.Hz, Cycle is 21.3us */ + +#define IR_RXFILT_VAL (8) /* Filter Threshold = 8*21.3 = ~128us < 200us */ +#define IR_RXIDLE_VAL (5) /* Idle Threshold = (5+1)*128*21.3 = ~16.4ms > 9ms */ + + +#define IR_RAW_BUF_SIZE 128 +#define DRV_VERSION "1.00" + + + +struct ir_raw_buffer { + unsigned int dcnt; /*Packet Count*/ + unsigned char buf[IR_RAW_BUF_SIZE]; +}; + +static struct lirc_buffer rbuf; + +DEFINE_SPINLOCK(sunxi_lirc_spinlock); + + + +static struct ir_raw_buffer ir_rawbuf; + +static int debug=0; + + + +static inline void ir_reset_rawbuffer(void) +{ + ir_rawbuf.dcnt = 0; +} + +static inline void ir_write_rawbuffer(unsigned char data) +{ + if (ir_rawbuf.dcnt < IR_RAW_BUF_SIZE) + ir_rawbuf.buf[ir_rawbuf.dcnt++] = data; + else + printk("ir_write_rawbuffer: IR Rx buffer full\n"); +} + +static inline unsigned char ir_read_rawbuffer(void) +{ + unsigned char data = 0x00; + + if (ir_rawbuf.dcnt > 0) + data = ir_rawbuf.buf[--ir_rawbuf.dcnt]; + + return data; +} + +static inline int ir_rawbuffer_empty(void) +{ + return (ir_rawbuf.dcnt == 0); +} + +static inline int ir_rawbuffer_full(void) +{ + return (ir_rawbuf.dcnt >= IR_RAW_BUF_SIZE); +} + +static void ir_clk_cfg(void) +{ +#ifdef SYS_CLK_CFG_EN + unsigned long rate = 3000000; /* 6 MHz */ +#else + unsigned long tmp = 0; +#endif + +#ifdef SYS_CLK_CFG_EN + apb_ir_clk = clk_get(NULL, "apb_ir0"); + if (!apb_ir_clk) { + printk("try to get apb_ir0 clock failed\n"); + return; + } + + ir_clk = clk_get(NULL, "ir0"); + if (!ir_clk) { + printk("try to get ir0 clock failed\n"); + return; + } + dprintk("trying to set clock via SYS_CLK_CFG_EN, when no error follows -> succeeded\n"); + if (clk_set_rate(ir_clk, rate)) + printk("set ir0 clock freq to 6M failed\n"); + + if (clk_enable(apb_ir_clk)) + printk("try to enable apb_ir_clk failed\n"); + + if (clk_enable(ir_clk)) + printk("try to enable apb_ir_clk failed\n"); + +#else + dprintk("setting clock via register manipulation\n"); + /* Enable APB Clock for IR */ + /* copied from sunxi-ir.c, but not sure if this will work, registers do not seem conform with data sheet */ + tmp = readl(CCM_BASE + 0x10); + tmp |= 0x1 << 10; /* IR */ + writel(tmp, CCM_BASE + 0x10); + + /* config Special Clock for IR (24/8=6MHz) */ + tmp = readl(CCM_BASE + 0x34); + tmp &= ~(0x3 << 8); + tmp |= (0x1 << 8); /* Select 24MHz */ + tmp |= (0x1 << 7); /* Open Clock */ + tmp &= ~(0x3f << 0); + tmp |= (7 << 0); /* Divisor = 8 */ + writel(tmp, CCM_BASE + 0x34); +#endif + + return; +} + +static void ir_clk_uncfg(void) +{ +#ifdef SYS_CLK_CFG_EN + clk_put(apb_ir_clk); + clk_put(ir_clk); +#endif + + return; +} +static void ir_sys_cfg(void) +{ +#ifdef SYS_GPIO_CFG_EN + ir_gpio_hdle = gpio_request_ex("ir_para", "ir0_rx"); + if (0 == ir_gpio_hdle) + printk("try to request ir_para gpio failed\n"); +#else + /* config IO: PIOB4 to IR_Rx */ + tmp = readl(PI_BASE + 0x24); /* PIOB_CFG0_REG */ + tmp &= ~(0xf << 16); + tmp |= (0x2 << 16); + writel(tmp, PI_BASE + 0x24); +#endif + + ir_clk_cfg(); + + return; +} + +static void ir_sys_uncfg(void) +{ +#ifdef SYS_GPIO_CFG_EN + gpio_release(ir_gpio_hdle, 2); +#endif + ir_clk_uncfg(); + + return; +} + +static void ir_reg_cfg(void) +{ + unsigned long tmp = 0; + /* Enable CIR Mode */ + tmp = 0x3 << 4; + writel(tmp, IR_BASE + IR_CTRL_REG); + + /* Config IR Sample Register */ + tmp = 0x0 << 0; /* Fsample = 3MHz/64 =46875Hz (21.3us) */ + + + tmp |= (IR_RXFILT_VAL & 0x3f) << 2; /* Set Filter Threshold */ + tmp |= (IR_RXIDLE_VAL & 0xff) << 8; /* Set Idle Threshold */ + writel(tmp, IR_BASE + IR_SPLCFG_REG); + + /* Invert Input Signal */ + writel(0x1 << 2, IR_BASE + IR_RXCFG_REG); + + + /* Clear All Rx Interrupt Status */ + writel(0xff, IR_BASE + IR_RXINTS_REG); + + /* Set Rx Interrupt Enable */ + tmp = (0x1 << 4) | 0x3; +#ifdef CONFIG_ARCH_SUN5I + tmp |= ((IR_FIFO_SIZE >> 2) - 1) << 8; /* Rx FIFO Threshold = FIFOsz/4 */ +#else + tmp |= ((IR_FIFO_SIZE >> 1) - 1) << 8; /* Rx FIFO Threshold = FIFOsz/2 */ +#endif + writel(tmp, IR_BASE + IR_RXINTE_REG); + + /* Enable IR Module */ + tmp = readl(IR_BASE + IR_CTRL_REG); + tmp |= 0x3; + writel(tmp, IR_BASE + IR_CTRL_REG); + + return; +} + + + +static void ir_setup(void) +{ + dprintk("ir_setup: ir setup start!!\n"); + + ir_reset_rawbuffer(); + ir_sys_cfg(); + ir_reg_cfg(); + + dprintk("ir_setup: ir setup end!!\n"); + + return; +} + +static inline unsigned char ir_get_data(void) +{ + return (unsigned char)(readl(IR_BASE + IR_RXDAT_REG)); +} + +static inline unsigned long ir_get_intsta(void) +{ + return readl(IR_BASE + IR_RXINTS_REG); +} + +static inline void ir_clr_intsta(unsigned long bitmap) +{ + unsigned long tmp = readl(IR_BASE + IR_RXINTS_REG); + + tmp &= ~0xff; + tmp |= bitmap&0xff; + writel(tmp, IR_BASE + IR_RXINTS_REG); +} + + + +void ir_packet_handler(unsigned char *buf, unsigned int dcnt) +{ + unsigned int i; + unsigned int lirc_val; + dprintk("Buffer length: %d",dcnt); + for(i=0;i> 8) & 0x3f; +#else + dcnt = (unsigned int) (ir_get_intsta() >> 8) & 0x1f; +#endif + + /* Read FIFO */ + for (i = 0; i < dcnt; i++) { + if (ir_rawbuffer_full()) { + dprintk("ir_irq_service: raw buffer full\n"); + break; + } else { + ir_write_rawbuffer(ir_get_data()); + } + } + + if (intsta & IR_RXINTS_RXPE) { /* Packet End */ + + ir_packet_handler(ir_rawbuf.buf, ir_rawbuf.dcnt); + dprintk("Buffer written\n"); + ir_rawbuf.dcnt = 0; + + wake_up_interruptible(&rbuf.wait_poll); + + + } + + if (intsta & IR_RXINTS_RXOF) {/* FIFO Overflow */ + /* flush raw buffer */ + ir_reset_rawbuffer(); + dprintk("ir_irq_service: Rx FIFO Overflow!!\n"); + } + + return IRQ_HANDLED; +} + + + +/* interpret lirc commands */ +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + + switch (cmd) { + case LIRC_GET_SEND_MODE: + return -ENOIOCTLCMD; + break; + + /* driver cannot send */ + case LIRC_SET_SEND_MODE: + return -ENOSYS; + break; + + case LIRC_GET_LENGTH: + return -ENOSYS; + break; + + case LIRC_SET_SEND_DUTY_CYCLE: + return -ENOSYS; + break; + + case LIRC_SET_SEND_CARRIER: + return -ENOSYS; + break; + + default: + return lirc_dev_fop_ioctl(filep, cmd, arg); + } + return 0; +} + + +static int set_use_inc(void* data) { + return 0; +} + +static void set_use_dec(void* data) { + +} + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = lirc_ioctl, + .read = lirc_dev_fop_read, // this and the rest is default + .write = lirc_dev_fop_write, + .poll = lirc_dev_fop_poll, + .open = lirc_dev_fop_open, + .release = lirc_dev_fop_close, + .llseek = no_llseek, +}; + +static struct lirc_driver driver = { + .name = LIRC_DRIVER_NAME, + .minor = -1, // assing automatically + .code_length = 1, + .sample_rate = 0, + .data = NULL, + .add_to_buf = NULL, + .rbuf = &rbuf, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .dev = NULL, + .owner = THIS_MODULE, +}; + +/* end of lirc device/driver stuff */ + +/* now comes THIS driver, above is lirc */ +static struct platform_driver lirc_sunxi_driver = { + .driver = { + .name = LIRC_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + + + + + +static int __init ir_init(void) +{ + int result; + /* Init read buffer. */ + result = lirc_buffer_init(&rbuf, sizeof(int), RBUF_LEN); + if (result < 0) + return -ENOMEM; + + result = platform_driver_register(&lirc_sunxi_driver); + if (result) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": lirc register returned %d\n", result); + goto exit_buffer_free; + } + + lirc_sunxi_dev = platform_device_alloc(LIRC_DRIVER_NAME, 0); + if (!lirc_sunxi_dev) { + result = -ENOMEM; + goto exit_driver_unregister; + } + + result = platform_device_add(lirc_sunxi_dev); + if (result) { + platform_device_put(lirc_sunxi_dev); + goto exit_driver_unregister; + } + + if (request_irq(IR_IRQNO, ir_irq_service, 0, "RemoteIR", + (void*) 0)) { + result = -EBUSY; + goto exit_device_unregister; + } + + ir_setup(); + + printk("IR Initial OK\n"); + + + + // 'driver' is the lirc driver + driver.features = LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2; + + driver.dev = &lirc_sunxi_dev->dev; // link THIS platform device to lirc driver + driver.minor = lirc_register_driver(&driver); + + if (driver.minor < 0) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": device registration failed with %d\n", result); + + result = -EIO; + goto exit_free_irq; + } + + printk(KERN_INFO LIRC_DRIVER_NAME ": driver registered!\n"); + + return 0; + + +exit_free_irq: + free_irq(IR_IRQNO, (void*) 0); + +exit_device_unregister: + platform_device_unregister(lirc_sunxi_dev); + +exit_driver_unregister: + platform_driver_unregister(&lirc_sunxi_driver); + +exit_buffer_free: + lirc_buffer_free(&rbuf); + + return result; +} + +static void __exit ir_exit(void) +{ + + free_irq(IR_IRQNO, (void*) 0); + ir_sys_uncfg(); + platform_device_unregister(lirc_sunxi_dev); + + platform_driver_unregister(&lirc_sunxi_driver); + + lirc_buffer_free(&rbuf); + lirc_unregister_driver(driver.minor); + printk(KERN_INFO LIRC_DRIVER_NAME ": cleaned up module\n"); + +} + +module_init(ir_init); +module_exit(ir_exit); + +MODULE_DESCRIPTION("Remote IR driver"); +MODULE_AUTHOR("Matthias Hoelling"); +MODULE_LICENSE("GPL"); + +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging messages"); +