Commit 58e29b61 authored by Miguel Jimenez Lopez's avatar Miguel Jimenez Lopez

sw: First version for the fmc-dio driver and related tools.

parent a76d4009
# include parent_common.mk for buildsystem's defines
#use absolute path for REPO_PARENT
REPO_PARENT=$(shell /bin/pwd)/..
-include $(REPO_PARENT)/parent_common.mk
# export git tree for subdirs
GIT_VERSION ?= $(shell git describe --dirty --long --tags)
export GIT_VERSION
# by default use the fmc-bus within the repository
FMC_BUS ?= fmc-bus
# FMC_BUS_ABS has to be absolut path, due to beeing passed to the Kbuild
FMC_BUS_ABS ?= $(abspath $(FMC_BUS) )
export FMC_BUS_ABS
DIRS = $(FMC_BUS_ABS) kernel tools
.PHONY: all clean modules install modules_install $(DIRS)
all clean modules install modules_install: $(DIRS)
clean: TARGET = clean
modules: TARGET = modules
install: TARGET = install
modules_install: TARGET = modules_install
$(DIRS):
$(MAKE) -C $@ $(TARGET)
$(FMC_BUS_ABS): fmc-bus-init_repo
# init submodule if missing
fmc-bus-init_repo:
@test -d $(FMC_BUS_ABS)/doc || ( echo "Checking out submodule $(FMC_BUS_ABS)"; git submodule update --init $(FMC_BUS_ABS) )
kernel: $(FMC_BUS_ABS)
KBUILD_EXTRA_SYMBOLS := $(FMC_BUS_ABS)/kernel/Module.symvers
# add versions of supermodule. It is useful when spec-sw is included as sub-module
# of a bigger project that we want to track
ifdef CONFIG_SUPER_REPO
ifdef CONFIG_SUPER_REPO_VERSION
SUBMODULE_VERSIONS += MODULE_INFO(version_$(CONFIG_SUPER_REPO),\"$(CONFIG_SUPER_REPO_VERSION)\");
endif
endif
# add versions of used submodules
SUBMODULE_VERSIONS += MODULE_INFO(version_fmc_bus,\"$(FMC_BUS_VERSION)\");
ccflags-y += -DADDITIONAL_VERSIONS="$(SUBMODULE_VERSIONS)"
# The library includes <sdb.h>, so point -I directtly there
# include our header before to avoid conflicts with the kernel
LINUXINCLUDE := -I$(FMC_BUS_ABS)/kernel/include -I$(src)/include/linux $(LINUXINCLUDE)
ccflags-y += -I$(src)/include
ccflags-y += -DGIT_VERSION=\"$(GIT_VERSION)\"
obj-m += wr-dio.o
wr-dio-y = fmc-dio.o
wr-dio-y += fmc-dio-internal.o
wr-dio-y += fmc-dio-mdev.o
wr-dio-y += fmc-dio-gpio.o
\ No newline at end of file
# include parent_common.mk for buildsystem's defines
#use absolute path for REPO_PARENT
REPO_PARENT=$(shell /bin/pwd)/../..
-include $(REPO_PARENT)/parent_common.mk
LINUX ?= /lib/modules/$(shell uname -r)/build
# by default use the fmc-bus within the repository
FMC_BUS ?= ../fmc-bus/
# FMC_BUS_ABS has to be absolut path, due to beeing passed to the Kbuild
FMC_BUS_ABS ?= $(abspath $(FMC_BUS) )
GIT_VERSION ?= $(shell git describe --dirty --long --tags)
export GIT_VERSION
FMC_BUS_VERSION ?= $(shell cd $(FMC_BUS_ABS); git describe --always --dirty --long --tags)
export FMC_BUS_VERSION
all modules:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) FMC_BUS_ABS=$(FMC_BUS_ABS) modules
install modules_install:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) modules_install
# be able to run the "clean" rule even if $(LINUX) is not valid
clean:
rm -rf *.o *~ .*.cmd *.ko *.mod.c .tmp_versions Module.symvers \
Module.markers modules.order
\ No newline at end of file
/*
* Copyright (C) 2019 CERN (www.sevensols.com)
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/slab.h>
#include <linux/fmc.h>
#include "fmc-dio.h"
static int fmc_dio_gpio_input(struct gpio_chip *chip, unsigned offset)
{
return -EAGAIN;
}
static int fmc_dio_gpio_output(struct gpio_chip *chip, unsigned offset, int value)
{
return -EAGAIN;
}
int fmc_dio_gpio_get(struct gpio_chip *chip, unsigned offset)
{
return -EAGAIN;
}
void fmc_dio_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
return;
}
static const char *fmc_dio_gpio_names[] = {
"dire", "fare", "baciare", "lettera", "testamento"
};
static struct gpio_chip fmc_dio_gpio_template = {
.label = "fmc-dio",
.owner = THIS_MODULE,
/* FIXME: request, free, for multi-function operation */
.direction_input = fmc_dio_gpio_input,
.direction_output = fmc_dio_gpio_output,
.get = fmc_dio_gpio_get,
.set = fmc_dio_gpio_set,
.base = -1, /* request dynamic */
.ngpio = 5,
.names = fmc_dio_gpio_names,
};
int fmc_dio_gpio_init(struct fmc_dio *dev)
{
struct fmc_device *fmc = dev->fmc;
struct gpio_chip *gc;
int ret;
gc = devm_kzalloc(&dev->fmc->dev, sizeof(*gc), GFP_KERNEL);
if (!gc)
return -ENOMEM;
*gc = fmc_dio_gpio_template;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0)
gc->dev = &fmc->dev;
#else
gc->parent = &fmc->dev;
#endif
ret = gpiochip_add(gc);
if (ret < 0)
goto out_free;
dev->gc = gc;
/* FIXME: program the DAC for each port (sysfs attributes?) */
return 0;
out_free:
kfree(gc);
return ret;
}
void fmc_dio_gpio_exit(struct fmc_dio *dev)
{
struct gpio_chip *gc = dev->gc;
#if LINUX_VERSION_CODE > KERNEL_VERSION(3,17,0)
gpiochip_remove(gc);
#else
int ret;
ret = gpiochip_remove(gc);
if (ret)
dev_err(&dio->pdev->dev, "DANGER %i! gpio chip can't be removed\n",
ret);
#endif
return;
}
This diff is collapsed.
/*
* Copyright (C) 2019 CERN (www.sevensols.com)
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#include <linux/module.h>
#include <linux/version.h>
#include "fmc-dio.h"
static int fmc_dio_mdev_open(struct inode *inode, struct file *file)
{
struct miscdevice *mdev_ptr = file->private_data;
// WARN: Update the private_data pointer to point to fmc_dio structure
file->private_data = container_of(mdev_ptr, struct fmc_dio, mdev);
return 0;
}
static int fmc_dio_mdev_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t fmc_dio_mdev_write(struct file *file, const char __user *buf,
size_t count, loff_t *offp)
{
return 0;
}
static ssize_t fmc_dio_mdev_read (struct file *file, char __user *buf,
size_t count, loff_t *offp)
{
return 0;
}
static long fmc_dio_mdev_unlocked_ioctl (struct file *file, unsigned int cmd,
unsigned long arg)
{
struct fmc_dio *dio = file->private_data;
return fmc_dio_int_ioctl(dio,cmd,arg);
}
static const struct file_operations fmc_dio_mdev_fops = {
.owner = THIS_MODULE,
.open = fmc_dio_mdev_open,
.release = fmc_dio_mdev_release,
.write = fmc_dio_mdev_write,
.read = fmc_dio_mdev_read,
.unlocked_ioctl = fmc_dio_mdev_unlocked_ioctl,
};
int fmc_dio_mdev_create(struct fmc_dio *dio)
{
dio->mdev.minor = MISC_DYNAMIC_MINOR;
dio->mdev.fops = &fmc_dio_mdev_fops;
dio->mdev.name = dio->name;
return misc_register(&dio->mdev);
}
void fmc_dio_mdev_destroy(struct fmc_dio *dio)
{
misc_deregister(&dio->mdev);
}
/*
* Copyright (C) 2019 Seven Solutions (www.sevensols.com)
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>
#include <linux/delay.h>
#include <linux/fmc.h>
#include <linux/fmc-sdb.h>
#include "fmc-dio.h"
/* Temporary pointer to the last FMC DIO */
static struct fmc_dio *last_dio;
/**
* FMC DIO Interrupt Service Routing (ISR)
*
* @param irq IRQ number
* @param dev_id Device identifier for the ISR
*
* @return IRQ_HANDLED if interrupt was handled properly and IRQ_NONE otherwise
*/
irqreturn_t fmc_dio_interrupt(int irq, void *dev_id)
{
irqreturn_t ret;
ret = fmc_dio_int_interrupt(dev_id);
return ret;
}
// Macros for the memory-mapped hardware resources
#define FMC_DIO_RESOURCE_DIO 0
#define FMC_DIO_RESOURCE_GPIO 1
#define FMC_DIO_RESOURCE_PPSG 2
/**
* This function creates the hardware resources for the FMC DIO.
*
* @param dio FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_resource_map(struct fmc_dio *dio)
{
int i,err;
struct platform_device *dev = dio->pdev;
struct fmc_device *fmc = dio->fmc;
struct resource *r;
void __iomem *mem;
// Map the resources
for(i = 0 ; i < dev->num_resources ; i++) {
r = platform_get_resource(dev, IORESOURCE_MEM, i);
if (!r || !r->start)
continue;
mem = ioremap(r->start, r->end + 1 - r->start);
if (!mem) {
dev_err(&dev->dev, "Remap for res %i %pr failed\n",
i, r);
return -ENOMEM;
}
switch(i)
{
case FMC_DIO_RESOURCE_DIO:
dio->dio = mem;
break;
case FMC_DIO_RESOURCE_GPIO:
dio->gpio = mem;
break;
case FMC_DIO_RESOURCE_PPSG:
dio->ppsg = mem;
break;
default:
/* Ugh? What is this? */
break;
};
}
// If some hardware resource is not detected, fail
if(!dio->dio || !dio->gpio || !dio->ppsg) {
printk("%s: ERROR in FPGA base pointers initialization. \n",__func__);
return -EINVAL;
}
// Map the IRQ
dio->irq = platform_get_irq(dev,0);
if(dio->irq < 0) {
printk("%s: ERROR in FMC DIO IRQ extraction. \n",__func__);
return -EINVAL;
}
err = request_any_context_irq(dio->irq, fmc_dio_interrupt,
IRQF_TRIGGER_HIGH, dio->name, dio);
if(err < 0) {
printk("%s: ERROR in request IRQ (%d). \n",__func__,err);
return err;
}
return 0;
}
/**
* This function releases the hardware resources for the FMC DIO.
*
* @param dio FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_resource_release(struct fmc_dio *dio)
{
// Release IRQ for FMC DIO
free_irq(dio->irq, dio);
return 0;
}
/**
* FMC DIO probe function for the platform_device
*
* @param dev FMC DIO platform device
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_pl_probe(struct platform_device *dev)
{
/* NOTE: fmc_device is created by the same driver than platform_device
* and it had filled the private data structure pointer of the
* platform_device before registering it.
*/
struct fmc_device *fmc;
struct fmc_dio *dio;
int ret = 0;
unsigned int devfn;
unsigned char busn;
dio = last_dio;
if(!dio) {
printk("%s: DIO internal structure is NULL \n",__func__);
ret = -EINVAL;
goto exit;
}
fmc = dio->fmc;
if(!fmc) {
printk("%s: FMC device is NULL \n",__func__);
ret = -EINVAL;
goto exit;
}
/* Set up the FMC DIO name for the Linux Miscdevice */
busn = (unsigned char) (fmc->device_id >> 8);
devfn = (unsigned int) (fmc->device_id & 0xFF);
snprintf(dio->name,FMC_DIO_NAME_MAX,
FMC_DIO_NAME_PATTERN,
busn,devfn);
// Specific board info
dio->board = dev->id_entry->driver_data;
dio->pdev = dev;
/* Map the resources for the FMC DIO */
ret = fmc_dio_resource_map(dio);
if(ret)
goto exit;
ret = fmc_dio_internal_create(dio);
if(ret)
goto exit;
/* Create a Linux Miscdevice for the FMC DIO */
ret = fmc_dio_mdev_create(dio);
if(ret)
goto exit;
/* Create Linux GPIO for the FMC DIO */
ret = fmc_dio_gpio_init(dio);
if(ret)
goto exit;
exit:
return ret;
}
/**
* FMC DIO remove function for the platform_device
*
* @param dev FMC DIO platform device
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_pl_remove(struct platform_device *dev)
{
struct fmc_device *fmc;
struct fmc_dio *dio;
int ret = 0;
dio = last_dio;
fmc = dio->fmc;
/* Release Linux GPIO for the FMC DIO */
fmc_dio_gpio_exit(dio);
/* Destroy the Linux Miscdevice for the FMC DIO */
fmc_dio_mdev_destroy(dio);
/* Destroy internal DIO structure */
fmc_dio_internal_destroy(dio);
/* Release the resources for the FMC DIO */
ret = fmc_dio_resource_release(dio);
if(ret)
return ret;
return ret;
}
/* Forward declarations */
int fmc_dio_probe(struct fmc_device *fmc);
int fmc_dio_remove(struct fmc_device *fmc);
/**
* List of supported platform
*/
static const struct platform_device_id fmc_dio_id_table[] = {
{ /* SPEC compatible */
.name = "fmc-dio-spec",
.driver_data = FMC_DIO_BOARD_SPEC,
},
{},
};
// FMC DIO platform driver
static struct platform_driver fmc_dio_pl_drv = {
.probe = fmc_dio_pl_probe,
.remove = fmc_dio_pl_remove,
.id_table = fmc_dio_id_table,
/* No suspend or resume by now */
.driver = {
.name = "fmc-dio",
.owner = THIS_MODULE,
},
};
// FMC DIO FRU identifier
static struct fmc_fru_id fd_fru_id[] = {
{
.product_name = "FmcDio5cha",
},
};
// FMC DIO driver structure (FMC)
static struct fmc_driver fmc_dio_drv = {
.version = FMC_VERSION,
.driver.name = KBUILD_MODNAME,
.probe = fmc_dio_probe,
.remove = fmc_dio_remove,
.id_table = {
.fru_id = fd_fru_id,
.fru_id_nr = ARRAY_SIZE(fd_fru_id),
},
};
/**
* FMC DIO probe function
*
* This function initializes the FMC DIO data structures.
*
* @param fmc FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
int fmc_dio_probe(struct fmc_device *fmc)
{
struct fmc_dio *dio;
int ret = 0;
/* Driver data */
dio = devm_kzalloc(&fmc->dev, sizeof(*dio), GFP_KERNEL);
if (!dio)
return -ENOMEM;
fmc_set_drvdata(fmc, dio);
dio->fmc = fmc;
/* FIXME: Recover the DIO internal structure pointer for a global temporary var */
last_dio = dio;
/* Now register the platform_driver */
ret = platform_driver_register(&fmc_dio_pl_drv);
if (ret < 0) {
fmc_driver_unregister(&fmc_dio_drv);
}
return ret;
}
/**
* FMC DIO remove function
*
* This function releases the FMC DIO data structures.
*
* @param fmc FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
int fmc_dio_remove(struct fmc_device *fmc)
{
//struct fmc_dio *dio = fmc_get_drvdata(fmc);
/* Free FMC DIO internal structure */
/* NOTE: Here is not necessary to free the memory because
* we use devm_kmalloc in the probe function
*/
platform_driver_unregister(&fmc_dio_pl_drv);
return 0;
}
/**
* FMC DIO init function
*
* This function is the entry point for the FMC DIO driver.
*
* @return 0 if success and a negative error code otherwise
*/
static int fmc_dio_init(void)
{
int ret;
ret = fmc_driver_register(&fmc_dio_drv);
return ret;
}
/**
* FMC DIO init function
*
* This function is the exit point for the FMC DIO driver.
*
*/
static void fmc_dio_exit(void)
{
fmc_driver_unregister(&fmc_dio_drv);
}
module_init(fmc_dio_init);
module_exit(fmc_dio_exit);
/* If no gpio lib is there, this weak applies */
/**
* FMC DIO GPIO init function
*
* @param dev FMC DIO device
*
* @return 0 if success and a negative error code otherwise
*/
int __weak fmc_dio_gpio_init(struct fmc_dio *dev)
{
return 0;
}
/**
* FMC DIO GPIO exit function
*
* @param dev FMC DIO device
*/
void __weak fmc_dio_gpio_exit(struct fmc_dio *dev)
{
return;
}
MODULE_VERSION(GIT_VERSION);
MODULE_LICENSE("GPL");
/*
* Copyright (C) 2019 CERN (www.sevensols.com)
* Author: Miguel Jimenez Lopez <miguel.jimenez@sevensols.com>
*
* Released according to the GNU GPL, version 2 or any later version.
*
* This work is part of the White Rabbit project, a research effort led
* by CERN, the European Institute for Nuclear Research.
*/
#ifdef __KERNEL__
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/irqreturn.h>
#include <linux/fmc.h>
//FIXME: Back hack to maintain the IOCTL command numbers
#include <linux/sockios.h>
#include "hw/wr-dio-regs.h"
#include "hw/ppsg-regs.h"
/* Forward declaration */
struct fmc_dio;
/* This is somehow generic, but I find no better place at this time */
#ifndef SET_HI32
# if BITS_PER_LONG > 32
# define SET_HI32(var, value) ((var) |= (long)(value) << 32)
# define GET_HI32(var) ((var) >> 32)
# else
# define SET_HI32(var, value) ((var) |= 0)
# define GET_HI32(var) 0
# endif
#endif
/* For GPIO we have no wb-gen header */
struct fmc_dio_gpio_block {
uint32_t clear;
uint32_t set;
uint32_t dir;
uint32_t status;
};
/* And this is our bit mapping */
#define FMC_DIO_GPIO_VALUE(bit) (1 << ((4 * (bit)) + 0))
/* FMC DIO name string pattern */
#define FMC_DIO_NAME_PATTERN "fmc-dio-%d:%d"
/* Size of the name field of the fmc_dio structure */
#define FMC_DIO_NAME_MAX 20
/* Board constants */
#define FMC_DIO_BOARD_SPEC 0
/**
* @brief FMC DIO structure
*
* This contains information regarding FMC DIO.
*/
struct fmc_dio {
char name[FMC_DIO_NAME_MAX]; /**< Name of the FMC DIO */
struct miscdevice mdev; /**< Linux Miscdevice for the FMC DIO */
struct platform_device *pdev; /**< Linux platform_device for the FMC DIO */
struct fmc_device *fmc; /** FMC device for the FMC DIO */
struct gpio_chip *gc; /**< Linux GPIO chip */
void __iomem *dio; /**< DIO IP block pointer */
void __iomem *gpio; /**< GPIO IP block pointer */
void __iomem *ppsg; /**< PPSG IP block pointer */
const char *irqdomain_name; /**< IRQ domain name */
int irq; /**< IRQ number for DIO FMC */
int board; /**< Board info */
void *priv; /**< Private data for FMC DIO */
};
// From fmc-dio-internal.c
extern irqreturn_t fmc_dio_int_interrupt(struct fmc_dio *dev);
extern int fmc_dio_int_ioctl(struct fmc_dio *dev, unsigned int ioctlcmd,
unsigned long arg);
extern int fmc_dio_internal_create(struct fmc_dio *dev);
extern void fmc_dio_internal_destroy(struct fmc_dio *dev);
// From fmc-dio-mdev.c
extern int fmc_dio_mdev_create(struct fmc_dio *dio);
extern void fmc_dio_mdev_destroy(struct fmc_dio *dio);
// From fmc-dio-gpio.c