/*
 * Copyright (C) 2005-2007 Jan Kiszka <jan.kiszka@web.de>.
 *
 * Xenomai is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Xenomai is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Xenomai; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <linux/version.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <asm/io.h>

#include <rtdm/rtserial.h>
#include <rtdm/rtdm_driver.h>

#define RT_16550_DRIVER_NAME    "xeno_16550A"

#define MAX_DEVICES        8

#define IN_BUFFER_SIZE        4096
#define OUT_BUFFER_SIZE        4096

#define DEFAULT_BAUD_BASE    115200
#define DEFAULT_TX_FIFO        16

#define PARITY_MASK        0x03
#define DATA_BITS_MASK        0x03
#define STOP_BITS_MASK        0x01
#define FIFO_MASK        0xC0
#define EVENT_MASK        0x0F

#define LCR_DLAB        0x80

#define FCR_FIFO        0x01
#define FCR_RESET_RX        0x02
#define FCR_RESET_TX        0x04

#define IER_RX            0x01
#define IER_TX            0x02
#define IER_STAT        0x04
#define IER_MODEM        0x08

#define IIR_MODEM        0x00
#define IIR_PIRQ        0x01
#define IIR_TX            0x02
#define IIR_RX            0x04
#define IIR_STAT        0x06
#define IIR_MASK        0x07

#define RHR            0    /* Receive Holding Buffer */
#define THR            0    /* Transmit Holding Buffer */
#define DLL            0    /* Divisor Latch LSB */
#define IER            1    /* Interrupt Enable Register */
#define DLM            1    /* Divisor Latch MSB */
#define IIR            2    /* Interrupt Id Register */
#define FCR            2    /* Fifo Control Register */
#define LCR            3    /* Line Control Register */
#define MCR            4    /* Modem Control Register */
#define LSR            5    /* Line Status Register */
#define MSR            6    /* Modem Status Register */

struct rt_16550_context {
    struct rtser_config config;    /* current device configuration */

rtdm_irq_t irq_handle;        /* device IRQ handle */
    rtdm_lock_t lock;        /* lock to protect context struct */

unsigned long base_addr;    /* hardware IO base address */
#ifdef CONFIG_XENO_DRIVERS_16550A_ANY
    int io_mode;            /* hardware IO-access mode */
#endif
    int tx_fifo;            /* cached global tx_fifo[<device>] */

int in_head;            /* RX ring buffer, head pointer */
    int in_tail;            /* RX ring buffer, tail pointer */
    size_t in_npend;        /* pending bytes in RX ring */
    int in_nwait;            /* bytes the user waits for */
    rtdm_event_t in_event;        /* raised to unblock reader */
    char in_buf[IN_BUFFER_SIZE];    /* RX ring buffer */
    volatile unsigned long in_lock;    /* single-reader lock */
    uint64_t *in_history;        /* RX timestamp buffer */

int out_head;            /* TX ring buffer, head pointer */
    int out_tail;            /* TX ring buffer, tail pointer */
    size_t out_npend;        /* pending bytes in TX ring */
    rtdm_event_t out_event;        /* raised to unblock writer */
    char out_buf[OUT_BUFFER_SIZE];    /* TX ring buffer */
    rtdm_mutex_t out_lock;        /* single-writer mutex */

uint64_t last_timestamp;    /* timestamp of last event */
    int ioc_events;            /* recorded events */
    rtdm_event_t ioc_event;        /* raised to unblock event waiter */
    volatile unsigned long ioc_event_lock;    /* single-waiter lock */

int ier_status;            /* IER cache */
    int mcr_status;            /* MCR cache */
    int status;            /* cache for LSR + soft-states */
    int saved_errors;        /* error cache for RTIOC_GET_STATUS */
};

static const struct rtser_config default_config = {
    0xFFFF, RTSER_DEF_BAUD, RTSER_DEF_PARITY, RTSER_DEF_BITS,
    RTSER_DEF_STOPB, RTSER_DEF_HAND, RTSER_DEF_FIFO_DEPTH, 0,
    RTSER_DEF_TIMEOUT, RTSER_DEF_TIMEOUT, RTSER_DEF_TIMEOUT,
    RTSER_DEF_TIMESTAMP_HISTORY, RTSER_DEF_EVENT_MASK, RTSER_DEF_RS485
};

static struct rtdm_device *device[MAX_DEVICES];

static unsigned int irq[MAX_DEVICES];
static unsigned long irqtype[MAX_DEVICES] = {
    [0 ... MAX_DEVICES-1] = RTDM_IRQTYPE_SHARED | RTDM_IRQTYPE_EDGE
};
static unsigned int baud_base[MAX_DEVICES];
static int tx_fifo[MAX_DEVICES];
static unsigned int start_index;

compat_module_param_array(irq, uint, MAX_DEVICES, 0400);
compat_module_param_array(baud_base, uint, MAX_DEVICES, 0400);
compat_module_param_array(tx_fifo, int, MAX_DEVICES, 0400);

MODULE_PARM_DESC(irq, "IRQ numbers of the serial devices");
MODULE_PARM_DESC(baud_base, "Maximum baud rate of the serial device "
         "(internal clock rate / 16)");
MODULE_PARM_DESC(tx_fifo, "Transmitter FIFO size");

