/*
 *  Driver for HSF softmodem virtual serial ports
 *
 *	Written by Marc Boucher <marc@linuxant.com>
 */

/*
 * Copyright (c) 2001-2002 Conexant Systems, Inc.
 * Copyright (c) 2003 Linuxant inc.
 * 
 * 1.  General Public License. This program is free software, and may
 * be redistributed or modified subject to the terms of the GNU General
 * Public License (version 2) or the GNU Lesser General Public License,
 * or (at your option) any later versions ("Open Source" code). You may
 * obtain a copy of the GNU General Public License at
 * http://www.fsf.org/copyleft/gpl.html and a copy of the GNU Lesser
 * General Public License at http://www.fsf.org/copyleft/less.html,
 * or you may alternatively write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA.
 * 
 * 2.   Disclaimer of Warranties. CONEXANT AND OTHER CONTRIBUTORS MAKE NO
 * REPRESENTATION ABOUT THE SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE.
 * IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTIES OF ANY KIND.
 * CONEXANT AND OTHER CONTRIBUTORS DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, GOOD TITLE AND AGAINST INFRINGEMENT.
 * 
 * This software has not been formally tested, and there is no guarantee that
 * it is free of errors including, but not limited to, bugs, defects,
 * interrupted operation, or unexpected results. Any use of this software is
 * at user's own risk.
 * 
 * 3.   No Liability.
 * 
 * (a) Conexant or contributors shall not be responsible for any loss or
 * damage to Company, its customers, or any third parties for any reason
 * whatsoever, and CONEXANT OR CONTRIBUTORS SHALL NOT BE LIABLE FOR ANY
 * ACTUAL, DIRECT, INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL, OR CONSEQUENTIAL
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED, WHETHER IN CONTRACT, STRICT OR OTHER LEGAL THEORY OF
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 * 
 * (b) User agrees to hold Conexant and contributors harmless from any
 * liability, loss, cost, damage or expense, including attorney's fees,
 * as a result of any claims which may be made by any person, including
 * but not limited to User, its agents and employees, its customers, or
 * any third parties that arise out of or result from the manufacture,
 * delivery, actual or alleged ownership, performance, use, operation
 * or possession of the software furnished hereunder, whether such claims
 * are based on negligence, breach of contract, absolute liability or any
 * other legal theory.
 * 
 * 4.   Notices. User hereby agrees not to remove, alter or destroy any
 * copyright, trademark, credits, other proprietary notices or confidential
 * legends placed upon, contained within or associated with the Software,
 * and shall include all such unaltered copyright, trademark, credits,
 * other proprietary notices or confidential legends on or in every copy of
 * the Software.
 * 
 */
#include <linux/version.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/tqueue.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/major.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <asm/serial.h>

#include "oscompat.h"
#include "ossysenv.h"
#include "comtypes.h"
#include "comctrl_ex.h"
#include "ostime_ex.h"

#include "serial_core.h"

#define NR_PORTS		1 // only one HSF port supported for now

#define HSF_ISR_PASS_LIMIT	256

#define HSF_READBUF_SIZE	256

static struct tty_driver hsf_tty_driver_normal;
static struct tty_driver hsf_tty_driver_callout;
static struct tty_struct *hsf_tty_table[NR_PORTS];
static struct termios *hsf_termios[NR_PORTS], *hsf_termios_locked[NR_PORTS];

static PI_COM_CTRL_T hsf_pcomctrl;       // Communication Controller interface
static HANDLE        hsf_hcomctrl;       // Communication Controller instance

static int hsf_rxenabled;
static int hsf_txenabled;

static int hsf_evt_rxchar;
static int hsf_evt_rxbreak;
static int hsf_evt_rxovrn;
static int hsf_evt_txempty;

static unsigned char hsf_readbuf[HSF_READBUF_SIZE];
static int hsf_readcount, hsf_readoffset;

static u_int hsf_mctrl_flags;

static struct tq_struct hsf_intr_tqueue;

static struct uart_info *hsf_uart_info;

#ifdef COMCTRL_MONITOR_POUND_UG_SUPPORTED
static int loglastcallstatus = 0;
#endif

static void
hsf_sched_intr(void)
{
	if(hsf_uart_info) {
		MOD_INC_USE_COUNT;
		if (OsModemThreadSchedule(&hsf_intr_tqueue) == 0) {
			MOD_DEC_USE_COUNT;
		}
	}
}

