/*
 * This module is a simple bridge between the ir receiver integrated
 * in the af9005 based DVB-T usb stick and lirc.
 * Load it before loading dvb-usb-af9005 instead of 
 * dvb-usb-af9005-remote
 *
 * Loosely based on the lirc_serial driver
 *
 * Copyright (C) 2007 Luca Olivetti <luca@ventoso.org>
 *
 *  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 "af9005.h"

#include "drivers/lirc.h"
#include "drivers/lirc_dev/lirc_dev.h"

#define LIRC_DRIVER_NAME "lirc_af9005"

/* debug */
int dvb_usb_af9005_lirc_debug;
module_param_named(debug, dvb_usb_af9005_lirc_debug, int, 0644);
MODULE_PARM_DESC(debug,
		 "enable (1) or disable (0) debug messages."
		 DVB_USB_DEBUG_STATUS);

#define deb_lirc(args...)   dprintk(dvb_usb_af9005_lirc_debug,0x01,args)

/* This MUST be a power of two!  It has to be larger than 1 as well. */

#define RBUF_LEN 256

static struct lirc_buffer rbuf;
static int opened = 0;
static struct timeval lasttv = { 0, 0 };

static void inline rbwrite(lirc_t l)
{
	if (lirc_buffer_full(&rbuf)) {	/* no new signals will be accepted */
		deb_lirc("Buffer overrun\n");
		return;
	}
	_lirc_buffer_write_1(&rbuf, (void *)&l);
}

int af9005_rc_decode(struct dvb_usb_device *d, u8 * data, int len, u32 * event,
		     int *state)
{
	lirc_t value;
	int i, pulse;
	struct timeval tv;
	long deltv;
	do_gettimeofday(&tv);

	if (opened) {
		d->props.rc_interval = 10;
		deltv = tv.tv_sec - lasttv.tv_sec;
		if (deltv > 15) {
			rbwrite(PULSE_MASK);
		} else {
			rbwrite(deltv * 1000000 + tv.tv_usec - lasttv.tv_usec -
				10000);
		}
		for (i = 0; i + 1 < len; i += 2) {
			value = (data[i] << 8) + data[i + 1];
			pulse = value & 1;
			value = (value & 0xfffe) << 1;	/* *4 */
			deb_lirc("write %d %x\n", pulse, value);
			rbwrite(pulse ? (value | PULSE_BIT) : value);
		}
		wake_up_interruptible(&rbuf.wait_poll);
	} else {
		deb_lirc("Not opened, not writing\n");
		d->props.rc_interval = 200;
	}
	lasttv = tv;
	return 0;
}

static int set_use_inc(void *data)
{
	opened++;
	return 0;
}

static void set_use_dec(void *data)
{
	opened--;
}

static struct lirc_plugin plugin = {
      name:LIRC_DRIVER_NAME,
      minor:-1,
      code_length:1,
      sample_rate:0,
      data:NULL,
      add_to_buf:NULL,
      get_queue:NULL,
      rbuf:&rbuf,
      set_use_inc:set_use_inc,
      set_use_dec:set_use_dec,
      ioctl:NULL,
      fops:NULL,
      owner:THIS_MODULE,
      features:LIRC_CAN_REC_MODE2,
};

int init_module(void)
{
	if ((plugin.minor = lirc_register_plugin(&plugin)) < 0) {
		printk(KERN_ERR LIRC_DRIVER_NAME ": register_chrdev failed!\n");
		return -EIO;
	}
	/* Init read buffer. */
	if (lirc_buffer_init(&rbuf, sizeof(lirc_t), RBUF_LEN) < 0) {
		lirc_unregister_plugin(plugin.minor);
		return -ENOMEM;
	}
	return 0;
}

void cleanup_module(void)
{
	lirc_unregister_plugin(plugin.minor);
	lirc_buffer_free(&rbuf);
	deb_lirc("cleaned up module\n");
}

struct dvb_usb_rc_key af9005_rc_keys[] = { };	/* dummy */
int af9005_rc_keys_size = 0;

EXPORT_SYMBOL(af9005_rc_keys);
EXPORT_SYMBOL(af9005_rc_keys_size);
EXPORT_SYMBOL(af9005_rc_decode);

MODULE_DESCRIPTION("Lirc bridge module for the af9005.");
MODULE_AUTHOR("Luca Olivetti");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL");