module_param(start_index, uint, 0400);
MODULE_PARM_DESC(start_index, "First device instance number to be used");

MODULE_LICENSE("GPL");
MODULE_AUTHOR("jan.kiszka@web.de");

#include "16550A_io.h"
#include "16550A_pnp.h"
#include "16550A_pci.h"

static inline int rt_16550_rx_interrupt(struct rt_16550_context *ctx,
                    uint64_t * timestamp)
{
    unsigned long base = ctx->base_addr;
    int mode = rt_16550_io_mode_from_ctx(ctx);
    int rbytes = 0;
    int lsr = 0;
    int c;

do {
        c = rt_16550_reg_in(mode, base, RHR);    /* read input char */

ctx->in_buf[ctx->in_tail] = c;
        if (ctx->in_history)
            ctx->in_history[ctx->in_tail] = *timestamp;
        ctx->in_tail = (ctx->in_tail + 1) & (IN_BUFFER_SIZE - 1);

if (++ctx->in_npend > IN_BUFFER_SIZE) {
            lsr |= RTSER_SOFT_OVERRUN_ERR;
            ctx->in_npend--;
        }

rbytes++;
        lsr &= ~RTSER_LSR_DATA;
        lsr |= (rt_16550_reg_in(mode, base, LSR) &
            (RTSER_LSR_DATA | RTSER_LSR_OVERRUN_ERR |
             RTSER_LSR_PARITY_ERR | RTSER_LSR_FRAMING_ERR |
             RTSER_LSR_BREAK_IND));
    } while (testbits(lsr, RTSER_LSR_DATA));

/* save new errors */
    ctx->status |= lsr;

/* If we are enforcing the RTSCTS control flow and the input
       buffer is busy above the specified high watermark, clear
       RTS. */
/*    if (uart->i_count >= uart->config.rts_hiwm &&
        (uart->config.handshake & RT_UART_RTSCTS) != 0 &&
        (uart->modem & MCR_RTS) != 0) {
        uart->modem &= ~MCR_RTS;
        rt_16550_reg_out(mode, base, MCR, uart->modem);
    }*/

return rbytes;
}

static inline void rt_16550_tx_interrupt(struct rt_16550_context *ctx)
{
    int c;
    int count;
    unsigned long base = ctx->base_addr;
    int mode = rt_16550_io_mode_from_ctx(ctx);

/*    if (uart->modem & MSR_CTS)*/
    {
        for (count = ctx->tx_fifo;
             (count > 0) && (ctx->out_npend > 0);
             count--, ctx->out_npend--) {
            c = ctx->out_buf[ctx->out_head++];
            rt_16550_reg_out(mode, base, THR, c);
            ctx->out_head &= (OUT_BUFFER_SIZE - 1);
        }
    }
}

static inline void rt_16550_stat_interrupt(struct rt_16550_context *ctx)
{
    unsigned long base = ctx->base_addr;
    int mode = rt_16550_io_mode_from_ctx(ctx);

ctx->status |= (rt_16550_reg_in(mode, base, LSR) &
            (RTSER_LSR_OVERRUN_ERR | RTSER_LSR_PARITY_ERR |
             RTSER_LSR_FRAMING_ERR | RTSER_LSR_BREAK_IND));
}

static int rt_16550_interrupt(rtdm_irq_t * irq_context)
{
    struct rt_16550_context *ctx;
    unsigned long base;
    int mode;
    int iir;
    uint64_t timestamp = rtdm_clock_read();
    int rbytes = 0;
    int events = 0;
    int modem;
    int ret = RTDM_IRQ_NONE;

ctx = rtdm_irq_get_arg(irq_context, struct rt_16550_context);
    base = ctx->base_addr;
    mode = rt_16550_io_mode_from_ctx(ctx);

rtdm_lock_get(&ctx->lock);

while (1) {
        iir = rt_16550_reg_in(mode, base, IIR) & IIR_MASK;
        if (testbits(iir, IIR_PIRQ))
            break;

if (iir == IIR_RX) {
            rbytes += rt_16550_rx_interrupt(ctx, &timestamp);
            events |= RTSER_EVENT_RXPEND;
        } else if (iir == IIR_STAT)
            rt_16550_stat_interrupt(ctx);
        else if (iir == IIR_TX)
            rt_16550_tx_interrupt(ctx);
        else if (iir == IIR_MODEM) {
            modem = rt_16550_reg_in(mode, base, MSR);
            if (modem & (modem << 4))
                events |= RTSER_EVENT_MODEMHI;
            if ((modem ^ 0xF0) & (modem << 4))
                events |= RTSER_EVENT_MODEMLO;
        }

ret = RTDM_IRQ_HANDLED;
    }

if (ctx->in_nwait > 0) {
        if ((ctx->in_nwait <= rbytes) || ctx->status) {
            ctx->in_nwait = 0;
            rtdm_event_signal(&ctx->in_event);
        } else
            ctx->in_nwait -= rbytes;
    }

if (ctx->status) {
        events |= RTSER_EVENT_ERRPEND;
        ctx->ier_status &= ~IER_STAT;
    }

if (testbits(events, ctx->config.event_mask)) {
        int old_events = ctx->ioc_events;

ctx->last_timestamp = timestamp;
        ctx->ioc_events = events;

if (!old_events)
            rtdm_event_signal(&ctx->ioc_event);
    }

if (testbits(ctx->ier_status, IER_TX) && (ctx->out_npend == 0)) {
        /* mask transmitter empty interrupt */
        ctx->ier_status &= ~IER_TX;

rtdm_event_signal(&ctx->out_event);
    }

/* update interrupt mask */
    rt_16550_reg_out(mode, base, IER, ctx->ier_status);

rtdm_lock_put(&ctx->lock);

return ret;
}

