spec-pci.c 8.13 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * 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.
 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/kmod.h>
#include <linux/sched.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/io.h>
#include <asm/unaligned.h>
23
#include <linux/version.h>
24
#include <linux/fs.h>
25
#include <linux/vmalloc.h>
26
#include <linux/uaccess.h>
27 28 29 30

#include "spec.h"
#include "loader-ll.h"

31 32 33
static char *spec_fw_name_45t = "fmc/spec-init.bin";
static char *spec_fw_name_100t = "fmc/spec-init-100T.bin";
char *spec_fw_name = "";
34 35
module_param_named(fw_name, spec_fw_name, charp, 0444);

36 37 38
int spec_use_msi = 0;
module_param_named(use_msi, spec_use_msi, int, 0444);

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
/**
 * According to the PCI device ID, load different golden
 */
static char *spec_golden_name_get(unsigned int device_id)
{
	if (strlen(spec_fw_name) > 0)
		return spec_fw_name;
	switch (device_id) {
	case PCI_DEVICE_ID_SPEC_45T:
		return spec_fw_name_45t;
	case PCI_DEVICE_ID_SPEC_100T:
		return spec_fw_name_100t;
	}
	return NULL;
}

55
/* Load the FPGA. This bases on loader-ll.c, a kernel/user space thing */
56
int spec_load_fpga(struct spec_dev *spec, const void *data, int size)
57 58
{
	struct device *dev = &spec->pdev->dev;
59
	int i, wrote;
60 61 62 63
	unsigned long j;

	/* loader_low_level is designed to run from user space too */
	wrote = loader_low_level(0 /* unused fd */,
64
				 spec->remap[2], data, size);
65 66
	j = jiffies + 2 * HZ;
	/* Wait for DONE interrupt  */
67
	while (1) {
68 69 70
		udelay(100);
		i = readl(spec->remap[2] + FCL_IRQ);
		if (i & 0x8) {
71
			dev_info(dev, "FPGA programming successful\n");
72
			break;
73 74
		}

75
		if (i & 0x4) {
76 77
			dev_err(dev, "FPGA program error after %i writes\n",
				wrote);
78
			return -ETIMEDOUT;
79 80 81 82
		}

		if (time_after(jiffies, j)) {
			dev_err(dev, "FPGA timeout after %i writes\n", wrote);
83
			return -ETIMEDOUT;
84 85
		}
	}
86
	gpiofix_low_level(0 /* unused fd */, spec->remap[2]);
87 88
	loader_reset_fpga(0 /* unused fd */, spec->remap[2]);

89
	return 0;
90 91 92 93 94 95 96 97
}

int spec_load_fpga_file(struct spec_dev *spec, char *name)
{
	struct device *dev = &spec->pdev->dev;
	const struct firmware *fw;
	int err = 0;

98 99 100 101 102
	if (!name) {
		dev_err(dev, "You must provide a golden binary\n");
		return -EINVAL;
	}

103 104 105 106 107
	err = request_firmware(&fw, name, dev);
	if (err < 0) {
		dev_err(dev, "request firmware \"%s\": error %i\n", name, err);
		return err;
	}
108
	dev_info(dev, "got file \"%s\", %zi (0x%zx) bytes\n",
Federico Vaga's avatar
Federico Vaga committed
109
		 name, fw->size, fw->size);
110 111

	err = spec_load_fpga(spec, fw->data, fw->size);
112
	release_firmware(fw);
113
	return err;
114 115
}

116 117 118 119 120 121 122 123
static int spec_reconfigure(struct spec_dev *spec, struct fmc_gateware *gw)
{
	int ret;

	if (spec->flags & SPEC_FLAG_FMC_REGISTERED)
		spec_fmc_destroy(spec);

	/* Load the golden FPGA binary to read the eeprom */
124 125
	ret = spec_load_fpga_file(spec,
				  spec_golden_name_get(spec->pdev->device));
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
	if (ret)
		return ret;

	return spec_fmc_create(spec, gw);
}

/* * * * * * MISC DEVICE * * * * * */
static int spec_mdev_simple_open(struct inode *inode, struct file *file)
{
	struct miscdevice *mdev_ptr = file->private_data;

	file->private_data = container_of(mdev_ptr, struct spec_dev, mdev);

	return 0;
}

static ssize_t spec_mdev_write_raw(struct file *f, const char __user *buf,
				   size_t count, loff_t *offp)
{
	struct spec_dev *spec = f->private_data;
	struct fmc_gateware gw;
	int err = 0;

	if (!count)
		return -EINVAL;

	/* Copy FPGA bitstream to kernel space */
	gw.len = count;
	gw.bitstream = vmalloc(count);
	if (!gw.bitstream)
		return -ENOMEM;
	if (copy_from_user(gw.bitstream, buf, gw.len)) {
		err = -EFAULT;
		goto out;
	}

	dev_dbg(&spec->pdev->dev, "writing FPGA %p %ld (%zu + %lld)\n",
		gw.bitstream, gw.len, count, *offp);
	/* Program FPGA */
	err = spec_reconfigure(spec, &gw);
	if (err)
		dev_err(&spec->pdev->dev,
			"Manually program FPGA bitstream from buffer: fail\n");
	else
		dev_info(&spec->pdev->dev,
			 "Manually program FPGA bitstream from buffer: success\n");
out:
	vfree(gw.bitstream);
	return err ? err : count;
}

static const struct file_operations spec_fops = {
	.owner = THIS_MODULE,
	.open = spec_mdev_simple_open,
	.write  = spec_mdev_write_raw,
};

static int spec_create_misc_device(struct spec_dev *spec)
{
	spec->mdev.minor = MISC_DYNAMIC_MINOR;
	spec->mdev.fops = &spec_fops;
	spec->mdev.name = spec->name;

	return misc_register(&spec->mdev);
}

static void spec_destroy_misc_device(struct spec_dev *spec)
{
	misc_deregister(&spec->mdev);
}
/* * * * * * END MISC DEVICE * * * * */

198
static int spec_probe(struct pci_dev *pdev,
199
		      const struct pci_device_id *id)
200 201 202 203 204 205 206 207 208 209
{
	struct spec_dev *spec;
	int i, ret;

	dev_info(&pdev->dev, " probe for device %04x:%04x\n",
		 pdev->bus->number, pdev->devfn);

	ret = pci_enable_device(pdev);
	if (ret < 0)
		return ret;
210
	 pci_set_master(pdev);
211 212 213 214 215 216

	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec)
		return -ENOMEM;
	spec->pdev = pdev;

217 218 219 220 221
	if (spec_use_msi) {
		/*
		 * This should be "4" but arch/x86/kernel/apic/io_apic.c
		 * says "x86 doesn't support multiple MSI yet".
		 */
222
		#if KERNEL_VERSION(3, 16, 0) > LINUX_VERSION_CODE
223
		ret = pci_enable_msi_block(pdev, 1);
224
		#else
225
		#if KERNEL_VERSION(4,11,0) > LINUX_VERSION_CODE
226
		ret = pci_enable_msi_exact(pdev, 1);
227 228 229
		#else
		ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY);
		#endif
230
		#endif
231 232 233 234
		if (ret < 0)
			dev_err(&pdev->dev, "%s: enable msi block: error %i\n",
				__func__, ret);
	}
235 236

	/* Remap our 3 bars */
237
	for (i = ret = 0; i < 3; i++) {
238
		struct resource *r = pdev->resource + (2 * i);
239

240 241 242
		if (!r->start)
			continue;
		spec->area[i] = r;
243
		if (r->flags & IORESOURCE_MEM) {
244 245
			spec->remap[i] = ioremap(r->start,
						r->end + 1 - r->start);
246 247 248
			if (!spec->remap[i])
				ret = -ENOMEM;
		}
249
	}
250 251
	if (ret)
		goto out_unmap;
