linux串口驱动分析



  • 硬件资源及描述
        s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)端口,每个端口都可以在中断模式或 DMA 模式下操作。UART 使用系统时钟可以支持最高 115.2Kbps 的波特率。每个 UART 通道对于接收器和发送器包括了 2 个 64 位的 FIFO。

寄存器名称地址在linux中的描述 (2410 和 2440 处理器对内存地址映射关系相同)
UART 线性控制寄存器(ULCONn)ULCON0 
ULCON1 
ULCON2 
0x50000000
0x50004000
0x50008000
linux-2.6.31/arch/arm/plat-s3c/include/plat/regs-serial.h

#define S3C2410_PA_UART0      

    (S3C24XX_PA_UART)

#define S3C2410_PA_UART1     

    (S3C24XX_PA_UART + 0x4000 )

#define S3C2410_PA_UART2     

    (S3C24XX_PA_UART + 0x8000 )

#define S3C2443_PA_UART3     

    (S3C24XX_PA_UART + 0xC000 )

linux-2.6.31/arch/arm/plat-s3c/include/plat/map.h

#define S3C24XX_VA_UART      

    S3C_VA_UART//静态映射后的虚拟地址

#define S3C2410_PA_UART      

    (0x50000000)//物理地址

#define S3C24XX_SZ_UART       SZ_1M
#define S3C_UART_OFFSET       (0x4000)
UART 控制寄存器(UCONn)UCON0 
UCON1
UCON2
0x50000004
0x50004004
0x50008004
#define S3C2410_UCON      (0x04)
UART FIFO 控制寄存器(UFCONn)UFCON0
UFCON1
UFCON2
0x50000008
0x50004008
0x50008008
#define S3C2410_UFCON      (0x08)
UART MODEM 控制寄存器(UMCONn)UMCON0
UMCON1
0x5000000C
0x5000400C
#define S3C2410_UMCON      (0x0C)
UART 接收发送状态寄存器(UTRSTATn)UTRSTAT0
UTRSTAT1
UTRSTAT2
0x50000010
0x50004010
0x50008010
#define S3C2410_UTRSTAT      (0x10)
UART 错误状态寄存器(UERSTATn)UERSTAT0
UERSTAT1
UERSTAT2
0x50000014
0x50004014
0x50008014
#define S3C2410_UERSTAT      (0x14)
UART FIFO 状态寄存器(UFSTATn)UFSTAT0
UFSTAT1
UFSTAT2
0x50000018
0x50004018
0x50008018
#define S3C2410_UFSTAT      (0x18)
UART MODEM 状态寄存器(UMSTATn)UFSTAT0
UFSTAT1
0x5000001C
0x5000401C
#define S3C2410_UMSTAT      (0x1C)
UART 发送缓存寄存器(UTXHn)UTXH0

UTXH1

UTXH2
0x50000020(L)
0x50000023(B)
0x50004020(L)
0x50004023(B)
0x50008020(L)
0x50008023(B)
#define S3C2410_UTXH      (0x20)
UART 接收缓存寄存器(URXHn)URXH0

URXH1

URXH2
0x50000024(L)
0x50000027(B)
0x50004024(L)
0x50004027(B)
0x50008024(L)
0x50008027(B)
#define S3C2410_URXH      (0x24)
UART 波特率除数寄存器(UBRDIVn)UBRDIV0
UBRDIV1
UBRDIV2
0x50000028
0x50004028
0x50008028
#define S3C2410_UBRDIV      (0x28)