static int rt_16550_set_config(struct rt_16550_context *ctx,
                   const struct rtser_config *config,
                   uint64_t **in_history_ptr)
{
    rtdm_lockctx_t lock_ctx;
    unsigned long base = ctx->base_addr;
    int mode = rt_16550_io_mode_from_ctx(ctx);
    int err = 0;

/* make line configuration atomic and IRQ-safe */
    rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

if (testbits(config->config_mask, RTSER_SET_BAUD)) {
        int dev_id = container_of(((void *)ctx),
                      struct rtdm_dev_context,
                      dev_private)->device->device_id;
        int baud_div;

ctx->config.baud_rate = config->baud_rate;
        baud_div = (baud_base[dev_id] + (ctx->config.baud_rate>>1)) /
            ctx->config.baud_rate;
        rt_16550_reg_out(mode, base, LCR, LCR_DLAB);
        rt_16550_reg_out(mode, base, DLL, baud_div & 0xff);
        rt_16550_reg_out(mode, base, DLM, baud_div >> 8);
    }

if (testbits(config->config_mask, RTSER_SET_PARITY))
        ctx->config.parity = config->parity & PARITY_MASK;
    if (testbits(config->config_mask, RTSER_SET_DATA_BITS))
        ctx->config.data_bits = config->data_bits & DATA_BITS_MASK;
    if (testbits(config->config_mask, RTSER_SET_STOP_BITS))
        ctx->config.stop_bits = config->stop_bits & STOP_BITS_MASK;

if (testbits(config->config_mask, RTSER_SET_PARITY |
                      RTSER_SET_DATA_BITS |
                      RTSER_SET_STOP_BITS |
                      RTSER_SET_BAUD)) {
        rt_16550_reg_out(mode, base, LCR,
                 (ctx->config.parity << 3) |
                 (ctx->config.stop_bits << 2) |
                 ctx->config.data_bits);
        ctx->status = 0;
        ctx->ioc_events &= ~RTSER_EVENT_ERRPEND;
    }

if (testbits(config->config_mask, RTSER_SET_FIFO_DEPTH)) {
        ctx->config.fifo_depth = config->fifo_depth & FIFO_MASK;
        rt_16550_reg_out(mode, base, FCR,
                 FCR_FIFO | FCR_RESET_RX | FCR_RESET_TX);
        rt_16550_reg_out(mode, base, FCR,
                 FCR_FIFO | ctx->config.fifo_depth);
    }

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

/* Timeout manipulation is not atomic. The user is supposed to take
       care not to use and change timeouts at the same time. */
    if (testbits(config->config_mask, RTSER_SET_TIMEOUT_RX))
        ctx->config.rx_timeout = config->rx_timeout;
    if (testbits(config->config_mask, RTSER_SET_TIMEOUT_TX))
        ctx->config.tx_timeout = config->tx_timeout;
    if (testbits(config->config_mask, RTSER_SET_TIMEOUT_EVENT))
        ctx->config.event_timeout = config->event_timeout;

if (testbits(config->config_mask, RTSER_SET_TIMESTAMP_HISTORY)) {
        /* change timestamp history atomically */
        rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

if (testbits
            (config->timestamp_history, RTSER_RX_TIMESTAMP_HISTORY)) {
            if (!ctx->in_history) {
                ctx->in_history = *in_history_ptr;
                *in_history_ptr = NULL;
                if (!ctx->in_history)
                    err = -ENOMEM;
            }
        } else {
            *in_history_ptr = ctx->in_history;
            ctx->in_history = NULL;
        }

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
    }

if (testbits(config->config_mask, RTSER_SET_EVENT_MASK)) {
        /* change event mask atomically */
        rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

ctx->config.event_mask = config->event_mask & EVENT_MASK;
        ctx->ioc_events = 0;

if (testbits(config->event_mask, RTSER_EVENT_RXPEND) &&
            (ctx->in_npend > 0))
            ctx->ioc_events |= RTSER_EVENT_RXPEND;

if (testbits(config->event_mask, RTSER_EVENT_ERRPEND)
            && ctx->status)
            ctx->ioc_events |= RTSER_EVENT_ERRPEND;

if (testbits(config->event_mask,
                 RTSER_EVENT_MODEMHI | RTSER_EVENT_MODEMLO))
            /* enable modem status interrupt */
            ctx->ier_status |= IER_MODEM;
        else
            /* disable modem status interrupt */
            ctx->ier_status &= ~IER_MODEM;
        rt_16550_reg_out(mode, base, IER, ctx->ier_status);

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
    }

if (testbits(config->config_mask, RTSER_SET_HANDSHAKE)) {
        /* change handshake atomically */
        rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

ctx->config.handshake = config->handshake;

switch (ctx->config.handshake) {
        case RTSER_RTSCTS_HAND:
            // ...?

default:    /* RTSER_NO_HAND */
            ctx->mcr_status =
                RTSER_MCR_DTR | RTSER_MCR_RTS | RTSER_MCR_OUT2;
            break;
        }
        rt_16550_reg_out(mode, base, MCR, ctx->mcr_status);

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
    }

return err;
}