252

253 254 255 256
	/* Put our 6 pins to a sane state (4 test points, 2 from FPGA) */
	gennum_mask_val(spec, 0xfc0, 0x000, GNGPIO_BYPASS_MODE); /* no AF */
	gennum_mask_val(spec, 0xfc0, 0xfc0, GNGPIO_DIRECTION_MODE); /* input */
	gennum_writel(spec, 0xffff, GNGPIO_INT_MASK_SET); /* disable */
257

258
	ret = spec_reconfigure(spec, NULL);
259 260
	if (ret)
		goto out_unmap;
261

262 263 264
	snprintf(spec->name, SPEC_NAME_LEN, "spec-%04x",
		 spec->pdev->bus->number << 8 | spec->pdev->devfn);

265
	/* Done */
266
	pci_set_drvdata(pdev, spec);
267 268 269 270 271 272 273

	ret = spec_create_misc_device(spec);
	if (ret) {
		dev_err(&spec->pdev->dev, "Error creating misc device\n");
		goto failed_misc;
	}

274
	return 0;
275

276 277
failed_misc:
	spec_fmc_destroy(spec);
278 279 280 281 282 283 284 285
out_unmap:
	for (i = 0; i < 3; i++) {
		if (spec->remap[i])
			iounmap(spec->remap[i]);
		spec->remap[i] = NULL;
		spec->area[i] = NULL;
	}
	pci_set_drvdata(pdev, NULL);
286
	if (spec_use_msi)
287
#if KERNEL_VERSION(4,11,0) > LINUX_VERSION_CODE
288
		pci_disable_msi(pdev);
289 290 291
#else
		pci_free_irq_vectors(pdev);
#endif
292 293 294
	pci_disable_device(pdev);
	kfree(spec);
	return ret;
295 296
}

297
static void spec_remove(struct pci_dev *pdev)
298 299 300 301 302 303
{
	struct spec_dev *spec = pci_get_drvdata(pdev);
	int i;

	dev_info(&pdev->dev, "remove\n");

304
	spec_destroy_misc_device(spec);
305
	spec_fmc_destroy(spec);
306
	for (i = 0; i < 3; i++) {
307 308
		if (spec->remap[i])
			iounmap(spec->remap[i]);
309 310 311 312 313
		spec->remap[i] = NULL;
		spec->area[i] = NULL;
	}
	pci_set_drvdata(pdev, NULL);
	kfree(spec);
314
#if KERNEL_VERSION(4,11,0) > LINUX_VERSION_CODE
315
	//pci_disable_msi(pdev);
316 317 318
#else
	pci_free_irq_vectors(pdev);
#endif
319 320 321 322
	pci_disable_device(pdev);

}

323
static const struct pci_device_id spec_idtable[] = {
324 325
	{ PCI_DEVICE(PCI_VENDOR_ID_CERN, PCI_DEVICE_ID_SPEC_45T) },
	{ PCI_DEVICE(PCI_VENDOR_ID_CERN, PCI_DEVICE_ID_SPEC_100T) },
326 327 328
	{ PCI_DEVICE(PCI_VENDOR_ID_GENNUM, PCI_DEVICE_ID_GN4124) },
	{ 0,},
};
329
MODULE_DEVICE_TABLE(pci, spec_idtable);
330 331 332 333 334 335 336 337

static struct pci_driver spec_driver = {
	.name = "spec",
	.id_table = spec_idtable,
	.probe = spec_probe,
	.remove = spec_remove,
};

338
static int __init spec_init(void)
339 340 341 342
{
	return pci_register_driver(&spec_driver);
}

343
static void __exit spec_exit(void)
344 345 346 347 348 349 350 351
{
	pci_unregister_driver(&spec_driver);

}

module_init(spec_init);
module_exit(spec_exit);

352
MODULE_VERSION(GIT_VERSION);
353
MODULE_LICENSE("GPL");
354

355
ADDITIONAL_VERSIONS;