static void
hsf_stop_tx(struct uart_port *port, u_int from_tty)
{
	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	hsf_txenabled = 0;
}

static void
hsf_start_tx(struct uart_port *port, u_int nonempty, u_int from_tty)
{
	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	hsf_txenabled = 1;
	if (nonempty) {
		hsf_sched_intr();
	}
}

static void
hsf_stop_rx(struct uart_port *port)
{
	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	hsf_rxenabled = 0;
}

static inline int
hsf_rx_ready(void)
{
    int r;

	if(!hsf_rxenabled)
		return FALSE;

    if(hsf_readoffset == hsf_readcount) {
		r = hsf_pcomctrl->Read(hsf_hcomctrl, hsf_readbuf, sizeof(hsf_readbuf));
        if(r < 0) {
			printk(KERN_ERR"%s: ComCtrlRead returned %d\n", __FUNCTION__, r);
		} else {
			hsf_readcount = r;
			hsf_readoffset = 0;
		}
	}

    return (hsf_readcount > hsf_readoffset) || hsf_evt_rxbreak || hsf_evt_rxovrn;
}

static inline void
hsf_rx_chars(struct uart_info *info)
{
	struct tty_struct *tty = info->tty;
	struct uart_port *port = info->port;
	int max_count = sizeof(hsf_readbuf);

	hsf_evt_rxchar = 0;

	while(max_count-- > 0 && hsf_rx_ready()) {
		if(tty->flip.count >= TTY_FLIPBUF_SIZE) {
			tty->flip.tqueue.routine((void *) tty);
			if(tty->flip.count >= TTY_FLIPBUF_SIZE) {
				return; // if TTY_DONT_FLIP is set
			}
		}

		if (hsf_evt_rxovrn) {
	    	hsf_evt_rxovrn = 0;
	    	port->icount.overrun++;

			*tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
			*tty->flip.char_buf_ptr++ = 0;
			tty->flip.count++;
			continue;
		}

		port->icount.rx++;

		if (hsf_evt_rxbreak) {
	    	hsf_evt_rxbreak = 0;
	    	port->icount.brk++;

			*tty->flip.flag_buf_ptr++ = TTY_BREAK;
		} else
			*tty->flip.flag_buf_ptr++ = TTY_NORMAL;

		*tty->flip.char_buf_ptr++ = hsf_readbuf[hsf_readoffset++];
		tty->flip.count++;
	}

	tty_flip_buffer_push(tty);
	return;
}

static inline int
hsf_tx_free(void)
{
	int r;
	UINT32 val = 0;

    if ((r=hsf_pcomctrl->Monitor(hsf_hcomctrl, COMCTRL_MONITOR_TXFREE, &val)) != COM_STATUS_SUCCESS) {
		printk(KERN_ERR "%s: ComCtrlMonitor COMCTRL_MONITOR_TXFREE failed, status=%d\n", __FUNCTION__, r);
		return 0;
	}

	//printk(KERN_DEBUG "%s: val=%lu\n", __FUNCTION__, val);
	return val;
}

static inline void
hsf_put_char(struct uart_port *port, unsigned char ch)
{
    int r;

	//printk(KERN_DEBUG "%s: ch=%x\n", __FUNCTION__, (int)ch);
	hsf_evt_txempty = 0;
    r = hsf_pcomctrl->Write(hsf_hcomctrl, &ch, 1);
    if(r != 1) {
		printk(KERN_ERR "%s: ComCtrlWrite returned %d\n", __FUNCTION__, r);
    }
}

static inline void
hsf_tx_chars(struct uart_info *info)
{
	struct uart_port *port = info->port;

	if (port->x_char) {
		hsf_put_char(port, port->x_char);
		port->icount.tx++;
		port->x_char = 0;
		return;
	}
	if (info->xmit.head == info->xmit.tail
	    || info->tty->stopped
	    || info->tty->hw_stopped) {
		hsf_stop_tx(info->port, 0);
		return;
	}

	while (hsf_tx_free() > 0) {
		hsf_put_char(port, info->xmit.buf[info->xmit.tail]);
		info->xmit.tail = (info->xmit.tail + 1) & (UART_XMIT_SIZE - 1);
		port->icount.tx++;
		if (info->xmit.head == info->xmit.tail)
			break;
	}

	if (CIRC_CNT(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE) <
			WAKEUP_CHARS)
		uart_event(info, EVT_WRITE_WAKEUP);

	if (info->xmit.head == info->xmit.tail)
		hsf_stop_tx(info->port, 0);
}

