Add Linux driver for PuzzleFW firmware

This commit is contained in:
Joris van Rantwijk 2024-08-02 21:02:17 +02:00
parent 3808d1051a
commit 12bcf4e4a9
4 changed files with 292 additions and 0 deletions

13
os/15_build_driver.sh Executable file
View File

@ -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

7
os/src/linux_driver/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.o
*.ko
*.mod
*.mod.c
.*.cmd
Module.symvers
modules.order

View File

@ -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

View File

@ -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");