Commit fb8fb72a authored by Federico Vaga's avatar Federico Vaga

FMC ADC first commit

Signed-off-by: 's avatarFederico Vaga <federico.vaga@gmail.com>
parent c5b2dedd
*~
.*cmd
.*ersions
*.o
*.ko
*.order
*.symvers
*.mod.c
*.pdf
*.toc
*.aux
*.log
*.lof
*.bib
*.bbl
*.blg
*.dvi
*.out
\#*\#
*.sh
# eclipse files
.pydevproject
.cproject
.project
.settings
LINUX ?= /lib/modules/$(shell uname -r)/build
ZIO ?= $(HOME)/zio
SPEC_SW ?= $(HOME)/spec-sw
KBUILD_EXTRA_SYMBOLS := $(ZIO)/Module.symvers $(SPEC_SW)/kernel/Module.symvers
ccflags-y = -I$(ZIO)/include -I$(SPEC_SW)/kernel -I$M
ccflags-y += -DDEBUG # temporary
subdirs-ccflags-y = $(ccflags-y)
obj-m := spec-fmc-adc.o
spec-fmc-adc-objs = fa-zio-drv.o
spec-fmc-adc-objs += fa-core.o
spec-fmc-adc-objs += fa-spec.o
spec-fmc-adc-objs += fa-zio-trg.o
all: modules
modules_install clean modules:
$(MAKE) -C $(LINUX) M=$(shell /bin/pwd) $@
/*
* core fmc-adc driver
*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@gmail.com>
* Copied from fine-delay fd-core.c
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include "spec.h"
#include "fmc-adc.h"
int fa_probe(struct spec_dev *spec)
{
struct spec_fa *fa;
int err;
pr_info("%s:%d\n", __func__, __LINE__);
fa = kzalloc(sizeof(struct spec_fa), GFP_KERNEL);
if (!fa)
return -ENOMEM;
spec->sub_priv = fa;
fa->spec = spec;
fa->base = spec->remap[0];
/* Initliaze sub-system (FIXME only ZIO at the moment) */
err = fa_zio_init(fa);
if (err) {
kfree(fa);
return err;
}
return 0;
}
void fa_remove(struct spec_dev *dev)
{
fa_zio_exit(dev->sub_priv);
kfree(dev->sub_priv);
}
static int fa_init(void)
{
int ret;
pr_debug("%s\n",__func__);
ret = fa_zio_register();
if (ret < 0)
return ret;
ret = fa_spec_init();
if (ret < 0) {
fa_zio_unregister();
return ret;
}
return 0;
}
static void fa_exit(void)
{
fa_spec_exit();
fa_zio_unregister();
}
module_init(fa_init);
module_exit(fa_exit);
MODULE_AUTHOR("Federico Vaga");
MODULE_DESCRIPTION("FMC-ADC Linux Driver");
MODULE_LICENSE("GPL");
/*
* SPEC interface for the fmc-adc driver
*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Federico Vaga <federico.vaga@gmail.com>
* Copied from fine-delay fd-spec.c
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/io.h>
#include "spec.h"
#include "fmc-adc.h"
static int fa_is_valid(int bus, int devfn)
{
/* FIXME: restrict to some of the spec devices with moduleparam */
pr_debug("%s: %x %x\n", __func__, bus, devfn);
return 1;
}
int fa_spec_init(void)
{
struct spec_dev *dev;
int ret, success = 0, retsave = 0, err = 0;
/* Scan the list and see what is there. Take hold of everything */
list_for_each_entry(dev, &spec_list, list) {
if (!fa_is_valid(dev->pdev->bus->number, dev->pdev->devfn))
continue;
pr_debug("%s: init %04x:%04x (%pR - %p)\n", __func__,
dev->pdev->bus->number, dev->pdev->devfn,
dev->area[0], dev->remap[0]);
ret = fa_probe(dev);
if (ret < 0) {
retsave = ret;
err++;
} else {
success++;
}
}
if (err) {
pr_err("%s: Setup of %i boards failed (%i succeeded)\n",
KBUILD_MODNAME, err, success);
pr_err("%s: last error: %i\n", KBUILD_MODNAME, retsave);
}
if (success) {
/* At least one board has been successfully initialized */
return 0;
}
return retsave; /* last error code */
}
void fa_spec_exit(void)
{
struct spec_dev *dev;
list_for_each_entry(dev, &spec_list, list) {
if (!fa_is_valid(dev->pdev->bus->number, dev->pdev->devfn))
continue;
pr_debug("%s: release %04x:%04x (%pR - %p)\n", __func__,
dev->pdev->bus->number, dev->pdev->devfn,
dev->area[0], dev->remap[0]);
fa_remove(dev);
}
}
This diff is collapsed.
/*
* Copyright CERN 2012, author Federico Vaga
*
* Trigger for the driver of the mezzanine ADC
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/zio.h>
#include <linux/zio-sysfs.h>
#include <linux/zio-buffer.h>
#include <linux/zio-trigger.h>
#include "spec.h"
#include "fmc-adc.h"
struct zfat_instance {
struct zio_ti ti;
struct spec_fa *fa;
unsigned int n_acq_dev; /* number of acquisitions on device memory */
unsigned int n_err; /* number of errors */
};
#define to_zfat_instance(_ti) container_of(_ti, struct zfat_instance, ti)
/* Device registers */
extern const struct zio_reg_desc zfad_regs[];
/* zio trigger attributes */
static DEFINE_ZATTR_STD(TRIG, zfat_std_zattr) = {
/* Number of shots */
ZATTR_REG(trig, ZATTR_TRIG_REENABLE, S_IRUGO | S_IWUGO, ZFAT_SHOTS_NB, 0),
/*
* The ADC has pre-sample and post-sample configuration. NSAMPLES doesn't
* apply in this case, so it is a read-only attribute update with the
* value calculated pre-sample + post-sample.
* If read from device, it contains the numer of samples acquired in a
* particular moment
*/
ZATTR_REG(trig, ZATTR_TRIG_NSAMPLES, S_IRUGO, ZFAT_CNT, 0),
};
static struct zio_attribute zfat_ext_zattr[] = {
/* Config register */
/* Hardware trigger selction
* 0: internal (data threshold)
* 1: external (front panel trigger input)
*/
ZATTR_EXT_REG("hw_select", S_IRUGO | S_IWUGO, ZFAT_CFG_INT_SEL, 0),
/*
* Hardware trigger polarity
* 0: positive edge/slope
* 1: negative edge/slope
*/
ZATTR_EXT_REG("polarity", S_IRUGO | S_IWUGO, ZFAT_CFG_HW_POL, 0),
/* Enable (1) or disable (0) hardware trigger */
ZATTR_EXT_REG("hw_trig_enable", S_IRUGO | S_IWUGO, ZFAT_CFG_HW_EN, 0),
/* Enable (1) or disable (0) software trigger */
ZATTR_EXT_REG("sw_trig_enable", S_IRUGO | S_IWUGO, ZFAT_CFG_SW_EN, 0),
/*
* Channel selection for internal trigger
* 0: channel 1
* 1: channel 2
* 2: channel 3
* 3: channel 4
*/
ZATTR_EXT_REG("int_select", S_IRUGO | S_IWUGO, ZFAT_CFG_INT_SEL, 0),
/* Threshold value for internal trigger */
ZATTR_EXT_REG("int_threshold", S_IRUGO | S_IWUGO, ZFAT_CFG_THRES, 0),
/* Delay */
ZATTR_EXT_REG("delay", S_IRUGO | S_IWUGO, ZFAT_DLY, 0),
/* Software */
PARAM_EXT_REG("sw_fire", S_IWUGO, ZFAT_SW, 0),
/* Position address */
ZATTR_EXT_REG("position_addr", S_IRUGO, ZFAT_POS, 0),
/* Pre-sample*/
ZATTR_EXT_REG("pre-sample", S_IRUGO | S_IWUGO, ZFAT_PRE, 0),
/* Post-sample*/
ZATTR_EXT_REG("post-sample", S_IRUGO | S_IWUGO, ZFAT_POST, 0),
};
/* FIXME: zio need an update, introduce PRE and POST and replace NSAMPLES*/
static void zfat_update_nsample(struct zio_ti *ti, enum zfadc_dregs_enum type,
uint32_t val)
{
struct zio_attribute_set *zattr_set = &ti->zattr_set;
struct zio_attribute *n = &zattr_set->std_zattr[ZATTR_TRIG_NSAMPLES];
/* 9-th attribute is PRE */
struct zio_attribute *pre = &zattr_set->ext_zattr[9];
/* 10-th attribute is POST */
struct zio_attribute *post = &zattr_set->ext_zattr[10];
if (type == ZFAT_PRE)
n->value = val + post->value;
else if (type == ZFAT_POST)
n->value = pre->value + val;
/*
* FIXME n->value must update current_control. Make propagete_value
* a public function?
*/
}
/* set a value to a FMC-ADC trigger register */
static int zfat_conf_set(struct device *dev, struct zio_attribute *zattr,
uint32_t usr_val)
{
const struct zio_reg_desc *reg = &zfad_regs[zattr->priv.addr];
struct zio_ti *ti = to_zio_ti(dev);
int err;
switch (zattr->priv.addr) {
case ZFAT_SW:
/* Fire if software trigger is enabled */
if (!ti->zattr_set.ext_zattr[3].value) {
dev_err(dev, "sw trigger must be enable");
return -EPERM;
}
/* Abort current acquisition if any */
err = zfa_common_conf_set(dev,
&zfad_regs[ZFA_CTL_FMS_CMD], 2);
if (err)
return err;
zio_trigger_abort(ti->cset);
/* Start a new acquisition */
zio_fire_trigger(ti);
break;
}
err = zfa_common_conf_set(dev, reg, usr_val);
if (err)
return err;
if ( zattr->priv.addr == ZFAT_PRE || zattr->priv.addr == ZFAT_POST) {
zfat_update_nsample(to_zio_ti(dev), zattr->priv.addr, usr_val);
}
return 0;
}
/* get the value of a FMC-ADC trigger register */
static int zfat_info_get(struct device *dev, struct zio_attribute *zattr,
uint32_t *usr_val)
{
zfa_common_info_get(dev, &zfad_regs[zattr->priv.addr], usr_val);
return 0;
}
static const struct zio_sysfs_operations zfat_s_op = {
.conf_set = zfat_conf_set,
.info_get = zfat_info_get,
};
irqreturn_t zfadc_irq(int irq, void *ptr)
{
struct zfat_instance *zfat = ptr;
uint32_t irq_status = 0, val;
zfa_common_info_get(&zfat->ti.cset->head.dev,
&zfad_regs[ZFA_IRQ_SRC],&irq_status);
dev_dbg(&zfat->ti.head.dev, "irq status = 0x%x\n", irq_status);
if (irq_status & (ZFAT_DMA_DONE | ZFAT_DMA_ERR)) {
if (irq_status & ZFAT_DMA_DONE) { /* DMA done*/
zio_trigger_data_done(zfat->ti.cset);
zfat->n_acq_dev--;
} else { /* DMA error */
zio_trigger_abort(zfat->ti.cset);
zfat->n_err++;
}
/* Enable all triggers */
zfa_common_conf_set(&zfat->ti.cset->head.dev,
&zfad_regs[ZFAT_CFG_SW_EN], 0);
zfa_common_conf_set(&zfat->ti.cset->head.dev,
&zfad_regs[ZFAT_CFG_HW_EN], 0);
/* Start state machine */
zfa_common_conf_set(&zfat->ti.cset->head.dev,
&zfad_regs[ZFA_CTL_FMS_CMD], 2);
}
if (irq_status & ZFAT_TRG_FIRE) { /* Trigger fire */
/*
* FIXME: I think we don't care about this because ZIO fires
* a fake trigger before DMA
*/
zfat->n_acq_dev++;
}
if (irq_status & ZFAT_ACQ_END) { /* Acquisition end */
/*
* We fire the trigger at the and of the acquisition because
* FMC-ADC allow DMA only when acquisition end and the
* state machine is in the idle status. When the real trigger
* fire the state machine is not idle. This has not any
* influence on the ZIO side: we are interested to DMA data
*/
zfa_common_info_get(&zfat->ti.cset->head.dev,
&zfad_regs[ZFA_STA_FSM],&val);
if (val == ZFA_STATE_IDLE) {
/* Stop state machine */
zfa_common_conf_set(&zfat->ti.cset->head.dev,
&zfad_regs[ZFA_CTL_FMS_CMD], 2);
/* Disable all triggers */
zfa_common_conf_set(&zfat->ti.cset->head.dev,
&zfad_regs[ZFAT_CFG_HW_EN], 0);
zfa_common_conf_set(&zfat->ti.cset->head.dev,
&zfad_regs[ZFAT_CFG_SW_EN], 0);
/* Fire ZIO trigger so it will DMA */
zio_fire_trigger(&zfat->ti);
} else {
/* we can't DMA if the state machine is not idle */
dev_warn(&zfat->ti.cset->head.dev,
"Can't start DMA on the last acquisition\n");
}
}
return IRQ_HANDLED;
}
/* create an instance of the FMC-ADC trigger */
static struct zio_ti *zfat_create(struct zio_trigger_type *trig,
struct zio_cset *cset,
struct zio_control *ctrl, fmode_t flags)
{
struct spec_dev *spec = cset->zdev->priv_d;
struct zfat_instance *zfat;
int err;
if (!spec) {
dev_err(&cset->head.dev, "no spec device defined\n");
return ERR_PTR(-ENODEV);
}
zfat = kzalloc(sizeof(struct zfat_instance), GFP_KERNEL);
if (!zfat)
return ERR_PTR(-ENOMEM);
zfat->fa = spec->sub_priv;
err = request_irq(spec->pdev->irq, zfadc_irq, IRQF_SHARED, "wr-nic", zfat);
if (err) {
dev_err(&spec->pdev->dev, "can't request irq %i (err %i)\n",
spec->pdev->irq, err);
return ERR_PTR(err);
}
/* Enable all interrupt */
zfa_common_conf_set(&cset->head.dev, &zfad_regs[ZFA_IRQ_MASK],
ZFAT_ALL);
return &zfat->ti;
}
static void zfat_destroy(struct zio_ti *ti)
{
/* Disable all interrupt */
zfa_common_conf_set(&ti->cset->head.dev, &zfad_regs[ZFA_IRQ_MASK],
ZFAT_NONE);
kfree(to_zfat_instance(ti));
}
/* status is active low on ZIO but active high on the FMC-ADC */
static void zfat_change_status(struct zio_ti *ti, unsigned int status)
{
/* Enable/Disable HW trigger */
zfa_common_conf_set(&ti->head.dev, &zfad_regs[ZFAT_CFG_HW_EN], !status);
/* Enable/Disable SW trigger */
zfa_common_conf_set(&ti->head.dev, &zfad_regs[ZFAT_CFG_SW_EN], !status);
}
/*
* Abort depends on the state machine status and DMA status.*/
static void zfat_abort(struct zio_cset *cset)
{
}
static const struct zio_trigger_operations zfat_ops = {
.create = zfat_create,
.destroy = zfat_destroy,
.change_status = zfat_change_status,
.abort = zfat_abort,
};
struct zio_trigger_type zfat_type = {
.owner = THIS_MODULE,
.zattr_set = {
.std_zattr = zfat_std_zattr,
.ext_zattr = zfat_ext_zattr,
.n_ext_attr = ARRAY_SIZE(zfat_ext_zattr),
},
.s_op = &zfat_s_op,
.t_op = &zfat_ops,
};
/*
* Copyright CERN 2012 (author Federico Vaga)
*
* Driver for the mezzanine ADC for the SPEC
*/
#ifndef _FMC_ADC_H_
#define _FMC_ADC_H_
#include "spec.h"
/* ADC register offset */
#define FA_DMA_MEM_OFF 0x00000
#define FA_IRQ_MEM_OFF 0x50000
#define FA_ADC_MEM_OFF 0x90000
struct spec_fa {
struct spec_dev *spec;
struct zio_device *hwzdev;
unsigned char __iomem *base; /* regs files are byte-oriented */
};
/*
* ZFA_CHx_MULT
* address offset between two registers of the same type on consecutive channel
*/
#define ZFA_CHx_MULT 5
/* Device registers */
enum zfadc_dregs_enum {
/* Device */
/* Control registers */
ZFA_CTL_FMS_CMD,
ZFA_CTL_CLK_EN,
ZFA_CTL_DAC_CLR_N,
ZFA_CTL_BSLIP,
ZFA_CTL_TEST_DATA_EN,
ZFA_CTL_TRIG_LED,
ZFA_CTL_ACQ_LED,
/* Status registers */
ZFA_STA_FSM,
ZFA_STA_SERDES_PLL,
ZFA_STA_SERDES_SYNCED,
/* Configuration register */
ZFDAC_CFG_HW_SEL,
ZFAT_CFG_HW_POL,
ZFAT_CFG_HW_EN,
ZFAT_CFG_SW_EN,
ZFAT_CFG_INT_SEL,
ZFAT_CFG_THRES,
/* Delay*/
ZFAT_DLY,
/* Software */
ZFAT_SW,
/* Number of shots */
ZFAT_SHOTS_NB,
/* Sample rate */
ZFAT_SR_DECI,
/* Position address */
ZFAT_POS,
/* Pre-sample */
ZFAT_PRE,
/* Post-sample */
ZFAT_POST,
/* Sample counter */
ZFAT_CNT,
/* Channel 1 */
ZFA_CH1_CTL_RANGE,
ZFA_CH1_CTL_TERM,
ZFA_CH1_STA,
ZFA_CH1_GAIN,
ZFA_CH1_OFFSET,
/* Channel 2 */
ZFA_CH2_CTL_RANGE,
ZFA_CH2_CTL_TERM,
ZFA_CH2_STA,
ZFA_CH2_GAIN,
ZFA_CH2_OFFSET,
/* Channel 3 */
ZFA_CH3_CTL_RANGE,
ZFA_CH3_CTL_TERM,
ZFA_CH3_STA,
ZFA_CH3_GAIN,
ZFA_CH3_OFFSET,
/* Channel 4 */
ZFA_CH4_CTL_RANGE,
ZFA_CH4_CTL_TERM,
ZFA_CH4_STA,
ZFA_CH4_GAIN,
ZFA_CH4_OFFSET,
/* Other*/
ZFA_CHx_CTL_RANGE,
ZFA_CHx_CTL_TERM,
ZFA_CHx_STA,
ZFA_CHx_GAIN,
ZFA_CHx_OFFSET,
ZFA_SW_R_NOADDRES,
/* DMA */
ZFA_DMA_CTL_SWP,
ZFA_DMA_CTL_ABORT,
ZFA_DMA_CTL_START,
ZFA_DMA_STA,
ZFA_DMA_ADDR,
ZFA_DMA_ADDR_L,
ZFA_DMA_ADDR_H,
ZFA_DMA_LEN,
ZFA_DMA_NEXT_L,
ZFA_DMA_NEXT_H,
ZFA_DMA_BR_DIR,
ZFA_DMA_BR_LAST,
/* IRQ */
ZFA_IRQ_MULTI,
ZFA_IRQ_SRC,
ZFA_IRQ_MASK,
};
/* All possible state of the state machine, other values are invalid*/
enum zfa_fsm_state {
ZFA_STATE_IDLE = 0x1,
ZFA_STATE_PRE,
ZFA_STATE_POST,
ZFA_STATE_WAIT,
ZFA_STATE_DECR,
};
/* All possible interrupt available */
enum zfat_irq {
ZFAT_NONE = 0x0,
ZFAT_DMA_DONE = 0x1,
ZFAT_DMA_ERR = 0x2,
ZFAT_TRG_FIRE = 0x4,
ZFAT_ACQ_END = 0x8,
ZFAT_ALL = 0xF,
};
#ifdef __KERNEL__ /* All the rest is only of kernel users */
#include <linux/zio.h>
#include <linux/zio-trigger.h>
static inline struct spec_fa *get_zfadc(struct device *dev)
{
switch (to_zio_head(dev)->zobj_type) {
case ZDEV:
return to_zio_dev(dev)->priv_d;
case ZCSET:
return to_zio_cset(dev)->zdev->priv_d;
case ZCHAN:
return to_zio_chan(dev)->cset->zdev->priv_d;
case ZTI:
return to_zio_ti(dev)->cset->zdev->priv_d;
default:
return NULL;
}
return NULL;
}
static inline struct spec_dev *get_spec(struct device *dev)
{
return get_zfadc(dev)->spec;
}
static inline uint32_t fa_read_reg(struct spec_fa *fa,
const struct zio_reg_desc *reg)
{
return readl(fa->base + reg->addr);
}
static inline void fa_write_reg(uint32_t val, struct spec_fa *fa,
const struct zio_reg_desc *reg)
{
writel(val, fa->base + reg->addr);
}
static inline int zfa_common_conf_set(struct device *dev,
const struct zio_reg_desc *reg,
uint32_t usr_val)
{
uint32_t cur, val;
if ((usr_val & (~reg->mask))) {
dev_err(dev, "the value must fit the mask 0x%x\n", reg->mask);
return -EINVAL;
}
/* Read current register*/
cur = fa_read_reg(get_zfadc(dev), reg);
/* Mask the value */
cur &= (~(reg->mask << reg->off));
/* Write the new value */
val = cur | (usr_val << reg->off);
/* FIXME re-write usr_val when possible (zio need a patch) */
/* If the attribute has a valid address */
fa_write_reg(val, get_zfadc(dev), reg);
return 0;
}
static inline void zfa_common_info_get(struct device *dev,
const struct zio_reg_desc *reg,
uint32_t *usr_val)
{
uint32_t cur;
/* Read current register*/
cur = fa_read_reg(get_zfadc(dev), reg);
/* Mask the value */
cur &= (reg->mask << reg->off);
/* Return the value */
*usr_val = cur >> reg->off;
}
extern struct zio_trigger_type zfat_type;
/* Registers lists used in fd-zio-drv.c and fd-zio-trg.c */
extern const struct zio_reg_desc zfad_regs[];
/* Functions exported by fd-core.c */
extern int fa_probe(struct spec_dev *dev);
extern void fa_remove(struct spec_dev *dev);
/* Functions exported by fa-zio.c */
extern int fa_zio_register(void);
extern void fa_zio_unregister(void);
extern int fa_zio_init(struct spec_fa *fa);
extern void fa_zio_exit(struct spec_fa *fa);
/* Functions exported by fa-spec.c */
extern int fa_spec_init(void);
extern void fa_spec_exit(void);
#endif /* __KERNEL__ */
#endif /* _FMC_ADC_H_ */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment