New ark3116 driver - how to get included into kernel?

From: Bart Hartgers
Date: Thu Sep 17 2009 - 08:58:27 EST


(Sorry for sending an HTML-ized version of this mail before)

Hi All,

I managed to write an improved ark3116 driver after I figured out that
it is just an 16450 UART with some USB glue logic.

The attached files can be compiled outside the kernel tree, and work
for 2.6.31. However, I would like this driver to (eventually) end up
in the kernel tree. In order to get there, who should I sent patches
against what? I've contributed code to the kernel before, but not in
the last 5 or so years, so I am a bit out of touch.

Compared to the old ark3116 driver this one offers the following improvements:
Â- cts/rts handshake support
Â- break signalling
Â- line error detection

Since it is a big step from the previous driver, it made little sense
to modify that one, so I created new driver, named ark316new. Also
this means that both could coexist if the new one doesn't work for
someone.

This driver also includes support for an ark3116-based IrDA dongle,
tested by Ondrej Zary.

Thanks in advance for any input/help.

Groeten,
Bart

>>
>> I also tried to include the IrDA fixes for the old ark3116.c driver
>> that were posted on this list recently, but I could not test if
>> these work, since I don't have the proper hardware.

2009/8/31 Bart Hartgers <bart.hartgers@xxxxxxxxx>
>
> 2009/8/29 Ondrej Zary <linux@xxxxxxxxxxxxxxxxxxxx>:
> > On Sunday 23 August 2009 14:46:16 Bart Hartgers wrote:
> >> Hi All,
> >>
> >> I managed to write an improved ark3116 driver after I figured out that
> >> it is just an 16450 UART with some USB glue logic.
> >>
> >> Compared to the old ark3116 driver it offers the following improvements:
> >> - cts/rts handshake support
> >> - break signalling
> >> - line error detection
> >>
> >> Since it is a big step from the previous driver, it made little sense
> >> to modify that one, so I created new driver, named ark316new. Also
> >> this means that both could coexist if the new one doesn't work for
> >> someone.
> >>
> >> I have no datasheet, so the driver is entirely based on reverse
> >> engineering, and some testing by others would be a good idea.
> >>
> >> I also tried to include the IrDA fixes for the old ark3116.c driver
> >> that were posted on this list recently, but I could not test if
> >> these work, since I don't have the proper hardware.
> >>
> >> Because of this, I would appreciate reports of both success and
> >> failure in using this driver.
> >
> > Tested the driver today with that Gembird UIR-22.
> >
> > It did not compile neither in 2.6.30.1 nor in 2.6.31-rc8 with the following
> > error:
> >
> > ark3116new.c:790: error: unknown field shutdown specified in initializer
> > ark3116new/ark3116new.c:790: warning: initialization from incompatible pointer
> > type
> >
> > Commenting that line produced a module that worked:
> > usb 1-1: new full speed USB device using uhci_hcd and address 2
> > usb 1-1: configuration #0 chosen from 1 choice
> > usb 1-1: config 0 descriptor??
> > usbcore: registered new interface driver usbserial
> > usbserial: USB Serial Driver core
> > USB Serial support registered for ARK3116 RS232/IrDA
> > ark3116new 1-1:0.0: ARK3116 RS232/IrDA converter detected
> > usb 1-1: ark3116new using IrDA mode
> > usb 1-1: ARK3116 RS232/IrDA converter now attached to ttyUSB0
> > usbcore: registered new interface driver ark3116new
> > ark3116new:v0.1:USB ARK3116 serial/IrDA driver
> > usb 1-1: ark3116new don't know how to do software flow control
> > NET: Registered protocol family 23
> >
> > Ran "irattach /dev/ttyUSB0 -s" and then "obexftp -i -l" to list files on Nokia
> > 6230i. It worked fine and file transfer too. So the IrDA mode works.
> >
> > When I unplugged the device, irattach remained running and some error message
> > was written to console every few seconds (sorry, I haven't recorded it).
> > Killing irattach then resulted in oops. But this might be expected as the
> > shutdown function was commented out.
> >
> Hi Ondrej,
>
> Thanks for testing this!
>
> I later discovered that there is a change with respect to
> shutdown-functionality between 2.6.28 (on which I made the driver) and
> 2.6.31. I have to update the driver to 2.6.current anyway.
>
> > I think that this driver should replace current ark3116 driver. Having two
> > drivers for the same hardware is a bad thing.
>
> I agree. However, given the experimental nature of the driver, I would
> prefer some more testing. Then again, the original driver is lacks
> some features, and making ark3116new default would increase its
> exposure ;-).
>
> Groeten,
> Bart
>
> >
> > --
> > Ondrej Zary
> >
>
>
>
> --
> Bart Hartgers - New e-mail: bart.hartgers@xxxxxxxxx



