forked from mirrors/linux
		
	fpga: lattice machxo2: Add Lattice MachXO2 support
This patch adds support to the FPGA manager for programming MachXO2 device’s internal flash memory, via slave SPI. Signed-off-by: Paolo Pisati <p.pisati@gmail.com> [atull@kernel.org: use existing FPGA mgr API] Signed-off-by: Alan Tull <atull@kernel.org> Signed-off-by: Moritz Fischer <mdf@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									d549ac081c
								
							
						
					
					
						commit
						88fb3a0023
					
				
					 3 changed files with 411 additions and 0 deletions
				
			
		|  | @ -69,6 +69,13 @@ config FPGA_MGR_ICE40_SPI | ||||||
| 	help | 	help | ||||||
| 	  FPGA manager driver support for Lattice iCE40 FPGAs over SPI. | 	  FPGA manager driver support for Lattice iCE40 FPGAs over SPI. | ||||||
| 
 | 
 | ||||||
|  | config FPGA_MGR_MACHXO2_SPI | ||||||
|  | 	tristate "Lattice MachXO2 SPI" | ||||||
|  | 	depends on SPI | ||||||
|  | 	help | ||||||
|  | 	  FPGA manager driver support for Lattice MachXO2 configuration | ||||||
|  | 	  over slave SPI interface. | ||||||
|  | 
 | ||||||
| config FPGA_MGR_TS73XX | config FPGA_MGR_TS73XX | ||||||
| 	tristate "Technologic Systems TS-73xx SBC FPGA Manager" | 	tristate "Technologic Systems TS-73xx SBC FPGA Manager" | ||||||
| 	depends on ARCH_EP93XX && MACH_TS72XX | 	depends on ARCH_EP93XX && MACH_TS72XX | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ obj-$(CONFIG_FPGA)			+= fpga-mgr.o | ||||||
| obj-$(CONFIG_FPGA_MGR_ALTERA_CVP)	+= altera-cvp.o | obj-$(CONFIG_FPGA_MGR_ALTERA_CVP)	+= altera-cvp.o | ||||||
| obj-$(CONFIG_FPGA_MGR_ALTERA_PS_SPI)	+= altera-ps-spi.o | obj-$(CONFIG_FPGA_MGR_ALTERA_PS_SPI)	+= altera-ps-spi.o | ||||||
| obj-$(CONFIG_FPGA_MGR_ICE40_SPI)	+= ice40-spi.o | obj-$(CONFIG_FPGA_MGR_ICE40_SPI)	+= ice40-spi.o | ||||||
|  | obj-$(CONFIG_FPGA_MGR_MACHXO2_SPI)	+= machxo2-spi.o | ||||||
| obj-$(CONFIG_FPGA_MGR_SOCFPGA)		+= socfpga.o | obj-$(CONFIG_FPGA_MGR_SOCFPGA)		+= socfpga.o | ||||||
| obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10)	+= socfpga-a10.o | obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10)	+= socfpga-a10.o | ||||||
| obj-$(CONFIG_FPGA_MGR_TS73XX)		+= ts73xx-fpga.o | obj-$(CONFIG_FPGA_MGR_TS73XX)		+= ts73xx-fpga.o | ||||||
|  |  | ||||||
							
								
								
									
										403
									
								
								drivers/fpga/machxo2-spi.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										403
									
								
								drivers/fpga/machxo2-spi.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,403 @@ | ||||||
|  | // SPDX-License-Identifier: GPL-2.0
 | ||||||
|  | /*
 | ||||||
|  |  * Lattice MachXO2 Slave SPI Driver | ||||||
|  |  * | ||||||
|  |  * Manage Lattice FPGA firmware that is loaded over SPI using | ||||||
|  |  * the slave serial configuration interface. | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2018 Paolo Pisati <p.pisati@gmail.com> | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <linux/delay.h> | ||||||
|  | #include <linux/fpga/fpga-mgr.h> | ||||||
|  | #include <linux/gpio/consumer.h> | ||||||
|  | #include <linux/module.h> | ||||||
|  | #include <linux/of.h> | ||||||
|  | #include <linux/spi/spi.h> | ||||||
|  | 
 | ||||||
|  | /* MachXO2 Programming Guide - sysCONFIG Programming Commands */ | ||||||
|  | #define IDCODE_PUB		{0xe0, 0x00, 0x00, 0x00} | ||||||
|  | #define ISC_ENABLE		{0xc6, 0x08, 0x00, 0x00} | ||||||
|  | #define ISC_ERASE		{0x0e, 0x04, 0x00, 0x00} | ||||||
|  | #define ISC_PROGRAMDONE		{0x5e, 0x00, 0x00, 0x00} | ||||||
|  | #define LSC_INITADDRESS		{0x46, 0x00, 0x00, 0x00} | ||||||
|  | #define LSC_PROGINCRNV		{0x70, 0x00, 0x00, 0x01} | ||||||
|  | #define LSC_READ_STATUS		{0x3c, 0x00, 0x00, 0x00} | ||||||
|  | #define LSC_REFRESH		{0x79, 0x00, 0x00, 0x00} | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Max CCLK in Slave SPI mode according to 'MachXO2 Family Data | ||||||
|  |  * Sheet' sysCONFIG Port Timing Specifications (3-36) | ||||||
|  |  */ | ||||||
|  | #define MACHXO2_MAX_SPEED		66000000 | ||||||
|  | 
 | ||||||
