calibrate.c 6.62 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Calibrate the output path.
 *
 * Copyright (C) 2012 CERN (www.cern.ch)
 * Author: Tomasz Wlostowski <tomasz.wlostowski@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 Lesser General Public License
 * version 2 as published by the Free Software Foundation or, at your
 * option, any later version.
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/delay.h>
18
//#include <linux/math64.h>
19 20 21 22 23
#include "fine-delay.h"
#include "hw/fd_main_regs.h"
#include "hw/acam_gpx.h"
#include "hw/fd_channel_regs.h"

24
/* This is the same as in ./acam.c: use only at init time */
25
static void acam_set_bypass(struct fd_dev *fd, int on)
26 27 28 29 30
{
	fd_writel(fd, on ? FD_GCR_BYPASS : 0, FD_REG_GCR);
}


31
static int acam_test_delay_transfer_function(struct fd_dev *fd)
32 33 34 35 36 37
{
	/* FIXME */
	return 0;
}

/* Evaluates 2nd order polynomial. Coefs have 32 fractional bits. */
38
static int fd_eval_polynomial(struct fd_dev *fd)
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
{
	int64_t x = fd->temp;
	int64_t *coef = fd->calib.frr_poly;

	return (coef[0] * x * x + coef[1] * x + coef[2]) >> 32;
}

/*
 * Measures the the FPGA-generated TDC start and the output of one of
 * the fine delay chips (channel) at a pre-defined number of taps
 * (fine). Retuns the delay in picoseconds. The measurement is
 * repeated and averaged (n_avgs) times. Also, the standard deviation
 * of the result can be written to (sdev) if it's not NULL.
 */
struct delay_stats {
54 55 56
	uint64_t avg;
	uint64_t min;
	uint64_t max;
57 58 59
};

/* Note: channel is the "internal" one: 0..3 */
60
static uint64_t output_delay_ps(struct fd_dev *fd, int ch, int fine, int n,
61 62 63
				struct delay_stats *stats)
{
	int i;
64 65
	uint64_t *results;
	uint64_t res, acc = 0;
66
	int rem;
67
	struct device *dev = &fd->fmc->dev;
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

	results = kmalloc(n * sizeof(*results), GFP_KERNEL);
	if (!results)
		return -ENOMEM;

	/* Disable the output for the channel being calibrated */
	fd_gpio_clr(fd, FD_GPIO_OUTPUT_EN(FD_CH_EXT(ch)));

	/* Enable the stop input in ACAM for the channel being calibrated */
	acam_writel(fd, AR0_TRiseEn(0) | AR0_TRiseEn(FD_CH_EXT(ch))
		    | AR0_HQSel | AR0_ROsc, 0);

	/* Program the output delay line setpoint */
	fd_ch_writel(fd, ch, fine, FD_REG_FRR);
	fd_ch_writel(fd, ch, FD_DCR_ENABLE | FD_DCR_MODE | FD_DCR_UPDATE,
		    FD_REG_DCR);
	fd_ch_writel(fd, ch, FD_DCR_FORCE_DLY | FD_DCR_ENABLE, FD_REG_DCR);

	/*
	 * Set the calibration pulse mask to genrate calibration
	 * pulses only on one channel at a time.  This minimizes the
	 * crosstalk in the output buffer which can severely decrease
	 * the accuracy of calibration measurements
	 */
	fd_writel(fd, FD_CALR_PSEL_W(1 << ch), FD_REG_CALR);
93
	udelay(1);
94 95 96 97 98 99

	/* Do n_avgs single measurements and average */
	for (i = 0; i < n; i++) {
		uint32_t fr;
		/* Re-arm the ACAM (it's working in a single-shot mode) */
		fd_writel(fd, FD_TDCSR_ALUTRIG, FD_REG_TDCSR);
100
		udelay(1);
101 102 103
		/* Produce a calib pulse on the TDC start and the output ch */
		fd_writel(fd, FD_CALR_CAL_PULSE |
			  FD_CALR_PSEL_W(1 << ch), FD_REG_CALR);
104
		udelay(1);
105
		/* read the tag, convert to picoseconds (fixed point: 16.16) */
106 107
		fr = acam_readl(fd, 8 /* fifo */) & 0x1ffff;

108
		res = fr * fd->bin;
109
		if (fd->verbose > 3)
110 111 112
			dev_info(dev, "%s: ch %i, fine %i, bin %x got %08x, "
				 "res 0x%016llx\n", __func__, ch, fine,
				 fd->bin, fr, res);
113 114 115 116 117 118
		results[i] = res;
		acc += res;
	}
	fd_ch_writel(fd, ch, 0, FD_REG_DCR);