--
Bart Hartgers - New e-mail: bart.hartgers@xxxxxxxxx



--
Bart Hartgers - New e-mail: bart.hartgers@xxxxxxxxx
obj-m := ark3116new.o

KDIR := /lib/modules/$(shell uname -r)/build
KMISC := /lib/modules/$(shell uname -r)/kernel/drivers/usb/serial
PWD := $(shell pwd)

EXTRA_CFLAGS += -I/usr/src/linux/drivers/usb/serial -I/usr/src/linux/include/linux/

modules:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) $(EXTRA_CFLAGS) modules

install: modules
install -d $(KMISC)
install -m 644 -c ark3116new.ko $(KMISC)
/sbin/depmod -a

clean:
rm -f *.mod.c *.mod *.o *.ko .*.cmd
rm -rf $(PWD)/.tmp_versions

/* -*- eval: c_set_style("linux") -*- */
/*
* Driver for the arkmicro 3116 usb2serial convertor chip.
*
* (C) Copyright 2009 by Bart Hartgers (bart.hartgers+ark3116@xxxxxxxxx)
*
* Supports full modem status lines, break, hardware flow control. Does not
* support software flow control, since I do not know how to enable it in hw.
*
* This driver is a new implementation. I initially dug into the old ark3116.c
* driver and suddenly realized the ark3116 is a 16450 with a USB interface
* glued to it. See comments at the bottom of this file.
*
* Some concepts and code borrowed from Simon Schulz' original ark3116.c.
*
*
* 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.
*
*/

#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/atomic.h>
#include <linux/ioctl.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/serial.h>
#include <linux/serial_reg.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/capability.h>


#define ARK_DEBUG 0

/*
* Version information
*/

#define DRIVER_VERSION "v0.2"
#define DRIVER_AUTHOR "Bart Hartgers <bart.hartgers+ark3116@xxxxxxxxx>"
#define DRIVER_DESC "USB ARK3116 serial/IrDA driver"
#define DRIVER_DEV_DESC "ARK3116 RS232/IrDA"
#define DRIVER_NAME "ark3116new"

/* usb timeout of 1 second */
#define ARK_TIMEOUT 1*HZ

static struct usb_device_id ark_id_table [] = {
{ USB_DEVICE(0x6547, 0x0232) }, /* RS232 */
{ USB_DEVICE(0x18ec, 0x3118) }, /* USB to IrDA */
{ }
};

static int debug=ARK_DEBUG;

MODULE_DEVICE_TABLE(usb, ark_id_table);

struct ark_private
{
/* for handling sleeping while waiting for
* msr change to happen
*/
wait_queue_head_t delta_msr_wait;
struct async_icount icount;
int irda; /* 1 for irda device */

/* protects hw register updates */
struct mutex lock;

int quot; /* baudrate divisor */
__u8 lcr; /* line control register value */
__u8 hcr; /* handshake control register (0x8)
* value */
/* flags - updated asynchronously */
atomic_t mcr; /* mcr value */
atomic_t msr;
atomic_t lsr;
};

static int ark_write_reg( struct usb_serial *serial, unsigned reg, __u8 val )
{
int result;
/* 0xfe 0x40 are magic values taken from original ark3116.c */
result = usb_control_msg( serial->dev,
usb_sndctrlpipe(serial->dev,0),
0xfe, 0x40, val, reg,
NULL, 0, ARK_TIMEOUT );
return result;
}

static int ark_read_reg( struct usb_serial *serial, unsigned reg, unsigned char *buf )
{
int result;
/* 0xfe 0xc0 are magic values taken from original ark3116.c */
result = usb_control_msg( serial->dev,
usb_rcvctrlpipe(serial->dev,0),
0xfe, 0xc0, 0, reg,
buf, 1, ARK_TIMEOUT );
if (result<0)
return result;
else
return buf[0];
}

static inline void ark_atomic_set_clear( unsigned set, unsigned clear, atomic_t *at )
{
if ((clear==0) && (set==0))
return;
#if defined(atomic_set_mask)
if (clear==0) {
atomic_set_mask( set, at );
return;
}
#endif
#if defined(atomic_clear_mask)
if (set==0) {
atomic_clear_mask( clear, at );
return;
}
#endif
/* operation needs to be atomic */
for(;;) {
register unsigned old=atomic_read( at );
register unsigned prev=atomic_cmpxchg( at, old, (old|set)&(~clear) );
if (likely(prev==old))
break;
}
}

static void ark_update_msr( struct usb_serial_port *port, __u8 msr )
{
struct ark_private *priv = usb_get_serial_port_data(port);

atomic_set(&priv->msr, msr );

if (msr & UART_MSR_ANY_DELTA) {
/* update input line counters */
if (msr & UART_MSR_DCTS)
priv->icount.cts++;
if (msr & UART_MSR_DDSR)
priv->icount.dsr++;
if (msr & UART_MSR_DDCD)
priv->icount.dcd++;
if (msr & UART_MSR_TERI)
priv->icount.rng++;
wake_up_interruptible(&priv->delta_msr_wait);
}
#if 0
/* Handle CTS flow control.
* I am not sure if this is really necessary:
* Because of hw handshake, the write URB will simply block
* when CTS is deasserted
*/
if (msr & UART_MSR_CTS) {
struct tty_struct *tty = tty_port_tty_get(&port->port);
if (tty && C_CRTSCTS(tty))
tty_wakeup(tty);
tty_kref_put(tty);
}
#endif
}

static void ark_combine_lsr( struct usb_serial_port *port, __u8 lsr )
{
struct ark_private *priv = usb_get_serial_port_data(port);

/* combine bits */
ark_atomic_set_clear( lsr, 0, &priv->lsr );

if (lsr&UART_LSR_BRK_ERROR_BITS) {
if (lsr & UART_LSR_BI)
priv->icount.brk++;
if (lsr & UART_LSR_FE)
priv->icount.frame++;
if (lsr & UART_LSR_PE)
priv->icount.parity++;
if (lsr & UART_LSR_OE)
priv->icount.overrun++;
}
}

static void ark_break_ctl( struct tty_struct *tty, int break_state )
{
struct usb_serial_port *port = tty->driver_data;
struct ark_private *priv = usb_get_serial_port_data(port);

/* LCR is also used for other things: protect access */
mutex_lock( &priv->lock );

if (break_state)
priv->lcr|=UART_LCR_SBC;
else
priv->lcr&=UART_LCR_SBC;

ark_write_reg( port->serial, UART_LCR, priv->lcr );

mutex_unlock( &priv->lock );
}

inline int calc_divisor( int bps )
{
/* Original ark3116 made some exceptions in rounding here
* because windows did the same. Assume that is not really
* necessary.
* Crystal is 12MHz, probably because of USB, but we divide by 4?
*/
return (12000000 + 2*bps) / (4*bps);
}

static void ark_set_termios( struct tty_struct *tty,
struct usb_serial_port *port,
struct ktermios *old_termios)
{
struct usb_serial *serial = port->serial;
struct ark_private *priv = usb_get_serial_port_data(port);
struct ktermios *termios = tty->termios;
unsigned cflag=termios->c_cflag;
int bps = tty_get_baud_rate(tty);
int quot;
__u8 lcr, hcr, eval;

/* set data bit count */
switch(cflag & CSIZE) {
case CS5:
lcr=UART_LCR_WLEN5;
break;
case CS6:
lcr=UART_LCR_WLEN6;
break;
case CS7:
lcr=UART_LCR_WLEN7;
break;
default:
case CS8:
lcr=UART_LCR_WLEN8;
break;
}
if (cflag & CSTOPB)
lcr |= UART_LCR_STOP;
if (cflag & PARENB)
lcr |= UART_LCR_PARITY;
if (!(cflag & PARODD))
lcr |= UART_LCR_EPAR;
#ifdef CMSPAR
if (cflag & CMSPAR)
lcr |= UART_LCR_SPAR;
#endif
/* handshake control */
hcr = (cflag & CRTSCTS) ? 0x03 : 0x00;

/* calc baudrate */
dbg("%s - setting bps to %d",__func__,bps);
eval = 0;
switch(bps) {
case 0:
quot = calc_divisor(9600);
break;
default:
if ((bps<75) || (bps>3000000)) {
bps = 9600;
}
quot = calc_divisor( bps );
break;
case 460800:
eval = 1;
quot = calc_divisor( bps );
break;
case 921600:
eval = 2;
quot = calc_divisor( bps );
break;
}

/* Update state: synchronize */
mutex_lock( &priv->lock );

/* keep old LCR_SBC bit */
lcr|=(priv->lcr & UART_LCR_SBC);

dbg("%s - setting hcr:0x%02x,lcr:0x%02x,quot:%d", __func__, hcr, lcr, quot );

/* handshake control */
if (priv->hcr!=hcr) {
priv->hcr=hcr;
ark_write_reg( serial, 0x8, hcr );
}

/* baudrate */
if (priv->quot!=quot) {
priv->quot=quot;
priv->lcr=lcr; /* need to write lcr anyway */

/* disable DMA since transmit/receive is
* shadowed by UART_DLL
*/
ark_write_reg( serial, UART_FCR, 0 );

ark_write_reg( serial, UART_LCR,
lcr|UART_LCR_DLAB);
ark_write_reg( serial, UART_DLL, quot & 0xff );
ark_write_reg( serial, UART_DLM, (quot>>8) & 0xff );

/* restore lcr */
ark_write_reg( serial, UART_LCR, lcr );
/* magic baudrate thingy: not sure what it does,
* but windows does this as well.
*/
ark_write_reg( serial, 0xe, eval );

/* enable DMA */
ark_write_reg( serial, UART_FCR, UART_FCR_DMA_SELECT );
} else if (priv->lcr!=lcr) {
priv->lcr=lcr;
ark_write_reg( serial, UART_LCR, lcr );
}

mutex_unlock( &priv->lock );

/* check for software flow control */
if (I_IXOFF(tty) || I_IXON(tty)) {
dev_warn( &serial->dev->dev,
"%s: don't know how to do software flow control\n",
KBUILD_MODNAME );
}

/* Don't rewrite B0 */
if (tty_termios_baud_rate(termios))
tty_termios_encode_baud_rate(termios, bps, bps);
}

static int ark_tiocmget(struct tty_struct *tty, struct file *file)
{
struct usb_serial_port *port = tty->driver_data;
struct ark_private *priv = usb_get_serial_port_data(port);

/* read modem status */
unsigned status = atomic_read( &priv->msr );
/* modem control is output */
unsigned ctrl = atomic_read( &priv->mcr );

return (status & UART_MSR_DSR ? TIOCM_DSR : 0) |
(status & UART_MSR_CTS ? TIOCM_CTS : 0) |
(status & UART_MSR_RI ? TIOCM_RI : 0) |
(status & UART_MSR_DCD ? TIOCM_CD : 0) |
(ctrl & UART_MCR_DTR ? TIOCM_DTR : 0) |
(ctrl & UART_MCR_RTS ? TIOCM_RTS : 0) |
(ctrl & UART_MCR_OUT1 ? TIOCM_OUT1 : 0) |
(ctrl & UART_MCR_OUT2 ? TIOCM_OUT2 : 0);
}

static int ark_tiocmset(struct tty_struct *tty, struct file *file,
unsigned set, unsigned clr)
{
struct usb_serial_port *port = tty->driver_data;
struct ark_private *priv = usb_get_serial_port_data(port);
unsigned setmask=0;
unsigned clrmask=0;

if (set & TIOCM_RTS)
setmask|=UART_MCR_RTS;
if (set & TIOCM_DTR)
setmask|=UART_MCR_DTR;
if (set & TIOCM_OUT1)
setmask|=UART_MCR_OUT1;
if (set & TIOCM_OUT2)
setmask|=UART_MCR_OUT2;
if (clr & TIOCM_RTS)
clrmask|=UART_MCR_RTS;
if (clr & TIOCM_DTR)
clrmask|=UART_MCR_DTR;
if (clr & TIOCM_OUT1)
clrmask|=UART_MCR_OUT1;
if (clr & TIOCM_OUT2)
clrmask|=UART_MCR_OUT2;

ark_atomic_set_clear( setmask, clrmask, &priv->mcr );

/* unfortunately, we need the mutex, to make sure that the value
* in priv->mcr is actually the one in the hardware
*/

mutex_lock( &priv->lock );
ark_write_reg( port->serial, UART_MCR, atomic_read(&priv->mcr) );
mutex_unlock( &priv->lock );

return 0;
}