|  | #define MACHXO2_LOW_DELAY_USEC		5 | ||||||
|  | #define MACHXO2_HIGH_DELAY_USEC		200 | ||||||
|  | #define MACHXO2_REFRESH_USEC		4800 | ||||||
|  | #define MACHXO2_MAX_BUSY_LOOP		128 | ||||||
|  | #define MACHXO2_MAX_REFRESH_LOOP	16 | ||||||
|  | 
 | ||||||
|  | #define MACHXO2_PAGE_SIZE		16 | ||||||
|  | #define MACHXO2_BUF_SIZE		(MACHXO2_PAGE_SIZE + 4) | ||||||
|  | 
 | ||||||
|  | /* Status register bits, errors and error mask */ | ||||||
|  | #define BUSY	12 | ||||||
|  | #define DONE	8 | ||||||
|  | #define DVER	27 | ||||||
|  | #define ENAB	9 | ||||||
|  | #define ERRBITS	23 | ||||||
|  | #define ERRMASK	7 | ||||||
|  | #define FAIL	13 | ||||||
|  | 
 | ||||||
|  | #define ENOERR	0 /* no error */ | ||||||
|  | #define EID	1 | ||||||
|  | #define ECMD	2 | ||||||
|  | #define ECRC	3 | ||||||
|  | #define EPREAM	4 /* preamble error */ | ||||||
|  | #define EABRT	5 /* abort error */ | ||||||
|  | #define EOVERFL	6 /* overflow error */ | ||||||
|  | #define ESDMEOF	7 /* SDM EOF */ | ||||||
|  | 
 | ||||||