void rt_16550_cleanup_ctx(struct rt_16550_context *ctx)
{
    rtdm_event_destroy(&ctx->in_event);
    rtdm_event_destroy(&ctx->out_event);
    rtdm_event_destroy(&ctx->ioc_event);
    rtdm_mutex_destroy(&ctx->out_lock);
}

int rt_16550_open(struct rtdm_dev_context *context,
          rtdm_user_info_t * user_info, int oflags)
{
    struct rt_16550_context *ctx;
    int dev_id = context->device->device_id;
    int err;
    uint64_t *dummy;
    rtdm_lockctx_t lock_ctx;

ctx = (struct rt_16550_context *)context->dev_private;

/* IPC initialisation - cannot fail with used parameters */
    rtdm_lock_init(&ctx->lock);
    rtdm_event_init(&ctx->in_event, 0);
    rtdm_event_init(&ctx->out_event, 0);
    rtdm_event_init(&ctx->ioc_event, 0);
    rtdm_mutex_init(&ctx->out_lock);

rt_16550_init_io_ctx(dev_id, ctx);

ctx->tx_fifo = tx_fifo[dev_id];

ctx->in_head = 0;
    ctx->in_tail = 0;
    ctx->in_npend = 0;
    ctx->in_nwait = 0;
    ctx->in_lock = 0;
    ctx->in_history = NULL;

ctx->out_head = 0;
    ctx->out_tail = 0;
    ctx->out_npend = 0;

ctx->ioc_events = 0;
    ctx->ioc_event_lock = 0;
    ctx->status = 0;
    ctx->saved_errors = 0;

rt_16550_set_config(ctx, &default_config, &dummy);

err = rtdm_irq_request(&ctx->irq_handle, irq[dev_id],
                   rt_16550_interrupt, irqtype[dev_id],
                   context->device->proc_name, ctx);
    if (err) {
        /* reset DTR and RTS */
        rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx), ctx->base_addr,
                 MCR, 0);

rt_16550_cleanup_ctx(ctx);

return err;
    }

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

/* enable interrupts */
    ctx->ier_status = IER_RX;
    rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx), ctx->base_addr, IER,
             IER_RX);

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

return 0;
}

int rt_16550_close(struct rtdm_dev_context *context,
           rtdm_user_info_t * user_info)
{
    struct rt_16550_context *ctx;
    unsigned long base;
    int mode;
    uint64_t *in_history;
    rtdm_lockctx_t lock_ctx;

ctx = (struct rt_16550_context *)context->dev_private;
    base = ctx->base_addr;
    mode = rt_16550_io_mode_from_ctx(ctx);

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

/* reset DTR and RTS */
    rt_16550_reg_out(mode, base, MCR, 0);

/* mask all UART interrupts and clear pending ones. */
    rt_16550_reg_out(mode, base, IER, 0);
    rt_16550_reg_in(mode, base, IIR);
    rt_16550_reg_in(mode, base, LSR);
    rt_16550_reg_in(mode, base, RHR);
    rt_16550_reg_in(mode, base, MSR);

in_history = ctx->in_history;
    ctx->in_history = NULL;

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

rtdm_irq_free(&ctx->irq_handle);

rt_16550_cleanup_ctx(ctx);

kfree(in_history);

return 0;
}