static inline int
hsf_tx_ready(void) {
	return (hsf_txenabled /*&& hsf_evt_txempty*/);
}

static void
hsf_intr(void *dev_id)
{
	unsigned int pass_counter = 0;

	while (hsf_uart_info && (hsf_rx_ready() || hsf_tx_ready())) {
		hsf_rx_chars(hsf_uart_info);

		if(hsf_tx_ready())
			hsf_tx_chars(hsf_uart_info);

		if (pass_counter++ > HSF_ISR_PASS_LIMIT)
			break;
	}

	MOD_DEC_USE_COUNT;
}

/*
 * Return TIOCSER_TEMT when transmitter is not busy.
 */
static u_int
hsf_tx_empty(struct uart_port *port)
{
	return hsf_evt_txempty ? TIOCSER_TEMT : 0;
}

static u_int
hsf_get_mctrl(struct uart_port *port)
{
	return hsf_mctrl_flags;
}

#ifdef COMCTRL_MONITOR_POUND_UG_SUPPORTED
static COM_STATUS
hsf_monitor(COMCTRL_MONITOR_CODE eCode, PVOID pMonitor)
{
	int r;

	if(!hsf_pcomctrl || !hsf_hcomctrl)
		return COM_STATUS_INST_NOT_INIT;

   	r = hsf_pcomctrl->Monitor(hsf_hcomctrl, eCode, pMonitor);

    if (r != COM_STATUS_SUCCESS) {
		printk(KERN_ERR "%s: ComCtrlMonitor %d failed, status=%d\n", __FUNCTION__, eCode, r);
	}

	return r;
}
#endif

static COM_STATUS
hsf_control(COMCTRL_CONTROL_CODE eCode, PVOID pControl)
{
	int r;

	if(!hsf_pcomctrl || !hsf_hcomctrl)
		return COM_STATUS_INST_NOT_INIT;

   	r = hsf_pcomctrl->Control(hsf_hcomctrl, eCode, pControl);

    if (r != COM_STATUS_SUCCESS) {
		printk(KERN_ERR "%s: ComCtrlControl %d failed, status=%d\n", __FUNCTION__, eCode, r);
	}

	return r;
}

static void
hsf_set_mctrl(struct uart_port *port, u_int mctrl)
{
	//printk(KERN_DEBUG "%s: mctrl=%x\n", __FUNCTION__, mctrl);

	if ((mctrl & TIOCM_RTS) && !(hsf_mctrl_flags & TIOCM_RTS)) {
		hsf_mctrl_flags |= TIOCM_RTS;
		hsf_control(COMCTRL_CONTROL_SETRTS, 0);
	}
	if (!(mctrl & TIOCM_RTS) && (hsf_mctrl_flags & TIOCM_RTS)) {
		hsf_mctrl_flags &= ~TIOCM_RTS;
		hsf_control(COMCTRL_CONTROL_CLRRTS, 0);
	}

	if ((mctrl & TIOCM_DTR) && !(hsf_mctrl_flags & TIOCM_DTR)) {
		hsf_mctrl_flags |= TIOCM_DTR;
		hsf_control(COMCTRL_CONTROL_SETDTR, 0);
	}
	if (!(mctrl & TIOCM_DTR) && (hsf_mctrl_flags & TIOCM_DTR)) {
		hsf_mctrl_flags &= ~TIOCM_DTR;
		hsf_control(COMCTRL_CONTROL_CLRDTR, 0);
	}
}

static void
hsf_break_ctl(struct uart_port *port, int break_state)
{
	//printk(KERN_DEBUG "%s: break_state=%d\n", __FUNCTION__, break_state);
#if 0
	if(break_state)
		printk(KERN_WARNING "%s: modem does not presently support sending/receiving BREAK\n", __FUNCTION__);
#endif
}

static void
hsf_event_handler(struct uart_port *port, UINT32 dwEvtMask)
{
	u_int mctrl_flags, orig_mctrl_flags;
	int sched_intr=0;

	orig_mctrl_flags = mctrl_flags = hsf_mctrl_flags;
   	//printk(KERN_DEBUG "%s: dwEvtMask=0x%04lx\n", __FUNCTION__, dwEvtMask);

	if((dwEvtMask & COMCTRL_EVT_RXCHAR)) {
	    hsf_evt_rxchar = 1;
	    sched_intr = 1;
	}

	if(dwEvtMask & COMCTRL_EVT_BREAK) {
	    hsf_evt_rxbreak = 1;
	    sched_intr = 1;
	}

	if((dwEvtMask & COMCTRL_EVT_RXOVRN)) {
	    hsf_evt_rxovrn = 1;
	    sched_intr = 1;
	}

#if 0
	if((dwEvtMask & COMCTRL_EVT_TXCHAR)) {
	}
#endif

	if((dwEvtMask & COMCTRL_EVT_TXEMPTY)) {
	    hsf_evt_txempty = 1;
	    sched_intr = 1;
	}

	if((dwEvtMask & COMCTRL_EVT_CTS)) {
		if(dwEvtMask & COMCTRL_EVT_CTSS) {
		    mctrl_flags |= TIOCM_CTS;
		} else {
		    mctrl_flags &= ~TIOCM_CTS;
		}
	}

	if(dwEvtMask & COMCTRL_EVT_DSR) {
		if(dwEvtMask & COMCTRL_EVT_DSRS) {
		    mctrl_flags |= TIOCM_DSR;
		} else {
		    mctrl_flags &= ~TIOCM_DSR;
		}
		port->icount.dsr++;
	}

	if(dwEvtMask & COMCTRL_EVT_RLSD) {
		if(dwEvtMask & COMCTRL_EVT_RLSDS) {
		    mctrl_flags |= TIOCM_CAR;
		} else {
		    mctrl_flags &= ~TIOCM_CAR;
		}
	}

	if(dwEvtMask & COMCTRL_EVT_RING) {
	    if(!(mctrl_flags & TIOCM_RNG)) {
		   	mctrl_flags |= TIOCM_RNG;
		port->icount.rng++;
	    }
	}

	if(dwEvtMask == 0 && (mctrl_flags & TIOCM_RNG)) {
		mctrl_flags &= ~TIOCM_RNG;
		port->icount.rng++;
	}

	if(hsf_mctrl_flags != mctrl_flags) {
		hsf_mctrl_flags = mctrl_flags;
#if 0
		printk(KERN_DEBUG "%cCTS %cDSR %cDCD %cRI\n",
				hsf_mctrl_flags&TIOCM_CTS?'+':'-',
				hsf_mctrl_flags&TIOCM_DSR?'+':'-',
				hsf_mctrl_flags&TIOCM_CAR?'+':'-',
				hsf_mctrl_flags&TIOCM_RNG?'+':'-');
#endif

		if(hsf_uart_info) {
			if((mctrl_flags & TIOCM_CAR) != (orig_mctrl_flags & TIOCM_CAR)) {
				uart_handle_dcd_change(hsf_uart_info, mctrl_flags & TIOCM_CAR);
#ifdef COMCTRL_MONITOR_POUND_UG_SUPPORTED
				if(loglastcallstatus && !(mctrl_flags & TIOCM_CAR)) {
					PORT_MONITOR_DATA monitorData;
					static char largebuf[PAGE_SIZE];
					char *p, *nl;

					monitorData.dwSize = sizeof(largebuf);
					monitorData.pBuf = largebuf;

					hsf_monitor(COMCTRL_MONITOR_POUND_UG, &monitorData);
					p = largebuf;
					while((nl = strchr(p, '\n'))) {
						printk(KERN_INFO "HSF: %.*s", (nl - p) + 1, p);
						p = nl + 1;
					}
					if(*p)
						printk(KERN_INFO "HSF: %s\n", p);

				}
#endif
			}
			if((mctrl_flags & TIOCM_CTS) != (orig_mctrl_flags & TIOCM_CTS))
				uart_handle_cts_change(hsf_uart_info, mctrl_flags & TIOCM_CTS);

			wake_up_interruptible(&hsf_uart_info->delta_msr_wait);
		}
	}

	if(sched_intr) {
		hsf_sched_intr();
	}

	return;
}

