Commit b33dab32 authored by Miguel Jimenez Lopez's avatar Miguel Jimenez Lopez

kernel: Remove NIC and DIO related files.

parent f2e7ef89
......@@ -20,30 +20,11 @@ ccflags-y += $(WR_NIC_CFLAGS)
ccflags-y += -DGIT_VERSION=\"$(GIT_VERSION)\"
ccflags-y += -DWR_NODE
# this is a bad hack. Sometimes we are a submodule, and wr-nic can
# only compile with recent versions, so let the caller disable it
# FIXME: this is incorrect if we get copied to the kernel proper.
CONFIG_WR_NIC ?= m
obj-m += spec.o
obj-$(CONFIG_WR_NIC) += wr-nic.o
spec-y = spec-pci.o
spec-y += spec-fmc.o
spec-y += spec-i2c.o
spec-y += spec-vic.o
spec-y += loader-ll.o
spec-y += spec-gpio-no.o
spec-$(CONFIG_GPIOLIB) += spec-gpio.o
wr-nic-y = wr-nic-core.o
wr-nic-y += wr-nic-eth.o
wr-nic-y += wr-nic-dio.o
wr-nic-y += wr_nic/device.o
wr-nic-y += wr_nic/endpoint.o
wr-nic-y += wr_nic/ethtool.o
wr-nic-y += wr_nic/nic-core.o
wr-nic-y += wr_nic/timestamp.o
wr-nic-y += wr_nic/pps.o
wr-nic-$(CONFIG_GPIOLIB) += wr-nic-gpio.o
spec-$(CONFIG_GPIOLIB) += spec-gpio.o
\ No newline at end of file
/**
* Copyright (C) 2013 CERN (www.cern.ch)
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* Released according to the GNU GPL, version 2 or any later version
*
* Driver for SPEC (Simple PCI Express FMC carrier) board.
* VIC (Vectored Interrupt Controller) support code.
*/
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fmc.h>
#include <linux/fmc-sdb.h>
#include "spec.h"
#include "hw/vic_regs.h"
#define VIC_MAX_VECTORS 32
#define VIC_SDB_VENDOR 0xce42
#define VIC_SDB_DEVICE 0x0013
/* A Vectored Interrupt Controller object */
struct vic_irq_controller {
/* It protects the handlers' vector */
spinlock_t vec_lock;
/* already-initialized flag */
int initialized;
/* Base address (FPGA-relative) */
uint32_t base;
/* Mapped base address of the VIC */
void *kernel_va;
/* Vector table */
struct vector {
/* Saved ID of the vector (for autodetection purposes) */
int saved_id;
/* Pointer to the assigned handler */
irq_handler_t handler;
/* FMC device that owns the interrupt */
struct fmc_device *requestor;
} vectors[VIC_MAX_VECTORS];
};
static inline void vic_writel(struct vic_irq_controller *vic, uint32_t value,
uint32_t offset)
{
writel(value, vic->kernel_va + offset);
}
static inline uint32_t vic_readl(struct vic_irq_controller *vic,
uint32_t offset)
{
return readl(vic->kernel_va + offset);
}
static int spec_vic_init(struct spec_dev *spec, struct fmc_device *fmc)
{
int i;
signed long vic_base;
struct vic_irq_controller *vic;
/*
* Try to look up the VIC in the SDB tree - note that IRQs
* shall be requested after the FMC driver has scanned the SDB tree
*/
vic_base =
fmc_find_sdb_device(fmc->sdb, VIC_SDB_VENDOR, VIC_SDB_DEVICE, NULL);
if (vic_base < 0) {
dev_err(&spec->pdev->dev,
"VIC controller not found, but a VIC interrupt requested. Wrong gateware?\n");
return -ENODEV;
}
dev_info(&spec->pdev->dev, "Found VIC @ 0x%lx\n", vic_base);
vic = kzalloc(sizeof(struct vic_irq_controller), GFP_KERNEL);
if (!vic)
return -ENOMEM;
spin_lock_init(&vic->vec_lock);
vic->kernel_va = spec->remap[0] + vic_base;
vic->base = (uint32_t) vic_base;
/* disable all IRQs, copy the vector table with pre-defined IRQ ids */
vic_writel(vic, 0xffffffff, VIC_REG_IDR);
for (i = 0; i < VIC_MAX_VECTORS; i++)
vic->vectors[i].saved_id =
vic_readl(vic, VIC_IVT_RAM_BASE + 4 * i);
/* config the VIC output: active high, edge, width = 256 tick (4 us) */
vic_writel(vic,
VIC_CTL_ENABLE | VIC_CTL_POL | VIC_CTL_EMU_EDGE |
VIC_CTL_EMU_LEN_W(250), VIC_REG_CTL);
vic->initialized = 1;
spec->vic = vic;
return 0;
}
static void spec_vic_exit(struct vic_irq_controller *vic)
{
if (!vic)
return;
/* Disable all irq lines and the VIC in general */
vic_writel(vic, 0xffffffff, VIC_REG_IDR);
vic_writel(vic, 0, VIC_REG_CTL);
kfree(vic);
}
/* NOTE: this function must be called while holding irq_lock */
irqreturn_t spec_vic_irq_dispatch(struct spec_dev *spec)
{
struct vic_irq_controller *vic = spec->vic;
int index, rv;
struct vector *vec;
if (unlikely(!vic))
goto fail;
/*
* Our parent IRQ handler: read the index value
* from the Vector Address Register, and find matching handler
*/
index = vic_readl(vic, VIC_REG_VAR) & 0xff;
if (index >= VIC_MAX_VECTORS) {
dev_err(&spec->pdev->dev, "Invalid VIC index %d (max %d)\n",
index, VIC_MAX_VECTORS);
goto fail;
}
vec = &vic->vectors[index];
if (!vec->handler) {
dev_err(&spec->pdev->dev,
"Handler not found for vector %d\n",
index);
goto fail;
}
rv = vec->handler(vec->saved_id, vec->requestor);
return rv;
fail:
return 0;
}
/* NOTE: this function must be called while holding irq_lock */
int spec_vic_irq_request(struct spec_dev *spec, struct fmc_device *fmc,
unsigned long id, irq_handler_t handler)
{
struct vic_irq_controller *vic;
int rv = 0, i;
/* First interrupt to be requested? Look up and init the VIC */
if (!spec->vic) {
rv = spec_vic_init(spec, fmc);
if (rv)
return rv;
}
vic = spec->vic;
for (i = 0; i < VIC_MAX_VECTORS; i++) {
/* find vector in stored table, assign handle, enable */
if (vic->vectors[i].saved_id == id) {
spin_lock(&spec->vic->vec_lock);
vic_writel(vic, i, VIC_IVT_RAM_BASE + 4 * i);
vic->vectors[i].requestor = fmc;
vic->vectors[i].handler = handler;
vic_writel(vic, (1 << i), VIC_REG_IER);
spin_unlock(&spec->vic->vec_lock);
return 0;
}
}
return -EINVAL;
}
int vic_is_managed(struct vic_irq_controller *vic, unsigned long id)
{
int i, ret;
if (!vic)
return 0;
for (i = 0; i < VIC_MAX_VECTORS; i++) {
if (vic->vectors[i].saved_id != id)
continue;
ret = vic_readl(vic, VIC_REG_IMR) & (1 << i);
return !!ret;
}
return 0;
}
/*
* vic_handler_count
* It counts how many handlers are registered within the VIC controller
*/
static inline int vic_handler_count(struct vic_irq_controller *vic)
{
int i, count;
for (i = 0, count = 0; i < VIC_MAX_VECTORS; ++i)
if (vic->vectors[i].handler)
count++;
return count;
}
/* NOTE: this function must be called while holding irq_lock */
void spec_vic_irq_free(struct spec_dev *spec, unsigned long id)
{
int i;
for (i = 0; i < VIC_MAX_VECTORS; i++) {
if (spec->vic->vectors[i].saved_id == id) {
spin_lock(&spec->vic->vec_lock);
vic_writel(spec->vic, 1 << i, VIC_REG_IDR);
vic_writel(spec->vic, id, VIC_IVT_RAM_BASE + 4 * i);
spec->vic->vectors[i].handler = NULL;
spin_unlock(&spec->vic->vec_lock);
}
}
/* Clean up the VIC if there are no more handlers */
if (!vic_handler_count(spec->vic)) {
spec_vic_exit(spec->vic);
spec->vic = NULL;
}
}
void spec_vic_irq_ack(struct spec_dev *spec, unsigned long id)
{
vic_writel(spec->vic, 0, VIC_REG_EOIR); /* ack the irq */
}
......@@ -38,8 +38,6 @@ struct spec_dev {
int irq_count; /* for mezzanine use too */
struct completion compl;
struct gpio_chip *gpio;
struct vic_irq_controller *vic;
spinlock_t irq_lock;
struct miscdevice mdev;
char name[SPEC_NAME_LEN];
......@@ -153,13 +151,5 @@ extern int spec_eeprom_write(struct fmc_device *fmc, uint32_t offset,
extern int spec_gpio_init(struct fmc_device *fmc);
extern void spec_gpio_exit(struct fmc_device *fmc);
/* Functions in spec-vic.c */
/* NOTE: these functions must be called while holding irq_lock */
int spec_vic_irq_request(struct spec_dev *spec, struct fmc_device *fmc,
unsigned long id, irq_handler_t handler);
void spec_vic_irq_free(struct spec_dev *spec, unsigned long id);
irqreturn_t spec_vic_irq_dispatch(struct spec_dev *spec);
extern void spec_vic_irq_ack(struct spec_dev *spec, unsigned long id);
extern int vic_is_managed(struct vic_irq_controller *vic, unsigned long id);
#endif /* __SPEC_H__ */
/*
* Copyright (C) 2010-2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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.
*/
#ifndef __WR_DIO_H__
#define __WR_DIO_H__
/* This should be included by both the kernel and the tools */
#ifdef __KERNEL__
#include "wbgen-regs/wr-dio-regs.h"
/* For GPIO we have no wb-gen header */
struct wrn_gpio_block {
uint32_t clear;
uint32_t set;
uint32_t dir;
uint32_t status;
};
/* And this is our bit mapping */
#define WRN_GPIO_VALUE(bit) (1 << ((4 * (bit)) + 0))
extern irqreturn_t wrn_dio_interrupt(struct fmc_device *fmc);
#endif /* __KERNEL__ */
enum wr_dio_cmd_name {
WR_DIO_CMD_PULSE,
WR_DIO_CMD_STAMP,
WR_DIO_CMD_DAC,
WR_DIO_CMD_INOUT,
};
/*
* This is how parameters are used (K == reply from kernel):
*
* CMD_PULSE:
* cmd->flags: F_NOW, F_REL, F_LOOP
* cmd->channel: the channel or the mask
* cmd->t[]: either 2 or 3 values (start, duration, loop)
* cmd->value: count of loops (0 to turn off)
*
* CMD_STAMP:
* cmd->flags: F_MASK, F_WAIT
* cmd->channel: the channel or the mask
* K: cmd->channel: the channel where we had stamps
* K: cmd->nstamp: number of valid stamps
* K: cmd->t[]: the stamps
*
* CMD_DAC:
* cmd->flags: none
* cmd->channel: which one
* cmd->value: the value
*
* CMD_INOUT:
* cmd->flags: F_MASK
* cmd->channel: the channel or the mask
* cmd->value: bits 0..4: WR-DIO, 8..12 value, 16..20 OEN, 24..28 term
*
*/
#define WR_DIO_INOUT_DIO (1 << 0)
#define WR_DIO_INOUT_VALUE (1 << 8)
#define WR_DIO_INOUT_OUTPUT (1 << 16)
#define WR_DIO_INOUT_TERM (1 << 24)
#define WR_DIO_N_STAMP 16 /* At least 5 * 3 */
struct wr_dio_cmd {
uint16_t command; /* from user */
uint16_t channel; /* 0..4 or mask from user */
uint32_t value; /* for DAC or I/O */
uint32_t flags;
uint32_t nstamp; /* from kernel, if IN_STAMP */
struct timespec t[WR_DIO_N_STAMP]; /* may be from user */
};
#define WR_DIO_F_NOW 0x01 /* Output is now, t[0] ignored */
#define WR_DIO_F_REL 0x02 /* t[0].tv_sec is relative */
#define WR_DIO_F_MASK 0x04 /* Channel is 0x00..0x1f */
#define WR_DIO_F_LOOP 0x08 /* Output should loop: t[2] is looping*/
#define WR_DIO_F_WAIT 0x10 /* Wait for event */
#endif /* __WR_DIO_H__ */
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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 "spec-nic.h"
#include "wr_nic/wr-nic.h"
static struct fmc_driver wrn_drv;
FMC_PARAM_BUSID(wrn_drv);
FMC_PARAM_GATEWARE(wrn_drv);
static char *wrn_filename = WRN_GATEWARE_DEFAULT_NAME;
module_param_named(file, wrn_filename, charp, 0444);
static char *wrn_wrc_filename = WRN_WRC_DEFAULT_NAME;
module_param_named(wrc, wrn_wrc_filename, charp, 0444);
static int wrn_show_sdb;
module_param_named(show_sdb, wrn_show_sdb, int, 0444);
static int wrn_load_wrc(struct fmc_device *fmc, char *name,
unsigned long ram, unsigned long ramsize)
{
const struct firmware *fw;
struct device *dev = fmc->hwdev;
int ret, count;
const uint32_t *p;
ret = request_firmware(&fw, name, dev);
if (ret < 0) {
dev_err(dev, "request firmware \"%s\": error %i\n",
name, ret);
return ret;
}
if (fw->size > ramsize) {
dev_err(dev, "firmware \"%s\" longer than ram (0x%lx)\n",
name, ramsize);
ret = -ENOMEM;
goto out;
}
for (count = 0, p = (void *)fw->data; count < fw->size; count += 4, p++)
fmc_writel(fmc, __cpu_to_be32(*p), ram + count);
out:
release_firmware(fw);
return ret;
}
int wrn_fmc_probe(struct fmc_device *fmc)
{
int index, need_wrc = 0, ret = 0;
struct device *dev = fmc->hwdev;
struct wrn_drvdata *dd;
signed long ram, syscon;
unsigned long ramsize;
char *filename;
/* We accept busid= to limit to some spec only */
index = fmc->op->validate(fmc, &wrn_drv);
if (index < 0) {
dev_info(fmc->hwdev, "not using \"%s\" according to "
"modparam\n", KBUILD_MODNAME);
return -ENODEV;
}
/* Driver data */
dd = devm_kzalloc(&fmc->dev, sizeof(*dd), GFP_KERNEL);
if (!dd)
return -ENOMEM;
fmc_set_drvdata(fmc, dd);
/*
* We first write a new binary within the spec. If the user
* passed "gateware=", use the per-board names instead of the
* global name
*/
if (wrn_drv.gw_n)
ret = fmc_reprogram(fmc, &wrn_drv, "", WRN_SDB_ADDR);
else
ret = fmc_reprogram(fmc, &wrn_drv, wrn_filename, WRN_SDB_ADDR);
if (ret <0) {
if (ret == -ESRCH) {
dev_info(fmc->hwdev, "%s: no gateware at index %i\n",
KBUILD_MODNAME, index);
return -ENODEV;
}
dev_err(dev, "write firmware \"%s\": error %i\n",
wrn_filename, ret);
return ret;
}
dev_info(dev, "Gateware successfully loaded\n");
/* FIXME: remove this parameter, fmc.ko shows it already */
if (wrn_show_sdb)
fmc_show_sdb_tree(fmc);
/*
* The gateware may not be including the WRC code, or the
* user may have asked for a specific file name. If so, load.
*/
ram = fmc_find_sdb_device(fmc->sdb, SDB_CERN, WRN_SDB_RAM, &ramsize);
syscon = fmc_find_sdb_device(fmc->sdb, SDB_CERN, WRN_SDB_SYSCON, NULL);
if (ram >= 0 && fmc_readl(fmc, ram) != 0x98000000)
need_wrc = 1;
filename = wrn_wrc_filename;
if (strcmp(wrn_wrc_filename, WRN_WRC_DEFAULT_NAME)) {
need_wrc = 1;
/*
* If the user changed it, use the new name.
* But "1" means "do load the default"
*/
if (!strcmp(wrn_wrc_filename, "1"))
filename = WRN_WRC_DEFAULT_NAME;
}
if (need_wrc && ((ram < 0) || (syscon < 0))) {
dev_err(dev, "can't reprogram WRC: SDB failure\n");
goto out;
}
if (need_wrc) {
unsigned long j = jiffies + HZ/2;
fmc_writel(fmc, 0x1deadbee, syscon);
while ( !(fmc_readl(fmc, syscon) & (1 << 28)) )
if (time_after(jiffies, j))
break;
if (time_after(jiffies, j)) {
dev_err(dev, "can't reset LM32\n");
fmc_writel(fmc, 0x0deadbee, syscon);
goto out;
}
ret = wrn_load_wrc(fmc, filename, ram, ramsize);
fmc_writel(fmc, 0x0deadbee, syscon);
if (ret)
goto out; /* message already reported */
if (fmc_readl(fmc, ram) != 0x98000000)
dev_warn(dev, "possible failure in wrc load\n");
else
dev_info(dev, "WRC program reloaded from \"%s\"\n",
filename);
}
/* After the LM32 started, give it time to set up */
msleep(200);
/* Register the gpio stuff, if we have kernel support */
ret = wrn_gpio_init(fmc);
if (ret < 0)
goto out;
/* The network device */
ret = wrn_eth_init(fmc);
if (ret < 0)
wrn_gpio_exit(fmc);
out:
return ret;
}
int wrn_fmc_remove(struct fmc_device *fmc)
{
wrn_eth_exit(fmc);
wrn_gpio_exit(fmc);
fmc_free_sdb_tree(fmc);
return 0;
}
static struct fmc_fru_id fd_fru_id[] = {
{
.product_name = "FmcDio5cha",
},
};
static struct fmc_driver wrn_fmc_drv = {
.version = FMC_VERSION,
.driver.name = KBUILD_MODNAME,
.probe = wrn_fmc_probe,
.remove = wrn_fmc_remove,
.id_table = {
.fru_id = fd_fru_id,
.fru_id_nr = ARRAY_SIZE(fd_fru_id),
},
};
static int wrn_init(void)
{
int ret;
ret = fmc_driver_register(&wrn_fmc_drv);
if (ret < 0)
return ret;
platform_driver_register(&wrn_driver);
if (ret < 0)
fmc_driver_unregister(&wrn_fmc_drv);
return ret;
}
static void wrn_exit(void)
{
platform_driver_unregister(&wrn_driver);
fmc_driver_unregister(&wrn_fmc_drv);
}
module_init(wrn_init);
module_exit(wrn_exit);
/* If no gpio lib is there, this weak applies */
int __weak wrn_gpio_init(struct fmc_device *fmc)
{
return 0;
}
void __weak wrn_gpio_exit(struct fmc_device *fmc)
{
}
MODULE_VERSION(GIT_VERSION);
MODULE_LICENSE("GPL");
ADDITIONAL_VERSIONS;
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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>
#if KERNEL_VERSION(4,11,0) > LINUX_VERSION_CODE
#include <linux/sched.h>
#else
#include <linux/sched/signal.h>
#endif
#include <linux/wait.h>
#include <linux/ktime.h>
#include <linux/atomic.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/fmc.h>
#include <linux/fmc-sdb.h>
#include <linux/rtnetlink.h>
#include "spec-nic.h"
#include "wr_nic/wr-nic.h"
#include "wr-dio.h"
#include "wbgen-regs/vic-regs.h"
#ifdef DIO_STAT
#define wrn_stat 1
#else
#define wrn_stat 0
#endif
/*
* FIXME (for the whole file: we use readl/writel, not fmc_read/fmc_writel)
*/
/* We need a clear mapping for the registers of the various bits */
struct regmap {
int trig_l;
int trig_h;
int cycle;
int pulse;
int fifo_tai_l;
int fifo_tai_h;
int fifo_cycle;
int fifo_status;
};
#define R(x) (offsetof(struct DIO_WB, x))
static struct regmap regmap[] = {
{
.trig_l = R(TRIG0),
.trig_h = R(TRIGH0),
.cycle = R(CYC0),
.pulse = R(PROG0_PULSE),
.fifo_tai_l = R(TSF0_R0),
.fifo_tai_h = R(TSF0_R1),
.fifo_cycle = R(TSF0_R2),
.fifo_status = R(TSF0_CSR),
}, {
.trig_l = R(TRIG1),
.trig_h = R(TRIGH1),
.cycle = R(CYC1),
.pulse = R(PROG1_PULSE),
.fifo_tai_l = R(TSF1_R0),
.fifo_tai_h = R(TSF1_R1),
.fifo_cycle = R(TSF1_R2),
.fifo_status = R(TSF1_CSR),
}, {
.trig_l = R(TRIG2),
.trig_h = R(TRIGH2),
.cycle = R(CYC2),
.pulse = R(PROG2_PULSE),
.fifo_tai_l = R(TSF2_R0),
.fifo_tai_h = R(TSF2_R1),
.fifo_cycle = R(TSF2_R2),
.fifo_status = R(TSF2_CSR),
}, {
.trig_l = R(TRIG3),
.trig_h = R(TRIGH3),
.cycle = R(CYC3),
.pulse = R(PROG3_PULSE),
.fifo_tai_l = R(TSF3_R0),
.fifo_tai_h = R(TSF3_R1),
.fifo_cycle = R(TSF3_R2),
.fifo_status = R(TSF3_CSR),
}, {
.trig_l = R(TRIG4),
.trig_h = R(TRIGH4),
.cycle = R(CYC4),
.pulse = R(PROG4_PULSE),
.fifo_tai_l = R(TSF4_R0),
.fifo_tai_h = R(TSF4_R1),
.fifo_cycle = R(TSF4_R2),
.fifo_status = R(TSF4_CSR),
}
};
#define WRN_DIO_IRQ_MASK \
(DIO_EIC_ISR_NEMPTY_0 \
| DIO_EIC_ISR_NEMPTY_1 \
| DIO_EIC_ISR_NEMPTY_2 \
| DIO_EIC_ISR_NEMPTY_3\
| DIO_EIC_ISR_NEMPTY_4)
/* This is the structure we need to manage interrupts and loop internally */
#define WRN_DIO_BUFFER_LEN 512
struct dio_channel {
struct timespec tsbuf[WRN_DIO_BUFFER_LEN];
int bhead, btail;
wait_queue_head_t q;
/* The input event may fire a new pulse on this or another channel */
struct timespec prevts, delay;
atomic_t count;
int target_channel;
};
struct dio_device {
struct dio_channel ch[5];
};
/* Instead of timespec_sub, just subtract the nanos */
static inline void wrn_ts_sub(struct timespec *ts, int nano)
{
set_normalized_timespec(ts, ts->tv_sec, ts->tv_nsec - nano);
}
/* This programs a new pulse without changing the width */
static void __wrn_new_pulse(struct wrn_drvdata *drvdata, int ch,
struct timespec *ts)
{
struct DIO_WB __iomem *dio = drvdata->wrdio_base;
void __iomem *base = dio;
struct regmap *map;
map = regmap + ch;
wrn_ts_sub(ts, 8); /* 1 cycle, to account for output latencies */
writel(ts->tv_nsec / 8, base + map->cycle);
writel(GET_HI32(ts->tv_sec), base + map->trig_h);
writel(ts->tv_sec, base + map->trig_l);
writel(1 << ch, &dio->R_LATCH);
}
static int wrn_dio_cmd_pulse(struct wrn_drvdata *drvdata,
struct wr_dio_cmd *cmd)
{
struct DIO_WB __iomem *dio = drvdata->wrdio_base;
void __iomem *base = dio;
struct PPSG_WB __iomem *ppsg = drvdata->ppsg_base;
struct dio_device *d = drvdata->mezzanine_data;
struct dio_channel *c;
struct regmap *map;
struct timespec *ts;
uint32_t reg;
int ch;
ch = cmd->channel;
if (ch > 4)
return -EINVAL; /* mask not supported */
c = d->ch + ch;
map = regmap + ch;
ts = cmd->t;
/* First, configure this bit as DIO output */
reg = readl(&dio->IOMODE);
writel(reg | (1 << 4*ch), &dio->IOMODE);
writel(ts[1].tv_nsec / 8, base + map->pulse); /* width */
if (cmd->flags & WR_DIO_F_NOW) {
/* if "now" we are done */
writel(1 << ch, &dio->PULSE);
return 0;
}
/* if relative, add current 40-bit second to timespec */
if (cmd->flags & WR_DIO_F_REL) {
uint32_t h1, l, h2;
unsigned long now;
h1 = readl(&ppsg->CNTR_UTCHI);
l = readl(&ppsg->CNTR_UTCLO);
h2 = readl(&ppsg->CNTR_UTCHI);
if (h2 != h1)
l = readl(&ppsg->CNTR_UTCLO);
now = l;
SET_HI32(now, h2);
ts->tv_sec += now;
}
if (cmd->flags & WR_DIO_F_LOOP) {
c->target_channel = ch;
/* c->count is used after the pulse, so remove the first */
if (cmd->value > 0)
cmd->value--;
atomic_set(&c->count, cmd->value);
c->prevts = ts[0]; /* our current setpoint */
c->delay = ts[2];
}
__wrn_new_pulse(drvdata, ch, ts);
return 0;
}
static int wrn_dio_cmd_stamp(struct wrn_drvdata *drvdata,
struct wr_dio_cmd *cmd)
{
struct dio_device *d = drvdata->mezzanine_data;
struct dio_channel *c = 0;
struct timespec *ts = cmd->t;
struct regmap *map;
int mask, ch, last;
int nstamp = 0;
if ((cmd->flags & (WR_DIO_F_MASK || WR_DIO_F_WAIT))
== (WR_DIO_F_MASK || WR_DIO_F_WAIT))
return -EINVAL; /* wait on several channels not supported */
again:
if (cmd->flags & WR_DIO_F_MASK) {
ch = 0;
last = 4;
mask = cmd->channel;
} else {
ch = cmd->channel;
last = ch;
mask = (1 << ch);
}
/* handle the 1-channel and mask case in the same loop */
c = d->ch + ch;
for (; ch <= last; ch++, c++) {
if (((1 << ch) & mask) == 0)
continue;
map = regmap + ch;
while (1) {
if (nstamp == WR_DIO_N_STAMP)
break;
if (c->bhead == c->btail)
break;
*ts = c->tsbuf[c->btail];
c->btail = (c->btail + 1) % WRN_DIO_BUFFER_LEN;
nstamp++;
ts++;
}
if (nstamp) {
cmd->channel = ch;
break;
}
}
cmd->nstamp = nstamp;
/* The user may asketo wait for timestamps, but for 1 channel only */
if (!nstamp && cmd->flags & WR_DIO_F_WAIT) {
ch--; c--; /* The for above incremeted them */
/*
* HACK: since 2.1.68 (Nov 1997) the ioctl is called locked.
* So we need to unlock, but that is dangerous for rmmod.
* Let's thus increase the module usage while sleeping
*/
try_module_get(THIS_MODULE);
rtnl_unlock();
wait_event_interruptible(c->q, c->bhead != c->btail);
rtnl_lock();
module_put(THIS_MODULE);
if (signal_pending(current))
return -ERESTARTSYS;
goto again;
}
if (!nstamp)
return -EAGAIN;
return 0;
}
static int wrn_dio_cmd_inout(struct wrn_drvdata *drvdata,
struct wr_dio_cmd *cmd)
{
struct DIO_WB __iomem *dio = drvdata->wrdio_base;
struct wrn_gpio_block __iomem *gpio = drvdata->gpio_base;
int mask, ch, last, bits;
uint32_t reg, iomode;
if (cmd->flags & WR_DIO_F_MASK) {
ch = 0;
last = 4;
mask = cmd->channel;
} else {
ch = cmd->channel;
last = ch;
mask = (1 << ch);
cmd->value <<= ch;
}
/* handle the 1-channel and mask case in the same loop */
for (; ch <= last; ch++) {
if (((1 << ch) & mask) == 0)
continue;
/* select the bits by shifting back the value field */
bits = cmd->value >> ch;
/* Obtain the current value in iomode */
reg = readl(&dio->IOMODE) & ~(0xF << 4*ch);
/* Select IO mode */
if (bits & WR_DIO_INOUT_DIO) {
if(bits & WR_DIO_INOUT_VALUE)
iomode = 2; /* WRPC connection */
else
iomode = 1; /* DIO connection */
} else {
iomode = 0; /* GPIO connection */
/* Output value is bit 0 (0x1) */
if (bits & WR_DIO_INOUT_VALUE)
writel(WRN_GPIO_VALUE(ch), &gpio->set);
else
writel(WRN_GPIO_VALUE(ch), &gpio->clear);
}
/* Appends to iomode TERM and OUTPUT_ENABLE_N bits */
iomode |= (((bits & WR_DIO_INOUT_TERM) != 0) << 3)
| (((bits & WR_DIO_INOUT_OUTPUT) == 0) << 2);
writel(reg | (iomode << 4*ch), &dio->IOMODE);
}
return 0;
}
int wrn_mezzanine_ioctl(struct net_device *dev, struct ifreq *rq,
int ioctlcmd)
{
struct wr_dio_cmd *cmd;
struct wrn_drvdata *drvdata = dev->dev.parent->platform_data;
ktime_t t, t0;
int ret;
if (ioctlcmd == PRIV_MEZZANINE_ID)
return -EAGAIN; /* Special marker */
if (ioctlcmd != PRIV_MEZZANINE_CMD)
return -ENOIOCTLCMD;
if (wrn_stat) {
t0 = ktime_get();
}
/* The cmd struct can't fit in the stack, so allocate it */
cmd = kmalloc(sizeof(*cmd), GFP_KERNEL);
if (!cmd)
return -ENOMEM;
ret = -EFAULT;
if (copy_from_user(cmd, rq->ifr_data, sizeof(*cmd)))
goto out;
switch(cmd->command) {
case WR_DIO_CMD_PULSE:
ret = wrn_dio_cmd_pulse(drvdata, cmd);
break;
case WR_DIO_CMD_STAMP:
ret = wrn_dio_cmd_stamp(drvdata, cmd);
break;
case WR_DIO_CMD_INOUT:
ret = wrn_dio_cmd_inout(drvdata, cmd);
break;
case WR_DIO_CMD_DAC:
ret = -ENOTSUPP;
goto out;
default:
ret = -EINVAL;
goto out;
}
if (copy_to_user(rq->ifr_data, cmd, sizeof(*cmd)))
return -EFAULT;
out:
kfree(cmd);
if (wrn_stat) {
t = ktime_sub(ktime_get(), t0);
dev_info(&dev->dev, "ioctl: %li ns\n", (long)ktime_to_ns(t));
}
return ret;
}
/* This is called from the interrupt handler to program a new pulse */
static void wrn_trig_next_pulse(struct wrn_drvdata *drvdata,int ch,
struct dio_channel *c, struct timespec *ts)
{
struct timespec newts;
if (c->target_channel == ch) {
c->prevts = timespec_add(c->prevts, c->delay);
newts = c->prevts;
} else {
newts = timespec_add(*ts, c->delay);
}
__wrn_new_pulse(drvdata, c->target_channel, &newts);
/* If the count is not-infinite, decrement it */
if (atomic_read(&c->count) > 0)
atomic_dec(&c->count);
}
irqreturn_t wrn_dio_interrupt(struct fmc_device *fmc)
{
struct platform_device *pdev = fmc->mezzanine_data;
struct wrn_drvdata *drvdata = pdev->dev.platform_data;
struct VIC_WB __iomem *vic = drvdata->vic_base;
struct DIO_WB __iomem *dio = drvdata->wrdio_base;
void __iomem *base = drvdata->wrdio_base;
struct dio_device *d = drvdata->mezzanine_data;
static ktime_t t_ini, t_end;
static int rate_avg;
struct dio_channel *c;
struct timespec *ts;
struct regmap *map;
uint32_t mask, reg;
int ch, chm;
if (unlikely(!fmc->eeprom)) {
dev_err(fmc->hwdev, "WR-DIO: No mezzanine, disabling irqs\n");
writel(~0, &dio->EIC_IDR);
writel(~0, &dio->EIC_ISR);
return IRQ_NONE;
}
/* Protect against interrupts taking 100% of cpu time */
if (ktime_to_ns(t_end)) {
int rate;
u64 offtime, ontime;
ontime = ktime_to_ns(t_end) - ktime_to_ns(t_ini);
t_ini = ktime_get();
offtime = ktime_to_ns(t_ini) - ktime_to_ns(t_end);
/* avoid __udivdi3 */
if (offtime > 100 * ontime)
rate = 0;
else
rate = ((int)ontime * 100) / (int)offtime;
rate_avg = (rate_avg * 1023 + rate) / 1024;
if (rate_avg > 80) {
dev_warn(fmc->hwdev, "DIO irq takes > 80%% CPU time: "
"disabling\n");
writel(WRN_VIC_MASK_DIO, &vic->IDR);
}
}
mask = readl(&dio->EIC_ISR) & WRN_DIO_IRQ_MASK;
/* Three indexes: channel, channel-mask, channel pointer */
for (ch = 0, chm = 1, c = d->ch; mask; ch++, chm <<= 1, c++) {
int h;
if (!(mask & chm))
continue;
mask &= ~chm;
/* Pull the FIFOs to the device structure */
map = regmap + ch;
ts = NULL;
while (1) {
reg = readl(base + map->fifo_status);
if (reg & 0x20000) /* empty */
break;
h = c->bhead;
ts = c->tsbuf + h;
c->bhead = (h + 1) % WRN_DIO_BUFFER_LEN;
if (c->bhead == c->btail)
c->btail = (c->btail + 1) % WRN_DIO_BUFFER_LEN;
/*
* fifo is not-empty, pick one sample. Read
* cycles last, as that operation pops the FIFO
*/
ts->tv_sec = 0;
SET_HI32(ts->tv_sec, readl(base + map->fifo_tai_h));
ts->tv_sec |= readl(base + map->fifo_tai_l);
ts->tv_nsec = 8 * readl(base + map->fifo_cycle);
/* subtract 5 cycles lost in input sync circuits */
wrn_ts_sub(ts, 40);
}
writel(chm, &dio->EIC_ISR); /* ack */
if (ts && atomic_read(&c->count) != 0) {
wrn_trig_next_pulse(drvdata, ch, c, ts);
}
wake_up_interruptible(&c->q);
}
t_end = ktime_get();
return IRQ_HANDLED;
}
/* Init and exit below are called when a netdevice is created/destroyed */
int wrn_mezzanine_init(struct net_device *dev)
{
struct wrn_drvdata *drvdata = dev->dev.parent->platform_data;
struct DIO_WB __iomem *dio = drvdata->wrdio_base;
struct dio_device *d;
int i;
/* Allocate the data structure and enable interrupts for stamping */
d = kzalloc(sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(d->ch); i++)
init_waitqueue_head(&d->ch[i].q);
drvdata->mezzanine_data = d;
/*
* Enable interrupts for FIFO, if there's no mezzanine the
* handler will notice and disable the interrupts
*/
writel(WRN_DIO_IRQ_MASK, &dio->EIC_IER);
return 0;
}
void wrn_mezzanine_exit(struct net_device *dev)
{
struct wrn_drvdata *drvdata = dev->dev.parent->platform_data;
struct DIO_WB __iomem *dio = drvdata->wrdio_base;
writel(~0, &dio->EIC_IDR);
if (drvdata->mezzanine_data)
kfree(drvdata->mezzanine_data);
}
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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/interrupt.h>
#include <linux/platform_device.h>
#include <linux/fmc.h>
#include <linux/fmc-sdb.h>
#include "spec.h"
#include "spec-nic.h"
#include "wr-dio.h"
#include "wr_nic/wr-nic.h"
#include "wbgen-regs/vic-regs.h"
/*
* nic-device.c defines a platform driver. We need to allocate
* a platform device each time this init function is called
* (part of the code is from wr_nic/module.c).
*/
static void wrn_release(struct device *dev)
{
/* nothing to do, but mandatory function */
pr_debug("%s\n", __func__);
}
static struct platform_device wrn_pdev = {
/* other fields filled after it's copied: see wrn_eth_init() */
.name = KBUILD_MODNAME,
.dev.release = &wrn_release,
};
#define WRN_ALL_MASK (WRN_VIC_MASK_NIC | WRN_VIC_MASK_TXTSU | WRN_VIC_MASK_DIO)
/* This is the interrupt handler, that uses the VIC to know which is which */
irqreturn_t wrn_handler(int irq, void *dev_id)
{
struct fmc_device *fmc = dev_id;
struct platform_device *pdev = fmc->mezzanine_data;
struct wrn_drvdata *drvdata;
struct VIC_WB *vic;
uint32_t vector;
irqreturn_t ret = IRQ_HANDLED;
if (!pdev) {
/* too early, just do nothing */
dev_info(fmc->hwdev, "early irq %i, ignoring\n", irq);
fmc->op->irq_ack(fmc);
return ret;
}
drvdata = pdev->dev.platform_data;
vic = (typeof(vic)) drvdata->vic_base;
/*
* VIC operation algorithm:
* - when a peripheral interrupt arrives (as seen in RISR register),
* it is masked by IMR and latched in bit of ISR register:
*
* ISR |= (RISR & IMR);
* if (ISR != 0) {
* int current_irq = priority_decode(ISR)
* VAR = IVT_RAM[current_irq];
* MASTER_IRQ = CTL.POL;
*
* wait (write to EOIR)
*
* if (CTL.EMU_ENA)
* pulse(MASTER_IRQ, CTL.EMU_LEN, !CTL.POL)
* } else {
* MASTER_IRQ = !CTL.POL
* }
*
* The VIC was inspired by the original VIC used in NXP's ARM MCUs.
*/
/* read pending vector address - the index of currently pending IRQ. */
vector = readl(&vic->VAR);
if (vector == WRN_VIC_ID_NIC)
ret = wrn_interrupt(irq, drvdata->wrn);
else if (vector == WRN_VIC_ID_TXTSU)
ret = wrn_tstamp_interrupt(irq, drvdata->wrn);
else if (vector == WRN_VIC_ID_DIO)
ret = wrn_dio_interrupt(fmc /* different arg! */);
fmc->op->irq_ack(fmc);
writel(0, &vic->EOIR);
return ret;
}
static int wrn_vic_init(struct fmc_device *fmc)
{
struct platform_device *pdev = fmc->mezzanine_data;
struct wrn_drvdata *drvdata = pdev->dev.platform_data;
struct VIC_WB *vic = (typeof(vic))drvdata->vic_base;
/* fill the vector table */
writel(WRN_VIC_ID_TXTSU, &vic->IVT_RAM[WRN_VIC_ID_TXTSU]);
writel(WRN_VIC_ID_NIC, &vic->IVT_RAM[WRN_VIC_ID_NIC]);
writel(WRN_VIC_ID_DIO, &vic->IVT_RAM[WRN_VIC_ID_DIO]);
/* 4us edge emulation timer (counts in 16ns steps) */
writel(VIC_CTL_ENABLE | VIC_CTL_POL | VIC_CTL_EMU_EDGE | \
VIC_CTL_EMU_LEN_W(4000 / 16), &vic->CTL);
writel(WRN_ALL_MASK, &vic->IER);
return 0;
}
static void wrn_vic_exit(struct fmc_device *fmc)
{
struct platform_device *pdev = fmc->mezzanine_data;
struct wrn_drvdata *drvdata = pdev->dev.platform_data;
struct VIC_WB *vic = (typeof(vic))drvdata->vic_base;
writel(0xff, &vic->IDR);
/* Interrupt line is !POL when EN == 0, so leave polarity bit on */
writel(VIC_CTL_POL, &vic->CTL);
}
struct wrn_core {
const char *name;
uint64_t vendor;
uint32_t device;
int offset;
};
/* These cores are used by the nic driver itself */
static struct wrn_core wrn_cores[] = {
[WRN_FB_NIC] = {"NIC", SDB_CERN, WRN_SDB_NIC},
[WRN_FB_EP] = {"Endpoint", SDB_CERN, WRN_SDB_EP},
[WRN_FB_PPSG] = {"PPS-Gen", SDB_CERN, WRN_SDB_PPSG},
[WRN_FB_TS] = {"Tx-Stamp", SDB_CERN, WRN_SDB_TS},
};
/* These cores are used by the wr-nic fmc device */
static struct wrn_core wrn_cores2[] = {
{
"VIC", SDB_CERN, WRN_SDB_VIC,
offsetof(struct wrn_drvdata, vic_base)
},{
"GPIO", SDB_CERN, WRN_SDB_GPIO,
offsetof(struct wrn_drvdata, gpio_base)
},{
"WR-DIO", SDB_7SOL, WRN_SDB_WRDIO,
offsetof(struct wrn_drvdata, wrdio_base)
},{
"PPS-Gen", SDB_CERN, WRN_SDB_PPSG,
offsetof(struct wrn_drvdata, ppsg_base)
}
};
struct fmc_gpio wrn_gpio_cfg[] = {
{
.gpio = FMC_GPIO_IRQ(1),
.mode = GPIOF_DIR_IN,
.irqmode = IRQF_TRIGGER_RISING,
}
};
int wrn_eth_init(struct fmc_device *fmc)
{
struct device *dev = fmc->hwdev;
struct resource *resarr;
struct platform_device *pdev;
struct wrn_drvdata *drvdata;
struct wrn_dev *wrn;
struct wrn_core *c;
struct spec_dev *spec = fmc->carrier_data;
signed long start;
unsigned long size;
int i, ret;
ret = fmc->op->irq_request(fmc, wrn_handler, "wr-nic", IRQF_SHARED);
if (ret < 0) {
dev_err(dev, "Can't request interrupt\n");
return ret;
}
/* FIXME: we should request irq0, self-test and then move to irq1 */
fmc->op->gpio_config(fmc, wrn_gpio_cfg, ARRAY_SIZE(wrn_gpio_cfg));
/* Make a copy of the platform device and register it */
ret = -ENOMEM;
pdev = kmemdup(&wrn_pdev, sizeof(wrn_pdev), GFP_KERNEL);
resarr = kzalloc(sizeof(*resarr) * ARRAY_SIZE(wrn_cores), GFP_KERNEL);
drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL);
wrn = kzalloc(sizeof(*wrn), GFP_KERNEL);
if (!pdev || !resarr || !drvdata || !wrn)
goto out_mem;
for (i = 0, c = wrn_cores; i < ARRAY_SIZE(wrn_cores); i++, c++) {
start = fmc_find_sdb_device(fmc->sdb, c->vendor, c->device,
&size);
if (start < 0) {
dev_err(dev, "Can't find sdb core \"%s\"\n", c->name);
goto out_mem;
}
resarr[i].name = c->name;
resarr[i].flags = IORESOURCE_MEM;
/* FIXME: spec-specific for the io area to be remapped */
resarr[i].start = spec->area[0]->start + start;
resarr[i].end = resarr[i].start + size - 1;
resarr[i].parent = spec->area[0];
}
for (i = 0, c = wrn_cores2; i < ARRAY_SIZE(wrn_cores2); i++, c++) {
start = fmc_find_sdb_device(fmc->sdb, c->vendor, c->device,
&size);
if (start < 0) {
dev_err(dev, "Can't find sdb core \"%s\"\n", c->name);
continue;
}
/* use c->offset to copy and already-remapped value */
*((void **)((u8 *)drvdata + c->offset)) =
fmc->fpga_base + start;
}
pdev->resource = resarr;
pdev->num_resources = ARRAY_SIZE(wrn_cores);
drvdata->wrn = wrn;
drvdata->fmc = fmc;
pdev->dev.platform_data = drvdata;
fmc->mezzanine_data = pdev;
platform_device_register(pdev);
wrn_vic_init(fmc);
wrn_pdev.id++; /* for the next one */
return 0;
out_mem:
kfree(wrn);
kfree(drvdata);
kfree(resarr);
kfree(pdev);
fmc->op->irq_free(fmc);
return ret;
}
void wrn_eth_exit(struct fmc_device *fmc)
{
struct platform_device *pdev = fmc->mezzanine_data;
struct wrn_drvdata *drvdata;
wrn_vic_exit(fmc);
if (pdev)
platform_device_unregister(pdev);
if (pdev) {
drvdata = pdev->dev.platform_data;
kfree(drvdata->wrn);
kfree(drvdata);
kfree(pdev->resource);
kfree(pdev);
}
fmc->mezzanine_data = NULL;
fmc->op->irq_free(fmc);
}
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.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/gpio.h>
#include <linux/fmc.h>
#include "spec-nic.h"
static inline struct fmc_device *gc_to_fmc(struct gpio_chip *gc)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0)
struct device *dev = gc->dev;
#else
struct device *dev = gc->parent;
#endif
return container_of(dev, struct fmc_device, dev);
}
static int wrn_gpio_input(struct gpio_chip *chip, unsigned offset)
{
//struct fmc_device *fmc = gc_to_fmc(chip);
//struct wrn_drvdata *dd = fmc_get_drvdata(fmc);
//fmc_writel(fmc, ...); /* FIXME */
return -EAGAIN;
}
static int wrn_gpio_output(struct gpio_chip *chip, unsigned offset, int value)
{
return -EAGAIN;
}
int wrn_gpio_get(struct gpio_chip *chip, unsigned offset)
{
return -EAGAIN;
}
void wrn_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
return;
}
static const char *wrn_gpio_names[] = {
"dire", "fare", "baciare", "lettera", "testamento"
};
static struct gpio_chip wrn_gpio_template = {
.label = "wr-nic",
.owner = THIS_MODULE,
/* FIXME: request, free, for multi-function operation */
.direction_input = wrn_gpio_input,
.direction_output = wrn_gpio_output,
.get = wrn_gpio_get,
.set = wrn_gpio_set,
.base = -1, /* request dynamic */
.ngpio = 5,
.names = wrn_gpio_names,
};
int wrn_gpio_init(struct fmc_device *fmc)
{
struct wrn_drvdata *dd = fmc_get_drvdata(fmc);
struct gpio_chip *gc;
int ret;
gc = devm_kzalloc(&fmc->dev, sizeof(*gc), GFP_KERNEL);
if (!gc)
return -ENOMEM;
*gc = wrn_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;
dd->gc = gc;
/* FIXME: program the DAC for each port (sysfs attributes?) */
return 0;
out_free:
kfree(gc);
return ret;
}
void wrn_gpio_exit(struct fmc_device *fmc)
{
struct wrn_drvdata *dd = fmc_get_drvdata(fmc);
struct gpio_chip *gc = dd->gc;
#if LINUX_VERSION_CODE > KERNEL_VERSION(3,17,0)
gpiochip_remove(gc);
#else
int ret;
ret = gpiochip_remove(gc);
if (ret)
dev_err(fmc->hwdev, "DANGER %i! gpio chip can't be removed\n",
ret);
#endif
return;
}
/*
* Device initialization and cleanup for White-Rabbit switch network interface
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Partly from previous work by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Partly from previous work by Emilio G. Cota <cota@braap.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/io.h>
#include "wr-nic.h"
#include "nic-mem.h"
#if WR_IS_NODE
#include "../spec-nic.h"
#endif
/* The remove function is used by probe, so it's not __devexit */
static int wrn_remove(struct platform_device *pdev)
{
#if WR_IS_NODE
struct wrn_drvdata *drvdata = pdev->dev.platform_data;
struct wrn_dev *wrn = drvdata->wrn;
#endif
#if WR_IS_SWITCH
struct wrn_dev *wrn = pdev->dev.platform_data;
#endif
int i;
if (WR_IS_SWITCH) {
spin_lock(&wrn->lock);
--wrn->use_count; /* Hmmm... looks like overkill... */
spin_unlock(&wrn->lock);
}
/* First of all, stop any transmission */
writel(0, &wrn->regs->CR);
/* Then remove devices, memory maps, interrupts */
for (i = 0; i < WRN_NR_ENDPOINTS; i++) {
if (wrn->dev[i]) {
wrn_mezzanine_exit(wrn->dev[i]);
wrn_endpoint_remove(wrn->dev[i]);
free_netdev(wrn->dev[i]);
wrn->dev[i] = NULL;
}
}
for (i = 0; i < ARRAY_SIZE(wrn->bases); i++) {
if (wrn->bases[i])
iounmap(wrn->bases[i]);
}
/* Unregister all interrupts that were registered */
for (i = 0; wrn->irq_registered; i++) {
static int irqs[] = WRN_IRQ_NUMBERS;
if (wrn->irq_registered & (1 << i))
free_irq(irqs[i], wrn);
wrn->irq_registered &= ~(1 << i);
}
return 0;
}
/* This helper is used by probe below */
static int __wrn_map_resources(struct platform_device *pdev)
{
int i;
struct resource *res;
void __iomem *ptr;
#if WR_IS_NODE
struct wrn_drvdata *drvdata = pdev->dev.platform_data;
struct wrn_dev *wrn = drvdata->wrn;
#endif
#if WR_IS_SWITCH
struct wrn_dev *wrn = pdev->dev.platform_data;
#endif
/*
* The memory regions are mapped once for all endpoints.
* We don't populate the whole array, but use the resource list
*/
for (i = 0; i < pdev->num_resources; i++) {
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
if (!res || !res->start)
continue;
ptr = ioremap(res->start, res->end + 1 - res->start);
if (!ptr) {
dev_err(&pdev->dev, "Remap for res %i (%pa) failed\n",
i, (void *) res->start);
return -ENOMEM;
}
/* Hack: find the block number and fill the array */
pr_debug("Remapped %pa (block %i) to %p\n",
(void *) res->start, i, ptr);
wrn->bases[i] = ptr;
}
return 0;
}
static int wrn_probe(struct platform_device *pdev)
{
struct net_device *netdev;
struct wrn_ep *ep;
#if WR_IS_NODE
struct wrn_drvdata *drvdata = pdev->dev.platform_data;
struct wrn_dev *wrn = drvdata->wrn;
#endif
#if WR_IS_SWITCH
struct wrn_dev *wrn = pdev->dev.platform_data;
#endif
int i, err = 0;
/* Lazily: irqs are not in the resource list */
static int irqs[] = WRN_IRQ_NUMBERS;
static char *irq_names[] = WRN_IRQ_NAMES;
static irq_handler_t irq_handlers[] = WRN_IRQ_HANDLERS;
/* No need to lock_irq: we only protect count and continue unlocked */
if (WR_IS_SWITCH) {
spin_lock(&wrn->lock);
if (++wrn->use_count != 1) {
--wrn->use_count;
spin_unlock(&wrn->lock);
return -EBUSY;
}
spin_unlock(&wrn->lock);
dev_err(&pdev->dev, "use count %i\n", wrn->use_count);
return -EBUSY;
}
/* Map our resource list and instantiate the shortcut pointers */
if ( (err = __wrn_map_resources(pdev)) )
goto out;
wrn->regs = wrn->bases[WRN_FB_NIC];
wrn->txtsu_regs = wrn->bases[WRN_FB_TS];
wrn->ppsg_regs = wrn->bases[WRN_FB_PPSG];
wrn->txd = ((void *)wrn->regs) + 0x80; /* was: TX1_D1 */
wrn->rxd = ((void *)wrn->regs) + 0x100; /* was: RX1_D1 */
wrn->databuf = (void *)wrn->regs + offsetof(struct NIC_WB, MEM);
tasklet_init(&wrn->rx_tlet, wrn_rx_interrupt, (unsigned long)wrn);
if (0)
dev_info(&pdev->dev, "regs %p, txd %p, rxd %p, buffer %p\n",
wrn->regs, wrn->txd, wrn->rxd, wrn->databuf);
if (WR_IS_SWITCH) {
/* Register the interrupt handlers (not shared) */
for (i = 0; i < ARRAY_SIZE(irq_names); i++) {
err = request_irq(irqs[i], irq_handlers[i],
IRQF_TRIGGER_LOW, irq_names[i], wrn);
if (err) goto out;
wrn->irq_registered |= 1 << i;
}
}
/* Reset the device, just to be sure, before making anything */
writel(0, &wrn->regs->CR);
mdelay(10);
/* Finally, register one interface per endpoint */
memset(wrn->dev, 0, sizeof(wrn->dev));
for (i = 0; i < WRN_NR_ENDPOINTS; i++) {
netdev = alloc_etherdev(sizeof(struct wrn_ep));
netdev->dev.parent = &pdev->dev;
if (!netdev) {
dev_err(&pdev->dev, "Etherdev alloc failed.\n");
err = -ENOMEM;
goto out;
}
/* The ep structure is filled before calling ep_probe */
ep = netdev_priv(netdev);
ep->wrn = wrn;
ep->ep_regs = wrn->bases[WRN_FB_EP] + i * FPGA_SIZE_EACH_EP;
ep->ep_number = i;
#if 0 /* FIXME: UPlink or not? */
if (i < WRN_NR_UPLINK)
set_bit(WRN_EP_IS_UPLINK, &ep->ep_flags);
#endif
/* The netdevice thing is registered from the endpoint */
err = wrn_endpoint_probe(netdev);
if (err == -ENODEV)
break;
if (err)
goto out;
/* This endpoint went in properly */
wrn->dev[i] = netdev;
err = wrn_mezzanine_init(netdev);
if (err)
dev_err(&pdev->dev, "Init mezzanine code: "
"error %i\n", err);
}
if (i == 0)
return -ENODEV; /* no endpoints */
for (i = 0; i < WRN_NR_TXDESC; i++) { /* Clear all tx descriptors */
struct wrn_txd *tx;
tx = wrn->txd + i;
writel(0, &tx->tx1);
}
/* Now, prepare RX descriptors */
for (i = 0; i < WRN_NR_RXDESC; i++) {
struct wrn_rxd *rx;
int offset;
rx = wrn->rxd + i;
offset = __wrn_desc_offset(wrn, WRN_DDIR_RX, i);
writel( (2000 << 16) | offset, &rx->rx3);
writel(NIC_RX1_D1_EMPTY, &rx->rx1);
}
/*
* make sure all head/tail are 0 -- not needed here, but if we
* disable and then re-enable, this _is_ needed
*/
wrn->next_tx_head = wrn->next_tx_tail = wrn->next_rx = 0;
writel(NIC_CR_RX_EN | NIC_CR_TX_EN, &wrn->regs->CR);
writel(WRN_IRQ_ALL, (void *)wrn->regs + 0x24 /* EIC_IER */);
wrn_tstamp_init(wrn);
err = 0;
out:
if (err) {
/* Call the remove function to avoid duplicating code */
wrn_remove(pdev);
} else {
dev_info(&pdev->dev, "White Rabbit NIC driver\n");
}
return err;
}
/* This is not static as ./module.c is going to register it */
struct platform_driver wrn_driver = {
.probe = wrn_probe,
.remove = wrn_remove, /* not __exit_p as probe calls it */
/* No suspend or resume by now */
.driver = {
.name = KBUILD_MODNAME,
.owner = THIS_MODULE,
},
};
/*
* Endoint-specific operations in the White-Rabbit switch network interface
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Partly from previous work by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Partly from previous work by Emilio G. Cota <cota@braap.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/io.h>
#include <linux/moduleparam.h>
#include "wr-nic.h"
static char *macaddr = "00:00:00:00:00:00";
module_param(macaddr, charp, 0444);
/* Copied from kernel net/utils.c, it converts from MAC string to u8 array */
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,17,0)
__weak int mac_pton(const char *s, u8 *mac)
#else
__weak bool mac_pton(const char *s, u8 *mac)
#endif
{
int i;
/* XX:XX:XX:XX:XX:XX */
if (strlen(s) < 3 * ETH_ALEN - 1)
return 0;
/* Don't dirty result unless string is valid MAC. */
for (i = 0; i < ETH_ALEN; i++) {
if (!strchr("0123456789abcdefABCDEF", s[i * 3]))
return 0;
if (!strchr("0123456789abcdefABCDEF", s[i * 3 + 1]))
return 0;
if (i != ETH_ALEN - 1 && s[i * 3 + 2] != ':')
return 0;
}
for (i = 0; i < ETH_ALEN; i++) {
mac[i] = (hex_to_bin(s[i * 3]) << 4) | hex_to_bin(s[i * 3 + 1]);
}
return 1;
}
/*
* Phy access: used by link status, enable, calibration ioctl etc.
* Called with endpoint lock (you'll lock the whole sequence of r/w)
*/
int wrn_phy_read(struct net_device *dev, int phy_id, int location)
{
struct wrn_ep *ep = netdev_priv(dev);
u32 val;
if (WR_IS_NODE) {
/*
* We cannot access the phy from Linux, because the phy
* is managed by the lm32 core. However, network manager
* insists on doing that, so we'd better not warn about it
*/
//WARN_ON(1); /* SPEC: no access */
return -1;
}
wrn_ep_write(ep, MDIO_CR, EP_MDIO_CR_ADDR_W(location));
while( (wrn_ep_read(ep, MDIO_ASR) & EP_MDIO_ASR_READY) == 0)
;
val = wrn_ep_read(ep, MDIO_ASR);
/* mask from wbgen macros */
return EP_MDIO_ASR_RDATA_R(val);
}
void wrn_phy_write(struct net_device *dev, int phy_id, int location,
int value)
{
struct wrn_ep *ep = netdev_priv(dev);
if (WR_IS_NODE) {
/*
* We cannot access the phy from Linux, because the phy
* is managed by the lm32 core. However, network manager
* insists on doing that, so we'd better not warn about it
*/
//WARN_ON(1); /* SPEC: no access */
return;
}
wrn_ep_write(ep, MDIO_CR,
EP_MDIO_CR_ADDR_W(location)
| EP_MDIO_CR_DATA_W(value)
| EP_MDIO_CR_RW);
while( (wrn_ep_read(ep, MDIO_ASR) & EP_MDIO_ASR_READY) == 0)
;
}
/* One link status poll per endpoint -- called with endpoint lock */
static void wrn_update_link_status(struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
u32 ecr, bmsr, bmcr, lpa;
bmsr = wrn_phy_read(dev, 0, MII_BMSR);
bmcr = wrn_phy_read(dev, 0, MII_BMCR);
//netdev_dbg(dev, "%s: read %x %x", __func__, bmsr, bmcr);
// printk("%s: read %x %x %x\n", __func__, bmsr, bmcr);
/* Link wnt down? */
if (!mii_link_ok(&ep->mii)) {
if(netif_carrier_ok(dev)) {
netif_carrier_off(dev);
clear_bit(WRN_EP_UP, &ep->ep_flags);
netdev_info(dev, "Link down.\n");
return;
}
return;
}
/* Currently the link is active */
if(netif_carrier_ok(dev)) {
/* Software already knows it's up */
return;
}
/* What follows is the bring-up step */
if (bmcr & BMCR_ANENABLE) { /* AutoNegotiation is enabled */
if (!(bmsr & BMSR_ANEGCOMPLETE)) {
/* Wait next timer, until it completes */
return;
}
lpa = wrn_phy_read(dev, 0, MII_LPA);
if (0) { /* was commented in minic */
wrn_ep_write(ep, FCR,
EP_FCR_TXPAUSE |EP_FCR_RXPAUSE
| EP_FCR_TX_THR_W(128)
| EP_FCR_TX_QUANTA_W(200));
}
netdev_info(dev, "Link up, lpa 0x%04x.\n", lpa);
} else {
/* No autonegotiation. It's up immediately */
netdev_info(dev, "Link up.\n");
}
netif_carrier_on(dev);
set_bit(WRN_EP_UP, &ep->ep_flags);
/* reset RMON counters */
ecr = wrn_ep_read(ep, ECR);
wrn_ep_write(ep, ECR, ecr | EP_ECR_RST_CNT);
wrn_ep_write(ep, ECR, ecr );
}
/* Actual timer function. Takes the lock and calls above function */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
static void wrn_ep_check_link(unsigned long dev_id)
{
struct net_device *dev = (struct net_device *) dev_id;
struct wrn_ep *ep = netdev_priv(dev);
#else
static void wrn_ep_check_link(struct timer_list *t)
{
struct wrn_ep *ep = from_timer(ep, t, ep_link_timer);
struct net_device *dev = ep->mii.dev;
#endif
unsigned long flags;
spin_lock_irqsave(&ep->lock, flags);
wrn_update_link_status(dev);
spin_unlock_irqrestore(&ep->lock, flags);
mod_timer(&ep->ep_link_timer, jiffies + WRN_LINK_POLL_INTERVAL);
}
/* Endpoint open and close turn on and off the timer */
int wrn_ep_open(struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
unsigned long timerarg = (unsigned long)dev;
#endif
int prio, prio_map;
if (WR_IS_NODE) {
netif_carrier_on(dev);
return 0; /* No access to EP registers in the SPEC */
}
/* Prepare hardware registers: first config, then bring up */
writel(0
| EP_VCR0_QMODE_W(0x3) /* unqualified port */
| EP_VCR0_PRIO_VAL_W(4), /* some mid priority */
&ep->ep_regs->VCR0);
/* Write default 802.1Q tag priority to traffic class mapping */
prio_map = 0;
for(prio=0; prio<8; ++prio) {
prio_map |= (0x7 & prio) << (prio*3);
}
writel(prio_map, &ep->ep_regs->TCAR);
/*
* enable RX timestamping (it has no impact on performance)
* and we need the RX OOB block to identify orginating endpoints
* for RXed packets -- Tom
*/
writel(EP_TSCR_EN_TXTS| EP_TSCR_EN_RXTS, &ep->ep_regs->TSCR);
writel(0
| EP_ECR_PORTID_W(ep->ep_number)
| EP_ECR_RST_CNT
| EP_ECR_TX_EN
| EP_ECR_RX_EN,
&ep->ep_regs->ECR);
wrn_phy_write(dev, 0, MII_LPA, 0);
wrn_phy_write(dev, 0, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
/* Prepare the timer for link-up notifications */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
setup_timer(&ep->ep_link_timer, wrn_ep_check_link, timerarg);
#else
timer_setup(&ep->ep_link_timer, wrn_ep_check_link, 0);
#endif
/* Not on spec. On spec this part of the function is never reached
* due to return in if(WR_IS_NODE) */
mod_timer(&ep->ep_link_timer, jiffies + WRN_LINK_POLL_INTERVAL);
return 0;
}
int wrn_ep_close(struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
if (WR_IS_NODE)
return 0; /* No access to EP registers in the SPEC */
/*
* Beware: the system loops in the del_timer_sync below if timer_setup
* had not been called either (see "if (WR_IS_NODE)" in ep_open above)
*/
writel(0, &ep->ep_regs->ECR);
del_timer_sync(&ep->ep_link_timer);
return 0;
}
/*
* The probe functions brings up the endpoint and the logical ethernet
* device within Linux. The actual network operations are in nic-core.c,
* as they are not endpoint-specific, while the mii stuff is in this file.
* The initial helper here shuts down everything, in case of failure or close
*/
static void __wrn_endpoint_shutdown(struct wrn_ep *ep)
{
/* Not much to do it seems */
writel(0, &ep->ep_regs->ECR); /* disable it all */
}
int wrn_endpoint_probe(struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
int epnum, err;
static u8 wraddr[6];
u32 val;
epnum = ep->ep_number;
if(WR_IS_NODE) {
/* If address is not provided as parameter read from lm32 */
if (is_zero_ether_addr(wraddr)) {
/* on the SPEC the lm32 already configured the mac address */
val = readl(&ep->ep_regs->MACH);
put_unaligned_be16(val, wraddr);
val = readl(&ep->ep_regs->MACL);
put_unaligned_be32(val, wraddr+2);
}
}
if(WR_IS_SWITCH) {
/* If the MAC address is 0, then randomize the first MAC */
/* Do not randomize for SPEC */
if (is_zero_ether_addr(wraddr)) {
pr_warn("wr_nic: missing MAC address, randomize\n");
/* randomize a MAC address, so lazy users can avoid ifconfig */
random_ether_addr(wraddr);
/* Clear the MSB on fourth octect to prevent bit overflow on OUI */
wraddr[3] &= 0x7F;
}
}
if (ep->ep_number == 0)
pr_info("WR-nic: Using address %pM\n", wraddr);
/* Use wraddr as MAC */
memcpy(dev->dev_addr, wraddr, ETH_ALEN);
pr_debug("wr_nic: assign MAC %pM to wr%d\n", dev->dev_addr, ep->ep_number);
/* Check whether the ep has been sinthetized or not */
val = readl(&ep->ep_regs->IDCODE);
if (val != WRN_EP_MAGIC) {
pr_info(KBUILD_MODNAME " EP%i (%s) has not been sintethized\n",
ep->ep_number, dev->name);
return -ENODEV;
}
/* Errors different from -ENODEV are fatal to insmod */
dev_alloc_name(dev, "wr%d");
wrn_netops_init(dev); /* function in ./nic-core.c */
wrn_ethtool_init(dev); /* function in ./ethtool.c */
/* Napi is not supported on this device */
ep->mii.dev = dev; /* Support for ethtool */
ep->mii.mdio_read = wrn_phy_read;
ep->mii.mdio_write = wrn_phy_write;
ep->mii.phy_id = 0;
ep->mii.phy_id_mask = 0x1f;
ep->mii.reg_num_mask = 0x1f;
ep->mii.force_media = 0;
ep->mii.advertising = ADVERTISE_1000XFULL;
ep->mii.full_duplex = 1;
/* Finally, register and succeed, or fail and undo */
err = register_netdev(dev);
/* Increment MAC address for next endpoint */
val = get_unaligned_be32(wraddr + 2);
put_unaligned_be32(val + 1, wraddr + 2);
if (err) {
netdev_err(dev, "Can't register device\n");
__wrn_endpoint_shutdown(ep);
/* ENODEV means "no more" for the caller, so avoid it */
return err == -ENODEV ? -EIO : err;
}
return 0;
}
/* Called for each endpoint, with a valid ep structure. The caller frees */
void wrn_endpoint_remove(struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
unregister_netdev(dev);
__wrn_endpoint_shutdown(ep);
}
/*
* Ethtool operations for White-Rabbit switch network interface
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Partly from previous work by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Partly from previous work by Emilio G. Cota <cota@braap.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/spinlock.h>
#include "wr-nic.h"
static int wrn_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{
struct wrn_ep *ep = netdev_priv(dev);
int ret;
spin_lock_irq(&ep->lock);
ret = mii_ethtool_gset(&ep->mii, cmd);
spin_unlock_irq(&ep->lock);
cmd->supported =
SUPPORTED_FIBRE | /* FIXME: copper sfp? */
SUPPORTED_Autoneg |
SUPPORTED_1000baseKX_Full;
cmd->advertising =
ADVERTISED_1000baseKX_Full |
ADVERTISED_Autoneg;
cmd->port = PORT_FIBRE;
cmd->speed = SPEED_1000;
cmd->duplex = DUPLEX_FULL;
cmd->autoneg = AUTONEG_ENABLE;
return ret;
}
static int wrn_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{
struct wrn_ep *ep = netdev_priv(dev);
int ret;
spin_lock_irq(&ep->lock);
ret = mii_ethtool_sset(&ep->mii, cmd);
spin_unlock_irq(&ep->lock);
return ret;
}
static int wrn_nwayreset(struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
int ret;
spin_lock_irq(&ep->lock);
ret = mii_nway_restart(&ep->mii);
spin_unlock_irq(&ep->lock);
return ret;
}
static void wrn_get_drvinfo(struct net_device *dev,
struct ethtool_drvinfo *info)
{
strlcpy(info->driver, KBUILD_MODNAME, sizeof(info->driver));
strlcpy(info->version, DRV_VERSION, sizeof(info->version));
strlcpy(info->bus_info, dev_name(dev->dev.parent),
sizeof(info->bus_info));
}
/*
* These are the operations we support. No coalescing is there since
* most of the traffic will just happen within the FPGA switching core.
* Similarly, other funcionality like ringparam are not used.
* get_eeprom/set_eeprom may be useful for a simple MAC address management.
*/
static const struct ethtool_ops wrn_ethtool_ops = {
.get_settings = wrn_get_settings,
.set_settings = wrn_set_settings,
.get_drvinfo = wrn_get_drvinfo,
.nway_reset = wrn_nwayreset,
/* Some of the default methods apply for us */
.get_link = ethtool_op_get_link,
/* FIXME: get_regs_len and get_regs may be useful for debugging */
};
int wrn_ethtool_init(struct net_device *netdev)
{
netdev->ethtool_ops = &wrn_ethtool_ops;
return 0;
}
/*
* Core file for White-Rabbit switch network interface
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Partly from previous work by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Partly from previous work by Emilio G. Cota <cota@braap.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <linux/net_tstamp.h>
#include <linux/version.h>
#include <asm/unaligned.h>
#include "wr-nic.h"
#if WR_IS_SWITCH
#include "wr_pstats.h"
#endif
#include "nic-mem.h"
#undef WRN_TRANS_UPDATE
#if KERNEL_VERSION(4, 7, 0) <= LINUX_VERSION_CODE
#define WRN_TRANS_UPDATE
#endif
#ifdef RHEL_RELEASE_VERSION
#if RHEL_RELEASE_VERSION(7, 0) <= RHEL_RELEASE_CODE
#define WRN_TRANS_UPDATE
#endif
#endif
#ifdef WRN_TRANS_UPDATE
#define trans_update(dev) netif_trans_update(dev)
#else
#define trans_update(dev) ((dev)->trans_start = jiffies)
#endif
#undef WRN_LAST_RX_RECORD
#if KERNEL_VERSION(4,11,0) <= LINUX_VERSION_CODE
#define WRN_LAST_RX_RECORD
#endif
#ifdef RHEL_RELEASE_VERSION
#if RHEL_RELEASE_VERSION(7, 0) <= RHEL_RELEASE_CODE
#define WRN_LAST_RX_RECORD
#endif
#endif
#ifdef WRN_LAST_RX_RECORD
#define record_last_rx(dev)
#else
#define record_last_rx(dev) ((dev)->last_rx = jiffies)
#endif
/*
* The following functions are the standard network device operations.
* They act on the _endpoint_ (as each Linux interface is one endpoint)
* so sometimes a call to something within ./endpoint.c is performed.
*/
static int wrn_open(struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
u32 val;
/* This is "open" just for an endpoint. The nic hw is already on */
//netdev_dbg(dev, "%s\n", __func__);
if (!is_valid_ether_addr(dev->dev_addr))
return -EADDRNOTAVAIL;
if(WR_IS_SWITCH) {
/* MACH gets the first two bytes, MACL the rest */
val = get_unaligned_be16(dev->dev_addr);
writel(val, &ep->ep_regs->MACH);
val = get_unaligned_be32(dev->dev_addr+2);
writel(val, &ep->ep_regs->MACL);
}
/* Mark it as down, and start the ep-specific polling timer */
clear_bit(WRN_EP_UP, &ep->ep_flags);
wrn_ep_open(dev);
/* Software-only management is in this file*/
if (netif_queue_stopped(dev))
netif_wake_queue(dev);
else
netif_start_queue(dev);
/*
* Set the MRU bit enough, to avoid issues. We used dev-mtu,
* but this MRU includes OOB and stuff. So let's put it to 2kB,
* which is the maximum allowed, and le software deal with
* malformed packets
*/
val = readl(&ep->ep_regs->RFCR) & ~EP_RFCR_MRU_MASK;
writel (val | EP_RFCR_MRU_W(2048), &ep->ep_regs->RFCR);
/* Most drivers call platform_set_drvdata() but we don't need it */
return 0;
}
static int wrn_close(struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
int ret;
if ( (ret = wrn_ep_close(dev)) )
return ret;
/* FIXME: software-only fixing at close time */
netif_stop_queue(dev);
netif_carrier_off(dev);
clear_bit(WRN_EP_UP, &ep->ep_flags);
return 0;
}
static int wrn_set_mac_address(struct net_device *dev, void* vaddr)
{
struct wrn_ep *ep = netdev_priv(dev);
struct sockaddr *addr = vaddr;
u32 val;
//netdev_dbg(dev, "%s\n", __func__);
if (!is_valid_ether_addr(addr->sa_data)) {
//netdev_dbg(dev, "%s: invalid\n", __func__);
return -EADDRNOTAVAIL;
}
memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
/* MACH gets the first two bytes, MACL the rest */
val = get_unaligned_be16(dev->dev_addr);
writel(val, &ep->ep_regs->MACH);
val = get_unaligned_be32(dev->dev_addr+2);
writel(val, &ep->ep_regs->MACL);
return 0;
}
/* Next descriptor */
static int __wrn_next_desc(int i)
{
return (i+1) % WRN_NR_DESC;
}
/* This is called with the lock taken */
static int __wrn_alloc_tx_desc(struct wrn_dev *wrn)
{
int ret = wrn->next_tx_head;
struct wrn_txd __iomem *tx;
tx = wrn->txd + ret;
/* Check if it's available */
if (readl(&tx->tx1) & NIC_TX1_D1_READY) {
pr_debug("%s: not free %i\n", __func__, ret);
return -ENOMEM;
}
wrn->next_tx_head = __wrn_next_desc(ret);
return ret;
}
/* Actual transmission over a single endpoint */
static void __wrn_tx_desc(struct wrn_ep *ep, int desc,
void *data, int len, int id, int do_stamp)
{
struct wrn_dev *wrn = ep->wrn;
int offset = __wrn_desc_offset(wrn, WRN_DDIR_TX, desc);
u32 *ptr = __wrn_desc_mem(wrn, WRN_DDIR_TX, desc);
struct wrn_txd __iomem *tx = wrn->txd + desc;
/* data */
pr_debug("%s: %i -- data %p, len %i ", __func__, __LINE__,
data, len);
pr_debug("-- desc %i (tx %p)\n", desc, tx);
__wrn_copy_out(ptr, data, len);
/* TX register 3: mask of endpoints (FIXME: broadcast) */
//printk("EP Num: %d\n", ep->ep_number);
writel(1<<ep->ep_number, &tx->tx3);
/* TX register 2: offset and length */
writel(offset | (len << 16), &tx->tx2);
/* TX register 1: id and masks -- and tx_enable if needed */
writel((len < 60 ? NIC_TX1_D1_PAD_E : 0) | NIC_TX1_D1_READY
| (do_stamp ? NIC_TX1_D1_TS_E : 0) | (id << 16),
&tx->tx1);
}
static int wrn_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
struct wrn_dev *wrn = ep->wrn;
struct skb_shared_info *info = skb_shinfo(skb);
unsigned long flags;
int desc;
int id;
int do_stamp = 0;
void *data; /* FIXME: move data and len to __wrn_tx_desc */
unsigned int len;
if (unlikely(skb->len > WRN_MTU)) {
/* FIXME: check this WRN_MTU is needed and used properly */
ep->stats.tx_errors++;
return -EMSGSIZE;
}
/* Allocate a descriptor and id (start from last allocated) */
if(WR_IS_SWITCH){
spin_lock_irqsave(&wrn->lock, flags);
}
desc = __wrn_alloc_tx_desc(wrn);
id = (wrn->id++) & 0xffff;
if (id == 0)
id = wrn->id++; /* 0 cannot be used in the SPEC */
if(WR_IS_SWITCH){
spin_unlock_irqrestore(&wrn->lock, flags);
}
if(WR_IS_NODE){
if (id == 0)
id = wrn->id++; /* 0 cannot be used in the SPEC */
}
if (desc < 0) /* error */
return desc;
data = skb->data;
len = skb->len;
//spin_lock_irqsave(&ep->lock, flags);
if (wrn->skb_desc[desc].skb) {
/* The timestamp has not been collected: silently discard it */
}
wrn->skb_desc[desc].skb = skb; /* Save for tx irq and stamping */
wrn->skb_desc[desc].frame_id = id; /* Save for tx irq and stamping */
//netif_stop_queue(dev); /* Queue stopped until tx is over (FIXME?) */
/* FIXME: check the WRN_EP_STAMPING_TX flag and its meaning */
if (info->tx_flags & SKBTX_HW_TSTAMP) {
/* hardware timestamping is enabled */
do_stamp = 1;
}
/* This both copies the data to the descriptr and fires tx */
__wrn_tx_desc(ep, desc, data, len, id, do_stamp);
/* We are done, this is trivial maiintainance*/
ep->stats.tx_packets++;
ep->stats.tx_bytes += len;
trans_update(dev);
//spin_unlock_irqrestore(&ep->lock, flags);
return 0;
}
#if WR_IS_SWITCH
int (*wr_nic_pstats_callback)(int epnum,
unsigned int ctr[PSTATS_CNT_PP]);
EXPORT_SYMBOL(wr_nic_pstats_callback);
static unsigned int nic_counters[PSTATS_CNT_PP];
static DEFINE_SPINLOCK(nic_counters_lock);
#endif
struct net_device_stats *wrn_get_stats(struct net_device *dev)
{
struct wrn_ep *ep = netdev_priv(dev);
#if WR_IS_SWITCH
if (wr_nic_pstats_callback) {
int i;
spin_lock(&nic_counters_lock);
wr_nic_pstats_callback(ep->ep_number, nic_counters);
if (0) {
/* A stupid diagnostics, happens oh so often... */
printk(KERN_INFO "counters for %i:", ep->ep_number);
for (i = 0; i < PSTATS_CNT_PP; i++)
printk(KERN_CONT " %u", nic_counters[i]);
printk(KERN_CONT "\n");
} else {
/* Recover values in the kernel structure */
ep->stats.rx_packets =
nic_counters[PSTATS_C_R_FRAME];
ep->stats.tx_packets =
nic_counters[PSTATS_C_T_FRAME];
ep->stats.rx_length_errors =
nic_counters[PSTATS_C_R_GIANT];
ep->stats.rx_crc_errors =
nic_counters[PSTATS_C_R_CRC_ERROR];
ep->stats.rx_fifo_errors =
nic_counters[PSTATS_C_R_OVERRUN];
ep->stats.tx_fifo_errors =
nic_counters[PSTATS_C_T_UNDERRUN];
}
spin_unlock(&nic_counters_lock);
}
#endif
return &ep->stats;
}
/*
* If we have a mezzanine, we need the ioctl as well as init/exit. Provide
* three weak functions here, so to link even if no mezzanine is there.
*/
int __weak wrn_mezzanine_ioctl(struct net_device *dev, struct ifreq *rq,
int cmd)
{
return -ENOIOCTLCMD;
}
int __weak wrn_mezzanine_init(struct net_device *dev)
{
return 0;
}
void __weak wrn_mezzanine_exit(struct net_device *dev)
{
return;
}
static int wrn_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
struct wrn_ep *ep = netdev_priv(dev);
int res;
u32 reg;
switch (cmd) {
case SIOCSHWTSTAMP:
return wrn_tstamp_ioctl(dev, rq, cmd);
case PRIV_IOCGCALIBRATE:
case PRIV_IOCGGETPHASE:
return -EOPNOTSUPP;
case PRIV_IOCREADREG:
if (get_user(reg, (u32 *)rq->ifr_data) < 0)
return -EFAULT;
if (reg > sizeof(struct EP_WB) || reg & 3)
return -EINVAL;
reg = readl((void *)ep->ep_regs + reg);
if (put_user(reg, (u32 *)rq->ifr_data) < 0)
return -EFAULT;
return 0;
case PRIV_IOCPHYREG:
/* this command allows to read and write a phy register */
if (get_user(reg, (u32 *)rq->ifr_data) < 0)
return -EFAULT;
if (reg & (1<<31)) {
wrn_phy_write(dev, 0, (reg >> 16) & 0xff,
reg & 0xffff);
return 0;
}
reg = wrn_phy_read(dev, 0, (reg >> 16) & 0xff);
if (put_user(reg, (u32 *)rq->ifr_data) < 0)
return -EFAULT;
return 0;
case PRIV_MEZZANINE_ID:
case PRIV_MEZZANINE_CMD:
/* Pass this to the mezzanine driver, or use internal weak */
return wrn_mezzanine_ioctl(dev, rq, cmd);
default:
spin_lock_irq(&ep->lock);
res = generic_mii_ioctl(&ep->mii, if_mii(rq), cmd, NULL);
spin_unlock_irq(&ep->lock);
return res;
}
}
static const struct net_device_ops wrn_netdev_ops = {
.ndo_open = wrn_open,
.ndo_stop = wrn_close,
.ndo_start_xmit = wrn_start_xmit,
.ndo_validate_addr = eth_validate_addr,
.ndo_get_stats = wrn_get_stats,
.ndo_set_mac_address = wrn_set_mac_address,
.ndo_do_ioctl = wrn_ioctl,
#if 0
/* Missing ops, possibly to add later */
.ndo_set_multicast_list = wrn_set_multicast_list,
.ndo_change_mtu = wrn_change_mtu,
/* There are several more, but not really useful for us */
#endif
};
int wrn_netops_init(struct net_device *dev)
{
dev->netdev_ops = &wrn_netdev_ops;
return 0;
}
/*
* From this onwards, it's all about interrupt management
*/
static void __wrn_rx_descriptor(struct wrn_dev *wrn, int desc)
{
struct net_device *dev;
struct wrn_ep *ep;
struct sk_buff *skb;
struct wrn_rxd __iomem *rx;
u32 r1, r2, r3, offset;
int epnum, off, len;
u32 ts_r, ts_f;
struct skb_shared_hwtstamps *hwts;
struct timespec ts;
u32 counter_ppsg; /* PPS generator nanosecond counter */
u32 utc;
s32 cntr_diff;
rx = wrn->rxd + desc;
r1 = readl(&rx->rx1);
r2 = readl(&rx->rx2);
r3 = readl(&rx->rx3);
/* So, this descriptor is not empty. Get the port (ep) */
offset = __wrn_desc_offset(wrn, WRN_DDIR_RX, desc);
if (unlikely(r1 & NIC_RX1_D1_ERROR)) {
pr_debug("%s: %i: %08x %08x %08x\n", __func__, desc,
r1, r2, r3);
goto err_out;
}
if (unlikely(!(r1 & NIC_RX1_D1_GOT_TS))) {
/*
* [sorry for confusion with the flag name - it should be
* GOT_OOB. I'll fix it later -- Tom]
*/
pr_err("No RX OOB? Something's seriously fkd....\n");
goto err_out;
}
epnum = NIC_RX1_D1_PORT_R(r1);
ts_r = NIC_RX1_D2_TS_R_R(r2);
ts_f = NIC_RX1_D2_TS_F_R(r2);
dev = wrn->dev[epnum];
ep = netdev_priv(dev);
/* Data and length */
off = NIC_RX1_D3_OFFSET_R(r3);
len = NIC_RX1_D3_LEN_R(r3);
skb = netdev_alloc_skb(dev, len + 16 /* FIXME: real size for rx */);
/* FIXME: handle allocation failure */
skb_reserve(skb, 2);
__wrn_copy_in(skb_put(skb, len), wrn->databuf + off, len);
/* Rewrite lenght (modified during rx) and mark it free ASAP */
writel( (2000 << 16) | offset, &rx->rx3);
writel(NIC_RX1_D1_EMPTY, &rx->rx1);
/* RX timestamping part */
wrn_ppsg_read_time(wrn, &counter_ppsg, &utc);
if (WR_IS_NODE)
if (counter_ppsg < ts_r)
utc--;
if (WR_IS_SWITCH)
if(counter_ppsg < REFCLK_FREQ/4 && ts_r > 3*REFCLK_FREQ/4)
utc--;
ts.tv_sec = (s32)utc & 0x7fffffff;
cntr_diff = (ts_r & 0xf) - ts_f;
/* the bit says the rising edge cnter is 1tick ahead */
if(cntr_diff == 1 || cntr_diff == (-0xf))
ts.tv_sec |= 0x80000000;
ts.tv_nsec = ts_r * NSEC_PER_TICK;
pr_debug("Timestamp: %li:%li, ahead = %d\n",
ts.tv_sec & 0x7fffffff,
ts.tv_nsec & 0x7fffffff,
ts.tv_sec & 0x80000000 ? 1 :0);
if(WR_IS_NODE){
/* SPEC: don't do the strange stuff for wr-ptp */
ts.tv_sec &= ~0x80000000;
ts.tv_nsec &= 0x7fffffff;
}
/* If the timestamp was reported as incorrect, pass 0 instead */
if (! (r1 & NIC_RX1_D1_TS_INCORRECT)) /* FIXME: bit name possibly? */
{
hwts = skb_hwtstamps(skb);
hwts->hwtstamp = timespec_to_ktime(ts);
}
skb->protocol = eth_type_trans(skb, dev);
skb->ip_summed = CHECKSUM_UNNECESSARY;
record_last_rx(dev);
ep->stats.rx_packets++;
ep->stats.rx_bytes += len;
netif_receive_skb(skb);
return;
err_out: /* Mark it free anyways -- with its full length */
writel( (2000 << 16) | offset, &rx->rx3);
writel(NIC_RX1_D1_EMPTY, &rx->rx1);
/* account the error to endpoint 0 -- we don't know who it is */
dev = wrn->dev[0];
ep = netdev_priv(dev);
ep->stats.rx_errors++;
}
/* This function is called in soft-irq context */
void wrn_rx_interrupt(unsigned long arg)
{
int desc;
struct wrn_dev *wrn = (void *)arg;
struct wrn_rxd __iomem *rx;
u32 reg;
while (1) {
desc = wrn->next_rx;
rx = wrn->rxd + desc;
reg = readl(&rx->rx1);
if (reg & NIC_RX1_D1_EMPTY)
break;
__wrn_rx_descriptor(wrn, desc);
wrn->next_rx = __wrn_next_desc(desc);
}
writel(NIC_EIC_IER_RCOMP, (void *)wrn->regs + 0x24 /* IER */);
}
/* This, lazily, remains in hard-irq context */
static void wrn_tx_interrupt(struct wrn_dev *wrn)
{
struct wrn_txd *tx;
struct sk_buff *skb;
struct skb_shared_info *info;
u32 reg;
int i;
/* Loop using our tail until one is not sent */
while ( (i = wrn->next_tx_tail) != wrn->next_tx_head) {
/* Check if this is txdone */
tx = wrn->txd + i;
reg = readl(&tx->tx1);
if (reg & NIC_TX1_D1_READY)
return; /* no more */
skb = wrn->skb_desc[i].skb;
if (!skb) {
pr_err("no socket in descriptor %i\n", i);
return;
}
info = skb_shinfo(skb);
if (info->tx_flags & SKBTX_HW_TSTAMP) {
/* hardware timestamping is enabled */
info->tx_flags |= SKBTX_IN_PROGRESS;
pr_debug("%s: %i -- in progress\n", __func__, __LINE__);
wrn_tx_tstamp_skb(wrn, i);
/* It has been freed if found; otherwise keep it */
} else {
dev_kfree_skb_irq(skb);
wrn->skb_desc[i].skb = 0;
}
wrn->next_tx_tail = __wrn_next_desc(i);
}
}
irqreturn_t wrn_interrupt(int irq, void *dev_id)
{
struct wrn_dev *wrn = dev_id;
struct NIC_WB *regs = wrn->regs;
u32 i, irqs;
irqs = readl((void *)regs + 0x2c /*EIC_ISR */);
i = readl(&regs->SR);
pr_debug("%s: irqs 0x%x, sr 0x%x\n", __func__, irqs, i);
if (irqs & NIC_EIC_ISR_TXERR) {
pr_err("%s: TX error\n", __func__); /* FIXME */
writel(NIC_EIC_ISR_TXERR, (void *)regs + 0x2c);
}
if (irqs & NIC_EIC_ISR_TCOMP) {
pr_debug("%s: TX complete\n", __func__);
wrn_tx_interrupt(wrn);
writel(NIC_EIC_ISR_TCOMP, (void *)regs + 0x2c);
}
if (irqs & NIC_EIC_ISR_RCOMP) {
pr_debug("%s: RX complete\n", __func__);
/*
* This must be processed in soft-irq context, as this is
* what is needed for socket processing. So disable
* the interrupt first, then run the tasklet
*/
writel(NIC_EIC_ISR_RCOMP, (void *)wrn->regs + 0x2c /* ISR */);
writel(NIC_EIC_IDR_RCOMP, (void *)wrn->regs + 0x20 /* IDR */);
tasklet_schedule(&wrn->rx_tlet);
}
return IRQ_HANDLED;
}
/*
* hardware-specific definitions for the White Rabbit NIC
*
* Copyright (C) 2010-2014 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __WR_NIC_HARDWARE_H__
#define __WR_NIC_HARDWARE_H__
#if (!defined WR_IS_NODE) && (!defined WR_IS_SWITCH)
#error "WR_NODE and WR_SWITCH not defined!"
#endif
#if WR_IS_SWITCH
/* This is the clock used in internal counters. */
#define REFCLK_FREQ (125000000 / 2)
#define NSEC_PER_TICK (NSEC_PER_SEC / REFCLK_FREQ)
/* The interrupt is one of those managed by our WRVIC device */
#define WRN_IRQ_BASE 192
#define WRN_IRQ_NIC (WRN_IRQ_BASE + 0)
#define WRN_IRQ_TSTAMP (WRN_IRQ_BASE + 1)
//#define WRN_IRQ_PPSG (WRN_IRQ_BASE + )
//#define WRN_IRQ_RTU (WRN_IRQ_BASE + )
//#define WRN_IRQ_RTUT (WRN_IRQ_BASE + )
/*
* V3 Memory map, temporarily (Jan 2012)
*
* 0x00000 - 0x1ffff: RT Subsystem
* 0x00000 - 0x0ffff: RT Subsystem Program Memory (16 - 64 kB)
* 0x10000 - 0x100ff: RT Subsystem UART
* 0x10100 - 0x101ff: RT Subsystem SoftPLL-adv
* 0x10200 - 0x102ff: RT Subsystem SPI Master
* 0x10300 - 0x103ff: RT Subsystem GPIO
* 0x10500 - 0x105ff: PPS gen
* 0x20000 - 0x3ffff: NIC
* 0x20000 - 0x20fff NIC control regs and descriptor area
* 0x28000 - 0x2bfff NIC packet buffer (16k)
* 0x30000 - 0x4ffff: Endpoints
* 0x30000 + N * 0x400 Endpoint N control registers
* 0x50000 - 0x50fff: VIC
* 0x51000 - 0x51fff: Tstamp unit
*/
/* This is the base address of all the FPGA regions (EBI1, CS0) */
#define FPGA_BASE_PPSG 0x10010500
#define FPGA_SIZE_PPSG 0x00000100
#define FPGA_BASE_NIC 0x10020000
#define FPGA_SIZE_NIC 0x00010000
#define FPGA_BASE_EP 0x10030000
#define FPGA_SIZE_EP 0x00010000
#define FPGA_SIZE_EACH_EP 0x400
#define FPGA_BASE_VIC 0x10050000 /* not used here */
#define FPGA_SIZE_VIC 0x00001000
#define FPGA_BASE_TS 0x10051000
#define FPGA_SIZE_TS 0x00001000
#endif /* WR_IS_SWITCH */
#if WR_IS_NODE
/* This is the clock used in internal counters. */
#define REFCLK_FREQ (125000000)
#define NSEC_PER_TICK (NSEC_PER_SEC / REFCLK_FREQ)
/* The interrupt is one of those managed by our WRVIC device */
#define WRN_IRQ_BASE 0 /* FIXME: relative to pci dev */
#define WRN_IRQ_NIC (WRN_IRQ_BASE + 0)
#define WRN_IRQ_TSTAMP /* (WRN_IRQ_BASE + 1) -- not used here */
//#define WRN_IRQ_PPSG (WRN_IRQ_BASE + )
//#define WRN_IRQ_RTU (WRN_IRQ_BASE + )
//#define WRN_IRQ_RTUT (WRN_IRQ_BASE + )
/*
* spec-wr-nic memory map (from SDB dump):
*
* 00000651:e6a542c9 WB4-Crossbar-GSI
* 0000ce42:00000011 WR-CORE (bridge: 00000000)
* 00000651:e6a542c9 WB4-Crossbar-GSI
* 0000ce42:66cfeb52 WB4-BlockRAM (00000000-00015fff)
* 00000651:eef0b198 WB4-Bridge-GSI (bridge: 00020000)
* 00000651:e6a542c9 WB4-Crossbar-GSI
* 0000ce42:ab28633a WR-Mini-NIC (00020000-000200ff)
* 0000ce42:650c2d4f WR-Endpoint (00020100-000201ff)
* 0000ce42:65158dc0 WR-Soft-PLL (00020200-000202ff)
* 0000ce42:de0d8ced WR-PPS-Generator (00020300-000203ff)
* 0000ce42:ff07fc47 WR-Periph-Syscon (00020400-000204ff)
* 0000ce42:e2d13d04 WR-Periph-UART (00020500-000205ff)
* 0000ce42:779c5443 WR-Periph-1Wire (00020600-000206ff)
* 0000ce42:779c5443 WR-Periph-1Wire (00020700-000207ff)
* 0000ce42:00000012 WR-NIC (00040000-0005ffff)
* 0000ce42:00000013 WB-VIC-Int.Control (00060000-000600ff)
* 0000ce42:00000014 WR-TXTSU (00061000-000610ff)
* 000075cb:00000002 WR-DIO-Core (bridge: 00062000)
* 00000651:e6a542c9 WB4-Crossbar-GSI
* 0000ce42:779c5443 WR-1Wire-master (00062000-000620ff)
* 0000ce42:123c5443 WB-I2C-Master (00062100-000621ff)
* 0000ce42:441c5143 WB-GPIO-Port (00062200-000622ff)
* 000075cb:00000001 WR-DIO-Registers (00062300-000623ff)
*
*/
/* This is the base address of memory regions (gennum bridge, bar 0) */
#define FPGA_BASE_LM32 0x00000000
#define FPGA_SIZE_LM32 0x00016000
#define FPGA_BASE_NIC 0x00020000
#define FPGA_SIZE_NIC 0x00000100
#define FPGA_BASE_EP 0x00020100
#define FPGA_SIZE_EP 0x00000100
#define FPGA_SIZE_EACH_EP 0x100 /* There is one only */
#define FPGA_BASE_PPSG 0x00020300
#define FPGA_SIZE_PPSG 0x00000100
#define FPGA_BASE_VIC 0x00060000 /* not used here */
#define FPGA_SIZE_VIC 0x00000100
#define FPGA_BASE_TS 0x00061000
#define FPGA_SIZE_TS 0x0000 100
#endif /* ifdef WR_IS_NODE */
enum fpga_blocks {
WRN_FB_NIC,
WRN_FB_EP,
WRN_FB_PPSG,
WRN_FB_TS,
WRN_NR_OF_BLOCKS,
};
/* In addition to the above enumeration, we scan for those many endpoints */
#if WR_IS_NODE
# define WRN_NR_ENDPOINTS 1
#endif
#if WR_IS_SWITCH
# define WRN_NR_ENDPOINTS 18
#endif
/* 8 tx and 8 rx descriptors */
#define WRN_NR_DESC 8
#define WRN_NR_TXDESC WRN_NR_DESC
#define WRN_NR_RXDESC WRN_NR_DESC
/* Magic number for endpoint */
#define WRN_EP_MAGIC 0xcafebabe
/*
* The following headers include the register lists, and have been
* generated by wbgen from .wb source files in svn
*/
#include "../wbgen-regs/endpoint-regs.h"
#include "../wbgen-regs/ppsg-regs.h"
#include "../wbgen-regs/nic-regs.h"
#include "../wbgen-regs/tstamp-regs.h"
/*
* To make thins easier, define the descriptor structures, for tx and rx
* Use functions in nic-mem.h to get pointes to them
*/
struct wrn_txd {
uint32_t tx1;
uint32_t tx2;
uint32_t tx3;
uint32_t unused;
};
struct wrn_rxd {
uint32_t rx1;
uint32_t rx2;
uint32_t rx3;
uint32_t unused;
};
/* Some more constants */
#define WRN_MTU 1540
#define WRN_DDATA_OFFSET 2 /* data in descriptors is offset by that much */
#endif /* __WR_NIC_HARDWARE_H__ */
/*
* This file hosts memory-specific inlines, separated for easier review
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "wr-nic.h"
#include <asm/unaligned.h>
/* Descriptor direction, used to locate descriptror data memory */
enum wrn_ddir {
WRN_DDIR_RX,
WRN_DDIR_TX
};
/* We place the descriptor memory in fixed positions (2kB each) */
static inline int __wrn_desc_offset(struct wrn_dev *wrn,
enum wrn_ddir dir, int nr)
{
if (dir == WRN_DDIR_RX) nr += WRN_NR_TXDESC;
return 0x800 * nr;
}
static inline u32 __iomem *__wrn_desc_mem(struct wrn_dev *wrn,
enum wrn_ddir dir, int nr)
{
/* note: this is a void pointer, but then we return a u32 ptr */
void __iomem *ptr = wrn->databuf;
return ptr + __wrn_desc_offset(wrn, dir, nr);
}
/* The two copy functions take arguments in the same order as memcpy */
static inline void __wrn_copy_out(u32 __iomem *to, void *from, int size)
{
u32 i;
from -= WRN_DDATA_OFFSET;
size += WRN_DDATA_OFFSET;
while (size > 0) {
i = get_unaligned_le32(from);
writel(i, to);
to++; from += sizeof(i);
size -= sizeof(i);
}
}
static inline void __wrn_copy_in(void *to, u32 __iomem *from, int size)
{
u32 i;
to -= WRN_DDATA_OFFSET;
size += WRN_DDATA_OFFSET;
while (size > 0) {
i = readl(from);
put_unaligned_le32(i, to);
to += sizeof(i); from++;
size -= sizeof(i);
}
}
/*
* Pulse per second management for White-Rabbit switch network interface
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/sockios.h>
#include <linux/net_tstamp.h>
#include "wr-nic.h"
void wrn_ppsg_read_time(struct wrn_dev *wrn, u32 *fine_counter, u32 *utc)
{
u32 utc1, utc2, cnt;
utc1 = readl(&wrn->ppsg_regs->CNTR_UTCLO);
cnt = readl(&wrn->ppsg_regs->CNTR_NSEC);
utc2 = readl(&wrn->ppsg_regs->CNTR_UTCLO);
if (utc2 != utc1)
cnt = readl(&wrn->ppsg_regs->CNTR_NSEC);
*utc = utc2;
*fine_counter = cnt;
}
/*\
* Timestamping routines for WR Switch
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/sockios.h>
#include <linux/net_tstamp.h>
#include "wr-nic.h"
/* This checks if we already received the timestamp interrupt */
void wrn_tx_tstamp_skb(struct wrn_dev *wrn, int desc)
{
struct skb_shared_hwtstamps *hwts;
struct wrn_desc_pending *d = wrn->skb_desc + desc;
struct sk_buff *skb = d->skb;
struct timespec ts;
u32 counter_ppsg; /* PPS generator nanosecond counter */
u32 utc;
if (!wrn->skb_desc[desc].valid)
return;
/* already reported by hardware: do the timestamping magic */
wrn_ppsg_read_time(wrn, &counter_ppsg, &utc);
/* We may be at the beginning og the next second */
if (counter_ppsg < d->cycles)
utc--;
ts.tv_sec = (s32)utc & 0x7fffffff;
ts.tv_nsec = d->cycles * NSEC_PER_TICK;
if (!(d->valid & TS_INVALID)) {
hwts = skb_hwtstamps(skb);
hwts->hwtstamp = timespec_to_ktime(ts);
skb_tstamp_tx(skb, hwts);
}
dev_kfree_skb_irq(skb);
/* release both the descriptor and the tstamp entry */
d->skb = 0;
d->valid = 0;
}
/* This function, called by txtsu records the timestamp for the descriptor */
static int record_tstamp(struct wrn_dev *wrn, u32 tsval, u32 idreg, u32 r2)
{
int frame_id = TXTSU_TSF_R1_FID_R(idreg);
int ts_incorrect = r2 & TXTSU_TSF_R2_INCORRECT;
struct skb_shared_hwtstamps *hwts;
struct timespec ts;
struct sk_buff *skb;
u32 utc, counter_ppsg; /* PPS generator nanosecond counter */
int i;
/* Find the skb in the descriptor array */
for (i = 0; i < WRN_NR_DESC; i++)
if (wrn->skb_desc[i].skb
&& wrn->skb_desc[i].frame_id == frame_id)
break;
if (i == WRN_NR_DESC) {
/* Not found: Must be a PTP frame sent from the SPEC! */
return 0;
}
skb = wrn->skb_desc[i].skb;
wrn_ppsg_read_time(wrn, &counter_ppsg, &utc);
if (counter_ppsg < (tsval & 0xfffffff))
utc--;
ts.tv_sec = (s32)utc & 0x7fffffff;
ts.tv_nsec = (tsval & 0xfffffff) * NSEC_PER_TICK;
/* Provide the timestamp only if 100% sure about its correctness */
if (!ts_incorrect) {
hwts = skb_hwtstamps(skb);
hwts->hwtstamp = timespec_to_ktime(ts);
skb_tstamp_tx(skb, hwts);
}
dev_kfree_skb_irq(skb);
wrn->skb_desc[i].skb = 0;
return 0;
}
irqreturn_t wrn_tstamp_interrupt(int irq, void *dev_id)
{
struct wrn_dev *wrn = dev_id;
struct TXTSU_WB *regs = wrn->txtsu_regs;
u32 r0, r1, r2;
if (!regs)
return IRQ_NONE; /* early interrupt? */
/* printk("%s: %i\n", __func__, __LINE__); */
/* FIXME: locking */
r0 = readl(&regs->TSF_R0);
r1 = readl(&regs->TSF_R1);
r2 = readl(&regs->TSF_R2);
record_tstamp(wrn, r0, r1, r2);
writel(TXTSU_EIC_IER_NEMPTY, &wrn->txtsu_regs->EIC_ISR); /* ack irq */
return IRQ_HANDLED;
}
int wrn_tstamp_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
struct wrn_ep *ep = netdev_priv(dev);
struct hwtstamp_config config;
if (copy_from_user(&config, rq->ifr_data, sizeof(config)))
return -EFAULT;
if (0)
netdev_dbg(dev, "%s: tx type %i, rx filter %i\n",
__func__, config.tx_type, config.rx_filter);
switch (config.tx_type) {
/* Set up time stamping on transmission */
case HWTSTAMP_TX_ON:
set_bit(WRN_EP_STAMPING_TX, &ep->ep_flags);
/* FIXME: enable timestamp on tx in hardware */
break;
case HWTSTAMP_TX_OFF:
/* FIXME: disable timestamp on tx in hardware */
clear_bit(WRN_EP_STAMPING_TX, &ep->ep_flags);
break;
default:
return -ERANGE;
}
/*
* For the time being, make this really simple and stupid: either
* time-tag _all_ the incoming packets or none of them.
*/
switch (config.rx_filter) {
case HWTSTAMP_FILTER_NONE:
/* FIXME: disable rx in hardware */
clear_bit(WRN_EP_STAMPING_RX, &ep->ep_flags);
break;
default: /* All other case: activate stamping */
/* FIXME: enable rx in hardware */
set_bit(WRN_EP_STAMPING_RX, &ep->ep_flags);
break;
}
/* FIXME: minic_update_ts_config(nic); */
if (copy_to_user(rq->ifr_data, &config, sizeof(config)))
return -EFAULT;
return 0;
}
void wrn_tstamp_init(struct wrn_dev *wrn)
{
/* enable TXTSU irq */
writel(TXTSU_EIC_IER_NEMPTY, &wrn->txtsu_regs->EIC_IER);
}
/*
* wr-nic definitions, structures and prototypes
*
* Copyright (C) 2010 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Partly from previous work by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Partly from previous work by Emilio G. Cota <cota@braap.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __WR_NIC_H__
#define __WR_NIC_H__
/* Private ioctls, (the first 2 are the same as they were in wr_minic.c */
#define PRIV_IOCGCALIBRATE (SIOCDEVPRIVATE + 1)
#define PRIV_IOCGGETPHASE (SIOCDEVPRIVATE + 2)
#define PRIV_IOCREADREG (SIOCDEVPRIVATE + 3)
#define PRIV_IOCPHYREG (SIOCDEVPRIVATE + 4)
/* The last two available are used for mezzanine-private stuff */
#define PRIV_MEZZANINE_ID (SIOCDEVPRIVATE + 14)
#define PRIV_MEZZANINE_CMD (SIOCDEVPRIVATE + 15)
#ifdef __KERNEL__ /* The rest is kernel-only */
/* The NIC can build for both the switch and the node. Prefer if to ifdef */
#if defined WR_NODE
# define WR_IS_NODE 1
# define WR_IS_SWITCH 0
#elif defined WR_SWITCH
# define WR_IS_NODE 0
# define WR_IS_SWITCH 1
#else
# error "Please define WR_NODE or WR_SWITCH"
#endif
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/mii.h> /* Needed for stuct mii_if_info in wrn_dev */
#include <linux/netdevice.h> /* Needed for net_device_stats in wrn_dev */
#include <linux/timer.h> /* Needed for struct time_list in wrn_dev*/
#include "nic-hardware.h" /* Magic numbers: please fix them as needed */
#define DRV_NAME "wr-nic" /* Used in messages and device/driver names */
#define DRV_VERSION "0.1" /* For ethtool->get_drvinfo -- FIXME: auto-vers */
/*
* Interrupt information should be hidden in resource lists,
* but the looping code would be hairy. So let's define three
* arrays of the same size, and code loops over these
*/
#if 0
#define WRN_IRQ_NUMBERS \
{WRN_IRQ_PPSG, WRN_IRQ_NIC, WRN_IRQ_RTU, WRN_IRQ_RTUT, WRN_IRQ_TSTAMP}
#define WRN_IRQ_NAMES \
{"wr-ppsg", "wr-nic", "wr-rtu", "wr-rtut", "wr-tstamp"}
#define WRN_IRQ_HANDLERS \
{NULL, wrn_interrupt, NULL, NULL, wrn_tstamp_interrupt}
#endif
/* Temporarily, two handlers only */
#define WRN_IRQ_NUMBERS {WRN_IRQ_NIC, WRN_IRQ_TSTAMP}
#define WRN_IRQ_NAMES {"wr-nic", "wr-tstamp"}
#define WRN_IRQ_HANDLERS {wrn_interrupt, wrn_tstamp_interrupt}
struct wrn_ep; /* Defined later */
/* We must remember skb, id and tstamp for each pending descriptor, */
struct wrn_desc_pending {
struct sk_buff *skb;
u8 valid;
u8 port_id;
u16 frame_id;
u32 cycles;
};
/* bits for "valid" field */
#define TS_PRESENT 1
#define TS_INVALID 2 /* as reported by hw: we return 0 as timestamp */
/*
* This is the main data structure for our NIC device. As for locking,
* the rule is that _either_ the wrn _or_ the endpoint is locked. Not both.
*/
struct wrn_dev {
/* Base addresses. It's easier with an array, but not all are used */
void __iomem *bases[WRN_NR_OF_BLOCKS];
struct NIC_WB __iomem *regs; /* shorthand for NIC-block registers */
struct TXTSU_WB __iomem *txtsu_regs; /* ... and the same for TXTSU */
struct PPSG_WB __iomem *ppsg_regs; /* ... */
spinlock_t lock;
struct tasklet_struct rx_tlet;
struct wrn_txd __iomem *txd;
struct wrn_rxd __iomem *rxd;
void __iomem *databuf; /* void to ease pointer arith */
int next_tx_head, next_tx_tail;
int next_rx;
/* For TX descriptors, we must keep track of the ownwer */
struct wrn_desc_pending skb_desc[WRN_NR_TXDESC];
int id;
struct net_device *dev[WRN_NR_ENDPOINTS];
/* FIXME: all dev fields must be verified */
//unsigned int rx_head, rx_avail, rx_base, rx_size;
//unsigned int tx_head, tx_avail, tx_base, tx_size;
//u32 cur_rx_desc;
int use_count; /* only used at probe time */
int irq_registered;
};
/* We need to disable the rx-complete interrupt, so get the masks */
#define WRN_IRQ_ALL (~0)
#define WRN_IRQ_ALL_BUT_RX (~NIC_EIC_IER_RCOMP)
#define WRN_IRQ_NONE 0
/* Each network device (endpoint) has one such priv structure */
struct wrn_ep {
struct wrn_dev *wrn;
struct EP_WB __iomem *ep_regs; /* each EP has its own memory */
spinlock_t lock;
struct timer_list ep_link_timer;
volatile unsigned long ep_flags;
struct mii_if_info mii; /* for ethtool operations */
int ep_number;
int pkt_count; /* used for tx stamping ID */
struct net_device_stats stats;
//struct sk_buff *current_skb;
//bool synced;
//bool syncing_counters;
//u32 cur_rx_desc;
};
#define WRN_LINK_POLL_INTERVAL (HZ/5)
enum ep_flags { /* only used in the ep_flags register */
WRN_EP_UP = 0,
WRN_EP_IS_UPLINK = 1,
WRN_EP_STAMPING_TX = 2,
WRN_EP_STAMPING_RX = 3,
};
/* Our resources. */
enum wrn_resnames {
/*
* The names are used as indexes in the resource array. Note that
* they are unrelated with the memory addresses: we can't have
* holes in the memory list, so these are _different_ values
*/
WRN_RES_MEM_EP_UP0,
WRN_RES_MEM_EP_UP1,
WRN_RES_MEM_EP_DP0,
WRN_RES_MEM_EP_DP1,
WRN_RES_MEM_EP_DP2,
WRN_RES_MEM_EP_DP3,
WRN_RES_MEM_EP_DP4,
WRN_RES_MEM_EP_DP5,
WRN_RES_MEM_EP_DP6,
WRN_RES_MEM_EP_DP7,
WRN_RES_MEM_PPSG,
WRN_RES_MEM_CALIBRATOR,
WRN_RES_MEM_NIC,
WRN_RES_MEM_TSTAMP,
/* Irq is last, so platform_get_resource() can use previous enums */
WRN_RES_IRQ,
};
/*
* Register access may be needed outside of specific files.
* Please note that this takes a register *name*, uppercase with no prefix.
*/
#define wrn_ep_read(ep, reg) __raw_readl(&(ep)->ep_regs->reg)
#define wrn_ep_write(ep, reg, val) __raw_writel((val), &(ep)->ep_regs->reg)
#define NIC_READ_PHY_CMD(addr) (((addr) & 0xff) << 16)
#define NIC_RESULT_DATA(val) ((val) & 0xffff)
#define NIC_WRITE_PHY_CMD(addr, value) ((((addr) & 0xff) << 16) \
| (1 << 31) \
| ((value) & 0xffff))
/* Structures straight from wr_minic.c -- should user-space include this? */
struct wrn_calibration_req {
int cmd;
int cal_present;
};
struct wrn_phase_req {
int ready;
u32 phase;
};
#define WRN_DMTD_AVG_SAMPLES 256
#define WRN_DMTD_MAX_PHASE 16384
#define WRN_CAL_TX_ON 1
#define WRN_CAL_TX_OFF 2
#define WRN_CAL_RX_ON 3
#define WRN_CAL_RX_OFF 4
#define WRN_CAL_RX_CHECK 5
/* This a WR-specific register in the mdio space */
#define WRN_MDIO_WR_SPEC 0x00000010
#define WRN_MDIO_WR_SPEC_TX_CAL 0x01 /* TX calib pattern */
#define WRN_MDIO_WR_SPEC_RX_CAL_STAT 0x02 /* RX calib status */
#define WRN_MDIO_WR_SPEC_CAL_CRST 0x04 /* Reset calibration counter */
/* Following functions are in nic-core.c */
extern irqreturn_t wrn_interrupt(int irq, void *dev_id);
extern int wrn_netops_init(struct net_device *netdev);
extern void wrn_rx_interrupt(unsigned long arg); /* tasklet */
/* Following data in device.c */
struct platform_driver;
extern struct platform_driver wrn_driver;
/* Following functions in ethtool.c */
extern int wrn_ethtool_init(struct net_device *netdev);
/* Following functions in endpoint.c */
extern int wrn_phy_read(struct net_device *dev, int phy_id, int location);
extern void wrn_phy_write(struct net_device *dev, int phy_id, int loc, int v);
extern int wrn_ep_open(struct net_device *dev);
extern int wrn_ep_close(struct net_device *dev);
extern int wrn_endpoint_probe(struct net_device *netdev);
extern void wrn_endpoint_remove(struct net_device *netdev);
/* Following functions from timestamp.c */
extern void wrn_tx_tstamp_skb(struct wrn_dev *wrn, int desc);
extern int wrn_tstamp_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
extern irqreturn_t wrn_tstamp_interrupt(int irq, void *dev_id);
extern void wrn_tstamp_init(struct wrn_dev *wrn);
/* Following functions from dmtd.c and pps.c */
extern int wrn_phase_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
extern int wrn_calib_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
extern void wrn_ppsg_read_time(struct wrn_dev *wrn, u32 *fine_cnt, u32 *utc);
/* Locally weak, designed for a mezzanine driver to implement */
extern int wrn_mezzanine_ioctl(struct net_device *dev, struct ifreq *rq,
int cmd);
extern int wrn_mezzanine_init(struct net_device *dev);
extern void wrn_mezzanine_exit(struct net_device *dev);
#endif /* __KERNEL__ */
#endif /* __WR_NIC_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