int rt_16550_ioctl(struct rtdm_dev_context *context,
           rtdm_user_info_t * user_info,
           unsigned int request, void *arg)
{
    rtdm_lockctx_t lock_ctx;
    struct rt_16550_context *ctx;
    int err = 0;
    unsigned long base;
    int mode;

ctx = (struct rt_16550_context *)context->dev_private;
    base = ctx->base_addr;
    mode = rt_16550_io_mode_from_ctx(ctx);

switch (request) {
    case RTSER_RTIOC_GET_CONFIG:
        if (user_info)
            err =
                rtdm_safe_copy_to_user(user_info, arg,
                           &ctx->config,
                           sizeof(struct
                              rtser_config));
        else
            memcpy(arg, &ctx->config,
                   sizeof(struct rtser_config));
        break;

case RTSER_RTIOC_SET_CONFIG: {
        struct rtser_config *config;
        struct rtser_config config_buf;
        uint64_t *hist_buf = NULL;

config = (struct rtser_config *)arg;

if (user_info) {
            err =
                rtdm_safe_copy_from_user(user_info, &config_buf,
                             arg,
                             sizeof(struct
                                rtser_config));
            if (err)
                return err;

config = &config_buf;
        }

if (testbits(config->config_mask, RTSER_SET_BAUD) &&
            (config->baud_rate >
             baud_base[context->device->device_id] ||
             config->baud_rate <= 0))
            /* invalid baudrate for this port */
            return -EINVAL;

if (testbits(config->config_mask,
                 RTSER_SET_TIMESTAMP_HISTORY)) {
            /*
             * Reflect the call to non-RT as we will likely
             * allocate or free the buffer.
             */
            if (rtdm_in_rt_context())
                return -ENOSYS;

if (testbits(config->timestamp_history,
                     RTSER_RX_TIMESTAMP_HISTORY))
                hist_buf = kmalloc(IN_BUFFER_SIZE *
                           sizeof(nanosecs_abs_t),
                           GFP_KERNEL);
        }

rt_16550_set_config(ctx, config, &hist_buf);

if (hist_buf)
            kfree(hist_buf);

break;
    }

case RTSER_RTIOC_GET_STATUS: {
        int status;

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

status = ctx->saved_errors | ctx->status;
        ctx->status = 0;
        ctx->saved_errors = 0;
        ctx->ioc_events &= ~RTSER_EVENT_ERRPEND;

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

if (user_info) {
            struct rtser_status status_buf;

status_buf.line_status =
                rt_16550_reg_in(mode, base, LSR) | status;
            status_buf.modem_status =
                rt_16550_reg_in(mode, base, MSR);

err =
                rtdm_safe_copy_to_user(user_info, arg,
                           &status_buf,
                           sizeof(struct
                              rtser_status));
        } else {
            ((struct rtser_status *)arg)->line_status =
                rt_16550_reg_in(mode, base, LSR) | status;
            ((struct rtser_status *)arg)->modem_status =
                rt_16550_reg_in(mode, base, MSR);
        }
        break;
    }

case RTSER_RTIOC_GET_CONTROL:
        if (user_info)
            err =
                rtdm_safe_copy_to_user(user_info, arg,
                           &ctx->mcr_status,
                           sizeof(int));
        else
            *(int *)arg = ctx->mcr_status;

break;

case RTSER_RTIOC_SET_CONTROL: {
        int new_mcr = (long)arg;

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
        ctx->mcr_status = new_mcr;
        rt_16550_reg_out(mode, base, MCR, new_mcr);
        rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
        break;
    }

case RTSER_RTIOC_WAIT_EVENT: {
        struct rtser_event ev = { .rxpend_timestamp = 0 };
        rtdm_toseq_t timeout_seq;

if (!rtdm_in_rt_context())
            return -ENOSYS;

/* Only one waiter allowed, stop any further attempts here. */
        if (test_and_set_bit(0, &ctx->ioc_event_lock))
            return -EBUSY;

rtdm_toseq_init(&timeout_seq, ctx->config.event_timeout);

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

while (!ctx->ioc_events) {
            /* Only enable error interrupt
               when the user waits for it. */
            if (testbits(ctx->config.event_mask,
                     RTSER_EVENT_ERRPEND)) {
                ctx->ier_status |= IER_STAT;
                rt_16550_reg_out(mode, base, IER,
                         ctx->ier_status);
            }

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

err = rtdm_event_timedwait(&ctx->ioc_event,
                           ctx->config.event_timeout,
                           &timeout_seq);
            if (err) {
                /* Device has been closed? */
                if (err == -EIDRM)
                    err = -EBADF;
                goto wait_unlock_out;
            }

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
        }

ev.events = ctx->ioc_events;
        ctx->ioc_events &=
            ~(RTSER_EVENT_MODEMHI | RTSER_EVENT_MODEMLO);

ev.last_timestamp = ctx->last_timestamp;
        ev.rx_pending = ctx->in_npend;

if (ctx->in_history)
            ev.rxpend_timestamp = ctx->in_history[ctx->in_head];

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

if (user_info)
            err =
                rtdm_safe_copy_to_user(user_info, arg, &ev,
                           sizeof(struct
                              rtser_event));
            else
                memcpy(arg, &ev, sizeof(struct rtser_event));

wait_unlock_out:
        /* release the simple event waiter lock */
        clear_bit(0, &ctx->ioc_event_lock);
        break;
    }

case RTSER_RTIOC_BREAK_CTL: {
        int lcr = ((long)arg & RTSER_BREAK_SET) << 6;

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

lcr |=
            (ctx->config.parity << 3) | (ctx->config.stop_bits << 2) |
            ctx->config.data_bits;

rt_16550_reg_out(mode, base, LCR, lcr);

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
        break;
    }

case RTIOC_PURGE: {
        int fcr = 0;

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
        if ((long)arg & RTDM_PURGE_RX_BUFFER) {
            ctx->in_head = 0;
            ctx->in_tail = 0;
            ctx->in_npend = 0;
            ctx->status = 0;
            fcr |= FCR_FIFO | FCR_RESET_RX;
            rt_16550_reg_in(mode, base, RHR);
        }
        if ((long)arg & RTDM_PURGE_TX_BUFFER) {
            ctx->out_head = 0;
            ctx->out_tail = 0;
            ctx->out_npend = 0;
            fcr |= FCR_FIFO | FCR_RESET_TX;
        }
        if (fcr) {
            rt_16550_reg_out(mode, base, FCR, fcr);
            rt_16550_reg_out(mode, base, FCR,
                     FCR_FIFO | ctx->config.fifo_depth);
        }
        rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
        break;
    }

default:
        err = -ENOTTY;
    }

return err;
}

