#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/wait.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/jiffies.h>
#include <linux/miscdevice.h>
#include <linux/debugfs.h>
#include <linux/poll.h>
#define DRIVER_NAME "axis_fifo"
#define READ_BUF_SIZE 128U
#define AXIS_FIFO_DEBUG_REG_NAME_MAX_LEN 4
#define XLLF_ISR_OFFSET 0x00
#define XLLF_IER_OFFSET 0x04
#define XLLF_TDFR_OFFSET 0x08
#define XLLF_TDFV_OFFSET 0x0c
#define XLLF_TDFD_OFFSET 0x10
#define XLLF_TLR_OFFSET 0x14
#define XLLF_RDFR_OFFSET 0x18
#define XLLF_RDFO_OFFSET 0x1c
#define XLLF_RDFD_OFFSET 0x20
#define XLLF_RLR_OFFSET 0x24
#define XLLF_SRR_OFFSET 0x28
#define XLLF_TDR_OFFSET 0x2C
#define XLLF_RDR_OFFSET 0x30
#define XLLF_RDFR_RESET_MASK 0xa5
#define XLLF_TDFR_RESET_MASK 0xa5
#define XLLF_SRR_RESET_MASK 0xa5
#define XLLF_INT_RPURE_MASK BIT(31)
#define XLLF_INT_RPORE_MASK BIT(30)
#define XLLF_INT_RPUE_MASK BIT(29)
#define XLLF_INT_TPOE_MASK BIT(28)
#define XLLF_INT_TC_MASK BIT(27)
#define XLLF_INT_RC_MASK BIT(26)
#define XLLF_INT_TSE_MASK BIT(25)
#define XLLF_INT_CLEAR_ALL GENMASK(31, 0)
static DEFINE_IDA(axis_fifo_ida);
struct axis_fifo {
int id;
void __iomem *base_addr;
unsigned int rx_fifo_depth;
unsigned int tx_fifo_depth;
int has_rx_fifo;
int has_tx_fifo;
wait_queue_head_t read_queue;
struct mutex read_lock;
wait_queue_head_t write_queue;
struct mutex write_lock;
struct device *dt_device;
struct miscdevice miscdev;
struct dentry *debugfs_dir;
};
struct axis_fifo_debug_reg {
const char * const name;
unsigned int offset;
};
static void reset_ip_core(struct axis_fifo *fifo)
{
iowrite32(XLLF_SRR_RESET_MASK, fifo->base_addr + XLLF_SRR_OFFSET);
iowrite32(XLLF_TDFR_RESET_MASK, fifo->base_addr + XLLF_TDFR_OFFSET);
iowrite32(XLLF_RDFR_RESET_MASK, fifo->base_addr + XLLF_RDFR_OFFSET);
iowrite32(XLLF_INT_TC_MASK | XLLF_INT_RC_MASK | XLLF_INT_RPURE_MASK |
XLLF_INT_RPORE_MASK | XLLF_INT_RPUE_MASK |
XLLF_INT_TPOE_MASK | XLLF_INT_TSE_MASK,
fifo->base_addr + XLLF_IER_OFFSET);
iowrite32(XLLF_INT_CLEAR_ALL, fifo->base_addr + XLLF_ISR_OFFSET);
}
static ssize_t axis_fifo_read(struct file *f, char __user *buf,
size_t len, loff_t *off)
{
struct axis_fifo *fifo = f->private_data;
size_t bytes_available;
unsigned int words_available;
unsigned int copied;
unsigned int copy;
unsigned int i;
int ret;
u32 tmp_buf[READ_BUF_SIZE];
if (f->f_flags & O_NONBLOCK) {
if (!mutex_trylock(&fifo->read_lock))
return -EAGAIN;
if (!ioread32(fifo->base_addr + XLLF_RDFO_OFFSET)) {
ret = -EAGAIN;
goto end_unlock;
}
} else {
mutex_lock(&fifo->read_lock);
ret = wait_event_interruptible(fifo->read_queue,
ioread32(fifo->base_addr + XLLF_RDFO_OFFSET));
if (ret)
goto end_unlock;
}
bytes_available = ioread32(fifo->base_addr + XLLF_RLR_OFFSET);
words_available = bytes_available / sizeof(u32);
if (bytes_available > len) {
ret = -EINVAL;
goto err_flush_rx;
}
if (bytes_available % sizeof(u32)) {
dev_err(fifo->dt_device, "received a packet that isn't word-aligned\n");
ret = -EIO;
goto err_flush_rx;
}
copied = 0;
while (words_available > 0) {
copy = min(words_available, READ_BUF_SIZE);
for (i = 0; i < copy; i++) {
tmp_buf[i] = ioread32(fifo->base_addr +
XLLF_RDFD_OFFSET);
}
words_available -= copy;
if (copy_to_user(buf + copied * sizeof(u32), tmp_buf,
copy * sizeof(u32))) {
ret = -EFAULT;
goto err_flush_rx;
}
copied += copy;
}
mutex_unlock(&fifo->read_lock);
return bytes_available;
err_flush_rx:
while (words_available--)
ioread32(fifo->base_addr + XLLF_RDFD_OFFSET);
end_unlock:
mutex_unlock(&fifo->read_lock);
return ret;
}
static ssize_t axis_fifo_write(struct file *f, const char __user *buf,
size_t len, loff_t *off)
{
struct axis_fifo *fifo = f->private_data;
unsigned int words_to_write;
u32 *txbuf;
int ret;
words_to_write = len / sizeof(u32);
if (!words_to_write || (len % sizeof(u32)) ||
(words_to_write > (fifo->tx_fifo_depth - 4)))
return -EINVAL;
if (f->f_flags & O_NONBLOCK) {
if (!mutex_trylock(&fifo->write_lock))
return -EAGAIN;
if (words_to_write > ioread32(fifo->base_addr +
XLLF_TDFV_OFFSET)) {
ret = -EAGAIN;
goto end_unlock;
}
} else {
mutex_lock(&fifo->write_lock);
ret = wait_event_interruptible(fifo->write_queue,
ioread32(fifo->base_addr + XLLF_TDFV_OFFSET) >= words_to_write);
if (ret)
goto end_unlock;
}
txbuf = vmemdup_user(buf, len);
if (IS_ERR(txbuf)) {
ret = PTR_ERR(txbuf);
goto end_unlock;
}
for (int i = 0; i < words_to_write; ++i)
iowrite32(txbuf[i], fifo->base_addr + XLLF_TDFD_OFFSET);
iowrite32(len, fifo->base_addr + XLLF_TLR_OFFSET);
ret = len;
kvfree(txbuf);
end_unlock:
mutex_unlock(&fifo->write_lock);
return ret;
}
static __poll_t axis_fifo_poll(struct file *f, poll_table *wait)
{
struct axis_fifo *fifo = f->private_data;
__poll_t mask = 0;
if (fifo->has_rx_fifo) {
poll_wait(f, &fifo->read_queue, wait);
if (ioread32(fifo->base_addr + XLLF_RDFO_OFFSET))
mask |= EPOLLIN | EPOLLRDNORM;
}
if (fifo->has_tx_fifo) {
poll_wait(f, &fifo->write_queue, wait);
if (ioread32(fifo->base_addr + XLLF_TDFV_OFFSET))
mask |= EPOLLOUT | EPOLLWRNORM;
}
return mask;
}
static irqreturn_t axis_fifo_irq(int irq, void *dw)
{
struct axis_fifo *fifo = dw;
u32 isr, ier, intr;
ier = ioread32(fifo->base_addr + XLLF_IER_OFFSET);
isr = ioread32(fifo->base_addr + XLLF_ISR_OFFSET);
intr = ier & isr;
if (intr & XLLF_INT_RC_MASK)
wake_up(&fifo->read_queue);
if (intr & XLLF_INT_TC_MASK)
wake_up(&fifo->write_queue);
if (intr & XLLF_INT_RPURE_MASK)
dev_err(fifo->dt_device, "receive under-read interrupt\n");
if (intr & XLLF_INT_RPORE_MASK)
dev_err(fifo->dt_device, "receive over-read interrupt\n");
if (intr & XLLF_INT_RPUE_MASK)
dev_err(fifo->dt_device, "receive underrun error interrupt\n");
if (intr & XLLF_INT_TPOE_MASK)
dev_err(fifo->dt_device, "transmit overrun error interrupt\n");
if (intr & XLLF_INT_TSE_MASK)
dev_err(fifo->dt_device,
"transmit length mismatch error interrupt\n");
iowrite32(XLLF_INT_CLEAR_ALL, fifo->base_addr + XLLF_ISR_OFFSET);
return IRQ_HANDLED;
}
static int axis_fifo_open(struct inode *inod, struct file *f)
{
struct axis_fifo *fifo = container_of(f->private_data,
struct axis_fifo, miscdev);
unsigned int flags = f->f_flags & O_ACCMODE;
f->private_data = fifo;
if ((flags == O_WRONLY || flags == O_RDWR) && !fifo->has_tx_fifo)
return -EPERM;
if ((flags == O_RDONLY || flags == O_RDWR) && !fifo->has_rx_fifo)
return -EPERM;
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = axis_fifo_open,
.read = axis_fifo_read,
.write = axis_fifo_write,
.poll = axis_fifo_poll,
};
static int axis_fifo_debugfs_regs_show(struct seq_file *m, void *p)
{
static const struct axis_fifo_debug_reg regs[] = {
{"isr", XLLF_ISR_OFFSET},
{"ier", XLLF_IER_OFFSET},
{"tdfv", XLLF_TDFV_OFFSET},
{"rdfo", XLLF_RDFO_OFFSET},
{ },
};
const struct axis_fifo_debug_reg *reg;
struct axis_fifo *fifo = m->private;
for (reg = regs; reg->name; ++reg) {
u32 val = ioread32(fifo->base_addr + reg->offset);
seq_printf(m, "%*s: 0x%08x\n", AXIS_FIFO_DEBUG_REG_NAME_MAX_LEN,
reg->name, val);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(axis_fifo_debugfs_regs);
static void axis_fifo_debugfs_init(struct axis_fifo *fifo)
{
fifo->debugfs_dir = debugfs_create_dir(dev_name(fifo->dt_device), NULL);
debugfs_create_file("regs", 0444, fifo->debugfs_dir, fifo,
&axis_fifo_debugfs_regs_fops);
}
static int axis_fifo_parse_dt(struct axis_fifo *fifo)
{
int ret;
unsigned int value;
struct device_node *node = fifo->dt_device->of_node;
ret = of_property_read_u32(node, "xlnx,axi-str-rxd-tdata-width",
&value);
if (ret) {
dev_err(fifo->dt_device, "missing xlnx,axi-str-rxd-tdata-width property\n");
goto end;
} else if (value != 32) {
dev_err(fifo->dt_device, "xlnx,axi-str-rxd-tdata-width only supports 32 bits\n");
ret = -EIO;
goto end;
}
ret = of_property_read_u32(node, "xlnx,axi-str-txd-tdata-width",
&value);
if (ret) {
dev_err(fifo->dt_device, "missing xlnx,axi-str-txd-tdata-width property\n");
goto end;
} else if (value != 32) {
dev_err(fifo->dt_device, "xlnx,axi-str-txd-tdata-width only supports 32 bits\n");
ret = -EIO;
goto end;
}
ret = of_property_read_u32(node, "xlnx,rx-fifo-depth",
&fifo->rx_fifo_depth);
if (ret) {
dev_err(fifo->dt_device, "missing xlnx,rx-fifo-depth property\n");
ret = -EIO;
goto end;
}
ret = of_property_read_u32(node, "xlnx,tx-fifo-depth",
&fifo->tx_fifo_depth);
if (ret) {
dev_err(fifo->dt_device, "missing xlnx,tx-fifo-depth property\n");
ret = -EIO;
goto end;
}
ret = of_property_read_u32(node, "xlnx,use-rx-data",
&fifo->has_rx_fifo);
if (ret) {
dev_err(fifo->dt_device, "missing xlnx,use-rx-data property\n");
ret = -EIO;
goto end;
}
ret = of_property_read_u32(node, "xlnx,use-tx-data",
&fifo->has_tx_fifo);
if (ret) {
dev_err(fifo->dt_device, "missing xlnx,use-tx-data property\n");
ret = -EIO;
goto end;
}
end:
return ret;
}
static int axis_fifo_probe(struct platform_device *pdev)
{
struct resource *r_mem;
struct device *dev = &pdev->dev;
struct axis_fifo *fifo = NULL;
int rc = 0;
int irq;
fifo = devm_kzalloc(dev, sizeof(*fifo), GFP_KERNEL);
if (!fifo)
return -ENOMEM;
dev_set_drvdata(dev, fifo);
fifo->dt_device = dev;
init_waitqueue_head(&fifo->read_queue);
init_waitqueue_head(&fifo->write_queue);
mutex_init(&fifo->read_lock);
mutex_init(&fifo->write_lock);
fifo->base_addr = devm_platform_get_and_ioremap_resource(pdev, 0, &r_mem);
if (IS_ERR(fifo->base_addr))
return PTR_ERR(fifo->base_addr);
rc = axis_fifo_parse_dt(fifo);
if (rc)
return rc;
reset_ip_core(fifo);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
rc = devm_request_irq(fifo->dt_device, irq, &axis_fifo_irq, 0,
DRIVER_NAME, fifo);
if (rc) {
dev_err(fifo->dt_device, "couldn't allocate interrupt %i\n",
irq);
return rc;
}
fifo->id = ida_alloc(&axis_fifo_ida, GFP_KERNEL);
if (fifo->id < 0)
return fifo->id;
fifo->miscdev.fops = &fops;
fifo->miscdev.minor = MISC_DYNAMIC_MINOR;
fifo->miscdev.parent = dev;
fifo->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%d",
DRIVER_NAME, fifo->id);
if (!fifo->miscdev.name) {
ida_free(&axis_fifo_ida, fifo->id);
return -ENOMEM;
}
rc = misc_register(&fifo->miscdev);
if (rc < 0) {
ida_free(&axis_fifo_ida, fifo->id);
return rc;
}
axis_fifo_debugfs_init(fifo);
return 0;
}
static void axis_fifo_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct axis_fifo *fifo = dev_get_drvdata(dev);
debugfs_remove(fifo->debugfs_dir);
misc_deregister(&fifo->miscdev);
ida_free(&axis_fifo_ida, fifo->id);
}
static const struct of_device_id axis_fifo_of_match[] = {
{ .compatible = "xlnx,axi-fifo-mm-s-4.1", },
{ .compatible = "xlnx,axi-fifo-mm-s-4.2", },
{ .compatible = "xlnx,axi-fifo-mm-s-4.3", },
{},
};
MODULE_DEVICE_TABLE(of, axis_fifo_of_match);
static struct platform_driver axis_fifo_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = axis_fifo_of_match,
},
.probe = axis_fifo_probe,
.remove = axis_fifo_remove,
};
static int __init axis_fifo_init(void)
{
return platform_driver_register(&axis_fifo_driver);
}
module_init(axis_fifo_init);
static void __exit axis_fifo_exit(void)
{
platform_driver_unregister(&axis_fifo_driver);
ida_destroy(&axis_fifo_ida);
}
module_exit(axis_fifo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jacob Feder <jacobsfeder@gmail.com>");
MODULE_DESCRIPTION("Xilinx AXI-Stream FIFO IP core driver");