static void ark_read_int_callback( struct urb *urb )
{
struct usb_serial_port *port = urb->context;
int status = urb->status;
const __u8 *data=urb->transfer_buffer;
int result;

switch (status) {
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
/* this urb is terminated, clean up */
dbg("%s - urb shutting down with status: %d",
__func__, status);
return;
default:
dbg("%s - nonzero urb status received: %d",
__func__, status);
break;
case 0: /* success */
/* discovered this by trail and error... */
if ((urb->actual_length==4) && (data[0]==0xe8))
{
const __u8 id=data[1]&UART_IIR_ID;
dbg("%s: iir=%02x",__func__,data[1]);
if (id==UART_IIR_MSI) {
dbg("%s: msr=%02x",__func__,data[3]);
ark_update_msr( port, data[3] );
break;
} else if (id==UART_IIR_RLSI) {
dbg("%s: lsr=%02x",__func__,data[2]);
ark_combine_lsr( port, data[2] );
break;
}
}
/*
* Not sure what this does yet...
*/
usb_serial_debug_data( debug, &port->dev,
__func__,
urb->actual_length,
urb->transfer_buffer);
break;
}

result = usb_submit_urb(urb, GFP_ATOMIC);
if (result)
dev_err(&urb->dev->dev,
"%s - Error %d submitting interrupt urb\n",
__func__, result);
}


/* Data comes in via the bulk (data) URB, erors/interrupts via the int URB.
* This means that we cannot be sure which data byte has an associated error
* condition, so we report an error for all data in the next bulk read.
*
* Actually, there might even be a window between the bulk data leaving the
* ark and reading/resetting the lsr in the read_bulk_callback where an
* interrupt for the next data block could come in.
* Without somekind of handshaking on the ark, we would have to report the
* error for the next block of data as well...
* For now, let's pretend this can't happen.
*/

static void send_to_tty( struct tty_struct *tty,
const unsigned char *chars,
size_t size, char flag )
{
if (size==0)
return;
if (flag == TTY_NORMAL) {
tty_insert_flip_string(tty, chars, size);
} else {
int i;
for(i=0; i<size; ++i)
tty_insert_flip_char(tty, chars[i], flag);
}
}

static void ark_read_bulk_callback(struct urb *urb)
{
struct usb_serial_port *port = urb->context;
struct ark_private *priv = usb_get_serial_port_data(port);
const __u8 *data = urb->transfer_buffer;
int status = urb->status;
struct tty_struct *tty;
unsigned long flags;
int result;
char flag;
__u8 lsr;

switch (status) {
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
/* this urb is terminated, clean up */
dbg("%s - urb shutting down with status: %d",
__func__, status);
return;
default:
dbg("%s - nonzero urb status received: %d",
__func__, status);
break;
case 0: /* success */

lsr = atomic_read(&priv->lsr);
ark_atomic_set_clear( 0, UART_LSR_BRK_ERROR_BITS,
&priv->lsr );

/* Throttle the device if requested by tty */
spin_lock_irqsave(&port->lock, flags);
port->throttled = port->throttle_req;
if (port->throttled) {
spin_unlock_irqrestore(&port->lock, flags);
return;
} else
spin_unlock_irqrestore(&port->lock, flags);

if (unlikely(lsr & UART_LSR_BI))
flag = TTY_BREAK;
else if (unlikely(lsr & UART_LSR_PE))
flag = TTY_PARITY;
else if (unlikely(lsr & UART_LSR_FE))
flag = TTY_FRAME;
else
flag = TTY_NORMAL;

tty = tty_port_tty_get(&port->port);
if (tty) {
tty_buffer_request_room(tty, urb->actual_length + 1);
/* overrun is special, not associated with a char */
if (unlikely(lsr & UART_LSR_OE))
tty_insert_flip_char(tty, 0, TTY_OVERRUN);
send_to_tty(tty, data, urb->actual_length, flag );
tty_flip_buffer_push(tty);
tty_kref_put(tty);
}
}
/* Continue reading from device */
#if 0
/* does not seem to be needed */
usb_fill_bulk_urb(urb, port->serial->dev,
usb_rcvbulkpipe(port->serial->dev,
port->bulk_in_endpointAddress),
urb->transfer_buffer,
urb->transfer_buffer_length,
ark_read_bulk_callback,
port);
#endif
result = usb_submit_urb(urb, GFP_ATOMIC);
if (result)
dev_err(&urb->dev->dev, "%s - failed resubmitting"
" read urb, error %d\n", __func__, result);
}

