16c550 串口驱动实现

更新时间:
2024-01-09
下载文档

16c550 串口驱动实现

本节将详细给出 16c550 串口驱动的实现,实现方式可配置为中断方式或轮询方式。

16c550 串口初始化

16c550 串口的初始化函数 sio16c550Init 实现如下:

INT  sio16c550Init (SIO16C550_CHAN *psiochan)
{
/*
* initialize the driver function pointers in the SIO_CHAN's
*/
    psiochan->pdrvFuncs = &sio16c550_drv_funcs;
    LW_SPIN_INIT(&psiochan->slock);
    psiochan->channel_mode = SIO_MODE_POLL;
    psiochan->switch_en    = 0;
    psiochan->hw_option    = (CLOCAL | CREAD | CS8);
    psiochan->mcr = MCR_OUT2;
    psiochan->lcr = 0;
    psiochan->ier = 0;
    psiochan->bdefer = LW_FALSE;
    psiochan->err_overrun = 0;
    psiochan->err_parity  = 0;
    psiochan->err_framing = 0;
    psiochan->err_break   = 0;
    psiochan->rx_trigger_level &= 0x3;
    /*
     * reset the chip
     */
    sio16c550SetBaud(psiochan, psiochan->baud);
    sio16c550SetHwOption(psiochan, psiochan->hw_option);
    return  (ERROR_NONE);
}

初始化主要完成串口私有化数据的初始化以及串口硬件的初始化等,串口硬件初始化包括波特率设置和线控参数的设置。

设置波特率

波特率设置的函数 sio16c550SetBaud 实现如下:

static INT sio16c550SetBaud (SIO16C550_CHAN *psiochan, ULONG  baud)
{
    INTREG  intreg;
INT     divisor = (INT)((psiochan->xtal + (8 * baud)) / (16 * baud));
......
    /*
     * disable interrupts during chip access
     */
    intreg = KN_INT_DISABLE();
    /*
     * Enable access to the divisor latches by setting DLAB in LCR.
     */
    SET_REG(psiochan, LCR, (LCR_DLAB | psiochan->lcr));
    /*
     * Set divisor latches.
     */
    SET_REG(psiochan, DLL, divisor);
    SET_REG(psiochan, DLM, (divisor >> 8));
    /*
     * Restore line control register
     */
    SET_REG(psiochan, LCR, psiochan->lcr);
    psiochan->baud = baud;
    KN_INT_ENABLE(intreg);
    return  (ERROR_NONE);
}

波特率的设置主要即是对波特率相关的寄存器进行配置。

线控参数设置

线控设置函数 sio16c550SetHwOption 实现如下:

static INT sio16c550SetHwOption (SIO16C550_CHAN *psiochan, ULONG  hw_option)
{
    ......
INTREG  intreg;
    hw_option |= HUPCL;                            /* need HUPCL option         */
    psiochan->lcr = 0;
    psiochan->mcr &= (~(MCR_RTS | MCR_DTR));      /* clear RTS and DTR bits     */
    switch (hw_option & CSIZE) {                  /* data bit set               */
    case CS5:
        psiochan->lcr = CHAR_LEN_5;
        break;
    case CS6:
        psiochan->lcr = CHAR_LEN_6;
        break;
    case CS7:
        psiochan->lcr = CHAR_LEN_7;
        break;
    case CS8:
        psiochan->lcr = CHAR_LEN_8;
        break;
    default:
        psiochan->lcr = CHAR_LEN_8;
        break;
    }
    if (hw_option & STOPB) {                    /* stop bit set                */
        psiochan->lcr |= LCR_STB;
    } else {
        psiochan->lcr |= ONE_STOP;
    }
    switch (hw_option & (PARENB | PARODD)) {
    case PARENB | PARODD:
        psiochan->lcr |= LCR_PEN;
        break;
    case PARENB:
        psiochan->lcr |= (LCR_PEN | LCR_EPS);
        break;
    default:
        psiochan->lcr |= PARITY_NONE;
        break;
    }
    SET_REG(psiochan, IER, 0);
    if (!(hw_option & CLOCAL)) {
    /*
    * !clocal enables hardware flow control(DTR/DSR)
    */
        psiochan->mcr |= (MCR_DTR | MCR_RTS);
        psiochan->ier &= (~TxFIFO_BIT);
        psiochan->ier |= IER_EMSI;            /* en modem status interrupt    */
    } else {
        psiochan->ier &= ~IER_EMSI;            /* dis modem status interrupt    */
    }
    intreg = KN_INT_DISABLE();
    SET_REG(psiochan, LCR, psiochan->lcr);
    SET_REG(psiochan, MCR, psiochan->mcr);
    /*
     * now reset the channel mode registers
     */
    SET_REG(psiochan, FCR, 
            ((psiochan->rx_trigger_level << 6) | 
             RxCLEAR | TxCLEAR | FIFO_ENABLE));
    if (hw_option & CREAD) {
        psiochan->ier |= RxFIFO_BIT;
    }
    if (psiochan->channel_mode == SIO_MODE_INT) {
        SET_REG(psiochan, IER, psiochan->ier);        /* enable interrupt        */
    }
    KN_INT_ENABLE(intreg);
    psiochan->hw_option = hw_option;
    return  (ERROR_NONE);
}