ULCON描述在linux中的描述 linux-2.6.31/arch/arm/plat-s3c/include/plat/regs-serial.h
Reserved[7]  
Infrared Mode[6]决定是否使用红外模式
0 =正常模式操作
1 = 红外接收发送模式
#define S3C2410_LCON_IRM          (1<<6)
Parity Mode[5:3]在UART发送接收操作中定义奇偶码的生成和检
验类型
0xx = No parity
100 = Odd parity
101 = Even parity
110 = Parity forced/checked as 1
111 = Parity forced/checked as 0
#define S3C2410_LCON_PNONE      (0x0)
#define S3C2410_LCON_PEVEN      (0x5 << 3)
#define S3C2410_LCON_PODD      (0x4 << 3)
#define S3C2410_LCON_PMASK      (0x7 << 3)
Number of Stop Bit[2]定义度搜按个停止位用于帧末信号
0 =每帧一个停止位
1 =每帧两个停止位
#define S3C2410_LCON_STOPB      (1<<2)
Word Length[1:0]指出发送接收每帧的数据位数
00 = 5-bits 01 = 6-bits
10 = 7-bits 11 = 8-bits
#define S3C2410_LCON_CS5      (0x0)
#define S3C2410_LCON_CS6      (0x1)
#define S3C2410_LCON_CS7      (0x2)
#define S3C2410_LCON_CS8      (0x3)
#define S3C2410_LCON_CSMASK      (0x3)
 
UFCON描述在linux中的描述

Tx FIFO Trigger Level
[7:6]决定发送FIFO的触发等级
00 = Empty 01 = 16-byte
10 = 32-byte 11 = 48-byte
#define S3C2440_UFCON_TXTRIG0      (0<<6)
#define S3C2440_UFCON_TXTRIG16      (1<<6)
#define S3C2440_UFCON_TXTRIG32      (2<<6)
#define S3C2440_UFCON_TXTRIG48      (3<<6)

Rx FIFO Trigger Level
[5:4]决定接收FIFO的触发等级
00 = 1-byte 01 = 8-byte
10 = 16-byte 11 = 32-byte
#define S3C2440_UFCON_RXTRIG1      (0<<4)
#define S3C2440_UFCON_RXTRIG8      (1<<4)
#define S3C2440_UFCON_RXTRIG16      (2<<4)
#define S3C2440_UFCON_RXTRIG32      (3<<4)
Reserved[3]  
Tx FIFO Reset[2]在重置FIFO后自动清除
0 = Normal
1= Tx FIFO reset
#define S3C2410_UFCON_RESETTX      (1<<2)
Rx FIFO Reset[1]在重置FIFO后自动清除
0 = Normal
1= Rx FIFO reset
#define S3C2410_UFCON_RESETRX      (1<<1)
#define S3C2410_UFCON_RESETBOTH      (3<<1)
FIFO Enable[0]0 = Disable
1 = Enable
 
默认设置为 
 #define S3C2410_UFCON_DEFAULT      (S3C2410_UFCON_FIFOMODE | \
                   S3C2410_UFCON_TXTRIG0  | \
                   S3C2410_UFCON_RXTRIG8 )

使能FIFO
Tx FIFO为空时触发中断
Rx FIFO中包含8个字节时触发中断
 
UFSTAT描述在linux中的表示
Reserved[15]  
Tx FIFO Full
[14]只要在发送操作中发送FIFO满,则自动置 1。
0 = 0-byte ≤ Tx FIFO data ≤ 63-byte
1 = Full
#define S3C2440_UFSTAT_TXFULL      (1<<14)
Tx FIFO Count[13:8]发送FIFO中的数据数量#define S3C2440_UFSTAT_TXSHIFT      (8)
Reserved[7]  
Rx FIFO Full[6]只要在接收操作中接收FIFO满,则自动置 1。
0 = 0-byte ≤ Rx FIFO data ≤ 63-byte
1 = Full
#define S3C2440_UFSTAT_RXFULL      (1<<6)
Rx FIFO Count[5:0]接收FIFO中的数据数量#define S3C2440_UFSTAT_RXSHIFT      (0)
    



  • uart驱动程序概述
        linux的uart驱动程序建立在tty驱动程序之上(串口也是一个终端),程序源代码主要在drivers/serial/目录下。其中 serial_core.c实现了uart设备的通用tty驱动层,其中定义了一组通用接口,包括3个结构体:uart_driver, uart_port,uart_ops( include/serial_core.h )。因此,实现一个平台的uart驱动程序只要实现这3个结构体即可。