ssize_t rt_16550_read(struct rtdm_dev_context * context,
              rtdm_user_info_t * user_info, void *buf, size_t nbyte)
{
    struct rt_16550_context *ctx;
    rtdm_lockctx_t lock_ctx;
    size_t read = 0;
    int pending;
    int block;
    int subblock;
    int in_pos;
    char *out_pos = (char *)buf;
    rtdm_toseq_t timeout_seq;
    ssize_t ret = -EAGAIN;    /* for non-blocking read */
    int nonblocking;

if (nbyte == 0)
        return 0;

if (user_info && !rtdm_rw_user_ok(user_info, buf, nbyte))
        return -EFAULT;

ctx = (struct rt_16550_context *)context->dev_private;

rtdm_toseq_init(&timeout_seq, ctx->config.rx_timeout);

/* non-blocking is handled separately here */
    nonblocking = (ctx->config.rx_timeout < 0);

/* only one reader allowed, stop any further attempts here */
    if (test_and_set_bit(0, &ctx->in_lock))
        return -EBUSY;

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

while (1) {
        /* switch on error interrupt - the user is ready to listen */
        if (!testbits(ctx->ier_status, IER_STAT)) {
            ctx->ier_status |= IER_STAT;
            rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx),
                     ctx->base_addr, IER,
                     ctx->ier_status);
        }

if (ctx->status) {
            if (testbits(ctx->status, RTSER_LSR_BREAK_IND))
                ret = -EPIPE;
            else
                ret = -EIO;
            ctx->saved_errors = ctx->status &
                (RTSER_LSR_OVERRUN_ERR | RTSER_LSR_PARITY_ERR |
                 RTSER_LSR_FRAMING_ERR | RTSER_SOFT_OVERRUN_ERR);
            ctx->status = 0;
            break;
        }

pending = ctx->in_npend;

if (pending > 0) {
            block = subblock = (pending <= nbyte) ? pending : nbyte;
            in_pos = ctx->in_head;

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

/* Do we have to wrap around the buffer end? */
            if (in_pos + subblock > IN_BUFFER_SIZE) {
                /* Treat the block between head and buffer end
                   separately. */
                subblock = IN_BUFFER_SIZE - in_pos;

if (user_info) {
                    if (rtdm_copy_to_user
                        (user_info, out_pos,
                         &ctx->in_buf[in_pos],
                         subblock) != 0) {
                        ret = -EFAULT;
                        goto break_unlocked;
                    }
                } else
                    memcpy(out_pos, &ctx->in_buf[in_pos],
                           subblock);

read += subblock;
                out_pos += subblock;

subblock = block - subblock;
                in_pos = 0;
            }

if (user_info) {
                if (rtdm_copy_to_user(user_info, out_pos,
                              &ctx->in_buf[in_pos],
                              subblock) != 0) {
                    ret = -EFAULT;
                    goto break_unlocked;
                }
            } else
                memcpy(out_pos, &ctx->in_buf[in_pos], subblock);

read += subblock;
            out_pos += subblock;
            nbyte -= block;

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

ctx->in_head =
                (ctx->in_head + block) & (IN_BUFFER_SIZE - 1);
            if ((ctx->in_npend -= block) == 0)
                ctx->ioc_events &= ~RTSER_EVENT_RXPEND;

if (nbyte == 0)
                break; /* All requested bytes read. */

continue;
        }

if (nonblocking)
            /* ret was set to EAGAIN in case of a real
               non-blocking call or contains the error
               returned by rtdm_event_wait[_until] */
            break;

ctx->in_nwait = nbyte;

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

ret = rtdm_event_timedwait(&ctx->in_event,
                       ctx->config.rx_timeout,
                       &timeout_seq);
        if (ret < 0) {
            if (ret == -EIDRM) {
                /* Device has been closed -
                   return immediately. */
                return -EBADF;
            }

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

nonblocking = 1;
            if (ctx->in_npend > 0) {
                /* Final turn: collect pending bytes
                   before exit. */
                continue;
            }

ctx->in_nwait = 0;
            break;
        }

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
    }

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

break_unlocked:
    /* Release the simple reader lock, */
    clear_bit(0, &ctx->in_lock);

if ((read > 0) && ((ret == 0) || (ret == -EAGAIN) ||
               (ret == -ETIMEDOUT) || (ret == -EINTR)))
        ret = read;

return ret;
}

