嵌入式Linux的串口驱动

CPU  : s3c2410

PLAT : 由smdk2410修改来的一块板子。(资料依旧非常有限)

        这次的项目,板子上面串口部分非常怪异。我们知道,samsung s3c2410这款芯片的UART控制器一共可以支持3个串行端口,如果不够用,还可以通过8250之类的芯片来扩展。而我手上的板子却不是这样实现的,它一共设计了7个端口,但是使用的方式比较特殊,port[1]和port[3]~port[6]这5个端口在任意时刻,只有一个有可能会被使用,考虑到这种特殊的使用方式,我们没有必要再承担一块8250芯片的成本,于是我们用一个映射到bank3(0x18000000)的寄存器来作为开关,实现虚拟的端口的切换,实际上port[3]~port[5]所用的硬件部分和port[1]是同一套,具体实现不再多言,这些对于写串口驱动来说貌似已经够用了。

下面开始修改源码(注意:红色部分是我添加或修改的

/linux-2.6.30.4/arch/arm/plat-s3c24xx/devs.c

         添加UART设备和资源描述结构体。

#if 0 //modified by B.Zhou
static struct resource s3c2410_uart3_resource[] = {
    [0] = {
        .start = S3C2443_PA_UART3,
        .end   = S3C2443_PA_UART3 + 0x3fff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_S3CUART_RX3,
        .end   = IRQ_S3CUART_ERR3,
        .flags = IORESOURCE_IRQ,
    },
};
#else
static struct resource s3c2410_uart3_resource[] = {
    [0] = {
        .start = S3C2410_PA_UART1,
        .end   = S3C2410_PA_UART1 + 0x3fff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_S3CUART_RX1,
        .end   = IRQ_S3CUART_ERR1,
        .flags = IORESOURCE_IRQ,
    }
};

static struct resource s3c2410_uart4_resource[] = {
    [0] = {
        .start = S3C2410_PA_UART1,
        .end   = S3C2410_PA_UART1 + 0x3fff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_S3CUART_RX1,
        .end   = IRQ_S3CUART_ERR1,
        .flags = IORESOURCE_IRQ,
    }
};

static struct resource s3c2410_uart5_resource[] = {
    [0] = {
        .start = S3C2410_PA_UART1,
        .end   = S3C2410_PA_UART1 + 0x3fff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_S3CUART_RX1,
        .end   = IRQ_S3CUART_ERR1,
        .flags = IORESOURCE_IRQ,
    }
};

static struct resource s3c2410_uart6_resource[] = {
    [0] = {
        .start = S3C2410_PA_UART1,
        .end   = S3C2410_PA_UART1 + 0x3fff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_S3CUART_RX1,
        .end   = IRQ_S3CUART_ERR1,
        .flags = IORESOURCE_IRQ,
    }
};
#endif


#if 1 //added by B.Zhou
static struct platform_device s3c24xx_uart_device4 = {
    .id        = 4,
};

static struct platform_device s3c24xx_uart_device5 = {
    .id        = 5,
};

static struct platform_device s3c24xx_uart_device6 = {
    .id        = 6,
};
#endif


struct platform_device *s3c24xx_uart_src[7 ] = {
    &s3c24xx_uart_device0,
    &s3c24xx_uart_device1,
    &s3c24xx_uart_device2,
    &s3c24xx_uart_device3,
#if 1 //added by B.Zhou
    &s3c24xx_uart_device4,
    &s3c24xx_uart_device5,
    &s3c24xx_uart_device6,
#endif

};

/linux-2.6.30.4/include/linux/autoconf.h

        这个文件中的常量来自“make menuconfig”中的内核配置信息。

#define CONFIG_SERIAL_SAMSUNG_UARTS 7 //modified by B.Zhou
(注意,这个常量每次通过“make menuconfig”修改内核配置信息后,都会还原为3)

/linux-2.6.30.4/include/linux/serial_reg.h

        在这里面添加bank3(0x18000000)寄存器相关的物理地址和虚拟地址的定义,具体如下:

#if 1 //added by B.Zhou for the driver of serial ports
#define pA_UART_Reg 0x18000000  //PA
#define vA_UART_Reg 0xe2000000  //VA
#define len_UART_Reg 0x00010000
#endif

/linux-2.6.30.4/arch/arm/mach-s3c2410/mach-smdk2410.c

        在这里面要实现的是对于上面地址的映射。

        先添加两个头文件:

#include <linux/serial_reg.h> // added by B.Zhou for the serial ports
#include <asm/memory.h> // added by B.Zhou for the __phys_to_pfn()

 

static struct map_desc smdk2410_iodesc[] __initdata = {
  /* nothing here yet */
  /* added by B.Zhou for the serial ports */
    {vA_UART_Reg, __phys_to_pfn(pA_UART_Reg), len_UART_Reg, MT_DEVICE_NONSHARED},

};

linux-2.6.30.4/arch/arm/include/asm/io.h

        添加静态变量,用于定义虚拟地址

volatile static unsigned long REG_SERIAL; // added by B.Zhou


/linux-2.6.30.4/drivers/serial/samsung.c

        首先添加一个头文件,用于寄存器操作。

#include <asm/io.h>  //added by B.Zhou for the "iowrite8()" and REG_SERIAL


        我们在使用端口port[3]~port[5]之前需要通过0x18000000的寄存器来完成端口选择,完成使用后,还要还原状态。这个工作应该在端口启动和关闭时完成。那么我们就需要为port[3]~port[5],修改“static int s3c24xx_serial_startup(struct uart_port *port)”和“static int s3c24xx_serial_shutdown(struct uart_port *port)”两个函数。具体做法为添加以下两部分代码:

#if 1 //added by B.Zhou


static void s3c24xx_serial_shutdown_3(struct uart_port *port)
{
    printk(KERN_INFO "/ncalling s3c24xx_serial_shutdown_3/n");
    s3c24xx_serial_shutdown(port);
    iowrite8(0x08,REG_SERIAL);
}

static void s3c24xx_serial_shutdown_4(struct uart_port *port)
{
    printk(KERN_INFO "/ncalling s3c24xx_serial_shutdown_4/n");
    s3c24xx_serial_shutdown(port);
    iowrite8(0x08,REG_SERIAL);
}

static void s3c24xx_serial_shutdown_5(struct uart_port *port)
{
    printk(KERN_INFO "/ncalling s3c24xx_serial_shutdown_5/n");
    s3c24xx_serial_shutdown(port);
    iowrite8(0x08,REG_SERIAL);
}

static void s3c24xx_serial_shutdown_6(struct uart_port *port)
{
    printk(KERN_INFO "/ncalling s3c24xx_serial_shutdown_6/n");
    s3c24xx_serial_shutdown(port);
    iowrite8(0x08,REG_SERIAL);
}

#endif

 

static int s3c24xx_serial_startup(struct uart_port *port)
{
    struct s3c24xx_uart_port *ourport = to_ourport(port);
    int ret;

    dbg("s3c24xx_serial_startup: port(line:%d)=%p (%08lx,%p)/n",
        port->line,port->mapbase, port->membase);
    rx_enabled(port) = 1;
#if 0 //modified by B.Zhou
    ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
              s3c24xx_serial_portname(port), ourport);
#else
    ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, IRQF_SHARED,
              s3c24xx_serial_portname(port), ourport);