	/* Calculate avg, min max */
119
	acc = div_u64_rem((acc + n / 2), n, &rem);
120 121
	if (stats) {
		stats->avg = acc;
122 123
		stats->min = ~0LL;
		stats->max = 0LL;
124 125 126 127
		for (i = 0; i < n; i++) {
			if (results[i] > stats->max) stats->max = results[i];
			if (results[i] < stats->min) stats->min = results[i];
		}
128
		if (fd->verbose > 2)
129 130 131
			dev_info(dev, "%s: ch %i, taps %i, count %i, result %llx "
				 "(max-min %llx)\n", __func__, ch, fine, n,
				 stats->avg, stats->max - stats->min);
132 133 134 135 136 137
	}
	kfree(results);

	return acc;
}

138 139 140 141 142 143
static void __pr_fixed(char *head, uint64_t val, char *tail)
{
	printk("%s%i.%03i%s", head, (int)(val >> 16),
	       ((int)(val & 0xffff) * 1000) >> 16, tail);
}

144
static int fd_find_8ns_tap(struct fd_dev *fd, int ch)
145 146
{
	int l = 0, mid, r = FD_NUM_TAPS - 1;
147 148
	uint64_t bias, dly;
	struct delay_stats stats;
149
	struct device *dev = &fd->fmc->dev;
150 151 152 153 154

	/*
	 * Measure the delay at zero setting, so it can be further
	 * subtracted to get only the delay part introduced by the
	 * delay line (ingoring the TDC, FPGA and routing delays).
155
	 * Use a binary search of the delay value.
156 157 158 159
	 */
	bias = output_delay_ps(fd, ch, 0, FD_CAL_STEPS, NULL);
	while( r - l > 1) {
		mid = ( l + r) / 2;
160
		dly = output_delay_ps(fd, ch, mid, FD_CAL_STEPS, &stats) - bias;
161
		if (fd->verbose > 1) {
162
			dev_info(dev, "%s: ch%i @ %-5i: ", __func__, ch, mid);
163
			__pr_fixed("bias ", bias, ", ");
164 165 166
			__pr_fixed("min ", stats.min - bias, ", ");
			__pr_fixed("avg ", stats.avg - bias, ", ");
			__pr_fixed("max ", stats.max - bias, "\n");
167
		}
168 169 170 171 172 173 174 175 176 177

		if(dly < 8000 << 16)
			l = mid;
		else
			r = mid;
	}
	return l;

}

178 179 180 181 182 183
/**
 * fd_calibrate_outputs
 * It calibrates the delay line by finding the correct 8ns-tap value
 * for each channel. This is done during ACAM initialization, so on driver
 * probe.
 */
184
int fd_calibrate_outputs(struct fd_dev *fd)
185 186
{
	int ret, ch;
187
	int measured, fitted, new;
188 189 190 191 192 193

	acam_set_bypass(fd, 1); /* not useful */
	fd_writel(fd, FD_TDCSR_START_EN | FD_TDCSR_STOP_EN, FD_REG_TDCSR);

	if ((ret = acam_test_delay_transfer_function(fd)) < 0)
		return ret;
194 195

	fd_read_temp(fd, 0);
196
	fitted = fd_eval_polynomial(fd);
197

198 199
	for (ch = FD_CH_1; ch <= FD_CH_LAST; ch++) {
		measured = fd_find_8ns_tap(fd, ch);
200 201 202 203 204
		new = measured;
		fd->ch[ch].frr_offset = new - fitted;

		fd_ch_writel(fd, ch, new, FD_REG_FRR);
		fd->ch[ch].frr_cur = new;
205
		if (fd->verbose > 1) {
206 207 208 209 210
			dev_info(&fd->fmc->dev,
				 "%s: ch%i: 8ns @%i (f %i, off %i, t %i.%02i)\n",
				 __func__, FD_CH_EXT(ch),
				 new, fitted, fd->ch[ch].frr_offset,
				 fd->temp / 16, (fd->temp & 0xf) * 100 / 16);
211
		}
212 213 214 215
	}
	return 0;
}

216 217 218 219 220
/**
 * fd_update_calibration
 * Called from a timer any few seconds. It updates the Delay line tap
 * according to the measured temperature
 */
221 222
void fd_update_calibration(unsigned long arg)
{
223
	struct fd_dev *fd = (void *)arg;
224 225 226 227 228 229 230 231 232
	int ch, fitted, new;

	fd_read_temp(fd, 0 /* not verbose */);
	fitted = fd_eval_polynomial(fd);

	for (ch = FD_CH_1; ch <= FD_CH_LAST; ch++) {
		new = fitted + fd->ch[ch].frr_offset;
		fd_ch_writel(fd, ch, new, FD_REG_FRR);
		fd->ch[ch].frr_cur = new;
233 234 235 236 237 238 239

		dev_dbg(&fd->fmc->dev,
			"%s: ch%i: 8ns @%i (f %i, off %i, t %i.%02i)\n",
			__func__, FD_CH_EXT(ch),
			new, fitted, fd->ch[ch].frr_offset,
			fd->temp / 16, (fd->temp & 0xf) * 100 / 16);

240 241 242 243 244
	}

	mod_timer(&fd->temp_timer, jiffies + HZ * fd_calib_period_s);
}