• linux串口驱动分析


    linux串口驱动分析



    • 硬件资源及描写叙述
            s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 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用于描写叙述一个UARTport(直接相应于一个串口)的I/Oport或I/O内存地址、FIFO大小、port类型等信息。串口核心层提供例如以下函数来加入1个port:

    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()用于启动port,申请port的发送、接收中断,使能port的发送和接收。

    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) ",
            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 ", ourport->rx_irq);
            return ret;
        }

        ourport->rx_claimed = 1;

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

        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 ", ourport->tx_irq);
            goto err;
        }

        ourport->tx_claimed = 1;

        dbg("s3c24xx_serial_startup ok ");

        /* 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 ", 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) ", udivslot, div & 15);
        }
        /* 设置字长 */
        switch (termios->c_cflag & CSIZE) {
        case CS5:
            dbg("config: 5bits/char ");
            ulcon = S3C2410_LCON_CS5;
            break;
        case CS6:
            dbg("config: 6bits/char ");
            ulcon = S3C2410_LCON_CS6;
            break;
        case CS7:
            dbg("config: 7bits/char ");
            ulcon = S3C2410_LCON_CS7;
            break;
        case CS8:
        default:
            dbg("config: 8bits/char ");
            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 ",
            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 ",
            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 ",
                    ch, uerstat);

                /* check for break */
                if (uerstat & S3C2410_UERSTAT_BREAK) {
                    dbg("break! ");
                    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





  • 相关阅读:
    RabbitMQ与AMQP协议详解
    MemCache超详细解读
    ASP.NET Web API通过ActionFilter来实现缓存
    ASP.NET Web API 通过Authentication特性来实现身份认证
    什么是CSR证书申请文件?
    跨平台的 SQL 客户端
    Java生成公私钥对
    git 删除错误提交的commit
    ServiceStack.Text反序列化lowercase_underscore_names格式的JSON
    .NET AES加解密(128位)
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/4297691.html
Copyright © 2020-2023  润新知