|  | static inline u8 get_err(unsigned long *status) | ||||||
|  | { | ||||||
|  | 	return (*status >> ERRBITS) & ERRMASK; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int get_status(struct spi_device *spi, unsigned long *status) | ||||||
|  | { | ||||||
|  | 	struct spi_message msg; | ||||||
|  | 	struct spi_transfer rx, tx; | ||||||
|  | 	static const u8 cmd[] = LSC_READ_STATUS; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	memset(&rx, 0, sizeof(rx)); | ||||||
|  | 	memset(&tx, 0, sizeof(tx)); | ||||||
|  | 	tx.tx_buf = cmd; | ||||||
|  | 	tx.len = sizeof(cmd); | ||||||
|  | 	rx.rx_buf = status; | ||||||
|  | 	rx.len = 4; | ||||||
|  | 	spi_message_init(&msg); | ||||||
|  | 	spi_message_add_tail(&tx, &msg); | ||||||
|  | 	spi_message_add_tail(&rx, &msg); | ||||||
|  | 	ret = spi_sync(spi, &msg); | ||||||
|  | 	if (ret) | ||||||
|  | 		return ret; | ||||||
|  | 
 | ||||||
|  | 	*status = be32_to_cpu(*status); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #ifdef DEBUG | ||||||
|  | static const char *get_err_string(u8 err) | ||||||
|  | { | ||||||
|  | 	switch (err) { | ||||||
|  | 	case ENOERR:	return "No Error"; | ||||||
|  | 	case EID:	return "ID ERR"; | ||||||
|  | 	case ECMD:	return "CMD ERR"; | ||||||
|  | 	case ECRC:	return "CRC ERR"; | ||||||
|  | 	case EPREAM:	return "Preamble ERR"; | ||||||
|  | 	case EABRT:	return "Abort ERR"; | ||||||
|  | 	case EOVERFL:	return "Overflow ERR"; | ||||||
|  | 	case ESDMEOF:	return "SDM EOF"; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return "Default switch case"; | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | static void dump_status_reg(unsigned long *status) | ||||||
|  | { | ||||||
|  | #ifdef DEBUG | ||||||
|  | 	pr_debug("machxo2 status: 0x%08lX - done=%d, cfgena=%d, busy=%d, fail=%d, devver=%d, err=%s\n", | ||||||
|  | 		 *status, test_bit(DONE, status), test_bit(ENAB, status), | ||||||
|  | 		 test_bit(BUSY, status), test_bit(FAIL, status), | ||||||
|  | 		 test_bit(DVER, status), get_err_string(get_err(status))); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int wait_until_not_busy(struct spi_device *spi) | ||||||
|  | { | ||||||
|  | 	unsigned long status; | ||||||
|  | 	int ret, loop = 0; | ||||||
|  | 
 | ||||||
|  | 	do { | ||||||
|  | 		ret = get_status(spi, &status); | ||||||
|  | 		if (ret) | ||||||
|  | 			return ret; | ||||||
|  | 		if (++loop >= MACHXO2_MAX_BUSY_LOOP) | ||||||
|  | 			return -EBUSY; | ||||||
|  | 	} while (test_bit(BUSY, &status)); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int machxo2_cleanup(struct fpga_manager *mgr) | ||||||
|  | { | ||||||
|  | 	struct spi_device *spi = mgr->priv; | ||||||
|  | 	struct spi_message msg; | ||||||
|  | 	struct spi_transfer tx[2]; | ||||||
|  | 	static const u8 erase[] = ISC_ERASE; | ||||||
|  | 	static const u8 refresh[] = LSC_REFRESH; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	memset(tx, 0, sizeof(tx)); | ||||||
|  | 	spi_message_init(&msg); | ||||||
|  | 	tx[0].tx_buf = &erase; | ||||||
|  | 	tx[0].len = sizeof(erase); | ||||||
|  | 	spi_message_add_tail(&tx[0], &msg); | ||||||
|  | 	ret = spi_sync(spi, &msg); | ||||||
|  | 	if (ret) | ||||||
|  | 		goto fail; | ||||||
|  | 
 | ||||||
|  | 	ret = wait_until_not_busy(spi); | ||||||
|  | 	if (ret) | ||||||
|  | 		goto fail; | ||||||
|  | 
 | ||||||
|  | 	spi_message_init(&msg); | ||||||
|  | 	tx[1].tx_buf = &refresh; | ||||||
|  | 	tx[1].len = sizeof(refresh); | ||||||
|  | 	tx[1].delay_usecs = MACHXO2_REFRESH_USEC; | ||||||
|  | 	spi_message_add_tail(&tx[1], &msg); | ||||||
|  | 	ret = spi_sync(spi, &msg); | ||||||
|  | 	if (ret) | ||||||
|  | 		goto fail; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | fail: | ||||||
|  | 	dev_err(&mgr->dev, "Cleanup failed\n"); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static enum fpga_mgr_states machxo2_spi_state(struct fpga_manager *mgr) | ||||||
|  | { | ||||||
|  | 	struct spi_device *spi = mgr->priv; | ||||||
|  | 	unsigned long status; | ||||||
|  | 
 | ||||||
|  | 	get_status(spi, &status); | ||||||
|  | 	if (!test_bit(BUSY, &status) && test_bit(DONE, &status) && | ||||||
|  | 	    get_err(&status) == ENOERR) | ||||||
|  | 		return FPGA_MGR_STATE_OPERATING; | ||||||
|  | 
 | ||||||
|  | 	return FPGA_MGR_STATE_UNKNOWN; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int machxo2_write_init(struct fpga_manager *mgr, | ||||||
|  | 			      struct fpga_image_info *info, | ||||||
|  | 			      const char *buf, size_t count) | ||||||
|  | { | ||||||
|  | 	struct spi_device *spi = mgr->priv; | ||||||
|  | 	struct spi_message msg; | ||||||
|  | 	struct spi_transfer tx[3]; | ||||||
|  | 	static const u8 enable[] = ISC_ENABLE; | ||||||
|  | 	static const u8 erase[] = ISC_ERASE; | ||||||
|  | 	static const u8 initaddr[] = LSC_INITADDRESS; | ||||||
|  | 	unsigned long status; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	if ((info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { | ||||||
|  | 		dev_err(&mgr->dev, | ||||||
|  | 			"Partial reconfiguration is not supported\n"); | ||||||
|  | 		return -ENOTSUPP; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	get_status(spi, &status); | ||||||
|  | 	dump_status_reg(&status); | ||||||
|  | 	memset(tx, 0, sizeof(tx)); | ||||||
|  | 	spi_message_init(&msg); | ||||||
|  | 	tx[0].tx_buf = &enable; | ||||||
|  | 	tx[0].len = sizeof(enable); | ||||||
|  | 	tx[0].delay_usecs = MACHXO2_LOW_DELAY_USEC; | ||||||
|  | 	spi_message_add_tail(&tx[0], &msg); | ||||||
|  | 
 | ||||||
|  | 	tx[1].tx_buf = &erase; | ||||||
|  | 	tx[1].len = sizeof(erase); | ||||||
|  | 	spi_message_add_tail(&tx[1], &msg); | ||||||
|  | 	ret = spi_sync(spi, &msg); | ||||||
|  | 	if (ret) | ||||||
|  | 		goto fail; | ||||||
|  | 
 | ||||||
|  | 	ret = wait_until_not_busy(spi); | ||||||
|  | 	if (ret) | ||||||
|  | 		goto fail; | ||||||
|  | 
 | ||||||
|  | 	get_status(spi, &status); | ||||||
|  | 	if (test_bit(FAIL, &status)) | ||||||
|  | 		goto fail; | ||||||
|  | 	dump_status_reg(&status); | ||||||
|  | 
 | ||||||
|  | 	spi_message_init(&msg); | ||||||
|  | 	tx[2].tx_buf = &initaddr; | ||||||
|  | 	tx[2].len = sizeof(initaddr); | ||||||
|  | 	spi_message_add_tail(&tx[2], &msg); | ||||||
|  | 	ret = spi_sync(spi, &msg); | ||||||
|  | 	if (ret) | ||||||
|  | 		goto fail; | ||||||
|  | 
 | ||||||
|  | 	get_status(spi, &status); | ||||||
|  | 	dump_status_reg(&status); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | fail: | ||||||
|  | 	dev_err(&mgr->dev, "Error during FPGA init.\n"); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int machxo2_write(struct fpga_manager *mgr, const char *buf, | ||||||
|  | 			 size_t count) | ||||||
|  | { | ||||||
|  | 	struct spi_device *spi = mgr->priv; | ||||||
|  | 	struct spi_message msg; | ||||||
|  | 	struct spi_transfer tx; | ||||||
|  | 	static const u8 progincr[] = LSC_PROGINCRNV; | ||||||
|  | 	u8 payload[MACHXO2_BUF_SIZE]; | ||||||
|  | 	unsigned long status; | ||||||
|  | 	int i, ret; | ||||||
|  | 
 | ||||||
|  | 	if (count % MACHXO2_PAGE_SIZE != 0) { | ||||||
|  | 		dev_err(&mgr->dev, "Malformed payload.\n"); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 	get_status(spi, &status); | ||||||
|  | 	dump_status_reg(&status); | ||||||
|  | 	memcpy(payload, &progincr, sizeof(progincr)); | ||||||
|  | 	for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) { | ||||||
|  | 		memcpy(&payload[sizeof(progincr)], &buf[i], MACHXO2_PAGE_SIZE); | ||||||
|  | 		memset(&tx, 0, sizeof(tx)); | ||||||
|  | 		spi_message_init(&msg); | ||||||
|  | 		tx.tx_buf = payload; | ||||||
|  | 		tx.len = MACHXO2_BUF_SIZE; | ||||||
|  | 		tx.delay_usecs = MACHXO2_HIGH_DELAY_USEC; | ||||||
|  | 		spi_message_add_tail(&tx, &msg); | ||||||
|  | 		ret = spi_sync(spi, &msg); | ||||||
|  | 		if (ret) { | ||||||
|  | 			dev_err(&mgr->dev, "Error loading the bitstream.\n"); | ||||||
|  | 			return ret; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	get_status(spi, &status); | ||||||
|  | 	dump_status_reg(&status); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int machxo2_write_complete(struct fpga_manager *mgr, | ||||||
|  | 				  struct fpga_image_info *info) | ||||||
|  | { | ||||||
|  | 	struct spi_device *spi = mgr->priv; | ||||||
|  | 	struct spi_message msg; | ||||||
|  | 	struct spi_transfer tx[2]; | ||||||
|  | 	static const u8 progdone[] = ISC_PROGRAMDONE; | ||||||
|  | 	static const u8 refresh[] = LSC_REFRESH; | ||||||
|  | 	unsigned long status; | ||||||
|  | 	int ret, refreshloop = 0; | ||||||
|  | 
 | ||||||
|  | 	memset(tx, 0, sizeof(tx)); | ||||||
|  | 	spi_message_init(&msg); | ||||||
|  | 	tx[0].tx_buf = &progdone; | ||||||
|  | 	tx[0].len = sizeof(progdone); | ||||||
|  | 	spi_message_add_tail(&tx[0], &msg); | ||||||
|  | 	ret = spi_sync(spi, &msg); | ||||||
|  | 	if (ret) | ||||||
|  | 		goto fail; | ||||||
|  | 	ret = wait_until_not_busy(spi); | ||||||
|  | 	if (ret) | ||||||
|  | 		goto fail; | ||||||
|  | 
 | ||||||
|  | 	get_status(spi, &status); | ||||||
|  | 	dump_status_reg(&status); | ||||||
|  | 	if (!test_bit(DONE, &status)) { | ||||||
|  | 		machxo2_cleanup(mgr); | ||||||
|  | 		goto fail; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	do { | ||||||
|  | 		spi_message_init(&msg); | ||||||
|  | 		tx[1].tx_buf = &refresh; | ||||||
|  | 		tx[1].len = sizeof(refresh); | ||||||
|  | 		tx[1].delay_usecs = MACHXO2_REFRESH_USEC; | ||||||
|  | 		spi_message_add_tail(&tx[1], &msg); | ||||||
|  | 		ret = spi_sync(spi, &msg); | ||||||
|  | 		if (ret) | ||||||
|  | 			goto fail; | ||||||
|  | 
 | ||||||
|  | 		/* check refresh status */ | ||||||
|  | 		get_status(spi, &status); | ||||||
|  | 		dump_status_reg(&status); | ||||||
|  | 		if (!test_bit(BUSY, &status) && test_bit(DONE, &status) && | ||||||
|  | 		    get_err(&status) == ENOERR) | ||||||
|  | 			break; | ||||||
|  | 		if (++refreshloop == MACHXO2_MAX_REFRESH_LOOP) { | ||||||
|  | 			machxo2_cleanup(mgr); | ||||||
|  | 			goto fail; | ||||||
|  | 		} | ||||||
|  | 	} while (1); | ||||||
|  | 
 | ||||||
|  | 	get_status(spi, &status); | ||||||
|  | 	dump_status_reg(&status); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | fail: | ||||||
|  | 	dev_err(&mgr->dev, "Refresh failed.\n"); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const struct fpga_manager_ops machxo2_ops = { | ||||||
|  | 	.state = machxo2_spi_state, | ||||||
|  | 	.write_init = machxo2_write_init, | ||||||
|  | 	.write = machxo2_write, | ||||||
|  | 	.write_complete = machxo2_write_complete, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static int machxo2_spi_probe(struct spi_device *spi) | ||||||
|  | { | ||||||
|  | 	struct device *dev = &spi->dev; | ||||||
|  | 
 | ||||||
|  | 	if (spi->max_speed_hz > MACHXO2_MAX_SPEED) { | ||||||
|  | 		dev_err(dev, "Speed is too high\n"); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return fpga_mgr_register(dev, "Lattice MachXO2 SPI FPGA Manager", | ||||||
|  | 				 &machxo2_ops, spi); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int machxo2_spi_remove(struct spi_device *spi) | ||||||
|  | { | ||||||
|  | 	struct device *dev = &spi->dev; | ||||||
|  | 
 | ||||||
|  | 	fpga_mgr_unregister(dev); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const struct of_device_id of_match[] = { | ||||||
|  | 	{ .compatible = "lattice,machxo2-slave-spi", }, | ||||||
|  | 	{} | ||||||
|  | }; | ||||||
|  | MODULE_DEVICE_TABLE(of, of_match); | ||||||
|  | 
 | ||||||
|  | static const struct spi_device_id lattice_ids[] = { | ||||||
|  | 	{ "machxo2-slave-spi", 0 }, | ||||||
|  | 	{ }, | ||||||
|  | }; | ||||||
|  | MODULE_DEVICE_TABLE(spi, lattice_ids); | ||||||
|  | 
 | ||||||
|  | static struct spi_driver machxo2_spi_driver = { | ||||||
|  | 	.driver = { | ||||||
|  | 		.name = "machxo2-slave-spi", | ||||||
|  | 		.of_match_table = of_match_ptr(of_match), | ||||||
|  | 	}, | ||||||
|  | 	.probe = machxo2_spi_probe, | ||||||
|  | 	.remove = machxo2_spi_remove, | ||||||
|  | 	.id_table = lattice_ids, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | module_spi_driver(machxo2_spi_driver) | ||||||
|  | 
 | ||||||
|  | MODULE_AUTHOR("Paolo Pisati <p.pisati@gmail.com>"); | ||||||
|  | MODULE_DESCRIPTION("Load Lattice FPGA firmware over SPI"); | ||||||
|  | MODULE_LICENSE("GPL v2"); | ||||||
		Loading…
	
		Reference in a new issue
	
	 Paolo Pisati
						Paolo Pisati