static int
hsf_comctrl_init(void)
{
	int r;

	//printk(KERN_DEBUG "%s\n", __FUNCTION__);

	hsf_pcomctrl = ComCtrlGetInterface();
	if(!hsf_pcomctrl) {
 		printk(KERN_ERR "%s: ComCtrlGetInterface error\n", __FUNCTION__);
		return -EIO;
	}

	if((r=hsf_pcomctrl->GetInterfaceVersion()) != COMCTRL_VERSION) {
   		printk(KERN_DEBUG "%s: ComCtrlGetInterfaceVersion returned 0x%d (expecting %d)\n", __FUNCTION__, r, COMCTRL_VERSION);
		hsf_pcomctrl = NULL;
		return -EIO;
	}

	hsf_hcomctrl = hsf_pcomctrl->Create();
	if(!hsf_hcomctrl) {
 		printk(KERN_DEBUG "%s: ComCtrlCreate failed!\n", __FUNCTION__);
		hsf_hcomctrl = NULL;
		hsf_pcomctrl = NULL;
		return -EIO;
	}
	return 0;
}

static void
hsf_comctrl_exit(void)
{
	if(hsf_pcomctrl && hsf_hcomctrl) {
		hsf_pcomctrl->Destroy(hsf_hcomctrl);

		hsf_pcomctrl = NULL;
		hsf_hcomctrl = NULL;
	}
}

static int
hsf_startup(struct uart_port *port, struct uart_info *info)
{
	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	if(!hsf_pcomctrl || !hsf_hcomctrl)
		return -ENODEV;

	if(hsf_uart_info)
		return -EBUSY;

	/* flush any characters or events received while we were shutdown */
	while(hsf_pcomctrl->Read(hsf_hcomctrl, hsf_readbuf, sizeof(hsf_readbuf)) > 0);
	hsf_readcount = hsf_readoffset = 0;
	hsf_evt_rxchar = 0;
	hsf_evt_rxbreak = 0;
	hsf_evt_rxovrn = 0;

	hsf_uart_info = info;

	hsf_rxenabled = 1;
	hsf_txenabled = 1;

	return 0;
}

static void
hsf_shutdown(struct uart_port *port, struct uart_info *info)
{
	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	hsf_rxenabled = 0;
	hsf_txenabled = 0;

	hsf_uart_info = NULL;
}

static void
hsf_change_speed(struct uart_port *port, u_int cflag, u_int iflag, u_int quot)
{
	PORT_CONFIG port_config;

	//printk(KERN_DEBUG "%s\n", __FUNCTION__);

	memset(&port_config, 0, sizeof(port_config));

	if(quot) {
		port_config.dwDteSpeed = port->uartclk / (16 * quot);
		port_config.dwValidFileds |= PC_DTE_SPEED;
	}

	if(cflag & PARENB) {
		if(cflag & PARODD)
			port_config.eParity = PC_PARITY_ODD;
		else
			port_config.eParity = PC_PARITY_EVEN;
	} else
			port_config.eParity = PC_PARITY_NONE;
	port_config.dwValidFileds |= PC_PARITY;

	if((cflag & CSIZE) == CS7) {
		port_config.eDataBits = PC_DATABITS_7;
	} else {
		port_config.eDataBits = PC_DATABITS_8;
	}
	port_config.dwValidFileds |= PC_DATA_BITS;

	if (cflag & CRTSCTS) {
		port_config.fCTS = TRUE;
		port_config.fRTS = TRUE;
	}
	port_config.dwValidFileds |= PC_CTS | PC_RTS;

	hsf_control(COMCTRL_CONTROL_PORTCONFIG, &port_config);
}

static void
hsf_enable_ms(struct uart_port *port)
{
}

static void
hsf_release_port(struct uart_port *port)
{
	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	hsf_mctrl_flags &= ~TIOCM_DSR; // until comctrl generates events for this

	if(hsf_pcomctrl && hsf_hcomctrl) {
		hsf_pcomctrl->Close(hsf_hcomctrl);
	}
}