该函数主要对串口的数据位、停止位、奇偶校验位进行了相关配置。

串口驱动操作结构体成员函数实现

对应串口驱动方法集结构体,16c550 的串口驱动主要实现了以下接口:

static INT sio16c550Ioctl(SIO16C550_CHAN *psiochan, INT cmd, LONG arg);
static INT sio16c550TxStartup(SIO16C550_CHAN  *psiochan);
static INT sio16c550CallbackInstall(SIO_CHAN  *pchan,
                                    INT        callbackType,
                                    INT      (*callback)(),
                                    VOID      *callbackArg);
static INT sio16c550PollInput(SIO16C550_CHAN  *psiochan, CHAR *pc);
static INT sio16c550PollOutput(SIO16C550_CHAN *psiochan, CHAR c);

下面将给出以上接口的具体实现。

串口接口控制

串口接口控制函数 sio16c550Ioctl 实现如下:

static INT sio16c550Ioctl (SIO16C550_CHAN *psiochan, INT cmd, LONG arg)
{
    INT  error = ERROR_NONE;
    switch (cmd) {
    case SIO_BAUD_SET:
        error = sio16c550SetBaud(psiochan, arg);
        break;
    case SIO_BAUD_GET:
        *((LONG *)arg) = psiochan->baud;
        break;
    case SIO_MODE_SET:
        error = sio16c550SetMode(psiochan, (INT)arg);
        break;
    case SIO_MODE_GET:
        *((LONG *)arg) = psiochan->channel_mode;
        break;
    case SIO_HW_OPTS_SET:
        error = sio16c550SetHwOption(psiochan, arg);
        break;
    case SIO_HW_OPTS_GET:
        *(LONG *)arg = psiochan->hw_option;
        break;
    case SIO_HUP:
        if (psiochan->hw_option & HUPCL) {
            error = sio16c550Hup(psiochan);
        }
        break;
    case SIO_OPEN:
        error = sio16c550Open(psiochan);
        break;
    case SIO_SWITCH_PIN_EN_SET:
        if ((INT)arg) {
            if (!psiochan->send_start) {
                _ErrorHandle(ENOSYS);
                error = PX_ERROR;
                break;
            }
            if (psiochan->switch_en == 0) {
                SEND_END(psiochan);
            }
            psiochan->switch_en = 1;
        } else {
            psiochan->switch_en = 0;
        }
        break;
    case SIO_SWITCH_PIN_EN_GET:
        *(LONG *)arg = psiochan->switch_en;
        break;
    default:
        _ErrorHandle(ENOSYS);
        error = PX_ERROR;
        break;
    }
    return  (error);
}

该接口主要实现的是对串口进行控制,在应用层中可以通过 open 打开 tty 设备,然后调用 ioctl 对串口进行控制,主要可以配置串口的波特率、串口模式、线控参数、打开/关闭等操作。

  • 关闭串口接口。

关闭串口函数 sio16c550Hup 实现如下:

static INT sio16c550Hup (SIO16C550_CHAN *psiochan)
{
    INTREG  intreg;
    intreg = KN_INT_DISABLE();
    psiochan->mcr &= (~(MCR_RTS | MCR_DTR));
    SET_REG(psiochan, MCR, psiochan->mcr);
    SET_REG(psiochan, FCR, (RxCLEAR | TxCLEAR));
    KN_INT_ENABLE(intreg);
    return  (ERROR_NONE);
}
  • 打开串口接口。

打开串口函数 sio16c550Open 实现如下:

static INT sio16c550Open (SIO16C550_CHAN *psiochan)
{
    INTREG  intreg;
    UINT8   mask;
    mask = (UINT8)(GET_REG(psiochan, MCR) & (MCR_RTS | MCR_DTR));
    if (mask != (MCR_RTS | MCR_DTR)) {
    /*
    * RTS and DTR not set yet
    */
        intreg = KN_INT_DISABLE();
        /*
         * set RTS and DTR TRUE
         */
        psiochan->mcr |= (MCR_DTR | MCR_RTS);
        SET_REG(psiochan, MCR, psiochan->mcr);
        /*
         * clear Tx and receive and enable FIFO
         */
        SET_REG(psiochan, FCR, 
                ((psiochan->rx_trigger_level << 6) | 
                 RxCLEAR | TxCLEAR | FIFO_ENABLE));
        KN_INT_ENABLE(intreg);
    }
    return  (ERROR_NONE);
}

驱动可通过调用 sio16c550Ioctl 并传递相应的 cmd 参数来实现串口打开或关闭操作。

串口接口模式设置

串口接口模式设置函数 sio16c550SetMode 实现如下:

static INT sio16c550SetMode (SIO16C550_CHAN *psiochan, INT newmode)
{
    INTREG  intreg;
    UINT8   mask;
    if ((newmode != SIO_MODE_POLL) && (newmode != SIO_MODE_INT)) {
        _ErrorHandle(EINVAL);
        return  (PX_ERROR);
    }
    if (psiochan->channel_mode == newmode) {
        return  (ERROR_NONE);
    }
    intreg = KN_INT_DISABLE();
    if (newmode == SIO_MODE_INT) {
    /*
    * Enable appropriate interrupts
     */
        if (psiochan->hw_option & CLOCAL) {
            SET_REG(psiochan, IER, (psiochan->ier | RxFIFO_BIT | TxFIFO_BIT));
        } else {
            mask = (UINT8)(GET_REG(psiochan, MSR) & MSR_CTS);
            /*
             * if the CTS is asserted enable Tx interrupt
             */
            if (mask & MSR_CTS) {
                psiochan->ier |= TxFIFO_BIT;    /* enable Tx interrupt */
            } else {
                psiochan->ier &= (~TxFIFO_BIT); /* disable Tx interrupt */
            }
            SET_REG(psiochan, IER, psiochan->ier);
        }
    } else {
      /*
       * disable all ns16550 interrupts
       */
        SET_REG(psiochan, IER, 0);
    }
    psiochan->channel_mode = newmode;
    KN_INT_ENABLE(intreg);
    return  (ERROR_NONE);
}

驱动可以调用该函数进行串口实现模式的切换,包括中断方式和轮询方式的切换。

串口接口启动发送

串口接口启动发送函数 sio16c550TxStartup 实现如下:

static INT sio16c550TxStartup (SIO16C550_CHAN *psiochan)
{
    INTREG  intreg;
    UINT8   mask;
    CHAR    cTx;
    if (psiochan->switch_en && (psiochan->hw_option & CLOCAL)) {
        SEND_START(psiochan);
        do {
            if (psiochan->pcbGetTxChar(psiochan->getTxArg, &cTx) != ERROR_NONE) {
                break;
            }
            while (!IS_Tx_HOLD_REG_EMPTY(psiochan));    
            SET_REG(psiochan, THR, cTx);
        } while (1);
        while (!IS_Tx_HOLD_REG_EMPTY(psiochan));
        SEND_END(psiochan);
        return  (ERROR_NONE);
    }
    if (psiochan->channel_mode == SIO_MODE_INT) {
        intreg = KN_INT_DISABLE();
        if (psiochan->hw_option & CLOCAL) {    
            psiochan->ier |= TxFIFO_BIT;
        } else {
            mask = (UINT8)(GET_REG(psiochan, MSR) & MSR_CTS);
            if (mask & MSR_CTS) {    
                psiochan->ier |= TxFIFO_BIT;
            } else {
                psiochan->ier &= (~TxFIFO_BIT);
            }
        }
        KN_SMP_MB();
        if (psiochan->int_ctx == 0) {
            SET_REG(psiochan, IER, psiochan->ier);
        }
        KN_INT_ENABLE(intreg);
        return  (ERROR_NONE);
    } else {
        _ErrorHandle(ENOSYS);
        return  (ENOSYS);
    }
}