#endif

    if (ret != 0) {
        printk(KERN_ERR "cannot get irq %d/n", ourport->rx_irq);
        return ret;
    }

    ourport->rx_claimed = 1;

    dbg("requesting tx irq.../n");

    tx_enabled(port) = 1;
#if 0 //modified by B.Zhou
    ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
              s3c24xx_serial_portname(port), ourport);
#else
    ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, IRQF_SHARED,
              s3c24xx_serial_portname(port), ourport);
#endif


    if (ret) {
        printk(KERN_ERR "cannot get irq %d/n", ourport->tx_irq);
        goto err;
    }

    ourport->tx_claimed = 1;

    dbg("s3c24xx_serial_startup ok/n");

    /* the port reset code should have done the correct
     * register setup for the port controls */

    return ret;

 err:
    s3c24xx_serial_shutdown(port);
    return ret;
}


#if 1 //added by B.Zhou

static int s3c24xx_serial_startup_3(struct uart_port *port)
{
    printk(KERN_INFO "/ncall s3c24xx_serial_startup_3/n");
    iowrite8(0x01,REG_SERIAL);
    return s3c24xx_serial_startup(port);
}

static int s3c24xx_serial_startup_4(struct uart_port *port)
{
    printk(KERN_INFO "/ncall s3c24xx_serial_startup_4/n");
    iowrite8(0x02,REG_SERIAL);
    return s3c24xx_serial_startup(port);
}

