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