tiny4412 串口驱动分析五 --- LDD3上TTY驱动程序源码

关于tty这部分请参考:

《Linux设备驱动开发详解 第二版》第14章 Linux终端设备驱动

《精通Linux设备驱动程序开发》第6章 串行设备驱动程序

《Linux设备驱动程序 第三版》第18章 TTY驱动程序

 

下面是一些串口相关的文档:

 http://pan.baidu.com/s/1mg20Umc

Makefile:

# Comment/uncomment the following line to disable/enable debugging
#DEBUG = y


# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
  DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines
else
  DEBFLAGS = -O2
endif

EXTRA_CFLAGS += $(DEBFLAGS)
EXTRA_CFLAGS += -I..

ifneq ($(KERNELRELEASE),)
# call from kernel build system

obj-m    := tiny_tty.o tiny_serial.o

else

#KERNELDIR ?= /lib/modules/$(shell uname -r)/build
KERNELDIR ?= /root/Tiny4412_android_4_1_2/linux-3.0.31
PWD       := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

depend .depend dep:
    $(CC) $(EXTRA_CFLAGS) -M *.c > .depend


ifeq (.depend,$(wildcard .depend))
include .depend
endif

tiny_tty.c

/*
 * Tiny TTY driver
 *
 * Copyright (C) 2002-2004 Greg Kroah-Hartman (greg@kroah.com)
 *
 *    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, version 2 of the License.
 *
 * This driver shows how to create a minimal tty driver.  It does not rely on
 * any backing hardware, but creates a timer that emulates data being received
 * from some kind of hardware.
 */

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>


#define DRIVER_VERSION "v2.0"
#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>"
#define DRIVER_DESC "Tiny TTY driver"

/* Module information */
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");

#define DELAY_TIME        HZ * 2    /* 2 seconds per character */
#define TINY_DATA_CHARACTER    't'

#define TINY_TTY_MAJOR        240    /* experimental range */
#define TINY_TTY_MINORS        4    /* only have 4 devices */

struct tiny_serial {
    struct tty_struct    *tty;        /* pointer to the tty for this device */
    int            open_count;    /* number of times this port has been opened */
    struct semaphore    sem;        /* locks this structure */
    struct timer_list    *timer;

    /* for tiocmget and tiocmset functions */
    int            msr;        /* MSR shadow */
    int            mcr;        /* MCR shadow */

    /* for ioctl fun */
    struct serial_struct    serial;
    wait_queue_head_t    wait;
    struct async_icount    icount;
};

static struct tiny_serial *tiny_table[TINY_TTY_MINORS];    /* initially all NULL */


static void tiny_timer(unsigned long timer_data)
{
    struct tiny_serial *tiny = (struct tiny_serial *)timer_data;
    struct tty_struct *tty;
    int i;
    char data[1] = {TINY_DATA_CHARACTER};
    int data_size = 1;

    if (!tiny)
        return;

    tty = tiny->tty;

    /* send the data to the tty layer for users to read.  This doesn't
     * actually push the data through unless tty->low_latency is set */
    for (i = 0; i < data_size; ++i) {
        if (!tty_buffer_request_room(tty, 1))
            tty_flip_buffer_push(tty);
        tty_insert_flip_char(tty, data[i], TTY_NORMAL);
    }
    tty_flip_buffer_push(tty);

    /* resubmit the timer again */
    tiny->timer->expires = jiffies + DELAY_TIME;
    add_timer(tiny->timer);
}

