/******************************************************************************\
This file is part of the Buildbotics firmware.
Copyright (c) 2015 - 2019, Buildbotics LLC
All rights reserved.
This file ("the software") is free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License,
version 2 as published by the Free Software Foundation. You should
have received a copy of the GNU General Public License, version 2
along with the software. If not, see .
The software 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the software. If not, see
.
For information regarding this software email:
"Joseph Coffland"
\******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Joseph Coffland");
MODULE_DESCRIPTION("Buildbotics controller serial port driver");
MODULE_VERSION("0.3");
#define DEVICE_NAME "ttyAMA0"
#define BUF_SIZE (1 << 16)
#define UART01x_LCRH_WLEN_bm 0x60
static int debug = 0;
module_param(debug, int, 0660);
struct ring_buf {
unsigned char *buf;
unsigned head;
unsigned tail;
};
static struct {
struct clk *clk;
struct ring_buf tx_buf;
struct ring_buf rx_buf;
spinlock_t lock;
unsigned open;
unsigned char __iomem *base;
wait_queue_head_t read_wait;
wait_queue_head_t write_wait;
unsigned irq;
unsigned im; // interrupt mask
unsigned brk_errs;
unsigned parity_errs;
unsigned frame_errs;
unsigned overruns;
int major;
struct class *class;
struct device *dev;
struct ktermios term;
} _port;
#define RING_BUF_INC(BUF, INDEX) \
do {(BUF).INDEX = ((BUF).INDEX + 1) & (BUF_SIZE - 1);} while (0)
#define RING_BUF_POP(BUF) RING_BUF_INC(BUF, tail)
#define RING_BUF_PUSH(BUF, C) \
do { \
(BUF).buf[(BUF).head] = C; \
mb(); \
RING_BUF_INC(BUF, head); \
} while (0)
#define RING_BUF_POKE(BUF) (BUF).buf[(BUF).head]
#define RING_BUF_PEEK(BUF) (BUF).buf[(BUF).tail]
#define RING_BUF_SPACE(BUF) ((((BUF).tail) - ((BUF).head + 1)) & (BUF_SIZE - 1))
#define RING_BUF_FILL(BUF) ((((BUF).head) - ((BUF).tail)) & (BUF_SIZE - 1))
#define RING_BUF_CLEAR(BUF) do {(BUF).head = (BUF).tail = 0;} while (0)
static unsigned _read(unsigned reg) {return readw_relaxed(_port.base + reg);}
static void _write(unsigned val, unsigned reg) {
writew_relaxed(val, _port.base + reg);
}
static void _tx_chars(void) {
unsigned fill = RING_BUF_FILL(_port.tx_buf);
while (fill--) {
// Check if UART FIFO full
if (_read(UART01x_FR) & UART01x_FR_TXFF) break;
_write(RING_BUF_PEEK(_port.tx_buf), UART01x_DR);
mb();
RING_BUF_POP(_port.tx_buf);
}
// Stop TX when buffer is empty
if (!RING_BUF_FILL(_port.tx_buf)) {
_port.im &= ~UART011_TXIM;
_write(_port.im, UART011_IMSC);
}
}
static void _rx_chars(void) {
unsigned space = RING_BUF_SPACE(_port.rx_buf);
while (space--) {
// Check if UART FIFO empty
unsigned status = _read(UART01x_FR);
if (status & UART01x_FR_RXFE) break;
// Read char from FIFO and update status
unsigned ch = _read(UART01x_DR);
// Record errors
if (ch & UART011_DR_BE) _port.brk_errs++;
if (ch & UART011_DR_PE) _port.parity_errs++;
if (ch & UART011_DR_FE) _port.frame_errs++;
if (ch & UART011_DR_OE) _port.overruns++;
// Queue char
RING_BUF_PUSH(_port.rx_buf, ch);
}
// Stop RX interrupts when buffer is full
if (!RING_BUF_SPACE(_port.rx_buf)) {
_port.im &= ~(UART011_RXIM | UART011_RTIM);
_write(_port.im, UART011_IMSC);
}
}
static int _read_status(void) {
int status = 0;
unsigned fr = _read(UART01x_FR);
unsigned cr = _read(UART011_CR);
if (fr & UART01x_FR_DSR) status |= TIOCM_LE; // DSR (data set ready)
if (cr & UART011_CR_DTR) status |= TIOCM_DTR; // DTR (data terminal ready)
if (cr & UART011_CR_RTS) status |= TIOCM_RTS; // RTS (request to send)
// TODO What is TIOCM_ST - Secondary TXD (transmit)?
// TODO What is TIOCM_SR - Secondary RXD (receive)?
if (fr & UART01x_FR_CTS) status |= TIOCM_CTS; // CTS (clear to send)
if (fr & UART01x_FR_DCD) status |= TIOCM_CD; // DCD (data carrier detect)
if (fr & UART011_FR_RI) status |= TIOCM_RI; // RI (ring)
if (fr & UART01x_FR_DSR) status |= TIOCM_DSR; // DSR (data set ready)
if (debug) printk(KERN_INFO "bbserial: _read_status() = %d\n", status);
return status;
}
static void _write_status(int status) {
if (debug) printk(KERN_INFO "bbserial: _write_status() = %d\n", status);
unsigned long flags;
spin_lock_irqsave(&_port.lock, flags);
unsigned cr = _read(UART011_CR);
// DTR (data terminal ready)
if (status & TIOCM_DTR) cr |= UART011_CR_DTR;
else cr &= ~UART011_CR_DTR;
// RTS (request to send)
if (status & TIOCM_RTS) cr |= UART011_CR_RTS;
else cr &= ~UART011_CR_RTS;
_write(cr, UART011_CR);
spin_unlock_irqrestore(&_port.lock, flags);
}
static struct ktermios *_get_term(void) {
unsigned lcrh = _read(UART011_LCRH);
unsigned cr = _read(UART011_CR);
// Baud rate
unsigned brd = _read(UART011_IBRD) << 6 | _read(UART011_FBRD);
speed_t baud = clk_get_rate(_port.clk) * 4 / brd;
tty_termios_encode_baud_rate(&_port.term, baud, baud);
// Data bits
unsigned cflag;
switch (lcrh & UART01x_LCRH_WLEN_bm) {
case UART01x_LCRH_WLEN_5: cflag = CS5; break;
case UART01x_LCRH_WLEN_6: cflag = CS6; break;
case UART01x_LCRH_WLEN_7: cflag = CS7; break;
default: cflag = CS8; break;
}
// Stop bits
if (lcrh & UART01x_LCRH_STP2) cflag |= CSTOPB;
// Parity
if (lcrh & UART01x_LCRH_PEN) {
cflag |= PARENB;
if (!(UART01x_LCRH_EPS & lcrh)) cflag |= PARODD;
if (UART011_LCRH_SPS & lcrh) cflag |= CMSPAR;
}
// Hardware flow control
if (cr & UART011_CR_CTSEN) cflag |= CRTSCTS;
_port.term.c_cflag = cflag;
return &_port.term;
}
static void _set_baud(speed_t baud) {
if (debug) printk(KERN_INFO "bbserial: baud=%d\n", baud);
unsigned brd = clk_get_rate(_port.clk) * 16 / baud;
if ((brd & 3) == 3) brd = (brd >> 2) + 1; // Round up
else brd >>= 2;
_write(brd & 0x3f, UART011_FBRD);
_write(brd >> 6, UART011_IBRD);
}
static int _set_term(struct ktermios *term) {
unsigned lcrh = UART01x_LCRH_FEN; // Enable FIFOs
unsigned cflag = term->c_cflag;
// Data bits
switch (cflag & CSIZE) {
case CS5: lcrh |= UART01x_LCRH_WLEN_5; break;
case CS6: lcrh |= UART01x_LCRH_WLEN_6; break;
case CS7: lcrh |= UART01x_LCRH_WLEN_7; break;
default: lcrh |= UART01x_LCRH_WLEN_8; break;
}
// Stop bits
if (cflag & CSTOPB) lcrh |= UART01x_LCRH_STP2;
// Parity
if (cflag & PARENB) {
lcrh |= UART01x_LCRH_PEN;
if (!(cflag & PARODD)) lcrh |= UART01x_LCRH_EPS;
if (cflag & CMSPAR) lcrh |= UART011_LCRH_SPS;
}
// Get baud rate
speed_t baud = tty_termios_baud_rate(term);
// Set
unsigned long flags;
spin_lock_irqsave(&_port.lock, flags);
// Hardware flow control
unsigned cr = _read(UART011_CR);
if (cflag & CRTSCTS) cr |= UART011_CR_CTSEN;
_write(0, UART011_CR); // Disable
_set_baud(baud); // Baud
_write(lcrh, UART011_LCRH); // Must be after baud
_write(cr, UART011_CR); // Enable
spin_unlock_irqrestore(&_port.lock, flags);
return 0;
}
static void _flush_input(void) {
unsigned long flags;
spin_lock_irqsave(&_port.lock, flags);
RING_BUF_CLEAR(_port.rx_buf);
spin_unlock_irqrestore(&_port.lock, flags);
}
static void _flush_output(void) {
unsigned long flags;
spin_lock_irqsave(&_port.lock, flags);
RING_BUF_CLEAR(_port.tx_buf);
spin_unlock_irqrestore(&_port.lock, flags);
}
static irqreturn_t _interrupt(int irq, void *id) {
unsigned long flags;
spin_lock_irqsave(&_port.lock, flags);
// Read and/or write
unsigned status = _read(UART011_MIS);
if (status & (UART011_RTIS | UART011_RXIS)) _rx_chars();
if (status & UART011_TXIS) _tx_chars();
unsigned txSpace = RING_BUF_SPACE(_port.tx_buf);
unsigned rxFill = RING_BUF_FILL(_port.rx_buf);
spin_unlock_irqrestore(&_port.lock, flags);
// Notify pollers
if (rxFill) wake_up_interruptible_poll(&_port.read_wait, POLLIN);
if (txSpace) wake_up_interruptible_poll(&_port.write_wait, POLLOUT);
return IRQ_HANDLED;
}
static void _enable_tx(void) {
unsigned long flags;
spin_lock_irqsave(&_port.lock, flags);
_port.im |= UART011_TXIM;
_write(_port.im, UART011_IMSC);
_tx_chars(); // Must prime the pump
spin_unlock_irqrestore(&_port.lock, flags);
}
static int _tx_enabled(void) {return _port.im & UART011_TXIM;}
static void _enable_rx(void) {
unsigned long flags;
spin_lock_irqsave(&_port.lock, flags);
_port.im |= UART011_RTIM | UART011_RXIM;
_write(_port.im, UART011_IMSC);
spin_unlock_irqrestore(&_port.lock, flags);
}
static int _rx_enabled(void) {return _port.im & (UART011_RTIM | UART011_RXIM);}
static int _dev_open(struct inode *inodep, struct file *filep) {
if (debug) printk(KERN_INFO "bbserial: open()\n");
if (_port.open) return -EBUSY;
_port.open = 1;
return 0;
}
static ssize_t _dev_read(struct file *filep, char *buffer, size_t len,
loff_t *offset) {
if (debug) printk(KERN_INFO "bbserial: read() len=%d overruns=%d\n", len,
_port.overruns);
ssize_t bytes = 0;
while (bytes < len && RING_BUF_FILL(_port.rx_buf)) {
put_user(RING_BUF_PEEK(_port.rx_buf), buffer++);
RING_BUF_POP(_port.rx_buf);
bytes++;
if (!_rx_enabled()) _enable_rx();
}
return bytes ? bytes : -EAGAIN;
}
static ssize_t _dev_write(struct file *filep, const char *buffer, size_t len,
loff_t *offset) {
if (debug)
printk(KERN_INFO "bbserial: write() len=%d tx=%d rx=%d\n",
len, RING_BUF_FILL(_port.tx_buf), RING_BUF_FILL(_port.rx_buf));
ssize_t bytes = 0;
while (bytes < len && RING_BUF_SPACE(_port.tx_buf)) {
get_user(RING_BUF_POKE(_port.tx_buf), buffer++);
RING_BUF_INC(_port.tx_buf, head);
bytes++;
if (!_tx_enabled()) _enable_tx();
}
return bytes ? bytes : -EAGAIN;
}
static int _dev_release(struct inode *inodep, struct file *filep) {
printk(KERN_INFO "bbserial: release()\n");
_port.open = 0;
return 0;
}
static unsigned _dev_poll(struct file *file, poll_table *wait) {
if (debug) {
unsigned events = poll_requested_events(wait);
printk(KERN_INFO "bbserial: poll(in=%s, out=%s)\n",
(events & POLLIN) ? "true" : "false",
(events & POLLOUT) ? "true" : "false");
}
poll_wait(file, &_port.read_wait, wait);
poll_wait(file, &_port.write_wait, wait);
unsigned ret = 0;
if (RING_BUF_FILL(_port.rx_buf)) ret |= POLLIN | POLLRDNORM;
if (RING_BUF_SPACE(_port.tx_buf)) ret |= POLLOUT | POLLWRNORM;
if (debug) printk(KERN_INFO "bbserial: tx=%d rx=%d\n",
RING_BUF_FILL(_port.tx_buf), RING_BUF_FILL(_port.rx_buf));
return ret;
}
static long _dev_ioctl(struct file *file, unsigned cmd, unsigned long arg) {
if (debug)
printk(KERN_INFO "bbserial: ioctl() cmd=0x%04x arg=%lu\n", cmd, arg);
int __user *ptr = (int __user *)arg;
int status;
switch (cmd) {
case TCGETS: { // Get serial port settings
struct ktermios *term = _get_term();
if (copy_to_user((void __user *)arg, &term, sizeof(struct termios)))
return -EFAULT;
return 0;
}
case TCSETS: { // Set serial port settings
struct ktermios term;
if (copy_from_user(&term, (void __user *)arg, sizeof(struct termios)))
return -EFAULT;
return _set_term(&term);
}
case TIOCMGET: // Get status of modem bits
put_user(_read_status(), ptr);
return 0;
case TIOCMSET: // Set status of modem bits
get_user(status, ptr);
_write_status(status);
return 0;
case TIOCMBIC: // Clear indicated modem bits
get_user(status, ptr);
_write_status(~status & _read_status());
return 0;
case TIOCMBIS: // Set indicated modem bits
get_user(status, ptr);
_write_status(status | _read_status());
return 0;
case TCFLSH: // Flush
if (arg == TCIFLUSH || arg == TCIOFLUSH) _flush_input();
if (arg == TCOFLUSH || arg == TCIOFLUSH) _flush_output();
return 0;
case TIOCINQ: return put_user(RING_BUF_FILL(_port.rx_buf), ptr);
case TIOCOUTQ: return put_user(RING_BUF_FILL(_port.tx_buf), ptr);
default: return -ENOIOCTLCMD;
}
return 0;
}
static struct file_operations _ops = {
.owner = THIS_MODULE,
.open = _dev_open,
.read = _dev_read,
.write = _dev_write,
.release = _dev_release,
.poll = _dev_poll,
.unlocked_ioctl = _dev_ioctl,
};
static int _probe(struct amba_device *dev, const struct amba_id *id) {
if (debug) printk(KERN_INFO "bbserial: probing\n");
// Allocate buffers
_port.tx_buf.buf = devm_kzalloc(&dev->dev, BUF_SIZE, GFP_KERNEL);
_port.rx_buf.buf = devm_kzalloc(&dev->dev, BUF_SIZE, GFP_KERNEL);
if (!_port.tx_buf.buf || !_port.rx_buf.buf) return -ENOMEM;
// Map IO memory
_port.base = devm_ioremap_resource(&dev->dev, &dev->res);
if (IS_ERR(_port.base)) {
dev_err(&dev->dev, "bbserial: failed to map IO memory\n");
return PTR_ERR(_port.base);
}
// Get and enable clock
_port.clk = devm_clk_get(&dev->dev, 0);
if (IS_ERR(_port.clk)) {
dev_err(&dev->dev, "bbserial: failed to get clock\n");
return PTR_ERR(_port.clk);
}
int ret = clk_prepare_enable(_port.clk);
if (ret) {
dev_err(&dev->dev, "bbserial: clock prepare failed\n");
return ret;
}
// Disable UART and mask interrupts
_write(0, UART011_CR);
_write(0, UART011_IMSC);
// Set default baud rate
_set_baud(38400);
// N81 & enable FIFOs, must be after baud
_write(UART01x_LCRH_WLEN_8 | UART01x_LCRH_FEN, UART011_LCRH);
// Enable, TX, RX, RTS, DTR & CTS
unsigned cr = UART01x_CR_UARTEN | UART011_CR_RXE | UART011_CR_TXE |
UART011_CR_RTS | UART011_CR_DTR | UART011_CR_CTSEN;
_write(cr, UART011_CR);
// Set interrupt FIFO trigger levels
_write(UART011_IFLS_RX2_8 | UART011_IFLS_TX6_8, UART011_IFLS);
// Clear pending interrupts
_write(0x7ff, UART011_ICR);
// Enable read interrupts
_port.im = 0;
_enable_rx();
// Allocate IRQ
_port.irq = dev->irq[0];
ret = request_irq(_port.irq, _interrupt, 0, "bbserial", &_port);
if (ret) {
dev_err(&dev->dev, "bbserial: request for IRQ failed\n");
clk_disable_unprepare(_port.clk);
return ret;
}
// Dynamically allocate device major number
_port.major = register_chrdev(0, DEVICE_NAME, &_ops);
if (_port.major < 0) {
clk_disable_unprepare(_port.clk);
dev_err(&dev->dev, "bbserial: failed to register a major number\n");
return _port.major;
}
// Register device class
_port.class = class_create(THIS_MODULE, "bbs");
if (IS_ERR(_port.class)) {
unregister_chrdev(_port.major, DEVICE_NAME);
clk_disable_unprepare(_port.clk);
dev_err(&dev->dev, "bbserial: failed to register device class\n");
return PTR_ERR(_port.class);
}
// Register device driver
_port.dev =
device_create(_port.class, 0, MKDEV(_port.major, 0), 0, DEVICE_NAME);
if (IS_ERR(_port.dev)) {
class_destroy(_port.class);
unregister_chrdev(_port.major, DEVICE_NAME);
dev_err(&dev->dev, "bbserial: failed to create the device\n");
return PTR_ERR(_port.dev);
}
return 0;
}
static int _remove(struct amba_device *dev) {
if (debug) printk(KERN_INFO "bbserial: removing\n");
unsigned long flags;
spin_lock_irqsave(&_port.lock, flags);
// Mask and clear interrupts
_write(0, UART011_IMSC);
_write(0x7ff, UART011_ICR);
// Disable UART
_write(0, UART011_CR);
spin_unlock_irqrestore(&_port.lock, flags);
// Free IRQ
free_irq(_port.irq, &_port);
synchronize_irq(_port.irq);
// Shut down the clock producer
clk_disable_unprepare(_port.clk);
// Unload char dev
device_destroy(_port.class, MKDEV(_port.major, 0));
class_unregister(_port.class);
class_destroy(_port.class);
unregister_chrdev(_port.major, DEVICE_NAME);
return 0;
}
static struct amba_id _ids[] = {
{
.id = 0x00041011,
.mask = 0x000fffff,
.data = 0,
},
{0, 0},
};
MODULE_DEVICE_TABLE(amba, _ids);
static struct amba_driver _driver = {
.drv = {.name = "bbserial"},
.id_table = _ids,
.probe = _probe,
.remove = _remove,
};
static int __init bbserial_init(void) {
printk(KERN_INFO "bbserial: loaded\n");
// Clear memory
memset(&_port, 0, sizeof(_port));
// Init lock
spin_lock_init(&_port.lock);
// Init wait queues
init_waitqueue_head(&_port.read_wait);
init_waitqueue_head(&_port.write_wait);
return amba_driver_register(&_driver);
}
static void __exit bbserial_exit(void) {
amba_driver_unregister(&_driver);
printk(KERN_INFO "bbserial: unloaded\n");
}
module_init(bbserial_init);
module_exit(bbserial_exit);