static int ark_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct usb_serial_port *port = tty->driver_data;
struct ark_private *priv = usb_get_serial_port_data(port);

switch(cmd) {
case TIOCMIWAIT:
for(;;) {
struct async_icount prev=priv->icount;
interruptible_sleep_on(&priv->delta_msr_wait);
/* see if a signal did it */
if (signal_pending(current))
return -ERESTARTSYS;
if ((prev.rng==priv->icount.rng) &&
(prev.dsr==priv->icount.dsr) &&
(prev.dcd==priv->icount.dcd) &&
(prev.cts==priv->icount.cts))
return -EIO;
if ((arg & TIOCM_RNG && (prev.rng!=priv->icount.rng)) ||
(arg & TIOCM_DSR && (prev.dsr!=priv->icount.dsr)) ||
(arg & TIOCM_CD && (prev.dcd!=priv->icount.dcd)) ||
(arg & TIOCM_CTS && (prev.cts!=priv->icount.cts)))
return 0;
}
break;
case TIOCGICOUNT: {
struct serial_icounter_struct icount;
struct async_icount cnow=priv->icount;
memset(&icount, 0, sizeof(icount));
icount.cts = cnow.cts;
icount.dsr = cnow.dsr;
icount.rng = cnow.rng;
icount.dcd = cnow.dcd;
icount.rx = cnow.rx;
icount.tx = cnow.tx;
icount.frame = cnow.frame;
icount.overrun = cnow.overrun;
icount.parity = cnow.parity;
icount.brk = cnow.brk;
icount.buf_overrun = cnow.buf_overrun;
if (copy_to_user((void __user *)arg, &icount, sizeof(icount)))
return -EFAULT;
return 0;
}
}
return -ENOIOCTLCMD;
}

static void ark_close( struct usb_serial_port *port )
{
struct usb_serial *serial = port->serial;

/* disable DMA */
ark_write_reg( serial, UART_FCR, 0 );

/* deactivate interrupts */
ark_write_reg( serial, UART_IER, 0 );

if (serial->dev) {
/* shutdown any bulk reads that might be going on */
if (serial->num_bulk_out)
usb_kill_urb(port->write_urb);
if (serial->num_bulk_in)
usb_kill_urb(port->read_urb);
if (serial->num_interrupt_in)
usb_kill_urb(port->interrupt_in_urb);
}
}

static int ark_open(struct tty_struct *tty, struct usb_serial_port *port,
struct file *filp)
{
struct ark_private *priv = usb_get_serial_port_data(port);
struct usb_serial *serial = port->serial;
int result;
unsigned char *buf;

buf = kmalloc( 4, GFP_KERNEL );
if (buf==NULL)
return -ENOMEM;

result = usb_serial_generic_open(tty, port, filp);
if (result) {
dbg("%s - usb_serial_generic_open failed: %d", __func__, result );
goto error_out;
}

/* setup termios */
if (tty)
ark_set_termios(tty, port, NULL );

/* remove any data still left: also clears error state */
ark_read_reg( serial, UART_RX, buf );

/* read modem status */
atomic_set( &priv->msr, ark_read_reg( serial, UART_MSR, buf ) );
/* read line status */
atomic_set( &priv->lsr, ark_read_reg( serial, UART_LSR, buf ) );

result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
if (result) {
dev_err(&port->dev, "submit irq_in urb failed %d\n",
result);
ark_close( port );
goto error_out;
}

/* activate interrupts */
ark_write_reg( port->serial, UART_IER, UART_IER_MSI|UART_IER_RLSI );

/* enable DMA */
ark_write_reg( port->serial, UART_FCR, UART_FCR_DMA_SELECT );

error_out:
kfree(buf);
return result;
}