serial_core(定义):samsumg.c (实现):                                                                   
struct uart_driver {
    struct module        *owner;
    const char        *driver_name;
    const char        *dev_name;
    int             major;
    int             minor;
    int             nr;
    struct console        *cons;

    /*
     * these are private; the low level driver should not
     * touch these; they should be initialised to NULL
     */
    struct uart_state    *state;
    struct tty_driver    *tty_driver;
};
static struct uart_driver s3c24xx_uart_drv = {
    .owner        = THIS_MODULE,
    .dev_name    = "s3c2410_serial",//设备名称,这个名称必须和
        //根文件系统中/etc/inittab文件中的串口名称一致

    .nr        = CONFIG_SERIAL_SAMSUNG_UARTS,
    .cons        = S3C24XX_SERIAL_CONSOLE,
    .driver_name    = S3C24XX_SERIAL_NAME,//驱动名称
    .major        = S3C24XX_SERIAL_MAJOR,
    .minor        = S3C24XX_SERIAL_MINOR,
};
       
        uart_driver封装了tty_driver,使底层uart驱动不用关心ttr_driver。一个tty驱动程序必须注册/注销 tty_driver,而uart驱动则变为注册/注销uart_driver。使用如下接口:

int uart_register_driver(struct uart_driver *drv);
void uart_unregister_driver(struct uart_driver *drv);
实际上,uart_register_driver()和uart_unregister_driver()中分别包含了tty_register_driver()和tty_unregister_driver()的操作。

serial_core(定义):samsumg.h  samsumg.c(实现):
struct uart_port {
    spinlock_t        lock;            /* port lock */
    unsigned long        iobase;            /* in/out[bwl] */
    unsigned char __iomem    *membase;        /* read/write[bwl] */
    unsigned int        (*serial_in)(struct uart_port *, int);
    void            (*serial_out)(struct uart_port *, int, int);
    unsigned int        irq;            /* irq number */
    unsigned int        uartclk;        /* base uart clock */
    unsigned int        fifosize;        /* tx fifo size */
    unsigned char        x_char;            /* xon/xoff char */
    unsigned char        regshift;        /* reg offset shift */
    unsigned char        iotype;            /* io access style */
    unsigned char        unused1;

#define UPIO_PORT        (0)
#define UPIO_HUB6        (1)
#define UPIO_MEM        (2)
#define UPIO_MEM32        (3)
#define UPIO_AU            (4)            /* Au1x00 type IO */
#define UPIO_TSI        (5)            /* Tsi108/109 type IO */
#define UPIO_DWAPB        (6)            /* DesignWare APB UART */
#define UPIO_RM9000        (7)            /* RM9000 type IO */

    unsigned int        read_status_mask;    /* driver specific */
    unsigned int        ignore_status_mask;    /* driver specific */
    struct uart_info    *info;            /* pointer to parent info */
    struct uart_icount    icount;            /* statistics */

    struct console        *cons;            /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
    unsigned long        sysrq;            /* sysrq timeout */
#endif