static int
hsf_request_port(struct uart_port *port)
{
	PORT_EVENT_HANDLER EvtHandler;
	int r;

	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	hsf_mctrl_flags = 0;

	hsf_evt_rxchar = 0;
	hsf_evt_rxbreak = 0;
	hsf_evt_rxovrn = 0;
	hsf_evt_txempty = 1;

	hsf_rxenabled = 0;
	hsf_txenabled = 0;

	hsf_readcount = hsf_readoffset = 0;

	INIT_TQUEUE(&hsf_intr_tqueue, hsf_intr, port);

	EvtHandler.pfnCallback = (void (*) (PVOID pRef, UINT32 dwEventMask))
	   				hsf_event_handler;
	EvtHandler.pRef = port;

	if ((r=hsf_pcomctrl->Configure(hsf_hcomctrl, COMCTRL_CONFIG_EVENT_HANDLER, &EvtHandler))) {
 		printk(KERN_DEBUG "%s: ComCtrlConfigure failed, r=%d!\n", __FUNCTION__, r);
		return -EIO;
	}

	if((r=hsf_pcomctrl->Open(hsf_hcomctrl))) {
 		printk(KERN_ERR "%s: open failed\n", __FUNCTION__);
		return -EIO;
	}

	hsf_mctrl_flags |= TIOCM_DSR; // until comctrl generates events for this

    return 0;
}

static void
hsf_config_port(struct uart_port *port, int flags)
{
	//printk(KERN_DEBUG "%s\n", __FUNCTION__);
	if (flags & UART_CONFIG_TYPE && hsf_request_port(port) == 0) {
		port->type = PORT_VIRTUAL;
	}
}

/*
 * Verify the new serial_struct (for TIOCSSERIAL).
 * The only change we allow are to the flags
 */
static int
hsf_verify_port(struct uart_port *port, struct serial_struct *ser)
{
	int ret = 0;
	if (ser->type != PORT_VIRTUAL)
		ret = -EINVAL;
	if (port->irq != ser->irq)
		ret = -EINVAL;
	/*if (ser->io_type != SERIAL_IO_MEM)
		ret = -EINVAL;*/
	if (port->uartclk / 16 != ser->baud_base)
		ret = -EINVAL;
	/*if ((void *)port->mapbase != ser->iomem_base)
		ret = -EINVAL;*/
	if (port->iobase != ser->port)
		ret = -EINVAL;
	if (ser->hub6 != 0)
		ret = -EINVAL;
	return ret;
}

static struct uart_ops hsf_pops = {
	tx_empty:		hsf_tx_empty,
	set_mctrl:		hsf_set_mctrl,
	get_mctrl:		hsf_get_mctrl,
	stop_tx:		hsf_stop_tx,
	start_tx:		hsf_start_tx,
	stop_rx:		hsf_stop_rx,
	enable_ms:		hsf_enable_ms,
	break_ctl:		hsf_break_ctl,
	startup:		hsf_startup,
	shutdown:		hsf_shutdown,
	change_speed:	hsf_change_speed,
	release_port:	hsf_release_port,
	request_port:	hsf_request_port,
	config_port:	hsf_config_port,
	verify_port:	hsf_verify_port,
};

static struct uart_port hsf_ports[NR_PORTS] = {
	{
		iobase:		COMCTRL_VERSION,
		uartclk:	BASE_BAUD * 16,
		fifosize:	16,
		ops:		&hsf_pops,
		flags:      ASYNC_BOOT_AUTOCONF,
	},
};

static int serialmajor = HSFSERIALMAJOR;
MODULE_PARM(serialmajor, "i");
MODULE_PARM_DESC(serialmajor, "Major device number for serial device");
#ifdef HSFCALOUTMAJOR
static int calloutmajor = HSFCALOUTMAJOR;
MODULE_PARM(calloutmajor, "i");
MODULE_PARM_DESC(calloutmajor, "Major device number for callout device");
#endif

#ifdef COMCTRL_MONITOR_POUND_UG_SUPPORTED
MODULE_PARM(loglastcallstatus, "i");
MODULE_PARM_DESC(loglastcallstatus, "Log AT#UG command output after each connection");
#endif

static struct uart_driver hsf_reg = {
	owner:				THIS_MODULE,
#ifdef CONFIG_DEVFS_FS
	normal_name:		"ttySHSF%d",
	callout_name:		"cuaHSF%d",
#else
	normal_name:		"ttySHSF",
	callout_name:		"cuaHSF",
#endif
	normal_driver:		&hsf_tty_driver_normal,
	callout_driver:		&hsf_tty_driver_callout,
	table:				hsf_tty_table,
	termios:			hsf_termios,
	termios_locked:		hsf_termios_locked,
	minor:				HSFSERIALMINOR,
	nr:					NR_PORTS,
	port:				hsf_ports,
};