static int ark_is_irda( struct usb_serial *serial )
{
__u16 vendor = le16_to_cpu(serial->dev->descriptor.idVendor);
__u16 product = le16_to_cpu(serial->dev->descriptor.idProduct);
if ((product==0x3118) && (vendor==0x18ec))
return 1;
return 0;
}


static int ark_attach( struct usb_serial *serial )
{
struct usb_serial_port *port=serial->port[0];
struct ark_private *priv;

/* make sure we have our end-points */
if ((serial->num_bulk_in==0) ||
(serial->num_bulk_out==0) ||
(serial->num_interrupt_in==0)) {
dev_err( &serial->dev->dev,
"%s - missing endpoint - "
"bulk in: %d, bulk out: %d, int in %d\n",
KBUILD_MODNAME,
serial->num_bulk_in,
serial->num_bulk_out,
serial->num_interrupt_in );
return -EINVAL;
}

priv = kzalloc( sizeof(struct ark_private),
GFP_KERNEL);
if (!priv)
return -ENOMEM;

init_waitqueue_head(&priv->delta_msr_wait);
mutex_init(&priv->lock);

priv->irda = ark_is_irda( serial );

usb_set_serial_port_data(port, priv);

/* setup the hardware */
ark_write_reg( serial, UART_IER, 0 );
/* disable DMA */
ark_write_reg( serial, UART_FCR, 0 );
/* handshake control */
priv->hcr = 0;
ark_write_reg( serial, 0x8 , 0 );
/* modem control */
atomic_set( &priv->mcr, 0 );
ark_write_reg( serial, UART_MCR, 0 );

if (priv->irda==0) {
ark_write_reg( serial, 0xb , 0 );
} else {
ark_write_reg( serial, 0xb , 1 );
ark_write_reg( serial, 0xc , 0 );
ark_write_reg( serial, 0xd , 0x41 );
ark_write_reg( serial, 0xa , 0x01 );
}

/* setup baudrate */
ark_write_reg( serial, UART_LCR, UART_LCR_DLAB );

/* setup for 9600 8N1 */
priv->quot=calc_divisor( 9600 );
ark_write_reg( serial, UART_DLL, priv->quot & 0xff );
ark_write_reg( serial, UART_DLM, (priv->quot>>8) & 0xff );

priv->lcr = UART_LCR_WLEN8;
ark_write_reg( serial, UART_LCR, UART_LCR_WLEN8 );

ark_write_reg( serial, 0x0e , 0 );

if (priv->irda) {
ark_write_reg( serial, 0x9 , 0 );
}

dev_info( &serial->dev->dev,
"%s using %s mode\n",
KBUILD_MODNAME,
priv->irda ? "IrDA" : "RS232" );
return 0;
}

static void ark_release( struct usb_serial *serial )
{
struct usb_serial_port *port=serial->port[0];
struct ark_private *priv = usb_get_serial_port_data(port);

/* device is closed, so URBs and DMA should be down */

usb_set_serial_port_data(port, NULL );

mutex_destroy( &priv->lock );

kfree( priv );
}

static struct usb_driver ark_driver = {
.name = DRIVER_NAME,
.probe = usb_serial_probe,
.disconnect = usb_serial_disconnect,
.id_table = ark_id_table,
.no_dynamic_id = 1,
};


static struct usb_serial_driver ark_device = {
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
},
.description = DRIVER_DEV_DESC,
.id_table = ark_id_table,
.usb_driver = &ark_driver,
.num_ports = 1,
.attach = ark_attach,
.release = ark_release,
.set_termios = ark_set_termios,
.tiocmget = ark_tiocmget,
.tiocmset = ark_tiocmset,
.ioctl = ark_ioctl,
.open = ark_open,
.close = ark_close,
.break_ctl = ark_break_ctl,
.read_int_callback = ark_read_int_callback,
.read_bulk_callback = ark_read_bulk_callback,
};


static int __init ark_init(void)
{
int retval;

retval = usb_serial_register(&ark_device);
if (retval==0) {
retval = usb_register(&ark_driver);
if (retval==0) {
printk( KERN_INFO "%s:"
DRIVER_VERSION ":"
DRIVER_DESC "\n",
KBUILD_MODNAME );
return retval;
}
usb_serial_deregister(&ark_device);
}
return retval;
}