    upf_t            flags;

#define UPF_FOURPORT        ((__force upf_t) (1 << 1))
#define UPF_SAK            ((__force upf_t) (1 << 2))
#define UPF_SPD_MASK        ((__force upf_t) (0x1030))
#define UPF_SPD_HI        ((__force upf_t) (0x0010))
#define UPF_SPD_VHI        ((__force upf_t) (0x0020))
#define UPF_SPD_CUST        ((__force upf_t) (0x0030))
#define UPF_SPD_SHI        ((__force upf_t) (0x1000))
#define UPF_SPD_WARP        ((__force upf_t) (0x1010))
#define UPF_SKIP_TEST        ((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ        ((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD        ((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY        ((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART        ((__force upf_t) (1 << 14))
#define UPF_NO_TXEN_TEST    ((__force upf_t) (1 << 15))
#define UPF_MAGIC_MULTIPLIER    ((__force upf_t) (1 << 16))
#define UPF_CONS_FLOW        ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ        ((__force upf_t) (1 << 24))
/* The exact UART type is known and should not be probed.  */
#define UPF_FIXED_TYPE        ((__force upf_t) (1 << 27))
#define UPF_BOOT_AUTOCONF    ((__force upf_t) (1 << 28))
#define UPF_FIXED_PORT        ((__force upf_t) (1 << 29))
#define UPF_DEAD        ((__force upf_t) (1 << 30))
#define UPF_IOREMAP        ((__force upf_t) (1 << 31))

#define UPF_CHANGE_MASK        ((__force upf_t) (0x17fff))
#define UPF_USR_MASK        ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

    unsigned int        mctrl;            /* current modem ctrl settings */
    unsigned int        timeout;        /* character-based timeout */
    unsigned int        type;            /* port type */
    const struct uart_ops    *ops;
    unsigned int        custom_divisor;
    unsigned int        line;            /* port index */
    resource_size_t        mapbase;        /* for ioremap */
    struct device        *dev;            /* parent device */
    unsigned char        hub6;            /* this should be in the 8250 driver */
    unsigned char        suspended;
    unsigned char        unused[2];
    void            *private_data;        /* generic platform data pointer */
};
struct s3c24xx_uart_port {//封装了uart_port
    unsigned char            rx_claimed;
    unsigned char            tx_claimed;
    unsigned int            pm_level;
    unsigned long            baudclk_rate;

    unsigned int            rx_irq;
    unsigned int            tx_irq;

    struct s3c24xx_uart_info    *info;
    struct s3c24xx_uart_clksrc    *clksrc;
    struct clk            *clk;
    struct clk            *baudclk;
    struct uart_port        port;

#ifdef CONFIG_CPU_FREQ
    struct notifier_block        freq_transition;
#endif
};

static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = { //3 uarts default
    [0] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
            .iotype        = UPIO_MEM,
            .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_MEM,
            .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_MEM,
            .irq        = IRQ_S3CUART_RX2,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 2,
        }
    },
#endif
#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
    [3] = {
        .port = {
            .lock        = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),
            .iotype        = UPIO_MEM,
            .irq        = IRQ_S3CUART_RX3,
            .uartclk    = 0,
            .fifosize    = 16,
            .ops        = &s3c24xx_serial_ops,
            .flags        = UPF_BOOT_AUTOCONF,
            .line        = 3,
        }
    }
#endif
};
       
        uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。串口核心层提供如下函数来添加1个端口:

int uart_add_one_port(struct uart_driver *drv, struct uart_port *port);
        对上述函数的调用应该发生在uart_register_driver()之后,uart_add_one_port()的一个最重要作用是封装了 tty_register_device()。
        uart_add_one_port()的“反函数”是uart_remove_one_port(),其中会调用tty_unregister_device(),原型为:
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port);

serial_core.h(定义):samsumg.c (实现):                                                                                           
struct uart_ops {
    unsigned int    (*tx_empty)(struct uart_port *);
    void        (*set_mctrl)(struct uart_port *, unsigned int mctrl);
    unsigned int    (*get_mctrl)(struct uart_port *);
    void        (*stop_tx)(struct uart_port *);
    void        (*start_tx)(struct uart_port *);
    void        (*send_xchar)(struct uart_port *, char ch);
    void        (*stop_rx)(struct uart_port *);
    void        (*enable_ms)(struct uart_port *);
    void        (*break_ctl)(struct uart_port *, int ctl);
    int        (*startup)(struct uart_port *);
    void        (*shutdown)(struct uart_port *);
    void        (*flush_buffer)(struct uart_port *);
    void        (*set_termios)(struct uart_port *, struct ktermios *new,
                       struct ktermios *old);
    void        (*set_ldisc)(struct uart_port *);
    void        (*pm)(struct uart_port *, unsigned int state,
                  unsigned int oldstate);
    int        (*set_wake)(struct uart_port *, unsigned int state);

    /*
     * Return a string describing the type of the port
     */
    const char *(*type)(struct uart_port *);

    /*
     * Release IO and memory resources used by the port.
     * This includes iounmap if necessary.
     */
    void        (*release_port)(struct uart_port *);

    /*
     * Request IO and memory resources used by the port.
     * This includes iomapping the port if necessary.
     */
    int        (*request_port)(struct uart_port *);
    void        (*config_port)(struct uart_port *, int);
    int        (*verify_port)(struct uart_port *, struct serial_struct *);
    int        (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
    void    (*poll_put_char)(struct uart_port *, unsigned char);
    int        (*poll_get_char)(struct uart_port *);
#endif
};
static struct uart_ops s3c24xx_serial_ops = {
    .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,
    .shutdown    = s3c24xx_serial_shutdown,
    .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,
};
在使用串口核心层这个通用串口tty驱动层的接口后,一个串口驱动要完成的主要工作将包括:
  1. 定义uart_driver、uart_ops、uart_port等结构体的实例并在适当的地方根据具体硬件和驱动的情况初始化它们,当然具体设备 xxx的驱动可以将这些结构套在新定义的xxx_uart_driver、xxx_uart_ops、xxx_uart_port之内。
  2. 在模块初始化时调用uart_register_driver()和uart_add_one_port()以注册UART驱动并添加端口,在模块卸载时调用uart_unregister_driver()和uart_remove_one_port()以注销UART驱动并移除端口。
  3.  根据具体硬件的datasheet实现uart_ops中的成员函数,这些函数的实现成为UART驱动的主体工作。
  • 串口驱动初始化过程
        在S3C2410 串口驱动的模块加载函数中会调用uart_register_driver()注册s3c24xx_uart_drv这个uart_driver,同时经过
s3c2410_serial_init()→s3c24xx_serial_init()→platform_driver_register()的调用导致s3c24xx_serial_probe()被执行,而s3c24xx_serial_probe()函数中会调用 s3c24xx_serial_init_port()初始化UART端口并调用uart_add_one_port()添加端口。
platform_device_driver 参考:
Linux Platform Device and Driver    Linux driver model ----- platform

回过头来看s3c24xx_uart_info结构体(s3c24xx_uart_port的成员),是一些针对s3c2440 uart 的信息,在/drivers/serial/s3c2440.c中:

static struct s3c24xx_uart_info s3c2440_uart_inf = {
    .name        = "Samsung S3C2440 UART",
    .type        = PORT_S3C2440,
    .fifosize    = 64,
    .rx_fifomask    = S3C2440_UFSTAT_RXMASK,
    .rx_fifoshift    = S3C2440_UFSTAT_RXSHIFT,
    .rx_fifofull    = S3C2440_UFSTAT_RXFULL,
    .tx_fifofull    = S3C2440_UFSTAT_TXFULL,
    .tx_fifomask    = S3C2440_UFSTAT_TXMASK,
    .tx_fifoshift    = S3C2440_UFSTAT_TXSHIFT,
    .get_clksrc    = s3c2440_serial_getsource,
    .set_clksrc    = s3c2440_serial_setsource,
    .reset_port    = s3c2440_serial_resetport,
};


  • 串口操作函数,uart_ops接口函数
S3C2410串口驱动uart_ops结构体的startup ()成员函数s3c24xx_serial_startup()用于启动端口,申请端口的发送、接收中断,使能端口的发送和接收。

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

    dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n",
        port->mapbase, port->membase);

    rx_enabled(port) = 1;//接收使能

    ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
              s3c24xx_serial_portname(port), ourport);//申请接收中断,s3c24xx_serial_rx_chars是中断处理函数

    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;//发送使能

    ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
              s3c24xx_serial_portname(port), ourport);//申请发送中断

    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;
}

s3c24xx_serial_startup()的“反函数”为s3c24xx_serial_shutdown(),其释放中断,禁止发送和接收。

static void s3c24xx_serial_shutdown(struct uart_port *port)
{
    struct s3c24xx_uart_port *ourport = to_ourport(port);

    if (ourport->tx_claimed) {
        free_irq(ourport->tx_irq, ourport);
        tx_enabled(port) = 0;
        ourport->tx_claimed = 0;
    }

    if (ourport->rx_claimed) {
        free_irq(ourport->rx_irq, ourport);
        ourport->rx_claimed = 0;
        rx_enabled(port) = 0;
    }
}

tx_empty()成员函数s3c24xx_serial_tx_empty()用于判断发送缓冲区是否为空,当使能FIFO模式的时候,判断UFSTATn寄存器,否则判断UTRSTATn寄存器的相应位。

static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port)
{
    struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
    unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT);
    unsigned long ufcon = rd_regl(port, S3C2410_UFCON);

    if (ufcon & S3C2410_UFCON_FIFOMODE) {
        if ((ufstat & info->tx_fifomask) != 0 ||
            (ufstat & info->tx_fifofull))
            return 0;

        return 1;
    }

    return s3c24xx_serial_txempty_nofifo(port);
}

static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
{
    return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE);
}
在samsung.h中定义了如下操作寄存器的宏:
/* register access controls */

#define portaddr(port, reg) ((port)->membase + (reg))

#define rd_regb(port, reg) (__raw_readb(portaddr(port, reg)))
#define rd_regl(port, reg) (__raw_readl(portaddr(port, reg)))  //read regester long

#define wr_regb(port, reg, val) __raw_writeb(val, portaddr(port, reg))
#define wr_regl(port, reg, val) __raw_writel(val, portaddr(port, reg))

start_tx ()成员函数s3c24xx_serial_start_tx()用于启动发送,而stop_rx()成员函数 s3c24xx_serial_stop_tx()用于停止发送。

static void s3c24xx_serial_start_tx(struct uart_port *port)
{
    struct s3c24xx_uart_port *ourport = to_ourport(port);

    if (!tx_enabled(port)) {
        if (port->flags & UPF_CONS_FLOW)
            s3c24xx_serial_rx_disable(port);

        enable_irq(ourport->tx_irq);
        tx_enabled(port) = 1;
    }
}
static void s3c24xx_serial_stop_tx(struct uart_port *port)
{
    struct s3c24xx_uart_port *ourport = to_ourport(port);

    if (tx_enabled(port)) {
        disable_irq_nosync(ourport->tx_irq);
        tx_enabled(port) = 0;
        if (port->flags & UPF_CONS_FLOW)
            s3c24xx_serial_rx_enable(port);
    }
}

        S3C2410 串口驱动uart_ops结构体的set_termios()成员函数用于改变端口的参数设置,包括波特率、字长、停止位、奇偶校验等,它会根据传递给它的port、termios参数成员的值设置S3C2410 UART的ULCONn、UCONn、UMCONn等寄存器。

static void s3c24xx_serial_set_termios(struct uart_port *port,
                       struct ktermios *termios,
                       struct ktermios *old)
{
    struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
    struct s3c24xx_uart_port *ourport = to_ourport(port);
    struct s3c24xx_uart_clksrc *clksrc = NULL;
    struct clk *clk = NULL;
    unsigned long flags;
    unsigned int baud, quot;
    unsigned int ulcon;
    unsigned int umcon;
    unsigned int udivslot = 0;

    /*
     * We don't support modem control lines.
     */
    termios->c_cflag &= ~(HUPCL | CMSPAR);
    termios->c_cflag |= CLOCAL;

    /*
     * Ask the core to calculate the divisor for us.请求内核计算分频以便产生对应的波特率
     */

    baud = uart_get_baud_rate(port, termios, old, 0, 115200*8);

    if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
        quot = port->custom_divisor;
    else
        quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud);

    /* check to see if we need  to change clock source 检查以确定是否需要改变时钟源 */

    if (ourport->clksrc != clksrc || ourport->baudclk != clk) {
        dbg("selecting clock %p\n", clk);
        s3c24xx_serial_setsource(port, clksrc);

        if (ourport->baudclk != NULL && !IS_ERR(ourport->baudclk)) {
            clk_disable(ourport->baudclk);
            ourport->baudclk  = NULL;
        }

        clk_enable(clk);

        ourport->clksrc = clksrc;
        ourport->baudclk = clk;
        ourport->baudclk_rate = clk ? clk_get_rate(clk) : 0;
    }

    if (ourport->info->has_divslot) {
        unsigned int div = ourport->baudclk_rate / baud;

        udivslot = udivslot_table[div & 15];
        dbg("udivslot = %04x (div %d)\n", udivslot, div & 15);
    }
    /* 设置字长 */
    switch (termios->c_cflag & CSIZE) {
    case CS5:
        dbg("config: 5bits/char\n");
        ulcon = S3C2410_LCON_CS5;
        break;
    case CS6:
        dbg("config: 6bits/char\n");
        ulcon = S3C2410_LCON_CS6;
        break;
    case CS7:
        dbg("config: 7bits/char\n");
        ulcon = S3C2410_LCON_CS7;
        break;
    case CS8:
    default:
        dbg("config: 8bits/char\n");
        ulcon = S3C2410_LCON_CS8;
        break;
    }

    /* preserve original lcon IR settings */
    ulcon |= (cfg->ulcon & S3C2410_LCON_IRM);

    if (termios->c_cflag & CSTOPB)
        ulcon |= S3C2410_LCON_STOPB;

    umcon = (termios->c_cflag & CRTSCTS) ? S3C2410_UMCOM_AFC : 0;

    if (termios->c_cflag & PARENB) {
        if (termios->c_cflag & PARODD)
            ulcon |= S3C2410_LCON_PODD;
        else
            ulcon |= S3C2410_LCON_PEVEN;
    } else {
        ulcon |= S3C2410_LCON_PNONE;
    }

    spin_lock_irqsave(&port->lock, flags);

    dbg("setting ulcon to %08x, brddiv to %d, udivslot %08x\n",
        ulcon, quot, udivslot);

    wr_regl(port, S3C2410_ULCON, ulcon);
    wr_regl(port, S3C2410_UBRDIV, quot);
    wr_regl(port, S3C2410_UMCON, umcon);

    if (ourport->info->has_divslot)
        wr_regl(port, S3C2443_DIVSLOT, udivslot);

    dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n",
        rd_regl(port, S3C2410_ULCON),
        rd_regl(port, S3C2410_UCON),
        rd_regl(port, S3C2410_UFCON));

    /*
     * Update the per-port timeout.
     */
    uart_update_timeout(port, termios->c_cflag, baud);

    /*
     * Which character status flags are we interested in?
     */
    port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
    if (termios->c_iflag & INPCK)
        port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY;

    /*
     * Which character status flags should we ignore?
     */
    port->ignore_status_mask = 0;
    if (termios->c_iflag & IGNPAR)
        port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
    if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
        port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;

    /*
     * Ignore all characters if CREAD is not set.
     */
    if ((termios->c_cflag & CREAD) == 0)
        port->ignore_status_mask |= RXSTAT_DUMMY_READ;

    spin_unlock_irqrestore(&port->lock, flags);
}


  • 接收和发送中断处理函数
        在S3C2410 串口驱动中,与数据收发关系最密切的函数不是上述uart_ops成员函数,而是s3c24xx_serial_startup()为发送和接收中断注册的中断处理函数s3c24xx_serial_rx_chars()和s3c24xx_serial_tx_chars()。
        s3c24xx_serial_rx_chars ()读取URXHn寄存器以获得接收到的字符,并调用 uart_insert_char() 将该字符添加了tty设备的flip缓冲区中,当接收到64个字符或者不再能接受到字符后,调用tty_flip_buffer_push()函数向上层“推”tty设备的flip缓冲。
       s3c24xx_serial_tx_chars()读取uart_info中环形缓冲区中的字符,写入调用UTXHn寄存器。