static int s3c24xx_serial_startup_5(struct uart_port *port)
{
    printk(KERN_INFO "/ncall s3c24xx_serial_startup_5/n");
    iowrite8(0x04,REG_SERIAL);
    return s3c24xx_serial_startup(port);
}

static int s3c24xx_serial_startup_6(struct uart_port *port)
{
    printk(KERN_INFO "/ncall s3c24xx_serial_startup_6/n");
    iowrite8(0x08,REG_SERIAL);
    return s3c24xx_serial_startup(port);
}
#endif


         添加用于port[3]~port[6]的操作函数结构体,实际上只是修改了端口启动和关闭的函数,具体如下:

#if 1 //added by B.Zhou
static struct uart_ops s3c24xx_serial_ops3 = {
    .pm        = s3c24xx_serial_pm,
    .tx_empty    = s3c24xx_serial_tx_empty,
    .get_mctrl    = s3c24xx_serial_get_mctrl,
    .set_mctrl    = s3c24xx_serial_set_mctrl,
    .stop_tx    = s3c24xx_serial_stop_tx,
    .start_tx    = s3c24xx_serial_start_tx,
    .stop_rx    = s3c24xx_serial_stop_rx,
    .enable_ms    = s3c24xx_serial_enable_ms,
    .break_ctl    = s3c24xx_serial_break_ctl,
    .startup    = s3c24xx_serial_startup_3,
    .shutdown    = s3c24xx_serial_shutdown_3,
    .set_termios    = s3c24xx_serial_set_termios,
    .type        = s3c24xx_serial_type,
    .release_port    = s3c24xx_serial_release_port,
    .request_port    = s3c24xx_serial_request_port,
    .config_port    = s3c24xx_serial_config_port,
    .verify_port    = s3c24xx_serial_verify_port,
};

static struct uart_ops s3c24xx_serial_ops4 = {
    .pm        = s3c24xx_serial_pm,
    .tx_empty    = s3c24xx_serial_tx_empty,
    .get_mctrl    = s3c24xx_serial_get_mctrl,
    .set_mctrl    = s3c24xx_serial_set_mctrl,
    .stop_tx    = s3c24xx_serial_stop_tx,
    .start_tx    = s3c24xx_serial_start_tx,
    .stop_rx    = s3c24xx_serial_stop_rx,
    .enable_ms    = s3c24xx_serial_enable_ms,
    .break_ctl    = s3c24xx_serial_break_ctl,
    .startup    = s3c24xx_serial_startup_4,
    .shutdown    = s3c24xx_serial_shutdown_4,
    .set_termios    = s3c24xx_serial_set_termios,
    .type        = s3c24xx_serial_type,
    .release_port    = s3c24xx_serial_release_port,
    .request_port    = s3c24xx_serial_request_port,
    .config_port    = s3c24xx_serial_config_port,
    .verify_port    = s3c24xx_serial_verify_port,
};

static struct uart_ops s3c24xx_serial_ops5 = {
    .pm        = s3c24xx_serial_pm,
    .tx_empty    = s3c24xx_serial_tx_empty,
    .get_mctrl    = s3c24xx_serial_get_mctrl,
    .set_mctrl    = s3c24xx_serial_set_mctrl,
    .stop_tx    = s3c24xx_serial_stop_tx,
    .start_tx    = s3c24xx_serial_start_tx,
    .stop_rx    = s3c24xx_serial_stop_rx,
    .enable_ms    = s3c24xx_serial_enable_ms,
    .break_ctl    = s3c24xx_serial_break_ctl,
    .startup    = s3c24xx_serial_startup_5,
    .shutdown    = s3c24xx_serial_shutdown_5,
    .set_termios    = s3c24xx_serial_set_termios,
    .type        = s3c24xx_serial_type,
    .release_port    = s3c24xx_serial_release_port,
    .request_port    = s3c24xx_serial_request_port,
    .config_port    = s3c24xx_serial_config_port,
    .verify_port    = s3c24xx_serial_verify_port,
};

static struct uart_ops s3c24xx_serial_ops6 = {
    .pm        = s3c24xx_serial_pm,
    .tx_empty    = s3c24xx_serial_tx_empty,
    .get_mctrl    = s3c24xx_serial_get_mctrl,
    .set_mctrl    = s3c24xx_serial_set_mctrl,
    .stop_tx    = s3c24xx_serial_stop_tx,
    .start_tx    = s3c24xx_serial_start_tx,
    .stop_rx    = s3c24xx_serial_stop_rx,
    .enable_ms    = s3c24xx_serial_enable_ms,
    .break_ctl    = s3c24xx_serial_break_ctl,
    .startup    = s3c24xx_serial_startup_6,
    .shutdown    = s3c24xx_serial_shutdown_6,
    .set_termios    = s3c24xx_serial_set_termios,
    .type        = s3c24xx_serial_type,
    .release_port    = s3c24xx_serial_release_port,
    .request_port    = s3c24xx_serial_request_port,
    .config_port    = s3c24xx_serial_config_port,
    .verify_port    = s3c24xx_serial_verify_port,
};
#endif

 

        在 “static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS]” 数组中添加port[3]~port[5]的端口描述结构体:

static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
    [0] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
            .iotype        = UPIO_PORT,
            .iobase        = S3C2410_PA_UART0,
            .irq        = IRQ_S3CUART_RX0,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 0,
        }
    },
    [1] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),
            .iotype        = UPIO_PORT,
            .iobase        = S3C2410_PA_UART1,
            .irq        = IRQ_S3CUART_RX1,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 1,
        }
    },
#if CONFIG_SERIAL_SAMSUNG_UARTS > 2

    [2] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),
            .iotype        = UPIO_PORT,
            .iobase        = S3C2410_PA_UART2,
            .irq        = IRQ_S3CUART_RX2,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 2,
        }
    },
#endif
#if 1 //added by B.Zhou
#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
    [3] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),
            .iotype        = UPIO_PORT,
            .iobase        = S3C2410_PA_UART1 ,
            .irq        = IRQ_S3CUART_RX1 ,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops3,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 3,
        }
    },

    [4] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[4].port.lock),
            .iotype        = UPIO_PORT,
            .iobase        = S3C2410_PA_UART1,
            .irq        = IRQ_S3CUART_RX1,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops4,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 4,
        }
    },

    [5] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[5].port.lock),
            .iotype        = UPIO_PORT,
            .iobase        = S3C2410_PA_UART1,
            .irq        = IRQ_S3CUART_RX1,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops5,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 5,
        }
    },

    [6] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[6].port.lock),
            .iotype        = UPIO_PORT,
            .iobase        = S3C2410_PA_UART1,
            .irq        = IRQ_S3CUART_RX1,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops6,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 6,
        }
    }

#endif

#endif
};

 

        找到模块初始化函数,为“REG_SERIAL” 赋值,也可以直接添加上面提到的“serial_reg.h” 头文件。

static int __init s3c24xx_serial_modinit(void)
{
    int ret;

#if 1 //added by B.Zhou
      REG_SERIAL = 0xe2000000;

#endif
    ret = uart_register_driver(&s3c24xx_uart_drv);
    if (ret < 0) {
        printk(KERN_ERR "failed to register UART driver/n");
        return -1;
    }
    return 0;
}

 

        找到端口初始化函数,做如下修改:

static int s3c24xx_serial_init_ports(struct s3c24xx_uart_info *info)
{
    struct s3c24xx_uart_port *ptr = s3c24xx_serial_ports;
    struct platform_device **platdev_ptr;
    int i;

    dbg("s3c24xx_serial_init_ports: initialising ports.../n");

    platdev_ptr = s3c24xx_uart_devs;
#if 1 //modified by B.Zhou
    for (i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++, ptr++) {
        if(i < 3)
            s3c24xx_serial_init_port(ptr, info, *(platdev_ptr+i));
        else
            s3c24xx_serial_init_port(ptr, info, *(platdev_ptr+1));
    }
#else

    for (i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++, ptr++, platdev_ptr++) {
        s3c24xx_serial_init_port(ptr, info, *platdev_ptr);
    }
#endif

    return 0;
}

 

        下面修改控制台初始化函数中的工作频率:

static int __init
s3c24xx_serial_console_setup(struct console *co, char *options)
{
    struct uart_port *port;
    int baud = 115200;  //modified by B.Zhou 9600
    int bits = 8;
    int parity = 'n';
    int flow = 'n';

    dbg("s3c24xx_serial_console_setup: co=%p (%d), %s/n",
        co, co->index, options);

    /* is this a valid port */

    if (co->index == -1 || co->index >= CONFIG_SERIAL_SAMSUNG_UARTS)
        co->index = 0;


    port = &s3c24xx_serial_ports[co->index].port;

    /* is the port configured? */

    if (port->mapbase == 0x0) {
        co->index = 0;
        port = &s3c24xx_serial_ports[co->index].port;
    }


    cons_uart = port;

    dbg("s3c24xx_serial_console_setup: port=%p (%d)/n", port, co->index);

    /*
     * Check whether an invalid uart number has been specified, and
     * if so, search for the first available port that does have
     * console support.
     */
    if (options)
        uart_parse_options(options, &baud, &parity, &bits, &flow);
    else
        s3c24xx_serial_get_options(port, &baud, &parity, &bits);

    dbg("s3c24xx_serial_console_setup: baud %d/n", baud);

    return uart_set_options(port, co, baud, parity, bits, flow);
}

 