static int tiny_open(struct tty_struct *tty, struct file *file)
{
    struct tiny_serial *tiny;
    struct timer_list *timer;
    int index;

    /* initialize the pointer in case something fails */
    tty->driver_data = NULL;

    /* get the serial object associated with this tty pointer */
    index = tty->index;
    tiny = tiny_table[index];
    if (tiny == NULL) {
        /* first time accessing this device, let's create it */
        tiny = kmalloc(sizeof(*tiny), GFP_KERNEL);
        if (!tiny)
            return -ENOMEM;

        sema_init(&tiny->sem, 1);
        tiny->open_count = 0;
        tiny->timer = NULL;

        tiny_table[index] = tiny;
    }

    down(&tiny->sem);

    /* save our structure within the tty structure */
    tty->driver_data = tiny;
    tiny->tty = tty;

    ++tiny->open_count;
    if (tiny->open_count == 1) {
        /* this is the first time this port is opened */
        /* do any hardware initialization needed here */

        /* create our timer and submit it */
        if (!tiny->timer) {
            timer = kmalloc(sizeof(*timer), GFP_KERNEL);
            if (!timer) {
                up(&tiny->sem);
                return -ENOMEM;
            }
            tiny->timer = timer;
        }
        init_timer(tiny->timer);
        tiny->timer->data = (unsigned long )tiny;
        tiny->timer->expires = jiffies + DELAY_TIME;
        tiny->timer->function = tiny_timer;
        add_timer(tiny->timer);
    }

    up(&tiny->sem);
    return 0;
}

static void do_close(struct tiny_serial *tiny)
{
    down(&tiny->sem);

    if (!tiny->open_count) {
        /* port was never opened */
        goto exit;
    }

    --tiny->open_count;
    if (tiny->open_count <= 0) {
        /* The port is being closed by the last user. */
        /* Do any hardware specific stuff here */

        /* shut down our timer */
        del_timer(tiny->timer);
    }
exit:
    up(&tiny->sem);
}

static void tiny_close(struct tty_struct *tty, struct file *file)
{
    struct tiny_serial *tiny = tty->driver_data;

    if (tiny)
        do_close(tiny);
}    

