Skip to content
Snippets Groups Projects
wr-nic-dio.c 3.35 KiB
Newer Older
/*
 * 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/fmc.h>
#include <linux/fmc-sdb.h>
#include <linux/ktime.h>
#include <asm/uaccess.h>
#include "spec-nic.h"
#include "wr_nic/wr-nic.h"
#define DIO_STAT

#ifdef DIO_STAT
#define wrn_stat 1
#else
#define wrn_stat 0
#endif

/* FIXME: should this access use fmc_readl/writel? */
static int wrn_dio_cmd_out(struct wrn_drvdata *drvdata,
			   struct wr_dio_cmd *cmd)
{
	struct DIO_WB __iomem *dio = drvdata->wrdio_base;
	struct PPSG_WB __iomem *ppsg = drvdata->ppsg_base;

	void __iomem *p;
	struct timespec *ts;
	uint32_t val;

	if (cmd->channel > 4)
		return -EINVAL; /* FIXME: mask */

	/* First, put this bit as output (FIXME: plain GPIO support?) */
	val = readl(&dio->OUT) | (1 << cmd->channel);
	writel(val, &dio->OUT);

	ts = cmd->t;

	/* if relative, add current 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;
		printk("relative: %li -> %li\n", now, ts->tv_sec);
	}

	/* if not "now", set trig, trigh, cycles */
	if (!(cmd->flags & WR_DIO_F_NOW)) {
		/* not now: set relevant registers */
		p = &dio->TRIG0;
		p += (cmd->channel * 12);
		printk("%i -> %p\n", GET_HI32(ts->tv_sec), p + 4);
		writel(GET_HI32(ts->tv_sec), p + 4);
		printk("%li -> %p\n", ts->tv_sec, p);
		writel(ts->tv_sec, p);
		printk("%li -> %p\n", ts->tv_nsec / 8, p + 8);
		writel(ts->tv_nsec / 8, p + 8);
	}

	/* set the width */
	ts++;
	p = &dio->PROG0_PULSE;
	p += cmd->channel * 4;
	printk("%li -> %p\n", ts->tv_nsec / 8, p);
	writel(ts->tv_nsec / 8, p);

	/* no loop yet (FIXME: interrupts) */

	if (cmd->flags & WR_DIO_F_NOW)
		writel(1 << cmd->channel, &dio->PULSE);
	else
		writel(1 << cmd->channel, &dio->R_LATCH);

	return 0;

}

static int wrn_dio_cmd_stamp(struct wrn_drvdata *drvdata,
			     struct wr_dio_cmd *cmd)
{
	return -ENOTSUPP;
}


int wrn_mezzanine_ioctl(struct net_device *dev, struct ifreq *rq,
	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_OUT:
		ret = wrn_dio_cmd_out(drvdata, cmd);
		break;
	case WR_DIO_CMD_STAMP:
		ret = wrn_dio_cmd_stamp(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;