#define S3C2410_UERSTAT_PARITY (0x1000)

static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id)
{
    struct s3c24xx_uart_port *ourport = dev_id;
    struct uart_port *port = &ourport->port;
    struct tty_struct *tty = port->info->port.tty;
    unsigned int ufcon, ch, flag, ufstat, uerstat;
    int max_count = 64;

    while (max_count-- > 0) {
        ufcon = rd_regl(port, S3C2410_UFCON);
        ufstat = rd_regl(port, S3C2410_UFSTAT);

        if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
            break;

        uerstat = rd_regl(port, S3C2410_UERSTAT);
        ch = rd_regb(port, S3C2410_URXH);

        if (port->flags & UPF_CONS_FLOW) {
            int txe = s3c24xx_serial_txempty_nofifo(port);

            if (rx_enabled(port)) {
                if (!txe) {
                    rx_enabled(port) = 0;
                    continue;
                }
            } else {
                if (txe) {
                    ufcon |= S3C2410_UFCON_RESETRX;
                    wr_regl(port, S3C2410_UFCON, ufcon);
                    rx_enabled(port) = 1;
                    goto out;
                }
                continue;
            }
        }

        /* insert the character into the buffer */

        flag = TTY_NORMAL;
        port->icount.rx++;

        if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {
            dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",
                ch, uerstat);

            /* check for break */
            if (uerstat & S3C2410_UERSTAT_BREAK) {
                dbg("break!\n");
                port->icount.brk++;
                if (uart_handle_break(port))
                    goto ignore_char;
            }

            if (uerstat & S3C2410_UERSTAT_FRAME)
                port->icount.frame++;
            if (uerstat & S3C2410_UERSTAT_OVERRUN)
                port->icount.overrun++;

            uerstat &= port->read_status_mask;

            if (uerstat & S3C2410_UERSTAT_BREAK)
                flag = TTY_BREAK;
            else if (uerstat & S3C2410_UERSTAT_PARITY)
                flag = TTY_PARITY;
            else if (uerstat & (S3C2410_UERSTAT_FRAME |
                        S3C2410_UERSTAT_OVERRUN))
                flag = TTY_FRAME;
        }

        if (uart_handle_sysrq_char(port, ch))
            goto ignore_char;

        uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,
                 ch, flag);

 ignore_char:
        continue;
    }
    tty_flip_buffer_push(tty);

 out:
    return IRQ_HANDLED;
}


static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{
    struct s3c24xx_uart_port *ourport = id;
    struct uart_port *port = &ourport->port;
    struct circ_buf *xmit = &port->info->xmit;
    int count = 256;

    if (port->x_char) {
        wr_regb(port, S3C2410_UTXH, port->x_char);
        port->icount.tx++;
        port->x_char = 0;
        goto out;
    }

    /* if there isnt anything more to transmit, or the uart is now
     * stopped, disable the uart and exit
    */

    if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
        s3c24xx_serial_stop_tx(port);
        goto out;
    }

    /* try and drain the buffer... */

    while (!uart_circ_empty(xmit) && count-- > 0) {
        if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull)
            break;

        wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
        xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
        port->icount.tx++;
    }

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

    if (uart_circ_empty(xmit))
        s3c24xx_serial_stop_tx(port);

 out:
    return IRQ_HANDLED;
}


  • 串口测试
getty 功能说明:设置终端机模式,连线速率和管制线路,但是s3c2440_serial1无法工作,原因可能是因为开发板上只有一个串口。

serial-test





GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