Add Linux driver for PuzzleFW firmware
This commit is contained in:
parent
3808d1051a
commit
12bcf4e4a9
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
. script_env
|
||||||
|
setup_toolchain
|
||||||
|
|
||||||
|
KDIR="$(realpath -m "$LINUX_DIR")"
|
||||||
|
|
||||||
|
cd src/linux_driver
|
||||||
|
make KDIR="$KDIR" ARCH=arm CROSS_COMPILE=arm-linux- clean
|
||||||
|
make KDIR="$KDIR" ARCH=arm CROSS_COMPILE=arm-linux- modules
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.mod
|
||||||
|
*.mod.c
|
||||||
|
.*.cmd
|
||||||
|
Module.symvers
|
||||||
|
modules.order
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
#
|
||||||
|
# To build this driver, you need a Linux kernel source tree,
|
||||||
|
# fully configured and built. Set KDIR to point to that tree.
|
||||||
|
#
|
||||||
|
|
||||||
|
obj-m += puzzlefw.o
|
||||||
|
|
||||||
|
.PHONY: modules
|
||||||
|
modules:
|
||||||
|
$(MAKE) -C $(KDIR) M=$(CURDIR) modules
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
$(MAKE) -C $(KDIR) M=$(CURDIR) clean
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
/*
|
||||||
|
* puzzlefw.c
|
||||||
|
*
|
||||||
|
* Device driver for the RedPitaya PuzzleFW firmware.
|
||||||
|
*
|
||||||
|
* Written in 2024 by Joris van Rantwijk.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_address.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/uio_driver.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define DRIVER_NAME "puzzlefw"
|
||||||
|
#define PUZZLEFW_FW_API 0x4a01
|
||||||
|
|
||||||
|
#define USERREG_INFO 0x00
|
||||||
|
#define USERREG_IRQ_ENABLE 0x10
|
||||||
|
#define PRIVREG_DMA_BUF_ADDR 0x00
|
||||||
|
#define PRIVREG_DMA_BUF_SIZE 0x04
|
||||||
|
|
||||||
|
|
||||||
|
/* Device-specific data. */
|
||||||
|
struct puzzlefw_drvdata {
|
||||||
|
struct platform_device * pdev;
|
||||||
|
struct uio_info * uioinfo;
|
||||||
|
void __iomem * userregs;
|
||||||
|
void __iomem * privregs;
|
||||||
|
dma_addr_t dma_buf_addr;
|
||||||
|
resource_size_t dma_buf_size;
|
||||||
|
char version_str[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Called by UIO framework when an interrupt occurs. */
|
||||||
|
static irqreturn_t puzzlefw_irq_handler(int irq, struct uio_info *uioinfo)
|
||||||
|
{
|
||||||
|
struct puzzlefw_drvdata *d = uioinfo->priv;
|
||||||
|
|
||||||
|
/* Disable interrupts. The user-space driver is responsible
|
||||||
|
for re-enabling interrupts after handling this interrupt. */
|
||||||
|
writel(0, d->userregs + USERREG_IRQ_ENABLE);
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Detect FPGA firmware.
|
||||||
|
*
|
||||||
|
* Note this function reads from the APB registers in the FPGA.
|
||||||
|
* This will cause the system to hang if the FPGA image is
|
||||||
|
* not loaded or not functional.
|
||||||
|
*/
|
||||||
|
static int puzzlefw_detect(struct puzzlefw_drvdata *d)
|
||||||
|
{
|
||||||
|
uint32_t fw_info;
|
||||||
|
unsigned int fw_api, fw_version_major, fw_version_minor;
|
||||||
|
|
||||||
|
dev_dbg(&d->pdev->dev, "probing firmware\n");
|
||||||
|
|
||||||
|
fw_info = readl(d->userregs + USERREG_INFO);
|
||||||
|
fw_api = fw_info >> 16;
|
||||||
|
fw_version_major = (fw_info >> 8) & 0xff;
|
||||||
|
fw_version_minor = fw_info & 0xff;
|
||||||
|
|
||||||
|
if (fw_api != PUZZLEFW_FW_API) {
|
||||||
|
dev_err(&d->pdev->dev,
|
||||||
|
"unrecognized firmware info register 0x%08x\n",
|
||||||
|
fw_info);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(&d->pdev->dev,
|
||||||
|
"detected PuzzleFW firmware version %u.%u\n",
|
||||||
|
fw_version_major, fw_version_minor);
|
||||||
|
|
||||||
|
snprintf(d->version_str, sizeof(d->version_str),
|
||||||
|
"%u.%u", fw_version_major, fw_version_minor);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Write address and size of DMA buffer in FPGA registers. */
|
||||||
|
static void puzzlefw_set_dma_window(
|
||||||
|
struct puzzlefw_drvdata *d,
|
||||||
|
dma_addr_t buf_addr,
|
||||||
|
size_t buf_size)
|
||||||
|
{
|
||||||
|
writel(0, d->privregs + PRIVREG_DMA_BUF_SIZE);
|
||||||
|
writel(buf_addr, d->privregs + PRIVREG_DMA_BUF_ADDR);
|
||||||
|
writel(buf_size, d->privregs + PRIVREG_DMA_BUF_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Register device instance. */
|
||||||
|
static int puzzlefw_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct puzzlefw_drvdata *drvdata;
|
||||||
|
struct device_node *mem_node;
|
||||||
|
struct resource mem_res;
|
||||||
|
struct resource *user_regs;
|
||||||
|
struct uio_info *uioinfo;
|
||||||
|
int irq;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Allocate device-specific data.
|
||||||
|
Will be released automatically when the driver detaches. */
|
||||||
|
drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
|
||||||
|
if (!drvdata) {
|
||||||
|
dev_err(&pdev->dev, "alloc drvdata failed\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
drvdata->pdev = pdev;
|
||||||
|
platform_set_drvdata(pdev, drvdata);
|
||||||
|
|
||||||
|
if (!pdev->dev.of_node) {
|
||||||
|
dev_err(&pdev->dev, "missing platform data\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_node = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
|
||||||
|
if (!mem_node) {
|
||||||
|
dev_err(&pdev->dev, "missing memory-region\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = of_address_to_resource(mem_node, 0, &mem_res);
|
||||||
|
of_node_put(mem_node);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "failed to get memory-region resource\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
drvdata->dma_buf_addr = mem_res.start;
|
||||||
|
drvdata->dma_buf_size = resource_size(&mem_res);
|
||||||
|
|
||||||
|
/* Get register user register range from device tree. */
|
||||||
|
user_regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
if (!user_regs) {
|
||||||
|
dev_err(&pdev->dev, "no resource in platform data\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get IRQ from device tree. */
|
||||||
|
irq = platform_get_irq(pdev, 0);
|
||||||
|
if (irq < 0) {
|
||||||
|
dev_err(&pdev->dev, "missing interrupt in platform data\n");
|
||||||
|
return irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map register ranges for access by kernel driver. */
|
||||||
|
drvdata->userregs = devm_platform_ioremap_resource(pdev, 0);
|
||||||
|
drvdata->privregs = devm_platform_ioremap_resource(pdev, 1);
|
||||||
|
|
||||||
|
/* Detect firmware. */
|
||||||
|
ret = puzzlefw_detect(drvdata);
|
||||||
|
if (ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register UIO device. */
|
||||||
|
uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo), GFP_KERNEL);
|
||||||
|
if (!uioinfo) {
|
||||||
|
dev_err(&pdev->dev, "alloc uioinfo failed\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
drvdata->uioinfo = uioinfo;
|
||||||
|
|
||||||
|
uioinfo->name = pdev->name;
|
||||||
|
uioinfo->version = drvdata->version_str;
|
||||||
|
|
||||||
|
/* Non-privileged registers can be accessed from user space via mmap. */
|
||||||
|
uioinfo->mem[0].memtype = UIO_MEM_PHYS;
|
||||||
|
uioinfo->mem[0].addr = user_regs->start;
|
||||||
|
uioinfo->mem[0].size = resource_size(user_regs);
|
||||||
|
uioinfo->mem[0].name = "registers";
|
||||||
|
|
||||||
|
/* DMA buffer can be accessed from user space via mmap. */
|
||||||
|
uioinfo->mem[1].memtype = UIO_MEM_PHYS;
|
||||||
|
uioinfo->mem[1].addr = drvdata->dma_buf_addr;
|
||||||
|
uioinfo->mem[1].size = drvdata->dma_buf_size;
|
||||||
|
uioinfo->mem[1].name = "dma";
|
||||||
|
|
||||||
|
/* IRQ managed by user space. */
|
||||||
|
uioinfo->irq = irq;
|
||||||
|
uioinfo->irq_flags = 0; /* IRQ not shared */
|
||||||
|
|
||||||
|
uioinfo->handler = puzzlefw_irq_handler;
|
||||||
|
uioinfo->irqcontrol = NULL;
|
||||||
|
uioinfo->open = NULL;
|
||||||
|
uioinfo->release = NULL;
|
||||||
|
|
||||||
|
uioinfo->priv = drvdata;
|
||||||
|
|
||||||
|
ret = devm_uio_register_device(&pdev->dev, uioinfo);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "uio_register_device failed\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(&pdev->dev, "%lu bytes DMA buffer\n",
|
||||||
|
(unsigned long)drvdata->dma_buf_size);
|
||||||
|
|
||||||
|
/* Set up privileged FPGA registers to access the DMA buffer. */
|
||||||
|
puzzlefw_set_dma_window(drvdata,
|
||||||
|
drvdata->dma_buf_addr, drvdata->dma_buf_size);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Unregister device instance. */
|
||||||
|
static int puzzlefw_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct puzzlefw_drvdata *drvdata = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
/* Block FPGA access to DMA buffer. */
|
||||||
|
puzzlefw_set_dma_window(drvdata, drvdata->dma_buf_addr, 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const struct of_device_id puzzlefw_of_match[] = {
|
||||||
|
{ .compatible = "jigsaw,puzzlefw" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static struct platform_driver puzzlefw_platform_driver = {
|
||||||
|
.probe = puzzlefw_probe,
|
||||||
|
.remove = puzzlefw_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = DRIVER_NAME,
|
||||||
|
.of_match_table = puzzlefw_of_match,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module_platform_driver(puzzlefw_platform_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Joris van Rantwijk");
|
||||||
|
MODULE_DESCRIPTION("RedPitaya-PuzzleFW driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
|
Loading…
Reference in New Issue