/*********************************************************************
 *		  
 * Filename:	  irport.c
 * Version:	  0.9
 * Description:   Serial driver for IrDA. 
 * Status:	  Experimental.
 * Author:	  Dag Brattli <dagb@cs.uit.no>
 * Created at:	  Sun Aug  3 13:49:59 1997
 * Modified at:   Sat May 23 23:15:20 1998
 * Modified by:   Dag Brattli <dagb@cs.uit.no>
 * Sources:	  serial.c by Linus Torvalds 
 * 
 *     Copyright (c) 1997,1998 Dag Brattli <dagb@cs.uit.no>
 *     All Rights Reserved.
 *     
 *     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.
 *
 *     Neither Dag Brattli nor University of Tromsų admit liability nor
 *     provide warranty for any of this software. This material is 
 *     provided "AS-IS" and at no charge.
 *
 *     NOTICE:
 *
 *     This driver is ment to be a small half duplex serial driver to be
 *     used for IR-chipsets that has a UART (16550) compatibility mode. If
 *     your chipset is is UART only, you should probably use IrTTY instead
 *     since the Linux serial driver is probably more robust and optimized.
 *
 *     The functions in this file may be used by FIR drivers, but this
 *     driver knows nothing about FIR drivers so don't ever insert such
 *     code into this file. Instead you should code your FIR driver in a
 *     separate file, and then call the functions in this file if
 *     necessary. This is becase it is difficult to use the Linux serial
 *     driver with a FIR driver becase they must share interrupts etc. Most
 *     FIR chipsets can function in advanced SIR mode, and you should
 *     probably use that mode instead of the UART compatibility mode (and
 *     then just forget about this file)
 *
 ********************************************************************/

#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/ioport.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/io.h>
#include <linux/errno.h>
#include <linux/init.h>

#include <linux/skbuff.h>
#include <linux/serial_reg.h>

#include <net/irda/irda.h>
#include <net/irda/irmod.h>
#include <net/irda/wrapper.h>
#include <net/irda/irport.h>

#define IO_EXTENT 8

/* static unsigned int io[]  = { 0x3e8, ~0, ~0, ~0 }; */
/* static unsigned int irq[] = { 11, 0, 0, 0 }; */

static void irport_write_wakeup(struct irda_device *idev);
static int  irport_write(int iobase, int fifo_size, __u8 *buf, int len);
static void irport_receive(struct irda_device *idev);

__initfunc(int irport_init(void))
{
/* 	int i; */

/* 	for ( i=0; (io[i] < 2000) && (i < 4); i++) { */
/* 		int ioaddr = io[i]; */
/* 		if (check_region(ioaddr, IO_EXTENT)) */
/* 			continue; */
/* 		if (irport_open( i, io[i], io2[i], irq[i], dma[i]) == 0) */
/* 			return 0; */
/* 	} */
/* 	return -ENODEV; */
	return 0;
}

/*
 * Function pc87108_cleanup ()
 *
 *    Close all configured chips
 *
 */
#ifdef MODULE
static void irport_cleanup(void)
{
/* 	int i; */

        DEBUG( 4, __FUNCTION__ "()\n");

	/* for ( i=0; i < 4; i++) { */
/* 		if ( dev_self[i]) */
/* 			irport_close( &(dev_self[i]->idev)); */
/* 	} */
}
#endif /* MODULE */

/*
 * Function irport_open (void)
 *
 *    Start IO port 
 *
 */
int irport_open(int iobase)
{
	DEBUG(4, __FUNCTION__ "(), iobase=%#x\n", iobase);

	/* Initialize UART */
	outb(UART_LCR_WLEN8, iobase+UART_LCR);  /* Reset DLAB */
	outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase+UART_MCR);
	
	/* Turn on interrups */
	outb((UART_IER_RLSI | UART_IER_RDI), iobase+UART_IER);

	return 0;
}

/*
 * Function irport_cleanup ()
 *
 *    Stop IO port
 *
 */
void irport_close(int iobase) 
{
	DEBUG(4, __FUNCTION__ "()\n");

	/* Reset UART */
	outb(0, iobase+UART_MCR);

	/* Turn off interrupts */
	outb(0, iobase+UART_IER);
}

/*
 * Function irport_change_speed (idev, speed)
 *
 *    Set speed of port to specified baudrate
 *
 */
void irport_change_speed( int iobase, int speed) 
{
	int fcr;    /* FIFO control reg */
	int lcr;    /* Line control reg */
	int divisor;

	DEBUG( 0, __FUNCTION__ "(), Setting speed to: %d\n", speed);

	/* Turn off interrupts */
	outb(0, iobase+UART_IER); 

	divisor = SPEED_MAX/speed;
	
	fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_14;
        
	/* IrDA ports use 8N1 */
	lcr = UART_LCR_WLEN8;
	
	outb(UART_LCR_DLAB | lcr, iobase+UART_LCR); /* Set DLAB */
	outb(divisor & 0xff,      iobase+UART_DLL); /* Set speed */
	outb(divisor >> 8,	  iobase+UART_DLM);
	outb(lcr,		  iobase+UART_LCR); /* Set 8N1	*/
	outb(fcr,		  iobase+UART_FCR); /* Enable FIFO's */

	/* Turn on receive interrups */
	outb(UART_IER_RLSI|UART_IER_RDI, iobase+UART_IER); 
}

/*
 * Function irport_write_wakeup (tty)
 *
 *    Called by the driver when there's room for more data.  If we have
 *    more packets to send, we send them here.
 *
 */