该函数主要完成一次串口接口发送操作。

串口接口安装回调

串口接口安装回调函数 sio16c550CallbackInstall 实现如下:

static INT sio16c550CallbackInstall (SIO_CHAN *pchan,
                                     INT       callbackType,
                                     INT       (*callback)(),
                                     VOID      *callbackArg)
{
    SIO16C550_CHAN *psiochan = (SIO16C550_CHAN *)pchan;
    switch (callbackType) {
    case SIO_CALLBACK_GET_TX_CHAR:
        psiochan->pcbGetTxChar  = callback;
        psiochan->getTxArg      = callbackArg;
        return  (ERROR_NONE);
    case SIO_CALLBACK_PUT_RCV_CHAR:
        psiochan->pcbPutRcvChar = callback;
        psiochan->putRcvArg     = callbackArg;
        return  (ERROR_NONE);
    default:
        _ErrorHandle(ENOSYS);
        return  (PX_ERROR);
    }
}

串口接口安装回调函数主要完成针对指定串口通道的发送或接收的回调函数的赋值。

串口接口轮询输入

串口接口轮询输入函数 sio16c550PollInput 实现如下:

static INT sio16c550PollInput (SIO16C550_CHAN *psiochan, CHAR *pc)
{
    UINT8 poll_status = GET_REG(psiochan, LSR);
    if ((poll_status & LSR_DR) == 0x00) {
        _ErrorHandle(EAGAIN);
        return  (PX_ERROR);
    }
    *pc = GET_REG(psiochan, RBR);            /* got a character                */
    return  (ERROR_NONE);
}

串口接口轮询输出

串口接口轮询输出函数 sio16c550PollOutput 实现:

static INT sio16c550PollOutput (SIO16C550_CHAN *psiochan, char c)
{
    UINT8 msr = GET_REG(psiochan, MSR);
    while (!IS_Tx_HOLD_REG_EMPTY(psiochan));    /* wait tx holding reg empty*/
    if (!(psiochan->hw_option & CLOCAL)) {      /* modem flow control       */
        if (msr & MSR_CTS) {
            SET_REG(psiochan, THR, c);
        } else {
            _ErrorHandle(EAGAIN);
            return  (PX_ERROR);
        }
    } else {
        SET_REG(psiochan, THR, c);
    }
    return  (ERROR_NONE);
}

上述两个接收主要实现的是轮询方式进行串口数据的收发操作。

中断服务函数

若使用中断方式,还需要实现串口中断服务函数,中断服务函数实现如下:

VOID sio16c550Isr (SIO16C550_CHAN *psiochan)
{
    volatile UINT8  iir;
             UINT8  msr;
             UINT8  ucRd;
             UINT8  ucTx;
             INT    i;
    psiochan->int_ctx = 1;
    KN_SMP_MB();
    iir = (UINT8)(GET_REG(psiochan, IIR) & 0x0f);
    while (GET_REG(psiochan, LSR) & RxCHAR_AVAIL) {    /*  receive data        */
        ucRd = GET_REG(psiochan, RBR);
        psiochan->pcbPutRcvChar(psiochan->putRcvArg, ucRd);
    }
    if ((psiochan->ier & TxFIFO_BIT) && 
        (GET_REG(psiochan, LSR) & LSR_THRE)) {    /*  transmit data        */
        for (i = 0; i < psiochan->fifo_len; i++) {
            if (psiochan->pcbGetTxChar(psiochan->getTxArg, &ucTx) < 0) {
                psiochan->ier &= (~TxFIFO_BIT);
                break;            
            } else {
                SET_REG(psiochan, THR, ucTx);    /* char to Transmit Holding Reg  */
            }
        }
    }
    if (iir == IIR_MSTAT) {                     /* modem status changed           */
        msr = GET_REG(psiochan, MSR);
        if (msr & MSR_DCTS) {
            if (msr & MSR_CTS) {
                psiochan->ier |= TxFIFO_BIT;    /* CTS was turned on           */
            } else {
                psiochan->ier &= (~TxFIFO_BIT); /* CTS was turned off        */
            }
        }
    }    
    KN_SMP_MB();
    psiochan->int_ctx = 0;
    KN_SMP_MB();
    SET_REG(psiochan, IER, psiochan->ier);    /*  update ier           */
}   

至此,即完成了 16c550 串口驱动的主要部分的编写。

文档内容是否对您有所帮助?
有帮助
没帮助