ssize_t rt_16550_write(struct rtdm_dev_context * context,
               rtdm_user_info_t * user_info, const void *buf,
               size_t nbyte)
{
    struct rt_16550_context *ctx;
    rtdm_lockctx_t lock_ctx;
    size_t written = 0;
    int free;
    int block;
    int subblock;
    int out_pos;
    char *in_pos = (char *)buf;
    rtdm_toseq_t timeout_seq;
    ssize_t ret;

if (nbyte == 0)
        return 0;

if (user_info && !rtdm_read_user_ok(user_info, buf, nbyte))
        return -EFAULT;

ctx = (struct rt_16550_context *)context->dev_private;

rtdm_toseq_init(&timeout_seq, ctx->config.rx_timeout);

/* Make write operation atomic. */
    ret = rtdm_mutex_timedlock(&ctx->out_lock, ctx->config.rx_timeout,
                   &timeout_seq);
    if (ret)
        return ret;

while (nbyte > 0) {
        rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

free = OUT_BUFFER_SIZE - ctx->out_npend;

if (free > 0) {
            block = subblock = (nbyte <= free) ? nbyte : free;
            out_pos = ctx->out_tail;

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

/* Do we have to wrap around the buffer end? */
            if (out_pos + subblock > OUT_BUFFER_SIZE) {
                /* Treat the block between head and buffer
                   end separately. */
                subblock = OUT_BUFFER_SIZE - out_pos;

if (user_info) {
                    if (rtdm_copy_from_user
                        (user_info,
                         &ctx->out_buf[out_pos],
                         in_pos, subblock) != 0) {
                        ret = -EFAULT;
                        break;
                    }
                } else
                    memcpy(&ctx->out_buf[out_pos], in_pos,
                           subblock);

written += subblock;
                in_pos += subblock;

subblock = block - subblock;
                out_pos = 0;
            }

if (user_info) {
                if (rtdm_copy_from_user
                    (user_info, &ctx->out_buf[out_pos],
                     in_pos, subblock) != 0) {
                    ret = -EFAULT;
                    break;
                }
            } else
                memcpy(&ctx->out_buf[out_pos], in_pos, block);

written += subblock;
            in_pos += subblock;
            nbyte -= block;

rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);

ctx->out_tail =
                (ctx->out_tail + block) & (OUT_BUFFER_SIZE - 1);
            ctx->out_npend += block;

/* unmask tx interrupt */
            ctx->ier_status |= IER_TX;
            rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx),
                     ctx->base_addr, IER,
                     ctx->ier_status);

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
            continue;
        }

rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);

ret =
            rtdm_event_timedwait(&ctx->out_event,
                     ctx->config.tx_timeout,
                     &timeout_seq);
        if (ret < 0) {
            if (ret == -EIDRM) {
                /* Device has been closed -
                   return immediately. */
                return -EBADF;
            }
            if (ret == -EWOULDBLOCK) {
                /* Fix error code for non-blocking mode. */
                ret = -EAGAIN;
            }
            break;
        }
    }

rtdm_mutex_unlock(&ctx->out_lock);

if ((written > 0) && ((ret == 0) || (ret == -EAGAIN) ||
                  (ret == -ETIMEDOUT) || (ret == -EINTR)))
        ret = written;

return ret;
}

static const struct rtdm_device __initdata device_tmpl = {
    .struct_version        = RTDM_DEVICE_STRUCT_VER,

.device_flags        = RTDM_NAMED_DEVICE | RTDM_EXCLUSIVE,
    .context_size        = sizeof(struct rt_16550_context),
    .device_name        = "",

.open_nrt        = rt_16550_open,

.ops = {
        .close_nrt    = rt_16550_close,

.ioctl_rt    = rt_16550_ioctl,
        .ioctl_nrt    = rt_16550_ioctl,

.read_rt    = rt_16550_read,

.write_rt    = rt_16550_write,
    },

.device_class        = RTDM_CLASS_SERIAL,
    .device_sub_class    = RTDM_SUBCLASS_16550A,
    .profile_version    = RTSER_PROFILE_VER,
    .driver_name        = RT_16550_DRIVER_NAME,
    .driver_version        = RTDM_DRIVER_VER(1, 5, 2),
    .peripheral_name    = "UART 16550A",
    .provider_name        = "Jan Kiszka",
};

void rt_16550_exit(void);

int __init rt_16550_init(void)
{
    struct rtdm_device *dev;
    unsigned long base;
    int mode;
    int err;
    int i;

rt_16550_pnp_init();
    rt_16550_pci_init();

for (i = 0; i < MAX_DEVICES; i++) {
        if (!rt_16550_addr_param(i))
            continue;

err = -EINVAL;
        if (!irq[i] || !rt_16550_addr_param_valid(i))
            goto cleanup_out;

dev = kmalloc(sizeof(struct rtdm_device), GFP_KERNEL);
        err = -ENOMEM;
        if (!dev)
            goto cleanup_out;

memcpy(dev, &device_tmpl, sizeof(struct rtdm_device));
        snprintf(dev->device_name, RTDM_MAX_DEVNAME_LEN, "rtser%d",
             start_index + i);
        dev->device_id = i;

dev->proc_name = dev->device_name;

err = rt_16550_init_io(i, dev->device_name);
        if (err)
            goto kfree_out;

if (baud_base[i] == 0)
            baud_base[i] = DEFAULT_BAUD_BASE;

if (tx_fifo[i] == 0)
            tx_fifo[i] = DEFAULT_TX_FIFO;

/* Mask all UART interrupts and clear pending ones. */
        base = rt_16550_base_addr(i);
        mode = rt_16550_io_mode(i);
        rt_16550_reg_out(mode, base, IER, 0);
        rt_16550_reg_in(mode, base, IIR);
        rt_16550_reg_in(mode, base, LSR);
        rt_16550_reg_in(mode, base, RHR);
        rt_16550_reg_in(mode, base, MSR);

err = rtdm_dev_register(dev);

if (err)
            goto release_io_out;

device[i] = dev;
    }

return 0;

release_io_out:
    rt_16550_release_io(i);

kfree_out:
    kfree(dev);

cleanup_out:
    rt_16550_exit();

return err;
}

void rt_16550_exit(void)
{
    int i;

for (i = 0; i < MAX_DEVICES; i++)
        if (device[i]) {
            rtdm_dev_unregister(device[i], 1000);
            rt_16550_release_io(i);
            kfree(device[i]);
        }

rt_16550_pci_cleanup();
    rt_16550_pnp_cleanup();
}