/linux-2.6.30.4/arch/arm/plat-s3c/init.c

        在这个文件中修改UART设备初始化函数:

void __init s3c24xx_init_uartdevs(char *name,
                  struct s3c24xx_uart_resources *res,
                  struct s3c2410_uartcfg *cfg, int no)
{
    struct platform_device *platdev;
    struct s3c2410_uartcfg *cfgptr = uart_cfgs;
    struct s3c24xx_uart_resources *resp;
    int uart;
    int uart_cur;
 
    memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);
 
    for (uart = 0; uart < no; uart++, cfg++, cfgptr++) {

        platdev = s3c24xx_uart_src[cfgptr->hwport];

        resp = res + cfgptr->hwport;

        s3c24xx_uart_devs[uart] = platdev;

        platdev->name = name;
        platdev->resource = resp->resources;
        platdev->num_resources = resp->nr_resources;

        platdev->dev.platform_data = cfgptr;
    }
#if 1  //added by B.Zhou
    for(uart_cur = 3; uart_cur < 7; uart_cur++, cfgptr++) {
        memcpy(cfgptr, cfg-2, sizeof(struct s3c2410_uartcfg));

        platdev = s3c24xx_uart_src[uart_cur];
       
        resp = res + cfgptr->hwport;

        s3c24xx_uart_devs[uart_cur] = platdev;

        platdev->name = name;
        platdev->resource = resp->resources;
        platdev->num_resources = resp->nr_resources;

        platdev->dev.platform_data = cfgptr;

    }
    nr_uarts = 7;
#else

    nr_uarts = no;
#endif
}

         至此,这个板子上的串口已经可以工作了,启动后会看到在“/dev” 文件夹下,生成了7个串口设备文件,分别是"s3c2410_serial0~
s3c2410_serial6 "。 但是还有一个问题,就是串口的缺省状态,比如波特率默认为9600,而我们更希望它是115200。

/linux-2.6.30.4/drivers/serial/serial_core.c

         找到UART驱动注册函数:

int uart_register_driver(struct uart_driver *drv)
{
    struct tty_driver *normal = NULL;
    int i, retval;

    BUG_ON(drv->state);

    /*
     * Maybe we should be using a slab cache for this, especially if
     * we have a large number of ports to handle.
     */
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
    retval = -ENOMEM;
    if (!drv->state)
        goto out;

    normal = alloc_tty_driver(drv->nr);
    if (!normal)
        goto out;

    drv->tty_driver = normal;

    normal->owner        = drv->owner;
    normal->driver_name    = drv->driver_name;
    normal->name        = drv->dev_name;
    normal->major        = drv->major;
    normal->minor_start    = drv->minor;
    normal->type        = TTY_DRIVER_TYPE_SERIAL;
    normal->subtype        = SERIAL_TYPE_NORMAL;
    normal->init_termios    = tty_std_termios;

#if 0 //modified by B.Zhou
    normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
#else
    normal->init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL;
    normal->init_termios.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOE);
    normal->init_termios.c_oflag &= ~OPOST;
    normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 115200;
#endif


    normal->flags        = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
    normal->driver_state    = drv;
    tty_set_operations(normal, &uart_ops);

    /*
     * Initialise the UART state(s).
     */
    for (i = 0; i < drv->nr; i++) {
        struct uart_state *state = drv->state + i;

        state->close_delay     = 500;    /* .5 seconds */
        state->closing_wait    = 30000;    /* 30 seconds */
        mutex_init(&state->mutex);

        tty_port_init(&state->info.port);
        init_waitqueue_head(&state->info.delta_msr_wait);
        tasklet_init(&state->info.tlet, uart_tasklet_action,
                 (unsigned long)state);
    }

    retval = tty_register_driver(normal);
 out:
    if (retval < 0) {
        put_tty_driver(normal);
        kfree(drv->state);
    }
    return retval;
}

         终于,串口驱动全部完成了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值