#ifndef CNXTSERIAL_INCLUDE_CORE
#define uart_init() 0
#define uart_exit() {}
#endif

#if defined(COMCTRL_MONITOR_POUND_UG_SUPPORTED) && defined(CONFIG_PROC_FS) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0))
static struct proc_dir_entry *hsf_proc_dir;
static struct proc_dir_entry *hsf_proc_unit_dir;
static struct proc_dir_entry *hsf_proc_lastcallstatus;

static int hsf_get_lastcallstatus(char *buf, char **start, off_t offset, int length)
{
	PORT_MONITOR_DATA monitorData;

	if(length > PAGE_SIZE)
		length = PAGE_SIZE;

	monitorData.dwSize = length;
	monitorData.pBuf = buf;

	if ((offset == 0) && hsf_monitor(COMCTRL_MONITOR_POUND_UG, &monitorData) == COM_STATUS_SUCCESS)
		return strlen(buf);
	else
		return -EIO;
}
#endif

static int __init
hsf_serial_init(void)
{
	int ret;

	hsf_reg.normal_major = serialmajor;
	hsf_reg.callout_major = calloutmajor;

	if(serialmajor == 0) {
		printk(KERN_ERR "%s: serialmajor parameter must be non-null\n", __FUNCTION__);
		return -EINVAL;
	}

	if(calloutmajor == 0) {
		printk(KERN_ERR "%s: calloutmajor parameter must be non-null\n", __FUNCTION__);
		return -EINVAL;
	}

	if(serialmajor == calloutmajor) {
		printk(KERN_ERR "%s: serialmajor and calloutmajor parameter values must differ\n", __FUNCTION__);
		return -EINVAL;
	}

	if ((ret = hsf_comctrl_init()) != 0)
		return ret;

	if ((ret = uart_init()) != 0) {
		hsf_comctrl_exit();
		return ret;
	}

	ret = uart_register_driver(&hsf_reg);
	if(ret) {
		uart_exit();
		hsf_comctrl_exit();
		return ret;
	}

#if defined(COMCTRL_MONITOR_POUND_UG_SUPPORTED) && defined(CONFIG_PROC_FS) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0))
	hsf_proc_dir = proc_mkdir("hsf", proc_root_driver);
	if(hsf_proc_dir) {
		hsf_proc_unit_dir = proc_mkdir("0", hsf_proc_dir);
		if(hsf_proc_unit_dir) {
			hsf_proc_lastcallstatus = create_proc_info_entry("lastcallstatus", 0, hsf_proc_unit_dir, hsf_get_lastcallstatus);
		}
	}
#endif

	return ret;
}

static void __exit
hsf_serial_exit(void)
{
#if defined(COMCTRL_MONITOR_POUND_UG_SUPPORTED) && defined(CONFIG_PROC_FS) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0))
	if(hsf_proc_lastcallstatus)
		remove_proc_entry("lastcallstatus", hsf_proc_unit_dir);
	if(hsf_proc_unit_dir)
		remove_proc_entry("0", hsf_proc_dir);
	if(hsf_proc_dir)
		remove_proc_entry("hsf", proc_root_driver);
#endif
	uart_unregister_driver(&hsf_reg);
	uart_exit();
	hsf_comctrl_exit();
}

module_init(hsf_serial_init);
module_exit(hsf_serial_exit);

EXPORT_NO_SYMBOLS;

MODULE_AUTHOR("Marc Boucher <marc@linuxant.com> for Conexant Systems Inc.");
MODULE_DESCRIPTION("HSF softmodem serial port driver");
MODULE_LICENSE("GPL");

#ifdef CNXTSERIAL_INCLUDE_CORE

#undef EXPORT_SYMBOL
#define EXPORT_SYMBOL(x)

#undef MODULE_DESCRIPTION
#define MODULE_DESCRIPTION(x)

#undef MODULE_LICENSE
#define MODULE_LICENSE(x)

#undef module_init
#define module_init(x)

#undef module_exit
#define module_exit(x)

#include "serial_core.c"
#endif