static int tiny_write(struct tty_struct *tty, 
              const unsigned char *buffer, int count)
{
    struct tiny_serial *tiny = tty->driver_data;
    int i;
    int retval = -EINVAL;

    if (!tiny)
        return -ENODEV;

    down(&tiny->sem);

    if (!tiny->open_count)
        /* port was not opened */
        goto exit;

    /* fake sending the data out a hardware port by
     * writing it to the kernel debug log.
     */
    printk(KERN_DEBUG "%s - ", __FUNCTION__);
    for (i = 0; i < count; ++i)
        printk("%02x ", buffer[i]);
    printk("
");
        
exit:
    up(&tiny->sem);
    return retval;
}

static int tiny_write_room(struct tty_struct *tty) 
{
    struct tiny_serial *tiny = tty->driver_data;
    int room = -EINVAL;

    if (!tiny)
        return -ENODEV;

    down(&tiny->sem);
    
    if (!tiny->open_count) {
        /* port was not opened */
        goto exit;
    }

    /* calculate how much room is left in the device */
    room = 255;

exit:
    up(&tiny->sem);
    return room;
}

#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

static void tiny_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
{
    unsigned int cflag;

    cflag = tty->termios->c_cflag;

    /* check that they really want us to change something */
    if (old_termios) {
        if ((cflag == old_termios->c_cflag) &&
            (RELEVANT_IFLAG(tty->termios->c_iflag) == 
             RELEVANT_IFLAG(old_termios->c_iflag))) {
            printk(KERN_DEBUG " - nothing to change...
");
            return;
        }
    }

    /* get the byte size */
    switch (cflag & CSIZE) {
        case CS5:
            printk(KERN_DEBUG " - data bits = 5
");
            break;
        case CS6:
            printk(KERN_DEBUG " - data bits = 6
");
            break;
        case CS7:
            printk(KERN_DEBUG " - data bits = 7
");
            break;
        default:
        case CS8:
            printk(KERN_DEBUG " - data bits = 8
");
            break;
    }
    
    /* determine the parity */
    if (cflag & PARENB)
        if (cflag & PARODD)
            printk(KERN_DEBUG " - parity = odd
");
        else
            printk(KERN_DEBUG " - parity = even
");
    else
        printk(KERN_DEBUG " - parity = none
");

    /* figure out the stop bits requested */
    if (cflag & CSTOPB)
        printk(KERN_DEBUG " - stop bits = 2
");
    else
        printk(KERN_DEBUG " - stop bits = 1
");

    /* figure out the hardware flow control settings */
    if (cflag & CRTSCTS)
        printk(KERN_DEBUG " - RTS/CTS is enabled
");
    else
        printk(KERN_DEBUG " - RTS/CTS is disabled
");
    
    /* determine software flow control */
    /* if we are implementing XON/XOFF, set the start and 
     * stop character in the device */
    if (I_IXOFF(tty) || I_IXON(tty)) {
        unsigned char stop_char  = STOP_CHAR(tty);
        unsigned char start_char = START_CHAR(tty);

        /* if we are implementing INBOUND XON/XOFF */
        if (I_IXOFF(tty))
            printk(KERN_DEBUG " - INBOUND XON/XOFF is enabled, "
                "XON = %2x, XOFF = %2x", start_char, stop_char);
        else
            printk(KERN_DEBUG" - INBOUND XON/XOFF is disabled");

        /* if we are implementing OUTBOUND XON/XOFF */
        if (I_IXON(tty))
            printk(KERN_DEBUG" - OUTBOUND XON/XOFF is enabled, "
                "XON = %2x, XOFF = %2x", start_char, stop_char);
        else
            printk(KERN_DEBUG" - OUTBOUND XON/XOFF is disabled");
    }

    /* get the baud rate wanted */
    printk(KERN_DEBUG " - baud rate = %d", tty_get_baud_rate(tty));
}

/* Our fake UART values */
#define MCR_DTR        0x01
#define MCR_RTS        0x02
#define MCR_LOOP    0x04
#define MSR_CTS        0x08
#define MSR_CD        0x10
#define MSR_RI        0x20
#define MSR_DSR        0x40

static int tiny_tiocmget(struct tty_struct *tty)
{
    struct tiny_serial *tiny = tty->driver_data;

    unsigned int result = 0;
    unsigned int msr = tiny->msr;
    unsigned int mcr = tiny->mcr;

    result = ((mcr & MCR_DTR)  ? TIOCM_DTR  : 0) |    /* DTR is set */
             ((mcr & MCR_RTS)  ? TIOCM_RTS  : 0) |    /* RTS is set */
             ((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) |    /* LOOP is set */
             ((msr & MSR_CTS)  ? TIOCM_CTS  : 0) |    /* CTS is set */
             ((msr & MSR_CD)   ? TIOCM_CAR  : 0) |    /* Carrier detect is set*/
             ((msr & MSR_RI)   ? TIOCM_RI   : 0) |    /* Ring Indicator is set */
             ((msr & MSR_DSR)  ? TIOCM_DSR  : 0);    /* DSR is set */

    return result;
}

static int tiny_tiocmset(struct tty_struct *tty, unsigned int set,
             unsigned int clear)
{
    struct tiny_serial *tiny = tty->driver_data;
    unsigned int mcr = tiny->mcr;

    if (set & TIOCM_RTS)
        mcr |= MCR_RTS;
    if (set & TIOCM_DTR)
        mcr |= MCR_RTS;

    if (clear & TIOCM_RTS)
        mcr &= ~MCR_RTS;
    if (clear & TIOCM_DTR)
        mcr &= ~MCR_RTS;

    /* set the new MCR value in the device */
    tiny->mcr = mcr;
    return 0;
}

static int tiny_proc_show(struct seq_file *m, void *v)
{
    struct tiny_serial *tiny;
    int i;

    seq_printf(m, "tinyserinfo:1.0 driver:%s
", DRIVER_VERSION);
    for (i = 0; i < TINY_TTY_MINORS; ++i) {
        tiny = tiny_table[i];
        if (tiny == NULL)
            continue;

        seq_printf(m, "%d
", i);
    }

    return 0;
}

#define tiny_ioctl tiny_ioctl_tiocgserial
static int tiny_ioctl(struct tty_struct *tty, unsigned int cmd,
              unsigned long arg)
{
    struct tiny_serial *tiny = tty->driver_data;

    if (cmd == TIOCGSERIAL) {
        struct serial_struct tmp;

        if (!arg)
            return -EFAULT;

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

        tmp.type        = tiny->serial.type;
        tmp.line        = tiny->serial.line;
        tmp.port        = tiny->serial.port;
        tmp.irq            = tiny->serial.irq;
        tmp.flags        = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ;
        tmp.xmit_fifo_size    = tiny->serial.xmit_fifo_size;
        tmp.baud_base        = tiny->serial.baud_base;
        tmp.close_delay        = 5*HZ;
        tmp.closing_wait    = 30*HZ;
        tmp.custom_divisor    = tiny->serial.custom_divisor;
        tmp.hub6        = tiny->serial.hub6;
        tmp.io_type        = tiny->serial.io_type;

        if (copy_to_user((void __user *)arg, &tmp, sizeof(struct serial_struct)))
            return -EFAULT;
        return 0;
    }
    return -ENOIOCTLCMD;
}
#undef tiny_ioctl

#define tiny_ioctl tiny_ioctl_tiocmiwait
static int tiny_ioctl(struct tty_struct *tty, unsigned int cmd,
              unsigned long arg)
{
    struct tiny_serial *tiny = tty->driver_data;

    if (cmd == TIOCMIWAIT) {
        DECLARE_WAITQUEUE(wait, current);
        struct async_icount cnow;
        struct async_icount cprev;

        cprev = tiny->icount;
        while (1) {
            add_wait_queue(&tiny->wait, &wait);
            set_current_state(TASK_INTERRUPTIBLE);
            schedule();
            remove_wait_queue(&tiny->wait, &wait);

            /* see if a signal woke us up */
            if (signal_pending(current))
                return -ERESTARTSYS;

            cnow = tiny->icount;
            if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
                cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)
                return -EIO; /* no change => error */
            if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
                ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
                ((arg & TIOCM_CD)  && (cnow.dcd != cprev.dcd)) ||
                ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
                return 0;
            }
            cprev = cnow;
        }

    }
    return -ENOIOCTLCMD;
}
#undef tiny_ioctl

#define tiny_ioctl tiny_ioctl_tiocgicount
static int tiny_ioctl(struct tty_struct *tty, unsigned int cmd,
              unsigned long arg)
{
    struct tiny_serial *tiny = tty->driver_data;

    if (cmd == TIOCGICOUNT) {
        struct async_icount cnow = tiny->icount;
        struct serial_icounter_struct 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;
}
#undef tiny_ioctl

/* the real tiny_ioctl function.  The above is done to get the small functions in the book */
static int tiny_ioctl(struct tty_struct *tty, unsigned int cmd,
              unsigned long arg)
{
    switch (cmd) {
    case TIOCGSERIAL:
        return tiny_ioctl_tiocgserial(tty, cmd, arg);
    case TIOCMIWAIT:
        return tiny_ioctl_tiocmiwait(tty, cmd, arg);
    case TIOCGICOUNT:
        return tiny_ioctl_tiocgicount(tty, cmd, arg);
    }

    return -ENOIOCTLCMD;
}

static int tiny_proc_open(struct inode *inode, struct file *file)
{
        return single_open(file, tiny_proc_show, NULL);
}


static const struct file_operations serial_proc_fops = {
        .owner          = THIS_MODULE,
        .open           = tiny_proc_open,
    .read           = seq_read,
        .llseek         = seq_lseek,
        .release        = single_release,
};

static struct tty_operations serial_ops = {
    .open = tiny_open,
    .close = tiny_close,
    .write = tiny_write,
    .write_room = tiny_write_room,
    .set_termios = tiny_set_termios,
    .proc_fops    = &serial_proc_fops,
    .tiocmget = tiny_tiocmget,
    .tiocmset = tiny_tiocmset,
    .ioctl = tiny_ioctl,
};

static struct tty_driver *tiny_tty_driver;

static int __init tiny_init(void)
{
    int retval;
    int i;

    /* allocate the tty driver */
    tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
    if (!tiny_tty_driver)
        return -ENOMEM;

    /* initialize the tty driver */
    tiny_tty_driver->owner = THIS_MODULE;
    tiny_tty_driver->driver_name = "tiny_tty";
    tiny_tty_driver->name = "ttty";
    tiny_tty_driver->major = TINY_TTY_MAJOR,
    tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
    tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL,
    tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV,
    tiny_tty_driver->init_termios = tty_std_termios;
    tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    tty_set_operations(tiny_tty_driver, &serial_ops);

    /* register the tty driver */
    retval = tty_register_driver(tiny_tty_driver);
    if (retval) {
        printk(KERN_ERR "failed to register tiny tty driver");
        put_tty_driver(tiny_tty_driver);
        return retval;
    }

    for (i = 0; i < TINY_TTY_MINORS; ++i)
        tty_register_device(tiny_tty_driver, i, NULL);

    printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION);
    return retval;
}

static void __exit tiny_exit(void)
{
    struct tiny_serial *tiny;
    int i;

    for (i = 0; i < TINY_TTY_MINORS; ++i)
        tty_unregister_device(tiny_tty_driver, i);
    tty_unregister_driver(tiny_tty_driver);

    /* shut down all of the timers and free the memory */
    for (i = 0; i < TINY_TTY_MINORS; ++i) {
        tiny = tiny_table[i];
        if (tiny) {
            /* close the port */
            while (tiny->open_count)
                do_close(tiny);

            /* shut down our timer and free the memory */
            del_timer(tiny->timer);
            kfree(tiny->timer);
            kfree(tiny);
            tiny_table[i] = NULL;
        }
    }
}

module_init(tiny_init);
module_exit(tiny_exit);

tiny_serial.c

/*
 * Tiny Serial driver
 *
 * Copyright (C) 2002-2004 Greg Kroah-Hartman (greg@kroah.com)
 *
 *    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, version 2 of the License.
 *
 * This driver shows how to create a minimal serial driver.  It does not rely on
 * any backing hardware, but creates a timer that emulates data being received
 * from some kind of hardware.
 */

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/module.h>


#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>"
#define DRIVER_DESC "Tiny serial driver"

/* Module information */
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");

#define DELAY_TIME        HZ * 2    /* 2 seconds per character */
#define TINY_DATA_CHARACTER    't'

#define TINY_SERIAL_MAJOR    240    /* experimental range */
#define TINY_SERIAL_MINORS    1    /* only have one minor */
#define UART_NR            1    /* only use one port */

#define TINY_SERIAL_NAME    "ttytiny"

#define MY_NAME            TINY_SERIAL_NAME

static struct timer_list *timer;

static void tiny_stop_tx(struct uart_port *port)
{
}

static void tiny_stop_rx(struct uart_port *port)
{
}

static void tiny_enable_ms(struct uart_port *port)
{
}

static void tiny_tx_chars(struct uart_port *port)
{
    struct circ_buf *xmit = &port->state->xmit;
    int count;

    if (port->x_char) {
        pr_debug("wrote %2x", port->x_char);
        port->icount.tx++;
        port->x_char = 0;
        return;
    }
    if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
        tiny_stop_tx(port);
        return;
    }

    count = port->fifosize >> 1;
    do {
        pr_debug("wrote %2x", xmit->buf[xmit->tail]);
        xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
        port->icount.tx++;
        if (uart_circ_empty(xmit))
            break;
    } while (--count > 0);

    if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
        uart_write_wakeup(port);

    if (uart_circ_empty(xmit))
        tiny_stop_tx(port);
}

static void tiny_start_tx(struct uart_port *port)
{
}

static void tiny_timer(unsigned long data)
{
    struct uart_port *port;
    struct tty_struct *tty;


    port = (struct uart_port *)data;
    if (!port)
        return;
    if (!port->state)
        return;
    tty = port->state->port.tty;
    if (!tty)
        return;

    /* add one character to the tty port */
    /* this doesn't actually push the data through unless tty->low_latency is set */
    tty_insert_flip_char(tty, TINY_DATA_CHARACTER, 0);

    tty_flip_buffer_push(tty);

    /* resubmit the timer again */
    timer->expires = jiffies + DELAY_TIME;
    add_timer(timer);

    /* see if we have any data to transmit */
    tiny_tx_chars(port);
}

static unsigned int tiny_tx_empty(struct uart_port *port)
{
    return 0;
}

static unsigned int tiny_get_mctrl(struct uart_port *port)
{
    return 0;
}

static void tiny_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
}

static void tiny_break_ctl(struct uart_port *port, int break_state)
{
}

static void tiny_set_termios(struct uart_port *port,
                 struct ktermios *new, struct ktermios *old)
{
    int baud, quot, cflag = new->c_cflag;
    /* get the byte size */
    switch (cflag & CSIZE) {
    case CS5:
        printk(KERN_DEBUG " - data bits = 5
");
        break;
    case CS6:
        printk(KERN_DEBUG " - data bits = 6
");
        break;
    case CS7:
        printk(KERN_DEBUG " - data bits = 7
");
        break;
    default: // CS8
        printk(KERN_DEBUG " - data bits = 8
");
        break;
    }

    /* determine the parity */
    if (cflag & PARENB)
        if (cflag & PARODD)
            pr_debug(" - parity = odd
");
        else
            pr_debug(" - parity = even
");
    else
        pr_debug(" - parity = none
");

    /* figure out the stop bits requested */
    if (cflag & CSTOPB)
        pr_debug(" - stop bits = 2
");
    else
        pr_debug(" - stop bits = 1
");

    /* figure out the flow control settings */
    if (cflag & CRTSCTS)
        pr_debug(" - RTS/CTS is enabled
");
    else
        pr_debug(" - RTS/CTS is disabled
");

    /* Set baud rate */
        baud = uart_get_baud_rate(port, new, old, 0, port->uartclk/16);
        quot = uart_get_divisor(port, baud);
    
    //UART_PUT_DIV_LO(port, (quot & 0xff));
    //UART_PUT_DIV_HI(port, ((quot & 0xf00) >> 8));
}

static int tiny_startup(struct uart_port *port)
{
    /* this is the first time this port is opened */
    /* do any hardware initialization needed here */

    /* create our timer and submit it */
    if (!timer) {
        timer = kmalloc(sizeof(*timer), GFP_KERNEL);
        if (!timer)
            return -ENOMEM;
    }
    init_timer(timer);
    timer->data = (unsigned long)port;
    timer->expires = jiffies + DELAY_TIME;
    timer->function = tiny_timer;
    add_timer(timer);
    return 0;
}

static void tiny_shutdown(struct uart_port *port)
{
    /* The port is being closed by the last user. */
    /* Do any hardware specific stuff here */

    /* shut down our timer */
    del_timer(timer);
}

static const char *tiny_type(struct uart_port *port)
{
    return "tinytty";
}

static void tiny_release_port(struct uart_port *port)
{

}

static int tiny_request_port(struct uart_port *port)
{
    return 0;
}

static void tiny_config_port(struct uart_port *port, int flags)
{
}

static int tiny_verify_port(struct uart_port *port, struct serial_struct *ser)
{
    return 0;
}

static struct uart_ops tiny_ops = {
    .tx_empty    = tiny_tx_empty,
    .set_mctrl    = tiny_set_mctrl,
    .get_mctrl    = tiny_get_mctrl,
    .stop_tx    = tiny_stop_tx,
    .start_tx    = tiny_start_tx,
    .stop_rx    = tiny_stop_rx,
    .enable_ms    = tiny_enable_ms,
    .break_ctl    = tiny_break_ctl,
    .startup    = tiny_startup,
    .shutdown    = tiny_shutdown,
    .set_termios    = tiny_set_termios,
    .type        = tiny_type,
    .release_port    = tiny_release_port,
    .request_port    = tiny_request_port,
    .config_port    = tiny_config_port,
    .verify_port    = tiny_verify_port,
};

static struct uart_port tiny_port = {
    .ops        = &tiny_ops,
};

static struct uart_driver tiny_reg = {
    .owner        = THIS_MODULE,
    .driver_name    = TINY_SERIAL_NAME,
    .dev_name    = TINY_SERIAL_NAME,
    .major        = TINY_SERIAL_MAJOR,
    .minor        = TINY_SERIAL_MINORS,
    .nr        = UART_NR,
};

static int __init tiny_init(void)
{
    int result;

    printk(KERN_INFO "Tiny serial driver loaded
");

    result = uart_register_driver(&tiny_reg);
    if (result)
        return result;

    result = uart_add_one_port(&tiny_reg, &tiny_port);
    if (result)
        uart_unregister_driver(&tiny_reg);

    return result;
}

module_init(tiny_init);
原文地址:https://www.cnblogs.com/pengdonglin137/p/4321936.html