module_init(rt_16550_init);
module_exit(rt_16550_exit);

rt serial 的编写的更多相关文章

  1. HttpClient_001_初步实现项目01的servlet,与项目02的servlet,之间数据访问

    HttpClient_001_初步实现项目01的servlet,与项目02的servlet,之间数据访问 代码下载地址: http://download.csdn.net/detail/poiuy19 ...

  2. JDK配置 java跨平台性

    jdk 虚拟机jre 依赖包javac 编译java 运行JAVA_HOME 一个存储jdk路径的自定义的变量,方便其他地方配置以后更改方便其他地方调用JAVA_HOME使用%JAVA_HOME%配置 ...

  3. 宜信开源|微服务任务调度平台SIA-TASK入手实践

    引言 最近宜信开源微服务任务调度平台SIA-TASK,SIA-TASK属于分布式的任务调度平台,使用起来简单方便,非常容易入手,部署搭建好SIA-TASK任务调度平台之后,编写TASK后配置JOB进行 ...

  4. 编写ros串口节点,使用官方serial包

    参考http://www.roswiki.com/read.php?tid=557&fid=39 1.通过sudo apt-get install ros-<distro>-ser ...

  5. 痞子衡嵌入式:飞思卡尔i.MX RT系列MCU启动那些事(3)- Serial Downloader模式(sdphost/MfgTool)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Serial Downloader模式. 在上一篇文章 Boot配置(BOOT Pin, eFUSE) ...

  6. 痞子衡嵌入式:飞思卡尔i.MX RT系列MCU启动那些事(13)- 从Serial(1-bit SPI) EEPROM/NOR恢复启动

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Serial EEPROM/NOR恢复启动. 在前几篇里痞子衡介绍的Boot Device都属于主动启 ...

  7. python3 Serial 串口助手的接收读取数据

    其实网上已经有许多python语言书写的串口,但大部分都是python2写的,没有找到一个合适的python编写的串口助手,只能自己来写一个串口助手,由于我只需要串口能够接收读取数据就可以了,故而这个 ...

  8. i.MX rt 系列微控制器的学习记录

    杂记 前言 我总是很希望自己能产生一种感知电压变化的能力,就像B站上的教学动图中,电流从电源流出时导线就像LED亮起来一样,我将指尖触到导线上就能感受到实时的电压变化.我在上学和工作时经常由于无法理解 ...

  9. 如何编写自己的Arduino库?

    一开始写Arduino 的时候很不习惯,没有main函数,因为好多东西都被隐藏了.一直想搞清楚,以便编写自己的库文件.于是研究一下午,下面是一些总结. Arduino工程的初步认识 一.目录规范 当你 ...

随机推荐

  1. 我追一个处女座的女孩快两个月了,我之前聊得很好,她说过有空call我去看电影,过了一个月她就不理我了,我喜欢她, 我是程序员,百度发不了那么多字。

    她刚刚进公司的时候,公司组织去打球,我叫她一起去她也去了,我和她聊了很多,聊得很自然,很开心,如我是哪个学习毕业的 我出来工作多久了等,她也聊了 她自己好多,她现在在读大学,只有周日上一天课那种. 我 ...

  2. Linux网络编程之聊天程序(TCP协议之select)

    服务器端:server.c #include <stdio.h> #include <stdlib.h> #include <errno.h> #include & ...

  3. 如何判断linux用户是否为root用户

    看命令行提示符 ,如果是#,那就是root用户,如下图:[root@localhost /]# 如果是$,那就是普通用户,如下图:[test@localhost ~]$

  4. 微信/易信公共平台开发(四):公众号调试器 (仿真微信平台,提供PHP源码)

    开发微信/易信公共平台时,调试往往很麻烦,一般只能在手机上边试边改, 或在服务器写日志.当你的服务器脚本有Bug时,手机上没有显示,追查是不容易的.我在开发过程中,编写了一个调试器, 能仿真微信/易信 ...

  5. IDEA20181.4破解

    1.下载破解补丁: http://idea.lanyus.com/jar/JetbrainsIdesCrack-4.2-release-sha1-3323d5d0b82e716609808090d3d ...

  6. webpack 实用配置总结

    1.webpack.config.js配置文件为: //处理共用.通用的js var webpack = require('webpack'); //处理html模板 var htmlWebpackP ...

  7. javascript 新知识

     document.compatMode 属性 BackCompat: Standards-compliant mode is not switched on. (Quirks Mode)  标准模式 ...

  8. 【图像算法】彩色图像切割专题八:基于MeanShift的彩色切割

    >原理曾经的博客中已经有对meanshift原理的解释,这里就不啰嗦了.国外的资料看这:http://people.csail.mit.edu/sparis/#cvpr07 >源代码 核心 ...

  9. WebService一些概念

    1.WebService,顾名思义就是基于Web的服务.它使用Web(HTTP)方式,接收和响应外部系统的某种请求.从而实现远程调用. 2.我们可以调用互联网上查询天气信息Web服务,然后将它嵌入到我 ...

  10. postgresql一般crud存储过程参考[转]

    http://blog.csdn.net/cdnight/article/details/18082255 这里是一份经过再三调试测试而成功的postgres数据库单表crud存储过程,请注意,对于多 ...