static void __exit ark_exit(void)
{
usb_deregister(&ark_driver);
usb_serial_deregister(&ark_device);
}

module_init(ark_init);
module_exit(ark_exit);

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");

module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Enable debug");

/*
* The following describes what I learned from studying the old
* ark3116.c driver, disassembling the windows driver, and some lucky
* guesses. Since I do not have any datasheet or other
* documentation, inaccuracies are almost guaranteed.
*
* Some specs for the ARK3116 can be found here:
* http://web.archive.org/web/20060318000438/www.arkmicro.com/en/products/view.php?id=10
* On that page, 2 GPIO pins are mentioned: I assume these are the
* OUT1 and OUT2 pins of the UART, so I added support for those
* through the MCR. Since the pins are not available on my hardware,
* I could not verify this.
* Also, it states there is "on-chip hardware flow control". I have
* discovered how to enable that. Unfortunately, I do not know how to
* enable XON/XOFF (software) flow control, which would need support
* from the chip as well to work. Because of the wording on the web
* page there is a real possibility the chip simply does not support
* software flow control.
*
* I got my ark3116 as part of a mobile phone adapter cable. On the
* PCB, the following numbered contacts are present:
*
* 1:- +5V
* 2:o DTR
* 3:i RX
* 4:i DCD
* 5:o RTS
* 6:o TX
* 7:i RI
* 8:i DSR
* 10:- 0V
* 11:i CTS
*
* On my chip, all signals seem to be 3.3V, but 5V tolerant. But that
* may be different for the one you have ;-).
*
* The windows driver limits the registers to 0-F, so I assume there
* are actually 16 present on the device.
*
* On an UART interrupt, 4 bytes of data come in on the interrupt
* endpoint. The bytes are 0xe8 IIR LSR MSR.
*
* The baudrate seems to be generated from the 12MHz crystal, using
* 4-times subsampling. So quot=12e6/(4*baud). Also see description
* of register E.
*
* Registers 0-7:
* These seem to be the same as for a regular 16450. The FCR is set
* to UART_FCR_DMA_SELECT (0x8), I guess to enable transfers between
* the UART and the USB bridge/DMA engine.
*
* Register 8:
* By trial and error, I found out that bit 0 enables hardware CTS,
* stopping TX when CTS is +5V. Bit 1 does the same for RTS, making
* RTS +5V when the 3116 cannot transfer the data to the USB bus
* (verified by disabling the reading URB). Note that as far as I can
* tell, the windows driver does NOT use this, so there might be some
* hardware bug or something.
*
* According to a patch provided here
* (http://lkml.org/lkml/2009/7/26/56), the ARK3116 can also be used
* as an IrDA dongle. Since I do not have such a thing, I could not
* investigate that aspect. However, I can speculate ;-).
*
* - IrDA encodes data differently than RS232. Most likely, one of
* the bits in registers 9..E enables the IR ENDEC (encoder/decoder).
* - Depending on the IR transceiver, the input and output need to be
* inverted, so there are probably bits for that as well.
* - IrDA is half-duplex, so there should be a bit for selecting that.
*
* This still leaves at least two registers unaccounted for. Perhaps
* The chip can do XON/XOFF or CRC in HW?
*
* Register 9:
* Set to 0x00 for IrDA, when the baudrate is initialised.
*
* Register A:
* Set to 0x01 for IrDA, at init.
*
* Register B:
* Set to 0x01 for IrDA, 0x00 for RS232, at init.
*
* Register C:
* Set to 00 for IrDA, at init.
*
* Register D:
* Set to 0x41 for IrDA, at init.
*
* Register E:
* Somekind of baudrate override. The windows driver seems to set
* this to 0x00 for normal baudrates, 0x01 for 460800, 0x02 for 921600.
* Since 460800 and 921600 cannot be obtained by dividing 3MHz by an integer,
* it could be somekind of subdivisor thingy.
* However,it does not seem to do anything: selecting 921600 (divisor 3,
* reg E=2), still gets 1 MHz. I also checked if registers 9, C or F would
* work, but they don't.
*
* Register F: unknown
*/