static void irport_write_wakeup(struct irda_device *idev)
{
	int actual = 0;
	int iobase;

	ASSERT(idev != NULL, return;);
	ASSERT(idev->magic == IRDA_DEVICE_MAGIC, return;);

	/* Finished with frame?  */
	if (idev->tx_buff.len > 0)  {
		/* Write data left in transmit buffer */
		actual = irport_write(idev->io.iobase2, idev->io.fifo_size, 
				      idev->tx_buff.data, idev->tx_buff.len);
		idev->tx_buff.data += actual;
		idev->tx_buff.len  -= actual;
	} else {
		iobase = idev->io.iobase2;
		/* 
		 *  Now serial buffer is almost free & we can start 
		 *  transmission of another packet 
		 */
		idev->netdev.tbusy = 0; /* Unlock */
		idev->stats.tx_packets++;

		/* Schedule network layer, so we can get some more frames */
		mark_bh(NET_BH);

		outb(UART_FCR_ENABLE_FIFO | 
		     UART_FCR_TRIGGER_14  |
		     UART_FCR_CLEAR_RCVR, iobase+UART_FCR); /* Enable FIFO's */

		/* Turn on receive interrupts */
		outb(UART_IER_RLSI|UART_IER_RDI, iobase+UART_IER); 
	}
}

/*
 * Function irport_write (driver)
 *
 *    
 *
 */
static int irport_write(int iobase, int fifo_size, __u8 *buf, int len)
{
	int actual = 0;

	/* Tx FIFO should be empty! */
	if (!(inb(iobase+UART_LSR) & UART_LSR_THRE)) {
		DEBUG( 0, __FUNCTION__ "(), failed, fifo not empty!\n");
		return -1;
	}
        
	/* Fill FIFO with current frame */
	while (( fifo_size-- > 0) && (actual < len)) {
		/* Transmit next byte */
		outb( buf[actual], iobase+UART_TX);

		actual++;
	}
        
	DEBUG(4, __FUNCTION__ "(), fifo_size %d ; %d sent of %d\n", 
	      fifo_size, actual, len);

	return actual;
}

/*
 * Function irport_xmit (void)
 *
 *    Transmits the current frame until FIFO is full, then
 *    waits until the next transmitt interrupt, and continues until the
 *    frame is transmited.
 */
int irport_hard_xmit(struct sk_buff *skb, struct device *dev)
{
	struct irda_device *idev;
	int actual = 0;
	int iobase;

	DEBUG(5, __FUNCTION__ "(), dev=%p\n", dev);

	ASSERT(dev != NULL, return 0;);
	
	idev = (struct irda_device *) dev->priv;

	ASSERT(idev != NULL, return 0;);
	ASSERT(idev->magic == IRDA_DEVICE_MAGIC, return 0;);

	iobase = idev->io.iobase2;

	/* Lock transmit buffer */
	if (irda_lock((void *) &dev->tbusy) == FALSE)
		return -EBUSY;
	
	/* Init tx buffer */
	idev->tx_buff.data = idev->tx_buff.head;

        /* Copy skb to tx_buff while wrapping, stuffing and making CRC */
	idev->tx_buff.len = async_wrap_skb(skb, idev->tx_buff.data, 
					   idev->tx_buff.truesize);
	
	idev->tx_buff.data += actual;
	idev->tx_buff.len  -= actual;

	/* Turn on transmit finished interrupt. Will fire immediately!  */
	outb(UART_IER_THRI, iobase+UART_IER); 

	dev_kfree_skb(skb);
	
	return 0;
}
        
/*
 * Function irport_receive (void)
 *
 *    Receive one frame from the infrared port
 *
 */
static void irport_receive(struct irda_device *idev) 
{
	int iobase;
	int boguscount = 0;

	if (!idev)
		return;

	DEBUG(4, __FUNCTION__ "()\n");

	iobase = idev->io.iobase2;

	/*  
	 * Receive all characters in Rx FIFO, unwrap and unstuff them. 
         * async_unwrap_char will deliver all found frames  
	 */
	do {
		async_unwrap_char(idev, inb(iobase+UART_RX));

		/* Make sure we don't stay here to long */
		if (boguscount++ > 32) {
			DEBUG(0,__FUNCTION__ "(), breaking!\n");
			break;
		}
	} while (inb(iobase+UART_LSR) & UART_LSR_DR);	
}

/*
 * Function irport_interrupt (irq, dev_id, regs)
 *
 *    Interrupt handler
 */
void irport_interrupt(int irq, void *dev_id, struct pt_regs *regs) 
{
	struct irda_device *idev = (struct irda_device *) dev_id;
	int iobase;
	int iir, lsr;
	int boguscount = 0;

	if (!idev) {
		printk(KERN_WARNING __FUNCTION__ 
		       "() irq %d for unknown device.\n", irq);
		return;
	}

	idev->netdev.interrupt = 1;

	iobase = idev->io.iobase2;

	iir = inb(iobase + UART_IIR) & UART_IIR_ID;
	while (iir) {
		/* Clear interrupt */
		lsr = inb(iobase+UART_LSR);

		if ((iir & UART_IIR_THRI) && (lsr & UART_LSR_THRE)) {
			/* Transmitter ready for data */
			irport_write_wakeup(idev);
		} else if ((iir & UART_IIR_RDI) && (lsr & UART_LSR_DR)) {
	       		/* Receive interrupt */
			irport_receive(idev);
		}
		
		/* Make sure we don't stay here to long */
		if (boguscount++ > 32)
			break;

 	        iir = inb(iobase + UART_IIR) & UART_IIR_ID;
	}
	idev->netdev.interrupt = 0;
}

#ifdef MODULE

/*
 * Function cleanup_module (void)
 *
 *    
 *
 */
void cleanup_module(void)
{
	irport_cleanup();
}

/*
 * Function init_module (void)
 *
 *    
 */
int init_module(void)
{
	if (irport_init() < 0) {
		cleanup_module();
		return 1;
	}
	return(0);
}

#endif /* MODULE */

