* [PATCH] [POWERPC] Xilinx: hwicap driver [not found] <1201890163-12219-1-git-send-email-stephen.neuendorffer@xilinx.com> @ 2008-02-01 20:03 ` Stephen Neuendorffer 2008-02-01 20:11 ` Grant Likely 0 siblings, 1 reply; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-01 20:03 UTC (permalink / raw) To: linuxppc-dev, grant.likely, jacmet, ntl This includes code for new fifo-based xps_hwicap in addition to the older opb_hwicap, which has a significantly different interface. The common code between the two drivers is largely shared. Significant differences exists between this driver and what is supported in the EDK drivers. In particular, most of the architecture-specific code for reconfiguring individual FPGA resources has been removed. This functionality is likely better provided in a user-space support library. In addition, read and write access is supported. In addition, although the xps_hwicap cores support interrupt-driver mode, this driver only supports polled operation, in order to make the code simpler, and since the interrupt processing overhead is likely to slow down the throughput under Linux. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Fixed to add mutexes, and a few style issues. --- I will get this right eventually. :) --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 1 + drivers/char/xilinx_hwicap/Makefile | 7 + drivers/char/xilinx_hwicap/buffer_icap.c | 380 ++++++++++++ drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ drivers/char/xilinx_hwicap/xilinx_hwicap.c | 904 ++++++++++++++++++++++++++++ drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ 9 files changed, 1992 insertions(+), 0 deletions(-) create mode 100644 drivers/char/xilinx_hwicap/Makefile create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ef1ed5d..157ae2a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -831,6 +831,13 @@ config DTLK To compile this driver as a module, choose M here: the module will be called dtlk. +config XILINX_HWICAP + tristate "Xilinx HWICAP Support" + depends on XILINX_VIRTEX + help + This option enables support for Xilinx Internal Configuration + Access Port (ICAP) driver. + config R3964 tristate "Siemens R3964 line discipline" ---help--- diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d5..3a278a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ ifeq ($(CONFIG_GENERIC_NVRAM),y) obj-$(CONFIG_NVRAM) += generic_nvram.o else diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile new file mode 100644 index 0000000..5491cbc --- /dev/null +++ b/drivers/char/xilinx_hwicap/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Xilinx OPB hwicap driver +# + +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o + +xilinx_hwicap_m-y := xilinx_hwicap.o fifo_icap.o buffer_icap.o diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c new file mode 100644 index 0000000..dfea2bd --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.c @@ -0,0 +1,380 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "buffer_icap.h" + +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ +#define XHI_MAX_BUFFER_BYTES 2048 +#define XHI_MAX_BUFFER_INTS (XHI_MAX_BUFFER_BYTES >> 2) + +/* File access and error constants */ +#define XHI_DEVICE_READ_ERROR -1 +#define XHI_DEVICE_WRITE_ERROR -2 +#define XHI_BUFFER_OVERFLOW_ERROR -3 + +#define XHI_DEVICE_READ 0x1 +#define XHI_DEVICE_WRITE 0x0 + +/* Constants for checking transfer status */ +#define XHI_CYCLE_DONE 0 +#define XHI_CYCLE_EXECUTING 1 + +/* buffer_icap register offsets */ + +/* Size of transfer, read & write */ +#define XHI_SIZE_REG_OFFSET 0x800L +/* offset into bram, read & write */ +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L +/* Read not Configure, direction of transfer. Write only */ +#define XHI_RNC_REG_OFFSET 0x808L +/* Indicates transfer complete. Read only */ +#define XHI_STATUS_REG_OFFSET 0x80CL + +/* Constants for setting the RNC register */ +#define XHI_CONFIGURE 0x0UL +#define XHI_READBACK 0x1UL + +/* Constants for the Done register */ +#define XHI_NOT_FINISHED 0x0UL +#define XHI_FINISHED 0x1UL + +#define XHI_BUFFER_START 0 + +/** + * buffer_icap_get_status: Get the contents of the status register. + * @parameter base_address: is the base address of the device + * + * The status register contains the ICAP status and the done bit. + * + * D8 - cfgerr + * D7 - dalign + * D6 - rip + * D5 - in_abort_l + * D4 - Always 1 + * D3 - Always 1 + * D2 - Always 1 + * D1 - Always 1 + * D0 - Done bit + **/ +static inline u32 buffer_icap_get_status(void __iomem *base_address) +{ + return in_be32(base_address + XHI_STATUS_REG_OFFSET); +} + +/** + * buffer_icap_get_bram: Reads data from the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset from which the data should be read. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline u32 buffer_icap_get_bram(void __iomem *base_address, + u32 offset) +{ + return in_be32(base_address + (offset << 2)); +} + +/** + * buffer_icap_busy: Return true if the icap device is busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_busy(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_NOT_FINISHED; +} + +/** + * buffer_icap_busy: Return true if the icap device is not busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_done(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_FINISHED; +} + +/** + * buffer_icap_set_size: Set the size register. + * @parameter base_address: is the base address of the device + * @parameter data: The size in bytes. + * + * The size register holds the number of 8 bit bytes to transfer between + * bram and the icap (or icap to bram). + **/ +static inline void buffer_icap_set_size(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_SIZE_REG_OFFSET, data); +} + +/** + * buffer_icap_mSetoffsetReg: Set the bram offset register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The bram offset register holds the starting bram address to transfer + * data from during configuration or write data to during readback. + **/ +static inline void buffer_icap_set_offset(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_BRAM_OFFSET_REG_OFFSET, data); +} + +/** + * buffer_icap_set_rnc: Set the RNC (Readback not Configure) register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The RNC register determines the direction of the data transfer. It + * controls whether a configuration or readback take place. Writing to + * this register initiates the transfer. A value of 1 initiates a + * readback while writing a value of 0 initiates a configuration. + **/ +static inline void buffer_icap_set_rnc(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_RNC_REG_OFFSET, data); +} + +/** + * buffer_icap_set_bram: Write data to the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset at which the data should be written. + * @parameter data: The value to be written to the bram offset. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline void buffer_icap_set_bram(void __iomem *base_address, + u32 offset, u32 data) +{ + out_be32(base_address + (offset << 2), data); +} + +/** + * buffer_icap_device_read: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, (count << 2)); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_READBACK); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, count << 2); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * Writing to the status register resets the ICAP logic in an internal + * version of the core. For the version of the core published in EDK, + * this is a noop. + **/ +void buffer_icap_reset(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_STATUS_REG_OFFSET, 0xFEFE); +} + +/** + * buffer_icap_set_configuration: Load a partial bitstream from system memory. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Kernel address of the partial bitstream. + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 num_writes = 0; + bool dirty = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = 0; i < size; i++) { + + /* Copy data to bram */ + buffer_icap_set_bram(base_address, buffer_count, data[i]); + dirty = 1; + + if (buffer_count < XHI_MAX_BUFFER_INTS - 1) { + buffer_count++; + continue; + } + + /* Write data to ICAP */ + status = buffer_icap_device_write( + drvdata, + XHI_BUFFER_START, + XHI_MAX_BUFFER_INTS); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + num_writes++; + dirty = 0; + } + + /* Write unwritten data to ICAP */ + if (dirty) { + /* Write data to ICAP */ + status = buffer_icap_device_write(drvdata, XHI_BUFFER_START, + buffer_count); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + } + return status; + } + + return 0; +}; + +/** + * buffer_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 read_count = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = XHI_MAX_BUFFER_INTS; i < size; i++) { + if (buffer_count == XHI_MAX_BUFFER_INTS) { + u32 words_remaining = size - i; + u32 words_to_read = + words_remaining < + XHI_MAX_BUFFER_INTS ? words_remaining : + XHI_MAX_BUFFER_INTS; + + /* Read data from ICAP */ + status = buffer_icap_device_read( + drvdata, + XHI_BUFFER_START, + words_to_read); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + read_count++; + } + + /* Copy data from bram */ + data[i] = buffer_icap_get_bram(base_address, buffer_count); + buffer_count++; + } + + return 0; +}; diff --git a/drivers/char/xilinx_hwicap/buffer_icap.h b/drivers/char/xilinx_hwicap/buffer_icap.h new file mode 100644 index 0000000..0318495 --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_BUFFER_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_BUFFER_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +void buffer_icap_reset(struct hwicap_drvdata *drvdata); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c new file mode 100644 index 0000000..0988314 --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "fifo_icap.h" + +/* Register offsets for the XHwIcap device. */ +#define XHI_GIER_OFFSET 0x1C /* Device Global Interrupt Enable Reg */ +#define XHI_IPISR_OFFSET 0x20 /* Interrupt Status Register */ +#define XHI_IPIER_OFFSET 0x28 /* Interrupt Enable Register */ +#define XHI_WF_OFFSET 0x100 /* Write FIFO */ +#define XHI_RF_OFFSET 0x104 /* Read FIFO */ +#define XHI_SZ_OFFSET 0x108 /* Size Register */ +#define XHI_CR_OFFSET 0x10C /* Control Register */ +#define XHI_SR_OFFSET 0x110 /* Status Register */ +#define XHI_WFV_OFFSET 0x114 /* Write FIFO Vacancy Register */ +#define XHI_RFO_OFFSET 0x118 /* Read FIFO Occupancy Register */ + +/* Device Global Interrupt Enable Register (GIER) bit definitions */ + +#define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ + +/** + * HwIcap Device Interrupt Status/Enable Registers + * + * Interrupt Status Register (IPISR) : This register holds the + * interrupt status flags for the device. These bits are toggle on + * write. + * + * Interrupt Enable Register (IPIER) : This register is used to enable + * interrupt sources for the device. + * Writing a '1' to a bit enables the corresponding interrupt. + * Writing a '0' to a bit disables the corresponding interrupt. + * + * IPISR/IPIER registers have the same bit definitions and are only defined + * once. + */ +#define XHI_IPIXR_RFULL_MASK 0x00000008 /* Read FIFO Full */ +#define XHI_IPIXR_WEMPTY_MASK 0x00000004 /* Write FIFO Empty */ +#define XHI_IPIXR_RDP_MASK 0x00000002 /* Read FIFO half full */ +#define XHI_IPIXR_WRP_MASK 0x00000001 /* Write FIFO half full */ +#define XHI_IPIXR_ALL_MASK 0x0000000F /* Mask of all interrupts */ + +/* Control Register (CR) */ +#define XHI_CR_SW_RESET_MASK 0x00000008 /* SW Reset Mask */ +#define XHI_CR_FIFO_CLR_MASK 0x00000004 /* FIFO Clear Mask */ +#define XHI_CR_READ_MASK 0x00000002 /* Read from ICAP to FIFO */ +#define XHI_CR_WRITE_MASK 0x00000001 /* Write from FIFO to ICAP */ + +/* Status Register (SR) */ +#define XHI_SR_CFGERR_N_MASK 0x00000100 /* Config Error Mask */ +#define XHI_SR_DALIGN_MASK 0x00000080 /* Data Alignment Mask */ +#define XHI_SR_RIP_MASK 0x00000040 /* Read back Mask */ +#define XHI_SR_IN_ABORT_N_MASK 0x00000020 /* Select Map Abort Mask */ +#define XHI_SR_DONE_MASK 0x00000001 /* Done bit Mask */ + + +#define XHI_WFO_MAX_VACANCY 1024 /* Max Write FIFO Vacancy, in words */ +#define XHI_RFO_MAX_OCCUPANCY 256 /* Max Read FIFO Occupancy, in words */ +/* The maximum amount we can request from fifo_icap_get_configuration + at once, in bytes. */ +#define XHI_MAX_READ_TRANSACTION_WORDS 0xFFF + + +/** + * fifo_icap_fifo_write: Write data to the write FIFO. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the 32-bit value to be written to the FIFO. + * + * This function will silently fail if the fifo is full. + **/ +static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, + u32 data) +{ + dev_dbg(drvdata->dev, "fifo_write: %x\n", data); + out_be32(drvdata->base_address + XHI_WF_OFFSET, data); +} + +/** + * fifo_icap_fifo_read: Read data from the Read FIFO. + * @parameter drvdata: a pointer to the drvdata. + * + * This function will silently fail if the fifo is empty. + **/ +static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) +{ + u32 data = in_be32(drvdata->base_address + XHI_RF_OFFSET); + dev_dbg(drvdata->dev, "fifo_read: %x\n", data); + return data; +} + +/** + * fifo_icap_set_read_size: Set the the size register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the size of the following read transaction, in words. + **/ +static inline void fifo_icap_set_read_size(struct hwicap_drvdata *drvdata, + u32 data) +{ + out_be32(drvdata->base_address + XHI_SZ_OFFSET, data); +} + +/** + * fifo_icap_start_config: Initiate a configuration (write) to the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_config(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_WRITE_MASK); + dev_dbg(drvdata->dev, "configuration started\n"); +} + +/** + * fifo_icap_start_readback: Initiate a readback from the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_READ_MASK); + dev_dbg(drvdata->dev, "readback started\n"); +} + +/** + * fifo_icap_busy: Return true if the ICAP is still processing a transaction. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) +{ + u32 status = in_be32(drvdata->base_address + XHI_SR_OFFSET); + dev_dbg(drvdata->dev, "Getting status = %x\n", status); + return (status & XHI_SR_DONE_MASK) ? 0 : 1; +} + +/** + * fifo_icap_write_fifo_vacancy: Query the write fifo available space. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely pushed into the write fifo. + **/ +static inline u32 fifo_icap_write_fifo_vacancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_WFV_OFFSET); +} + +/** + * fifo_icap_read_fifo_occupancy: Query the read fifo available data. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely read from the read fifo. + **/ +static inline u32 fifo_icap_read_fifo_occupancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_RFO_OFFSET); +} + +/** + * fifo_icap_set_configuration: Send configuration data to the ICAP. + * @parameter drvdata: a pointer to the drvdata. + * @parameter frame_buffer: a pointer to the data to be written to the + * ICAP device. + * @parameter num_words: the number of words (32 bit) to write to the ICAP + * device. + + * This function writes the given user data to the Write FIFO in + * polled mode and starts the transfer of the data to + * the ICAP device. + **/ +int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 write_fifo_vacancy = 0; + u32 retries = 0; + u32 remaining_words; + + dev_dbg(drvdata->dev, "fifo_set_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Read/Write + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + /* + * Set up the buffer pointer and the words to be transferred. + */ + remaining_words = num_words; + + while (remaining_words > 0) { + /* + * Wait until we have some data in the fifo. + */ + while (write_fifo_vacancy == 0) { + write_fifo_vacancy = + fifo_icap_write_fifo_vacancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + /* + * Write data into the Write FIFO. + */ + while ((write_fifo_vacancy != 0) && + (remaining_words > 0)) { + fifo_icap_fifo_write(drvdata, *frame_buffer); + + remaining_words--; + write_fifo_vacancy--; + frame_buffer++; + } + /* Start pushing whatever is in the FIFO into the ICAP. */ + fifo_icap_start_config(drvdata); + } + + /* Wait until the write has finished. */ + while (fifo_icap_busy(drvdata)) { + retries++; + if (retries > XHI_MAX_RETRIES) + break; + } + + dev_dbg(drvdata->dev, "done fifo_set_configuration\n"); + + /* + * If the requested number of words have not been read from + * the device then indicate failure. + */ + if (remaining_words != 0) + return -EIO; + + return 0; +} + +/** + * fifo_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + * + * This function reads the specified number of words from the ICAP device in + * the polled mode. + */ +int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 read_fifo_occupancy = 0; + u32 retries = 0; + u32 *data = frame_buffer; + u32 remaining_words; + u32 words_to_read; + + dev_dbg(drvdata->dev, "fifo_get_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Write/Read + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + remaining_words = num_words; + + while (remaining_words > 0) { + words_to_read = remaining_words; + /* The hardware has a limit on the number of words + that can be read at one time. */ + if (words_to_read > XHI_MAX_READ_TRANSACTION_WORDS) + words_to_read = XHI_MAX_READ_TRANSACTION_WORDS; + + remaining_words -= words_to_read; + + fifo_icap_set_read_size(drvdata, words_to_read); + fifo_icap_start_readback(drvdata); + + while (words_to_read > 0) { + /* Wait until we have some data in the fifo. */ + while (read_fifo_occupancy == 0) { + read_fifo_occupancy = + fifo_icap_read_fifo_occupancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + if (read_fifo_occupancy > words_to_read) + read_fifo_occupancy = words_to_read; + + words_to_read -= read_fifo_occupancy; + + /* Read the data from the Read FIFO. */ + while (read_fifo_occupancy != 0) { + *data++ = fifo_icap_fifo_read(drvdata); + read_fifo_occupancy--; + } + } + } + + dev_dbg(drvdata->dev, "done fifo_get_configuration\n"); + + return 0; +} + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * This function forces the software reset of the complete HWICAP device. + * All the registers will return to the default value and the FIFO is also + * flushed as a part of this software reset. + */ +void fifo_icap_reset(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Reset the device by setting/clearing the RESET bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_SW_RESET_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_SW_RESET_MASK)); + +} + +/** + * fifo_icap_flush_fifo: This function flushes the FIFOs in the device. + * @parameter drvdata: a pointer to the drvdata. + */ +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Flush the FIFO by setting/clearing the FIFO Clear bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_FIFO_CLR_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_FIFO_CLR_MASK)); +} + diff --git a/drivers/char/xilinx_hwicap/fifo_icap.h b/drivers/char/xilinx_hwicap/fifo_icap.h new file mode 100644 index 0000000..4d3068d --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_FIFO_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_FIFO_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +/* Reads integers from the device into the storage buffer. */ +int fifo_icap_get_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +/* Writes integers to the device from the storage buffer. */ +int fifo_icap_set_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +void fifo_icap_reset(struct hwicap_drvdata *drvdata); +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata); + +#endif diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c new file mode 100644 index 0000000..a93daa2 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -0,0 +1,904 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/* + * This is the code behind /dev/xilinx_icap -- it allows a user-space + * application to use the Xilinx ICAP subsystem. + * + * The following operations are possible: + * + * open open the port and initialize for access. + * release release port + * write Write a bitstream to the configuration processor. + * read Read a data stream from the configuration processor. + * + * After being opened, the port is initialized and accessed to avoid a + * corrupted first read which may occur with some hardware. The port + * is left in a desynched state, requiring that a synch sequence be + * transmitted before any valid configuration data. A user will have + * exclusive access to the device while it remains open, and the state + * of the ICAP cannot be guaranteed after the device is closed. Note + * that a complete reset of the core and the state of the ICAP cannot + * be performed on many versions of the cores, hence users of this + * device should avoid making inconsistent accesses to the device. In + * particular, accessing the read interface, without first generating + * a write containing a readback packet can leave the ICAP in an + * inaccessible state. + * + * Note that in order to use the read interface, it is first necessary + * to write a request packet to the write interface. i.e., it is not + * possible to simply readback the bitstream (or any configuration + * bits) from a device without specifically requesting them first. + * The code to craft such packets is intended to be part of the + * user-space application code that uses this device. The simplest + * way to use this interface is simply: + * + * cp foo.bit /dev/xilinx_icap + * + * Note that unless foo.bit is an appropriately constructed partial + * bitstream, this has a high likelyhood of overwriting the design + * currently programmed in the FPGA. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <asm/semaphore.h> +#include <linux/sysctl.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#ifdef CONFIG_OF +/* For open firmware. */ +#include <linux/of_device.h> +#include <linux/of_platform.h> +#endif + +#include "xilinx_hwicap.h" +#include "buffer_icap.h" +#include "fifo_icap.h" + +#define DRIVER_NAME "xilinx_icap" + +#define HWICAP_REGS (0x10000) + +/* dynamically allocate device number */ +static int xhwicap_major; +static int xhwicap_minor; +#define HWICAP_DEVICES 1 + +module_param(xhwicap_major, int, S_IRUGO); +module_param(xhwicap_minor, int, S_IRUGO); + +/* An array, which is set to true when the device is registered. */ +static bool probed_devices[HWICAP_DEVICES]; + +static struct class *icap_class; + +#define UNIMPLEMENTED 0xFFFF + +static const struct config_registers v2_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = 11, + .KEY = 12, + .CBC = 13, + .IDCODE = 14, + .AXSS = UNIMPLEMENTED, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; + +static const struct config_registers v4_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; +static const struct config_registers v5_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = 14, + .CSOB = 15, + .WBSTAR = 16, + .TIMER = 17, + .BOOTSTS = 18, + .CTL_1 = 19, +}; + +/** + * hwicap_command_desync: Send a DESYNC command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command desynchronizes the ICAP After this command, a + * bitstream containing a NULL packet, followed by a SYNCH packet is + * required before the ICAP will recognize commands. + */ +int hwicap_command_desync(struct hwicap_drvdata *drvdata) +{ + u32 buffer[4]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_DESYNCH; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); +} + +/** + * hwicap_command_capture: Send a CAPTURE command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command captures all of the flip flop states so they will be + * available during readback. One can use this command instead of + * enabling the CAPTURE block in the design. + */ +int hwicap_command_capture(struct hwicap_drvdata *drvdata) +{ + u32 buffer[7]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_GCAPTURE; + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_DUMMY_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data + * present in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); + +} + +/** + * hwicap_get_configuration_register: Query a configuration register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter reg: a constant which represents the configuration + * register value to be returned. + * Examples: XHI_IDCODE, XHI_FLR. + * @parameter RegData: returns the value of the register. + * + * Sends a query packet to the ICAP and then receives the response. + * The icap is left in Synched state. + */ +int hwicap_get_configuration_register(struct hwicap_drvdata *drvdata, + u32 reg, u32 *RegData) +{ + int status; + u32 buffer[6]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_read(reg) | 1; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + status = drvdata->config->set_configuration(drvdata, + &buffer[0], index); + if (status) + return status; + + /* + * Read the configuration register + */ + status = drvdata->config->get_configuration(drvdata, RegData, 1); + if (status) + return status; + + return 0; +} + +int hwicap_initialize_hwicap(struct hwicap_drvdata *drvdata) +{ + int status; + u32 idcode; + + dev_dbg(drvdata->dev, "initializing\n"); + + /* Abort any current transaction, to make sure we have the + * ICAP in a good state. */ + dev_dbg(drvdata->dev, "Reset...\n"); + drvdata->config->reset(drvdata); + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + /* Attempt to read the IDCODE from ICAP. This + * may not be returned correctly, due to the design of the + * hardware. + */ + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); + status = hwicap_get_configuration_register( + drvdata, drvdata->config_regs->IDCODE, &idcode); + dev_dbg(drvdata->dev, "IDCODE = %x\n", idcode); + if (status) + return status; + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + return 0; +} + +static ssize_t +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t bytes_to_read = 0; + u32 *kbuf; + u32 words; + u32 bytes_remaining; + int status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->read_buffer_in_use) { + /* If there are leftover bytes in the buffer, just */ + /* return them and don't try to read more from the */ + /* ICAP device. */ + bytes_to_read = + (count < drvdata->read_buffer_in_use) ? count : + drvdata->read_buffer_in_use; + + /* Return the data currently in the read buffer. */ + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { + status = -EFAULT; + goto error; + } + drvdata->read_buffer_in_use -= bytes_to_read; + memcpy(drvdata->read_buffer + bytes_to_read, + drvdata->read_buffer, 4 - bytes_to_read); + } else { + /* Get new data from the ICAP, and return was was requested. */ + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + /* The ICAP device is only able to read complete */ + /* words. If a number of bytes that do not correspond */ + /* to complete words is requested, then we read enough */ + /* words to get the required number of bytes, and then */ + /* save the remaining bytes for the next read. */ + + /* Determine the number of words to read, rounding up */ + /* if necessary. */ + words = ((count + 3) >> 2); + bytes_to_read = words << 2; + + if (bytes_to_read > PAGE_SIZE) + bytes_to_read = PAGE_SIZE; + + /* Ensure we only read a complete number of words. */ + bytes_remaining = bytes_to_read & 3; + bytes_to_read &= ~3; + words = bytes_to_read >> 2; + + status = drvdata->config->get_configuration(drvdata, + kbuf, words); + + /* If we didn't read correctly, then bail out. */ + if (status) { + free_page((unsigned long)kbuf); + goto error; + } + + /* If we fail to return the data to the user, then bail out. */ + if (copy_to_user(buf, kbuf, bytes_to_read)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + memcpy(kbuf, drvdata->read_buffer, bytes_remaining); + drvdata->read_buffer_in_use = bytes_remaining; + free_page((unsigned long)kbuf); + } + status = bytes_to_read; + error: + up(&drvdata->sem); + return status; +} + +static ssize_t +hwicap_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t written = 0; + ssize_t left = count; + u32 *kbuf; + ssize_t len; + ssize_t status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + left += drvdata->write_buffer_in_use; + + /* Only write multiples of 4 bytes. */ + if (left < 4) { + status = 0; + goto error; + } + + kbuf = (u32 *) __get_free_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + while (left > 3) { + /* only write multiples of 4 bytes, so there might */ + /* be as many as 3 bytes left (at the end). */ + len = left; + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + len &= ~3; + + if (drvdata->write_buffer_in_use) { + memcpy(kbuf, drvdata->write_buffer, + drvdata->write_buffer_in_use); + if (copy_from_user( + (((char *)kbuf) + (drvdata->write_buffer_in_use)), + buf + written, + len - (drvdata->write_buffer_in_use))) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } else { + if (copy_from_user(kbuf, buf + written, len)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } + + status = drvdata->config->set_configuration(drvdata, + kbuf, len >> 2); + + if (status) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + if (drvdata->write_buffer_in_use) { + len -= drvdata->write_buffer_in_use; + left -= drvdata->write_buffer_in_use; + drvdata->write_buffer_in_use = 0; + } + written += len; + left -= len; + } + if ((left > 0) && (left < 4)) { + if (!copy_from_user(drvdata->write_buffer, + buf + written, left)) { + drvdata->write_buffer_in_use = left; + written += left; + left = 0; + } + } + + free_page((unsigned long)kbuf); + status = written; + error: + up(&drvdata->sem); + return status; +} + +static int hwicap_open(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata; + int status; + + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->is_open) { + status = -EBUSY; + goto error; + } + + status = hwicap_initialize_hwicap(drvdata); + if (status) { + dev_err(drvdata->dev, "Failed to open file"); + goto error; + } + + file->private_data = drvdata; + drvdata->write_buffer_in_use = 0; + drvdata->read_buffer_in_use = 0; + drvdata->is_open = 1; + + error: + up(&drvdata->sem); + return status; +} + +static int hwicap_release(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata = file->private_data; + int i; + int status = 0; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->write_buffer_in_use) { + /* Flush write buffer. */ + for (i = drvdata->write_buffer_in_use; i < 4; i++) + drvdata->write_buffer[i] = 0; + + status = drvdata->config->set_configuration(drvdata, + (u32 *) drvdata->write_buffer, 1); + if (status) + goto error; + } + + status = hwicap_command_desync(drvdata); + if (status) + goto error; + + error: + drvdata->is_open = 0; + up(&drvdata->sem); + return status; +} + +static struct file_operations hwicap_fops = { + .owner = THIS_MODULE, + .write = hwicap_write, + .read = hwicap_read, + .open = hwicap_open, + .release = hwicap_release, +}; + +static int __devinit hwicap_setup(struct device *dev, int id, + const struct resource *regs_res, + const struct hwicap_driver_config *config, + const struct config_registers *config_regs) +{ + dev_t devt; + struct hwicap_drvdata *drvdata = NULL; + int retval = 0; + + dev_info(dev, "Xilinx icap port driver\n"); + + if (id < 0) { + for (id = 0; id < HWICAP_DEVICES; id++) + if (!probed_devices[id]) + break; + } + if (id < 0 || id >= HWICAP_DEVICES) { + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); + return -EINVAL; + } + if (probed_devices[id]) { + dev_err(dev, "cannot assign to %s%i; it is already in use\n", + DRIVER_NAME, id); + return -EBUSY; + } + + probed_devices[id] = 1; + + devt = MKDEV(xhwicap_major, xhwicap_minor + id); + + drvdata = kmalloc(sizeof(struct hwicap_drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + return -ENOMEM; + } + memset((void *)drvdata, 0, sizeof(struct hwicap_drvdata)); + dev_set_drvdata(dev, (void *)drvdata); + + if (!regs_res) { + dev_err(dev, "Couldn't get registers resource\n"); + retval = -EFAULT; + goto failed1; + } + + drvdata->mem_start = regs_res->start; + drvdata->mem_end = regs_res->end; + drvdata->mem_size = regs_res->end - regs_res->start + 1; + + if (!request_mem_region(drvdata->mem_start, + drvdata->mem_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at %p\n", + (void *)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + drvdata->devt = devt; + drvdata->dev = dev; + drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); + if (!drvdata->base_address) { + dev_err(dev, "ioremap() failed\n"); + goto failed2; + } + + drvdata->config = config; + drvdata->config_regs = config_regs; + + init_MUTEX(&drvdata->sem); + drvdata->is_open = 0; + + dev_info(dev, "ioremap %lx to %p with size %x\n", + (unsigned long int)drvdata->mem_start, + drvdata->base_address, drvdata->mem_size); + + cdev_init(&drvdata->cdev, &hwicap_fops); + drvdata->cdev.owner = THIS_MODULE; + retval = cdev_add(&drvdata->cdev, devt, 1); + if (retval) { + dev_err(dev, "cdev_add() failed\n"); + goto failed3; + } + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ + class_device_create(icap_class, NULL, devt, NULL, DRIVER_NAME); + return 0; /* success */ + + failed3: + iounmap(drvdata->base_address); + + failed2: + release_mem_region(regs_res->start, drvdata->mem_size); + + failed1: + kfree(drvdata); + + return retval; +} + +static struct hwicap_driver_config buffer_icap_config = { + .get_configuration = buffer_icap_get_configuration, + .set_configuration = buffer_icap_set_configuration, + .reset = buffer_icap_reset, +}; + +static struct hwicap_driver_config fifo_icap_config = { + .get_configuration = fifo_icap_get_configuration, + .set_configuration = fifo_icap_set_configuration, + .reset = fifo_icap_reset, +}; + +static int __devexit hwicap_remove(struct device *dev) +{ + struct hwicap_drvdata *drvdata; + + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); + + if (!drvdata) + return 0; + + class_device_destroy(icap_class, drvdata->devt); + cdev_del(&drvdata->cdev); + iounmap(drvdata->base_address); + release_mem_region(drvdata->mem_start, drvdata->mem_size); + kfree(drvdata); + dev_set_drvdata(dev, NULL); + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; + + return 0; /* success */ +} + +static int __devinit hwicap_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct config_registers *regs; + const char *family; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = pdev->dev.platform_data; + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + + return hwicap_setup(&pdev->dev, pdev->id, res, + &buffer_icap_config, regs); +} + +static int __devexit hwicap_drv_remove(struct platform_device *pdev) +{ + return hwicap_remove(&pdev->dev); +} + +static struct platform_driver hwicap_platform_driver = { + .probe = hwicap_drv_probe, + .remove = hwicap_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +#if defined(CONFIG_OF) +static int __devinit +hwicap_of_probe(struct of_device *op, const struct of_device_id *match) +{ + struct resource res; + const unsigned int *id; + const char *family; + int rc; + const struct hwicap_driver_config *config = match->data; + const struct config_registers *regs; + + dev_dbg(&op->dev, "hwicap_of_probe(%p, %p)\n", op, match); + + rc = of_address_to_resource(op->node, 0, &res); + if (rc) { + dev_err(&op->dev, "invalid address\n"); + return rc; + } + + id = of_get_property(op->node, "port-number", NULL); + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = of_get_property(op->node, "xlnx,family", NULL); + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + return hwicap_setup(&op->dev, id ? *id : -1, &res, config, + regs); +} + +static int __devexit hwicap_of_remove(struct of_device *op) +{ + return hwicap_remove(&op->dev); +} + +/* Match table for of_platform binding */ +static const struct of_device_id __devinit hwicap_of_match[] = { + { .compatible = "xlnx,opb-hwicap-1.00.b", .data = &buffer_icap_config}, + { .compatible = "xlnx,xps-hwicap-1.00.a", .data = &fifo_icap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, hwicap_of_match); + +static struct of_platform_driver hwicap_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = hwicap_of_match, + .probe = hwicap_of_probe, + .remove = __devexit_p(hwicap_of_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +/* Registration helpers to keep the number of #ifdefs to a minimum */ +static inline int __devinit hwicap_of_register(void) +{ + pr_debug("hwicap: calling of_register_platform_driver()\n"); + return of_register_platform_driver(&hwicap_of_driver); +} + +static inline void __devexit hwicap_of_unregister(void) +{ + of_unregister_platform_driver(&hwicap_of_driver); +} +#else /* CONFIG_OF */ +/* CONFIG_OF not enabled; do nothing helpers */ +static inline int __devinit hwicap_of_register(void) { return 0; } +static inline void __devexit hwicap_of_unregister(void) { } +#endif /* CONFIG_OF */ + +static int __devinit hwicap_module_init(void) +{ + dev_t devt; + int retval; + + icap_class = class_create(THIS_MODULE, "xilinx_config"); + + if (xhwicap_major) { + devt = MKDEV(xhwicap_major, xhwicap_minor); + retval = register_chrdev_region( + devt, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + } else { + retval = alloc_chrdev_region(&devt, + xhwicap_minor, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + xhwicap_major = MAJOR(devt); + } + + retval = platform_driver_register(&hwicap_platform_driver); + + if (retval) + goto failed1; + + retval = hwicap_of_register(); + + if (retval) + goto failed2; + + return retval; + + failed2: + platform_driver_unregister(&hwicap_platform_driver); + + failed1: + unregister_chrdev_region(devt, HWICAP_DEVICES); + + return retval; +} + +static void __devexit hwicap_module_cleanup(void) +{ + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); + + class_destroy(icap_class); + + platform_driver_unregister(&hwicap_platform_driver); + + hwicap_of_unregister(); + + unregister_chrdev_region(devt, HWICAP_DEVICES); +} + +module_init(hwicap_module_init); +module_exit(hwicap_module_cleanup); + +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h new file mode 100644 index 0000000..b6b47d0 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2007 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ +#define XILINX_HWICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +struct hwicap_drvdata { + u32 write_buffer_in_use; /* Always in [0,3] */ + u8 write_buffer[4]; + u32 read_buffer_in_use; /* Always in [0,3] */ + u8 read_buffer[4]; + u32 mem_start; /* phys. address of the control registers */ + u32 mem_end; /* phys. address of the control registers */ + u32 mem_size; + void __iomem *base_address;/* virt. address of the control registers */ + + struct device *dev; + struct cdev cdev; /* Char device structure */ + dev_t devt; + + const struct hwicap_driver_config *config; + const struct config_registers *config_regs; + void *private_data; + bool is_open; + bool is_accessing; +}; + +struct hwicap_driver_config { + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + void (*reset)(struct hwicap_drvdata *drvdata); +}; + +/* Number of times to poll the done regsiter */ +#define XHI_MAX_RETRIES 10 + +/************ Constant Definitions *************/ + +#define XHI_PAD_FRAMES 0x1 + +/* Mask for calculating configuration packet headers */ +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL +#define XHI_TYPE_MASK 0x7 +#define XHI_REGISTER_MASK 0xF +#define XHI_OP_MASK 0x3 + +#define XHI_TYPE_SHIFT 29 +#define XHI_REGISTER_SHIFT 13 +#define XHI_OP_SHIFT 27 + +#define XHI_TYPE_1 1 +#define XHI_TYPE_2 2 +#define XHI_OP_WRITE 2 +#define XHI_OP_READ 1 + +/* Address Block Types */ +#define XHI_FAR_CLB_BLOCK 0 +#define XHI_FAR_BRAM_BLOCK 1 +#define XHI_FAR_BRAM_INT_BLOCK 2 + +struct config_registers { + u32 CRC; + u32 FAR; + u32 FDRI; + u32 FDRO; + u32 CMD; + u32 CTL; + u32 MASK; + u32 STAT; + u32 LOUT; + u32 COR; + u32 MFWR; + u32 FLR; + u32 KEY; + u32 CBC; + u32 IDCODE; + u32 AXSS; + u32 C0R_1; + u32 CSOB; + u32 WBSTAR; + u32 TIMER; + u32 BOOTSTS; + u32 CTL_1; +}; + +/* Configuration Commands */ +#define XHI_CMD_NULL 0 +#define XHI_CMD_WCFG 1 +#define XHI_CMD_MFW 2 +#define XHI_CMD_DGHIGH 3 +#define XHI_CMD_RCFG 4 +#define XHI_CMD_START 5 +#define XHI_CMD_RCAP 6 +#define XHI_CMD_RCRC 7 +#define XHI_CMD_AGHIGH 8 +#define XHI_CMD_SWITCH 9 +#define XHI_CMD_GRESTORE 10 +#define XHI_CMD_SHUTDOWN 11 +#define XHI_CMD_GCAPTURE 12 +#define XHI_CMD_DESYNCH 13 +#define XHI_CMD_IPROG 15 /* Only in Virtex5 */ +#define XHI_CMD_CRCC 16 /* Only in Virtex5 */ +#define XHI_CMD_LTIMER 17 /* Only in Virtex5 */ + +/* Packet constants */ +#define XHI_SYNC_PACKET 0xAA995566UL +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_READ << XHI_OP_SHIFT)) + +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_WRITE << XHI_OP_SHIFT)) + +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF + +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL +#define XHI_TYPE_1_HEADER_BYTES 4 +#define XHI_TYPE_2_HEADER_BYTES 8 + +/* Constant to use for CRC check when CRC has been disabled */ +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL + +/** + * hwicap_type_1_read: Generates a Type 1 read packet header. + * @parameter: Register is the address of the register to be read back. + * + * Generates a Type 1 read packet header, which is used to indirectly + * read registers in the configuration logic. This packet must then + * be sent through the icap device, and a return packet received with + * the information. + **/ +static inline u32 hwicap_type_1_read(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_READ << XHI_OP_SHIFT); +} + +/** + * hwicap_type_1_write: Generates a Type 1 write packet header + * @parameter: Register is the address of the register to be read back. + **/ +static inline u32 hwicap_type_1_write(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_WRITE << XHI_OP_SHIFT); +} + +#endif -- 1.5.3.4-dirty ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-02-01 20:03 ` [PATCH] [POWERPC] Xilinx: hwicap driver Stephen Neuendorffer @ 2008-02-01 20:11 ` Grant Likely 2008-02-01 21:12 ` Stephen Neuendorffer [not found] ` <1201900363-25230-1-git-send-email-stephen.neuendorffer@xilinx.com> 0 siblings, 2 replies; 21+ messages in thread From: Grant Likely @ 2008-02-01 20:11 UTC (permalink / raw) To: Stephen Neuendorffer; +Cc: linuxppc-dev, ntl On 2/1/08, Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> wrote: > This includes code for new fifo-based xps_hwicap in addition to the > older opb_hwicap, which has a significantly different interface. The > common code between the two drivers is largely shared. > > Significant differences exists between this driver and what is > supported in the EDK drivers. In particular, most of the > architecture-specific code for reconfiguring individual FPGA resources > has been removed. This functionality is likely better provided in a > user-space support library. In addition, read and write access is > supported. In addition, although the xps_hwicap cores support > interrupt-driver mode, this driver only supports polled operation, in > order to make the code simpler, and since the interrupt processing > overhead is likely to slow down the throughput under Linux. > > Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> > > Fixed to add mutexes, and a few style issues. Inconsistent indentation (tabs vs. spaces) in some places. Otherwise: Acked-by: Grant Likely <grant.likely@secretlab.ca> g. > +static int __devexit hwicap_remove(struct device *dev) > +{ > + struct hwicap_drvdata *drvdata; > + > + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); > + > + if (!drvdata) > + return 0; > + > + class_device_destroy(icap_class, drvdata->devt); > + cdev_del(&drvdata->cdev); > + iounmap(drvdata->base_address); > + release_mem_region(drvdata->mem_start, drvdata->mem_size); > + kfree(drvdata); > + dev_set_drvdata(dev, NULL); > + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; > + > + return 0; /* success */ > +} Here in particular -- Grant Likely, B.Sc., P.Eng. Secret Lab Technologies Ltd. ^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH] [POWERPC] Xilinx: hwicap driver 2008-02-01 20:11 ` Grant Likely @ 2008-02-01 21:12 ` Stephen Neuendorffer [not found] ` <1201900363-25230-1-git-send-email-stephen.neuendorffer@xilinx.com> 1 sibling, 0 replies; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-01 21:12 UTC (permalink / raw) To: linuxppc-dev, grant.likely, jacmet, ntl This includes code for new fifo-based xps_hwicap in addition to the older opb_hwicap, which has a significantly different interface. The common code between the two drivers is largely shared. Significant differences exists between this driver and what is supported in the EDK drivers. In particular, most of the architecture-specific code for reconfiguring individual FPGA resources has been removed. This functionality is likely better provided in a user-space support library. In addition, read and write access is supported. In addition, although the xps_hwicap cores support interrupt-driver mode, this driver only supports polled operation, in order to make the code simpler, and since the interrupt processing overhead is likely to slow down the throughput under Linux. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Fixed to add mutexes, and a few style issues. Acked-by: Grant Likely <grant.likely@secretlab.ca> --- I will get this right eventually. :) --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 1 + drivers/char/xilinx_hwicap/Makefile | 7 + drivers/char/xilinx_hwicap/buffer_icap.c | 380 ++++++++++++ drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ drivers/char/xilinx_hwicap/xilinx_hwicap.c | 904 ++++++++++++++++++++++++++++ drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ 9 files changed, 1992 insertions(+), 0 deletions(-) create mode 100644 drivers/char/xilinx_hwicap/Makefile create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ef1ed5d..157ae2a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -831,6 +831,13 @@ config DTLK To compile this driver as a module, choose M here: the module will be called dtlk. +config XILINX_HWICAP + tristate "Xilinx HWICAP Support" + depends on XILINX_VIRTEX + help + This option enables support for Xilinx Internal Configuration + Access Port (ICAP) driver. + config R3964 tristate "Siemens R3964 line discipline" ---help--- diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d5..3a278a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ ifeq ($(CONFIG_GENERIC_NVRAM),y) obj-$(CONFIG_NVRAM) += generic_nvram.o else diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile new file mode 100644 index 0000000..5491cbc --- /dev/null +++ b/drivers/char/xilinx_hwicap/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Xilinx OPB hwicap driver +# + +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o + +xilinx_hwicap_m-y := xilinx_hwicap.o fifo_icap.o buffer_icap.o diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c new file mode 100644 index 0000000..dfea2bd --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.c @@ -0,0 +1,380 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "buffer_icap.h" + +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ +#define XHI_MAX_BUFFER_BYTES 2048 +#define XHI_MAX_BUFFER_INTS (XHI_MAX_BUFFER_BYTES >> 2) + +/* File access and error constants */ +#define XHI_DEVICE_READ_ERROR -1 +#define XHI_DEVICE_WRITE_ERROR -2 +#define XHI_BUFFER_OVERFLOW_ERROR -3 + +#define XHI_DEVICE_READ 0x1 +#define XHI_DEVICE_WRITE 0x0 + +/* Constants for checking transfer status */ +#define XHI_CYCLE_DONE 0 +#define XHI_CYCLE_EXECUTING 1 + +/* buffer_icap register offsets */ + +/* Size of transfer, read & write */ +#define XHI_SIZE_REG_OFFSET 0x800L +/* offset into bram, read & write */ +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L +/* Read not Configure, direction of transfer. Write only */ +#define XHI_RNC_REG_OFFSET 0x808L +/* Indicates transfer complete. Read only */ +#define XHI_STATUS_REG_OFFSET 0x80CL + +/* Constants for setting the RNC register */ +#define XHI_CONFIGURE 0x0UL +#define XHI_READBACK 0x1UL + +/* Constants for the Done register */ +#define XHI_NOT_FINISHED 0x0UL +#define XHI_FINISHED 0x1UL + +#define XHI_BUFFER_START 0 + +/** + * buffer_icap_get_status: Get the contents of the status register. + * @parameter base_address: is the base address of the device + * + * The status register contains the ICAP status and the done bit. + * + * D8 - cfgerr + * D7 - dalign + * D6 - rip + * D5 - in_abort_l + * D4 - Always 1 + * D3 - Always 1 + * D2 - Always 1 + * D1 - Always 1 + * D0 - Done bit + **/ +static inline u32 buffer_icap_get_status(void __iomem *base_address) +{ + return in_be32(base_address + XHI_STATUS_REG_OFFSET); +} + +/** + * buffer_icap_get_bram: Reads data from the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset from which the data should be read. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline u32 buffer_icap_get_bram(void __iomem *base_address, + u32 offset) +{ + return in_be32(base_address + (offset << 2)); +} + +/** + * buffer_icap_busy: Return true if the icap device is busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_busy(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_NOT_FINISHED; +} + +/** + * buffer_icap_busy: Return true if the icap device is not busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_done(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_FINISHED; +} + +/** + * buffer_icap_set_size: Set the size register. + * @parameter base_address: is the base address of the device + * @parameter data: The size in bytes. + * + * The size register holds the number of 8 bit bytes to transfer between + * bram and the icap (or icap to bram). + **/ +static inline void buffer_icap_set_size(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_SIZE_REG_OFFSET, data); +} + +/** + * buffer_icap_mSetoffsetReg: Set the bram offset register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The bram offset register holds the starting bram address to transfer + * data from during configuration or write data to during readback. + **/ +static inline void buffer_icap_set_offset(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_BRAM_OFFSET_REG_OFFSET, data); +} + +/** + * buffer_icap_set_rnc: Set the RNC (Readback not Configure) register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The RNC register determines the direction of the data transfer. It + * controls whether a configuration or readback take place. Writing to + * this register initiates the transfer. A value of 1 initiates a + * readback while writing a value of 0 initiates a configuration. + **/ +static inline void buffer_icap_set_rnc(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_RNC_REG_OFFSET, data); +} + +/** + * buffer_icap_set_bram: Write data to the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset at which the data should be written. + * @parameter data: The value to be written to the bram offset. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline void buffer_icap_set_bram(void __iomem *base_address, + u32 offset, u32 data) +{ + out_be32(base_address + (offset << 2), data); +} + +/** + * buffer_icap_device_read: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, (count << 2)); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_READBACK); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, count << 2); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * Writing to the status register resets the ICAP logic in an internal + * version of the core. For the version of the core published in EDK, + * this is a noop. + **/ +void buffer_icap_reset(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_STATUS_REG_OFFSET, 0xFEFE); +} + +/** + * buffer_icap_set_configuration: Load a partial bitstream from system memory. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Kernel address of the partial bitstream. + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 num_writes = 0; + bool dirty = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = 0; i < size; i++) { + + /* Copy data to bram */ + buffer_icap_set_bram(base_address, buffer_count, data[i]); + dirty = 1; + + if (buffer_count < XHI_MAX_BUFFER_INTS - 1) { + buffer_count++; + continue; + } + + /* Write data to ICAP */ + status = buffer_icap_device_write( + drvdata, + XHI_BUFFER_START, + XHI_MAX_BUFFER_INTS); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + num_writes++; + dirty = 0; + } + + /* Write unwritten data to ICAP */ + if (dirty) { + /* Write data to ICAP */ + status = buffer_icap_device_write(drvdata, XHI_BUFFER_START, + buffer_count); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + } + return status; + } + + return 0; +}; + +/** + * buffer_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 read_count = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = XHI_MAX_BUFFER_INTS; i < size; i++) { + if (buffer_count == XHI_MAX_BUFFER_INTS) { + u32 words_remaining = size - i; + u32 words_to_read = + words_remaining < + XHI_MAX_BUFFER_INTS ? words_remaining : + XHI_MAX_BUFFER_INTS; + + /* Read data from ICAP */ + status = buffer_icap_device_read( + drvdata, + XHI_BUFFER_START, + words_to_read); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + read_count++; + } + + /* Copy data from bram */ + data[i] = buffer_icap_get_bram(base_address, buffer_count); + buffer_count++; + } + + return 0; +}; diff --git a/drivers/char/xilinx_hwicap/buffer_icap.h b/drivers/char/xilinx_hwicap/buffer_icap.h new file mode 100644 index 0000000..0318495 --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_BUFFER_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_BUFFER_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +void buffer_icap_reset(struct hwicap_drvdata *drvdata); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c new file mode 100644 index 0000000..0988314 --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "fifo_icap.h" + +/* Register offsets for the XHwIcap device. */ +#define XHI_GIER_OFFSET 0x1C /* Device Global Interrupt Enable Reg */ +#define XHI_IPISR_OFFSET 0x20 /* Interrupt Status Register */ +#define XHI_IPIER_OFFSET 0x28 /* Interrupt Enable Register */ +#define XHI_WF_OFFSET 0x100 /* Write FIFO */ +#define XHI_RF_OFFSET 0x104 /* Read FIFO */ +#define XHI_SZ_OFFSET 0x108 /* Size Register */ +#define XHI_CR_OFFSET 0x10C /* Control Register */ +#define XHI_SR_OFFSET 0x110 /* Status Register */ +#define XHI_WFV_OFFSET 0x114 /* Write FIFO Vacancy Register */ +#define XHI_RFO_OFFSET 0x118 /* Read FIFO Occupancy Register */ + +/* Device Global Interrupt Enable Register (GIER) bit definitions */ + +#define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ + +/** + * HwIcap Device Interrupt Status/Enable Registers + * + * Interrupt Status Register (IPISR) : This register holds the + * interrupt status flags for the device. These bits are toggle on + * write. + * + * Interrupt Enable Register (IPIER) : This register is used to enable + * interrupt sources for the device. + * Writing a '1' to a bit enables the corresponding interrupt. + * Writing a '0' to a bit disables the corresponding interrupt. + * + * IPISR/IPIER registers have the same bit definitions and are only defined + * once. + */ +#define XHI_IPIXR_RFULL_MASK 0x00000008 /* Read FIFO Full */ +#define XHI_IPIXR_WEMPTY_MASK 0x00000004 /* Write FIFO Empty */ +#define XHI_IPIXR_RDP_MASK 0x00000002 /* Read FIFO half full */ +#define XHI_IPIXR_WRP_MASK 0x00000001 /* Write FIFO half full */ +#define XHI_IPIXR_ALL_MASK 0x0000000F /* Mask of all interrupts */ + +/* Control Register (CR) */ +#define XHI_CR_SW_RESET_MASK 0x00000008 /* SW Reset Mask */ +#define XHI_CR_FIFO_CLR_MASK 0x00000004 /* FIFO Clear Mask */ +#define XHI_CR_READ_MASK 0x00000002 /* Read from ICAP to FIFO */ +#define XHI_CR_WRITE_MASK 0x00000001 /* Write from FIFO to ICAP */ + +/* Status Register (SR) */ +#define XHI_SR_CFGERR_N_MASK 0x00000100 /* Config Error Mask */ +#define XHI_SR_DALIGN_MASK 0x00000080 /* Data Alignment Mask */ +#define XHI_SR_RIP_MASK 0x00000040 /* Read back Mask */ +#define XHI_SR_IN_ABORT_N_MASK 0x00000020 /* Select Map Abort Mask */ +#define XHI_SR_DONE_MASK 0x00000001 /* Done bit Mask */ + + +#define XHI_WFO_MAX_VACANCY 1024 /* Max Write FIFO Vacancy, in words */ +#define XHI_RFO_MAX_OCCUPANCY 256 /* Max Read FIFO Occupancy, in words */ +/* The maximum amount we can request from fifo_icap_get_configuration + at once, in bytes. */ +#define XHI_MAX_READ_TRANSACTION_WORDS 0xFFF + + +/** + * fifo_icap_fifo_write: Write data to the write FIFO. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the 32-bit value to be written to the FIFO. + * + * This function will silently fail if the fifo is full. + **/ +static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, + u32 data) +{ + dev_dbg(drvdata->dev, "fifo_write: %x\n", data); + out_be32(drvdata->base_address + XHI_WF_OFFSET, data); +} + +/** + * fifo_icap_fifo_read: Read data from the Read FIFO. + * @parameter drvdata: a pointer to the drvdata. + * + * This function will silently fail if the fifo is empty. + **/ +static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) +{ + u32 data = in_be32(drvdata->base_address + XHI_RF_OFFSET); + dev_dbg(drvdata->dev, "fifo_read: %x\n", data); + return data; +} + +/** + * fifo_icap_set_read_size: Set the the size register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the size of the following read transaction, in words. + **/ +static inline void fifo_icap_set_read_size(struct hwicap_drvdata *drvdata, + u32 data) +{ + out_be32(drvdata->base_address + XHI_SZ_OFFSET, data); +} + +/** + * fifo_icap_start_config: Initiate a configuration (write) to the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_config(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_WRITE_MASK); + dev_dbg(drvdata->dev, "configuration started\n"); +} + +/** + * fifo_icap_start_readback: Initiate a readback from the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_READ_MASK); + dev_dbg(drvdata->dev, "readback started\n"); +} + +/** + * fifo_icap_busy: Return true if the ICAP is still processing a transaction. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) +{ + u32 status = in_be32(drvdata->base_address + XHI_SR_OFFSET); + dev_dbg(drvdata->dev, "Getting status = %x\n", status); + return (status & XHI_SR_DONE_MASK) ? 0 : 1; +} + +/** + * fifo_icap_write_fifo_vacancy: Query the write fifo available space. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely pushed into the write fifo. + **/ +static inline u32 fifo_icap_write_fifo_vacancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_WFV_OFFSET); +} + +/** + * fifo_icap_read_fifo_occupancy: Query the read fifo available data. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely read from the read fifo. + **/ +static inline u32 fifo_icap_read_fifo_occupancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_RFO_OFFSET); +} + +/** + * fifo_icap_set_configuration: Send configuration data to the ICAP. + * @parameter drvdata: a pointer to the drvdata. + * @parameter frame_buffer: a pointer to the data to be written to the + * ICAP device. + * @parameter num_words: the number of words (32 bit) to write to the ICAP + * device. + + * This function writes the given user data to the Write FIFO in + * polled mode and starts the transfer of the data to + * the ICAP device. + **/ +int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 write_fifo_vacancy = 0; + u32 retries = 0; + u32 remaining_words; + + dev_dbg(drvdata->dev, "fifo_set_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Read/Write + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + /* + * Set up the buffer pointer and the words to be transferred. + */ + remaining_words = num_words; + + while (remaining_words > 0) { + /* + * Wait until we have some data in the fifo. + */ + while (write_fifo_vacancy == 0) { + write_fifo_vacancy = + fifo_icap_write_fifo_vacancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + /* + * Write data into the Write FIFO. + */ + while ((write_fifo_vacancy != 0) && + (remaining_words > 0)) { + fifo_icap_fifo_write(drvdata, *frame_buffer); + + remaining_words--; + write_fifo_vacancy--; + frame_buffer++; + } + /* Start pushing whatever is in the FIFO into the ICAP. */ + fifo_icap_start_config(drvdata); + } + + /* Wait until the write has finished. */ + while (fifo_icap_busy(drvdata)) { + retries++; + if (retries > XHI_MAX_RETRIES) + break; + } + + dev_dbg(drvdata->dev, "done fifo_set_configuration\n"); + + /* + * If the requested number of words have not been read from + * the device then indicate failure. + */ + if (remaining_words != 0) + return -EIO; + + return 0; +} + +/** + * fifo_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + * + * This function reads the specified number of words from the ICAP device in + * the polled mode. + */ +int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 read_fifo_occupancy = 0; + u32 retries = 0; + u32 *data = frame_buffer; + u32 remaining_words; + u32 words_to_read; + + dev_dbg(drvdata->dev, "fifo_get_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Write/Read + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + remaining_words = num_words; + + while (remaining_words > 0) { + words_to_read = remaining_words; + /* The hardware has a limit on the number of words + that can be read at one time. */ + if (words_to_read > XHI_MAX_READ_TRANSACTION_WORDS) + words_to_read = XHI_MAX_READ_TRANSACTION_WORDS; + + remaining_words -= words_to_read; + + fifo_icap_set_read_size(drvdata, words_to_read); + fifo_icap_start_readback(drvdata); + + while (words_to_read > 0) { + /* Wait until we have some data in the fifo. */ + while (read_fifo_occupancy == 0) { + read_fifo_occupancy = + fifo_icap_read_fifo_occupancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + if (read_fifo_occupancy > words_to_read) + read_fifo_occupancy = words_to_read; + + words_to_read -= read_fifo_occupancy; + + /* Read the data from the Read FIFO. */ + while (read_fifo_occupancy != 0) { + *data++ = fifo_icap_fifo_read(drvdata); + read_fifo_occupancy--; + } + } + } + + dev_dbg(drvdata->dev, "done fifo_get_configuration\n"); + + return 0; +} + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * This function forces the software reset of the complete HWICAP device. + * All the registers will return to the default value and the FIFO is also + * flushed as a part of this software reset. + */ +void fifo_icap_reset(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Reset the device by setting/clearing the RESET bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_SW_RESET_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_SW_RESET_MASK)); + +} + +/** + * fifo_icap_flush_fifo: This function flushes the FIFOs in the device. + * @parameter drvdata: a pointer to the drvdata. + */ +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Flush the FIFO by setting/clearing the FIFO Clear bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_FIFO_CLR_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_FIFO_CLR_MASK)); +} + diff --git a/drivers/char/xilinx_hwicap/fifo_icap.h b/drivers/char/xilinx_hwicap/fifo_icap.h new file mode 100644 index 0000000..4d3068d --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_FIFO_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_FIFO_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +/* Reads integers from the device into the storage buffer. */ +int fifo_icap_get_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +/* Writes integers to the device from the storage buffer. */ +int fifo_icap_set_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +void fifo_icap_reset(struct hwicap_drvdata *drvdata); +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata); + +#endif diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c new file mode 100644 index 0000000..24f6aef --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -0,0 +1,904 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/* + * This is the code behind /dev/xilinx_icap -- it allows a user-space + * application to use the Xilinx ICAP subsystem. + * + * The following operations are possible: + * + * open open the port and initialize for access. + * release release port + * write Write a bitstream to the configuration processor. + * read Read a data stream from the configuration processor. + * + * After being opened, the port is initialized and accessed to avoid a + * corrupted first read which may occur with some hardware. The port + * is left in a desynched state, requiring that a synch sequence be + * transmitted before any valid configuration data. A user will have + * exclusive access to the device while it remains open, and the state + * of the ICAP cannot be guaranteed after the device is closed. Note + * that a complete reset of the core and the state of the ICAP cannot + * be performed on many versions of the cores, hence users of this + * device should avoid making inconsistent accesses to the device. In + * particular, accessing the read interface, without first generating + * a write containing a readback packet can leave the ICAP in an + * inaccessible state. + * + * Note that in order to use the read interface, it is first necessary + * to write a request packet to the write interface. i.e., it is not + * possible to simply readback the bitstream (or any configuration + * bits) from a device without specifically requesting them first. + * The code to craft such packets is intended to be part of the + * user-space application code that uses this device. The simplest + * way to use this interface is simply: + * + * cp foo.bit /dev/xilinx_icap + * + * Note that unless foo.bit is an appropriately constructed partial + * bitstream, this has a high likelyhood of overwriting the design + * currently programmed in the FPGA. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <asm/semaphore.h> +#include <linux/sysctl.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#ifdef CONFIG_OF +/* For open firmware. */ +#include <linux/of_device.h> +#include <linux/of_platform.h> +#endif + +#include "xilinx_hwicap.h" +#include "buffer_icap.h" +#include "fifo_icap.h" + +#define DRIVER_NAME "xilinx_icap" + +#define HWICAP_REGS (0x10000) + +/* dynamically allocate device number */ +static int xhwicap_major; +static int xhwicap_minor; +#define HWICAP_DEVICES 1 + +module_param(xhwicap_major, int, S_IRUGO); +module_param(xhwicap_minor, int, S_IRUGO); + +/* An array, which is set to true when the device is registered. */ +static bool probed_devices[HWICAP_DEVICES]; + +static struct class *icap_class; + +#define UNIMPLEMENTED 0xFFFF + +static const struct config_registers v2_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = 11, + .KEY = 12, + .CBC = 13, + .IDCODE = 14, + .AXSS = UNIMPLEMENTED, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; + +static const struct config_registers v4_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; +static const struct config_registers v5_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = 14, + .CSOB = 15, + .WBSTAR = 16, + .TIMER = 17, + .BOOTSTS = 18, + .CTL_1 = 19, +}; + +/** + * hwicap_command_desync: Send a DESYNC command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command desynchronizes the ICAP After this command, a + * bitstream containing a NULL packet, followed by a SYNCH packet is + * required before the ICAP will recognize commands. + */ +int hwicap_command_desync(struct hwicap_drvdata *drvdata) +{ + u32 buffer[4]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_DESYNCH; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); +} + +/** + * hwicap_command_capture: Send a CAPTURE command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command captures all of the flip flop states so they will be + * available during readback. One can use this command instead of + * enabling the CAPTURE block in the design. + */ +int hwicap_command_capture(struct hwicap_drvdata *drvdata) +{ + u32 buffer[7]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_GCAPTURE; + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_DUMMY_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data + * present in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); + +} + +/** + * hwicap_get_configuration_register: Query a configuration register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter reg: a constant which represents the configuration + * register value to be returned. + * Examples: XHI_IDCODE, XHI_FLR. + * @parameter RegData: returns the value of the register. + * + * Sends a query packet to the ICAP and then receives the response. + * The icap is left in Synched state. + */ +int hwicap_get_configuration_register(struct hwicap_drvdata *drvdata, + u32 reg, u32 *RegData) +{ + int status; + u32 buffer[6]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_read(reg) | 1; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + status = drvdata->config->set_configuration(drvdata, + &buffer[0], index); + if (status) + return status; + + /* + * Read the configuration register + */ + status = drvdata->config->get_configuration(drvdata, RegData, 1); + if (status) + return status; + + return 0; +} + +int hwicap_initialize_hwicap(struct hwicap_drvdata *drvdata) +{ + int status; + u32 idcode; + + dev_dbg(drvdata->dev, "initializing\n"); + + /* Abort any current transaction, to make sure we have the + * ICAP in a good state. */ + dev_dbg(drvdata->dev, "Reset...\n"); + drvdata->config->reset(drvdata); + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + /* Attempt to read the IDCODE from ICAP. This + * may not be returned correctly, due to the design of the + * hardware. + */ + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); + status = hwicap_get_configuration_register( + drvdata, drvdata->config_regs->IDCODE, &idcode); + dev_dbg(drvdata->dev, "IDCODE = %x\n", idcode); + if (status) + return status; + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + return 0; +} + +static ssize_t +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t bytes_to_read = 0; + u32 *kbuf; + u32 words; + u32 bytes_remaining; + int status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->read_buffer_in_use) { + /* If there are leftover bytes in the buffer, just */ + /* return them and don't try to read more from the */ + /* ICAP device. */ + bytes_to_read = + (count < drvdata->read_buffer_in_use) ? count : + drvdata->read_buffer_in_use; + + /* Return the data currently in the read buffer. */ + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { + status = -EFAULT; + goto error; + } + drvdata->read_buffer_in_use -= bytes_to_read; + memcpy(drvdata->read_buffer + bytes_to_read, + drvdata->read_buffer, 4 - bytes_to_read); + } else { + /* Get new data from the ICAP, and return was was requested. */ + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + /* The ICAP device is only able to read complete */ + /* words. If a number of bytes that do not correspond */ + /* to complete words is requested, then we read enough */ + /* words to get the required number of bytes, and then */ + /* save the remaining bytes for the next read. */ + + /* Determine the number of words to read, rounding up */ + /* if necessary. */ + words = ((count + 3) >> 2); + bytes_to_read = words << 2; + + if (bytes_to_read > PAGE_SIZE) + bytes_to_read = PAGE_SIZE; + + /* Ensure we only read a complete number of words. */ + bytes_remaining = bytes_to_read & 3; + bytes_to_read &= ~3; + words = bytes_to_read >> 2; + + status = drvdata->config->get_configuration(drvdata, + kbuf, words); + + /* If we didn't read correctly, then bail out. */ + if (status) { + free_page((unsigned long)kbuf); + goto error; + } + + /* If we fail to return the data to the user, then bail out. */ + if (copy_to_user(buf, kbuf, bytes_to_read)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + memcpy(kbuf, drvdata->read_buffer, bytes_remaining); + drvdata->read_buffer_in_use = bytes_remaining; + free_page((unsigned long)kbuf); + } + status = bytes_to_read; + error: + up(&drvdata->sem); + return status; +} + +static ssize_t +hwicap_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t written = 0; + ssize_t left = count; + u32 *kbuf; + ssize_t len; + ssize_t status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + left += drvdata->write_buffer_in_use; + + /* Only write multiples of 4 bytes. */ + if (left < 4) { + status = 0; + goto error; + } + + kbuf = (u32 *) __get_free_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + while (left > 3) { + /* only write multiples of 4 bytes, so there might */ + /* be as many as 3 bytes left (at the end). */ + len = left; + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + len &= ~3; + + if (drvdata->write_buffer_in_use) { + memcpy(kbuf, drvdata->write_buffer, + drvdata->write_buffer_in_use); + if (copy_from_user( + (((char *)kbuf) + (drvdata->write_buffer_in_use)), + buf + written, + len - (drvdata->write_buffer_in_use))) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } else { + if (copy_from_user(kbuf, buf + written, len)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } + + status = drvdata->config->set_configuration(drvdata, + kbuf, len >> 2); + + if (status) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + if (drvdata->write_buffer_in_use) { + len -= drvdata->write_buffer_in_use; + left -= drvdata->write_buffer_in_use; + drvdata->write_buffer_in_use = 0; + } + written += len; + left -= len; + } + if ((left > 0) && (left < 4)) { + if (!copy_from_user(drvdata->write_buffer, + buf + written, left)) { + drvdata->write_buffer_in_use = left; + written += left; + left = 0; + } + } + + free_page((unsigned long)kbuf); + status = written; + error: + up(&drvdata->sem); + return status; +} + +static int hwicap_open(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata; + int status; + + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->is_open) { + status = -EBUSY; + goto error; + } + + status = hwicap_initialize_hwicap(drvdata); + if (status) { + dev_err(drvdata->dev, "Failed to open file"); + goto error; + } + + file->private_data = drvdata; + drvdata->write_buffer_in_use = 0; + drvdata->read_buffer_in_use = 0; + drvdata->is_open = 1; + + error: + up(&drvdata->sem); + return status; +} + +static int hwicap_release(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata = file->private_data; + int i; + int status = 0; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->write_buffer_in_use) { + /* Flush write buffer. */ + for (i = drvdata->write_buffer_in_use; i < 4; i++) + drvdata->write_buffer[i] = 0; + + status = drvdata->config->set_configuration(drvdata, + (u32 *) drvdata->write_buffer, 1); + if (status) + goto error; + } + + status = hwicap_command_desync(drvdata); + if (status) + goto error; + + error: + drvdata->is_open = 0; + up(&drvdata->sem); + return status; +} + +static struct file_operations hwicap_fops = { + .owner = THIS_MODULE, + .write = hwicap_write, + .read = hwicap_read, + .open = hwicap_open, + .release = hwicap_release, +}; + +static int __devinit hwicap_setup(struct device *dev, int id, + const struct resource *regs_res, + const struct hwicap_driver_config *config, + const struct config_registers *config_regs) +{ + dev_t devt; + struct hwicap_drvdata *drvdata = NULL; + int retval = 0; + + dev_info(dev, "Xilinx icap port driver\n"); + + if (id < 0) { + for (id = 0; id < HWICAP_DEVICES; id++) + if (!probed_devices[id]) + break; + } + if (id < 0 || id >= HWICAP_DEVICES) { + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); + return -EINVAL; + } + if (probed_devices[id]) { + dev_err(dev, "cannot assign to %s%i; it is already in use\n", + DRIVER_NAME, id); + return -EBUSY; + } + + probed_devices[id] = 1; + + devt = MKDEV(xhwicap_major, xhwicap_minor + id); + + drvdata = kmalloc(sizeof(struct hwicap_drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + return -ENOMEM; + } + memset((void *)drvdata, 0, sizeof(struct hwicap_drvdata)); + dev_set_drvdata(dev, (void *)drvdata); + + if (!regs_res) { + dev_err(dev, "Couldn't get registers resource\n"); + retval = -EFAULT; + goto failed1; + } + + drvdata->mem_start = regs_res->start; + drvdata->mem_end = regs_res->end; + drvdata->mem_size = regs_res->end - regs_res->start + 1; + + if (!request_mem_region(drvdata->mem_start, + drvdata->mem_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at %p\n", + (void *)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + drvdata->devt = devt; + drvdata->dev = dev; + drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); + if (!drvdata->base_address) { + dev_err(dev, "ioremap() failed\n"); + goto failed2; + } + + drvdata->config = config; + drvdata->config_regs = config_regs; + + init_MUTEX(&drvdata->sem); + drvdata->is_open = 0; + + dev_info(dev, "ioremap %lx to %p with size %x\n", + (unsigned long int)drvdata->mem_start, + drvdata->base_address, drvdata->mem_size); + + cdev_init(&drvdata->cdev, &hwicap_fops); + drvdata->cdev.owner = THIS_MODULE; + retval = cdev_add(&drvdata->cdev, devt, 1); + if (retval) { + dev_err(dev, "cdev_add() failed\n"); + goto failed3; + } + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ + class_device_create(icap_class, NULL, devt, NULL, DRIVER_NAME); + return 0; /* success */ + + failed3: + iounmap(drvdata->base_address); + + failed2: + release_mem_region(regs_res->start, drvdata->mem_size); + + failed1: + kfree(drvdata); + + return retval; +} + +static struct hwicap_driver_config buffer_icap_config = { + .get_configuration = buffer_icap_get_configuration, + .set_configuration = buffer_icap_set_configuration, + .reset = buffer_icap_reset, +}; + +static struct hwicap_driver_config fifo_icap_config = { + .get_configuration = fifo_icap_get_configuration, + .set_configuration = fifo_icap_set_configuration, + .reset = fifo_icap_reset, +}; + +static int __devexit hwicap_remove(struct device *dev) +{ + struct hwicap_drvdata *drvdata; + + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); + + if (!drvdata) + return 0; + + class_device_destroy(icap_class, drvdata->devt); + cdev_del(&drvdata->cdev); + iounmap(drvdata->base_address); + release_mem_region(drvdata->mem_start, drvdata->mem_size); + kfree(drvdata); + dev_set_drvdata(dev, NULL); + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; + + return 0; /* success */ +} + +static int __devinit hwicap_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct config_registers *regs; + const char *family; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = pdev->dev.platform_data; + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + + return hwicap_setup(&pdev->dev, pdev->id, res, + &buffer_icap_config, regs); +} + +static int __devexit hwicap_drv_remove(struct platform_device *pdev) +{ + return hwicap_remove(&pdev->dev); +} + +static struct platform_driver hwicap_platform_driver = { + .probe = hwicap_drv_probe, + .remove = hwicap_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +#if defined(CONFIG_OF) +static int __devinit +hwicap_of_probe(struct of_device *op, const struct of_device_id *match) +{ + struct resource res; + const unsigned int *id; + const char *family; + int rc; + const struct hwicap_driver_config *config = match->data; + const struct config_registers *regs; + + dev_dbg(&op->dev, "hwicap_of_probe(%p, %p)\n", op, match); + + rc = of_address_to_resource(op->node, 0, &res); + if (rc) { + dev_err(&op->dev, "invalid address\n"); + return rc; + } + + id = of_get_property(op->node, "port-number", NULL); + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = of_get_property(op->node, "xlnx,family", NULL); + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + return hwicap_setup(&op->dev, id ? *id : -1, &res, config, + regs); +} + +static int __devexit hwicap_of_remove(struct of_device *op) +{ + return hwicap_remove(&op->dev); +} + +/* Match table for of_platform binding */ +static const struct of_device_id __devinit hwicap_of_match[] = { + { .compatible = "xlnx,opb-hwicap-1.00.b", .data = &buffer_icap_config}, + { .compatible = "xlnx,xps-hwicap-1.00.a", .data = &fifo_icap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, hwicap_of_match); + +static struct of_platform_driver hwicap_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = hwicap_of_match, + .probe = hwicap_of_probe, + .remove = __devexit_p(hwicap_of_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +/* Registration helpers to keep the number of #ifdefs to a minimum */ +static inline int __devinit hwicap_of_register(void) +{ + pr_debug("hwicap: calling of_register_platform_driver()\n"); + return of_register_platform_driver(&hwicap_of_driver); +} + +static inline void __devexit hwicap_of_unregister(void) +{ + of_unregister_platform_driver(&hwicap_of_driver); +} +#else /* CONFIG_OF */ +/* CONFIG_OF not enabled; do nothing helpers */ +static inline int __devinit hwicap_of_register(void) { return 0; } +static inline void __devexit hwicap_of_unregister(void) { } +#endif /* CONFIG_OF */ + +static int __devinit hwicap_module_init(void) +{ + dev_t devt; + int retval; + + icap_class = class_create(THIS_MODULE, "xilinx_config"); + + if (xhwicap_major) { + devt = MKDEV(xhwicap_major, xhwicap_minor); + retval = register_chrdev_region( + devt, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + } else { + retval = alloc_chrdev_region(&devt, + xhwicap_minor, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + xhwicap_major = MAJOR(devt); + } + + retval = platform_driver_register(&hwicap_platform_driver); + + if (retval) + goto failed1; + + retval = hwicap_of_register(); + + if (retval) + goto failed2; + + return retval; + + failed2: + platform_driver_unregister(&hwicap_platform_driver); + + failed1: + unregister_chrdev_region(devt, HWICAP_DEVICES); + + return retval; +} + +static void __devexit hwicap_module_cleanup(void) +{ + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); + + class_destroy(icap_class); + + platform_driver_unregister(&hwicap_platform_driver); + + hwicap_of_unregister(); + + unregister_chrdev_region(devt, HWICAP_DEVICES); +} + +module_init(hwicap_module_init); +module_exit(hwicap_module_cleanup); + +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h new file mode 100644 index 0000000..b6b47d0 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2007 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ +#define XILINX_HWICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +struct hwicap_drvdata { + u32 write_buffer_in_use; /* Always in [0,3] */ + u8 write_buffer[4]; + u32 read_buffer_in_use; /* Always in [0,3] */ + u8 read_buffer[4]; + u32 mem_start; /* phys. address of the control registers */ + u32 mem_end; /* phys. address of the control registers */ + u32 mem_size; + void __iomem *base_address;/* virt. address of the control registers */ + + struct device *dev; + struct cdev cdev; /* Char device structure */ + dev_t devt; + + const struct hwicap_driver_config *config; + const struct config_registers *config_regs; + void *private_data; + bool is_open; + bool is_accessing; +}; + +struct hwicap_driver_config { + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + void (*reset)(struct hwicap_drvdata *drvdata); +}; + +/* Number of times to poll the done regsiter */ +#define XHI_MAX_RETRIES 10 + +/************ Constant Definitions *************/ + +#define XHI_PAD_FRAMES 0x1 + +/* Mask for calculating configuration packet headers */ +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL +#define XHI_TYPE_MASK 0x7 +#define XHI_REGISTER_MASK 0xF +#define XHI_OP_MASK 0x3 + +#define XHI_TYPE_SHIFT 29 +#define XHI_REGISTER_SHIFT 13 +#define XHI_OP_SHIFT 27 + +#define XHI_TYPE_1 1 +#define XHI_TYPE_2 2 +#define XHI_OP_WRITE 2 +#define XHI_OP_READ 1 + +/* Address Block Types */ +#define XHI_FAR_CLB_BLOCK 0 +#define XHI_FAR_BRAM_BLOCK 1 +#define XHI_FAR_BRAM_INT_BLOCK 2 + +struct config_registers { + u32 CRC; + u32 FAR; + u32 FDRI; + u32 FDRO; + u32 CMD; + u32 CTL; + u32 MASK; + u32 STAT; + u32 LOUT; + u32 COR; + u32 MFWR; + u32 FLR; + u32 KEY; + u32 CBC; + u32 IDCODE; + u32 AXSS; + u32 C0R_1; + u32 CSOB; + u32 WBSTAR; + u32 TIMER; + u32 BOOTSTS; + u32 CTL_1; +}; + +/* Configuration Commands */ +#define XHI_CMD_NULL 0 +#define XHI_CMD_WCFG 1 +#define XHI_CMD_MFW 2 +#define XHI_CMD_DGHIGH 3 +#define XHI_CMD_RCFG 4 +#define XHI_CMD_START 5 +#define XHI_CMD_RCAP 6 +#define XHI_CMD_RCRC 7 +#define XHI_CMD_AGHIGH 8 +#define XHI_CMD_SWITCH 9 +#define XHI_CMD_GRESTORE 10 +#define XHI_CMD_SHUTDOWN 11 +#define XHI_CMD_GCAPTURE 12 +#define XHI_CMD_DESYNCH 13 +#define XHI_CMD_IPROG 15 /* Only in Virtex5 */ +#define XHI_CMD_CRCC 16 /* Only in Virtex5 */ +#define XHI_CMD_LTIMER 17 /* Only in Virtex5 */ + +/* Packet constants */ +#define XHI_SYNC_PACKET 0xAA995566UL +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_READ << XHI_OP_SHIFT)) + +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_WRITE << XHI_OP_SHIFT)) + +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF + +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL +#define XHI_TYPE_1_HEADER_BYTES 4 +#define XHI_TYPE_2_HEADER_BYTES 8 + +/* Constant to use for CRC check when CRC has been disabled */ +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL + +/** + * hwicap_type_1_read: Generates a Type 1 read packet header. + * @parameter: Register is the address of the register to be read back. + * + * Generates a Type 1 read packet header, which is used to indirectly + * read registers in the configuration logic. This packet must then + * be sent through the icap device, and a return packet received with + * the information. + **/ +static inline u32 hwicap_type_1_read(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_READ << XHI_OP_SHIFT); +} + +/** + * hwicap_type_1_write: Generates a Type 1 write packet header + * @parameter: Register is the address of the register to be read back. + **/ +static inline u32 hwicap_type_1_write(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_WRITE << XHI_OP_SHIFT); +} + +#endif -- 1.5.3.4-dirty ^ permalink raw reply related [flat|nested] 21+ messages in thread
[parent not found: <1201900363-25230-1-git-send-email-stephen.neuendorffer@xilinx.com>]
* RE: [PATCH] [POWERPC] Xilinx: hwicap driver [not found] ` <1201900363-25230-1-git-send-email-stephen.neuendorffer@xilinx.com> @ 2008-02-05 0:38 ` Stephen Neuendorffer 2008-02-05 17:24 ` [PATCH 1/2] " Stephen Neuendorffer [not found] ` <1202232250-24971-1-git-send-email-stephen.neuendorffer@xilinx.com> 2 siblings, 0 replies; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-05 0:38 UTC (permalink / raw) To: Stephen Neuendorffer, linuxppc-dev, grant.likely, jacmet, ntl [-- Attachment #1: Type: text/plain, Size: 67648 bytes --] Grant, One of the modifications to the include files got lost, so this won't compile. I also have the corresponding update to booting-without-of.txt queued up. However, webmail won't show me the message id for the thread (grr.), so I'll send it tomorrow when I'm in the office. Steve -----Original Message----- From: Stephen Neuendorffer [mailto:stephen.neuendorffer@xilinx.com] Sent: Fri 2/1/2008 1:12 PM To: linuxppc-dev@ozlabs.org; grant.likely@secretlab.ca; jacmet@sunsite.dk; ntl@pobox.com Cc: Stephen Neuendorffer Subject: [PATCH] [POWERPC] Xilinx: hwicap driver This includes code for new fifo-based xps_hwicap in addition to the older opb_hwicap, which has a significantly different interface. The common code between the two drivers is largely shared. Significant differences exists between this driver and what is supported in the EDK drivers. In particular, most of the architecture-specific code for reconfiguring individual FPGA resources has been removed. This functionality is likely better provided in a user-space support library. In addition, read and write access is supported. In addition, although the xps_hwicap cores support interrupt-driver mode, this driver only supports polled operation, in order to make the code simpler, and since the interrupt processing overhead is likely to slow down the throughput under Linux. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Fixed to add mutexes, and a few style issues. Acked-by: Grant Likely <grant.likely@secretlab.ca> --- I will get this right eventually. :) --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 1 + drivers/char/xilinx_hwicap/Makefile | 7 + drivers/char/xilinx_hwicap/buffer_icap.c | 380 ++++++++++++ drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ drivers/char/xilinx_hwicap/xilinx_hwicap.c | 904 ++++++++++++++++++++++++++++ drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ 9 files changed, 1992 insertions(+), 0 deletions(-) create mode 100644 drivers/char/xilinx_hwicap/Makefile create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ef1ed5d..157ae2a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -831,6 +831,13 @@ config DTLK To compile this driver as a module, choose M here: the module will be called dtlk. +config XILINX_HWICAP + tristate "Xilinx HWICAP Support" + depends on XILINX_VIRTEX + help + This option enables support for Xilinx Internal Configuration + Access Port (ICAP) driver. + config R3964 tristate "Siemens R3964 line discipline" ---help--- diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d5..3a278a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ ifeq ($(CONFIG_GENERIC_NVRAM),y) obj-$(CONFIG_NVRAM) += generic_nvram.o else diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile new file mode 100644 index 0000000..5491cbc --- /dev/null +++ b/drivers/char/xilinx_hwicap/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Xilinx OPB hwicap driver +# + +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o + +xilinx_hwicap_m-y := xilinx_hwicap.o fifo_icap.o buffer_icap.o diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c new file mode 100644 index 0000000..dfea2bd --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.c @@ -0,0 +1,380 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "buffer_icap.h" + +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ +#define XHI_MAX_BUFFER_BYTES 2048 +#define XHI_MAX_BUFFER_INTS (XHI_MAX_BUFFER_BYTES >> 2) + +/* File access and error constants */ +#define XHI_DEVICE_READ_ERROR -1 +#define XHI_DEVICE_WRITE_ERROR -2 +#define XHI_BUFFER_OVERFLOW_ERROR -3 + +#define XHI_DEVICE_READ 0x1 +#define XHI_DEVICE_WRITE 0x0 + +/* Constants for checking transfer status */ +#define XHI_CYCLE_DONE 0 +#define XHI_CYCLE_EXECUTING 1 + +/* buffer_icap register offsets */ + +/* Size of transfer, read & write */ +#define XHI_SIZE_REG_OFFSET 0x800L +/* offset into bram, read & write */ +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L +/* Read not Configure, direction of transfer. Write only */ +#define XHI_RNC_REG_OFFSET 0x808L +/* Indicates transfer complete. Read only */ +#define XHI_STATUS_REG_OFFSET 0x80CL + +/* Constants for setting the RNC register */ +#define XHI_CONFIGURE 0x0UL +#define XHI_READBACK 0x1UL + +/* Constants for the Done register */ +#define XHI_NOT_FINISHED 0x0UL +#define XHI_FINISHED 0x1UL + +#define XHI_BUFFER_START 0 + +/** + * buffer_icap_get_status: Get the contents of the status register. + * @parameter base_address: is the base address of the device + * + * The status register contains the ICAP status and the done bit. + * + * D8 - cfgerr + * D7 - dalign + * D6 - rip + * D5 - in_abort_l + * D4 - Always 1 + * D3 - Always 1 + * D2 - Always 1 + * D1 - Always 1 + * D0 - Done bit + **/ +static inline u32 buffer_icap_get_status(void __iomem *base_address) +{ + return in_be32(base_address + XHI_STATUS_REG_OFFSET); +} + +/** + * buffer_icap_get_bram: Reads data from the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset from which the data should be read. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline u32 buffer_icap_get_bram(void __iomem *base_address, + u32 offset) +{ + return in_be32(base_address + (offset << 2)); +} + +/** + * buffer_icap_busy: Return true if the icap device is busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_busy(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_NOT_FINISHED; +} + +/** + * buffer_icap_busy: Return true if the icap device is not busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_done(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_FINISHED; +} + +/** + * buffer_icap_set_size: Set the size register. + * @parameter base_address: is the base address of the device + * @parameter data: The size in bytes. + * + * The size register holds the number of 8 bit bytes to transfer between + * bram and the icap (or icap to bram). + **/ +static inline void buffer_icap_set_size(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_SIZE_REG_OFFSET, data); +} + +/** + * buffer_icap_mSetoffsetReg: Set the bram offset register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The bram offset register holds the starting bram address to transfer + * data from during configuration or write data to during readback. + **/ +static inline void buffer_icap_set_offset(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_BRAM_OFFSET_REG_OFFSET, data); +} + +/** + * buffer_icap_set_rnc: Set the RNC (Readback not Configure) register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The RNC register determines the direction of the data transfer. It + * controls whether a configuration or readback take place. Writing to + * this register initiates the transfer. A value of 1 initiates a + * readback while writing a value of 0 initiates a configuration. + **/ +static inline void buffer_icap_set_rnc(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_RNC_REG_OFFSET, data); +} + +/** + * buffer_icap_set_bram: Write data to the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset at which the data should be written. + * @parameter data: The value to be written to the bram offset. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline void buffer_icap_set_bram(void __iomem *base_address, + u32 offset, u32 data) +{ + out_be32(base_address + (offset << 2), data); +} + +/** + * buffer_icap_device_read: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, (count << 2)); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_READBACK); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, count << 2); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * Writing to the status register resets the ICAP logic in an internal + * version of the core. For the version of the core published in EDK, + * this is a noop. + **/ +void buffer_icap_reset(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_STATUS_REG_OFFSET, 0xFEFE); +} + +/** + * buffer_icap_set_configuration: Load a partial bitstream from system memory. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Kernel address of the partial bitstream. + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 num_writes = 0; + bool dirty = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = 0; i < size; i++) { + + /* Copy data to bram */ + buffer_icap_set_bram(base_address, buffer_count, data[i]); + dirty = 1; + + if (buffer_count < XHI_MAX_BUFFER_INTS - 1) { + buffer_count++; + continue; + } + + /* Write data to ICAP */ + status = buffer_icap_device_write( + drvdata, + XHI_BUFFER_START, + XHI_MAX_BUFFER_INTS); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + num_writes++; + dirty = 0; + } + + /* Write unwritten data to ICAP */ + if (dirty) { + /* Write data to ICAP */ + status = buffer_icap_device_write(drvdata, XHI_BUFFER_START, + buffer_count); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + } + return status; + } + + return 0; +}; + +/** + * buffer_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 read_count = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = XHI_MAX_BUFFER_INTS; i < size; i++) { + if (buffer_count == XHI_MAX_BUFFER_INTS) { + u32 words_remaining = size - i; + u32 words_to_read = + words_remaining < + XHI_MAX_BUFFER_INTS ? words_remaining : + XHI_MAX_BUFFER_INTS; + + /* Read data from ICAP */ + status = buffer_icap_device_read( + drvdata, + XHI_BUFFER_START, + words_to_read); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + read_count++; + } + + /* Copy data from bram */ + data[i] = buffer_icap_get_bram(base_address, buffer_count); + buffer_count++; + } + + return 0; +}; diff --git a/drivers/char/xilinx_hwicap/buffer_icap.h b/drivers/char/xilinx_hwicap/buffer_icap.h new file mode 100644 index 0000000..0318495 --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_BUFFER_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_BUFFER_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +void buffer_icap_reset(struct hwicap_drvdata *drvdata); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c new file mode 100644 index 0000000..0988314 --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "fifo_icap.h" + +/* Register offsets for the XHwIcap device. */ +#define XHI_GIER_OFFSET 0x1C /* Device Global Interrupt Enable Reg */ +#define XHI_IPISR_OFFSET 0x20 /* Interrupt Status Register */ +#define XHI_IPIER_OFFSET 0x28 /* Interrupt Enable Register */ +#define XHI_WF_OFFSET 0x100 /* Write FIFO */ +#define XHI_RF_OFFSET 0x104 /* Read FIFO */ +#define XHI_SZ_OFFSET 0x108 /* Size Register */ +#define XHI_CR_OFFSET 0x10C /* Control Register */ +#define XHI_SR_OFFSET 0x110 /* Status Register */ +#define XHI_WFV_OFFSET 0x114 /* Write FIFO Vacancy Register */ +#define XHI_RFO_OFFSET 0x118 /* Read FIFO Occupancy Register */ + +/* Device Global Interrupt Enable Register (GIER) bit definitions */ + +#define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ + +/** + * HwIcap Device Interrupt Status/Enable Registers + * + * Interrupt Status Register (IPISR) : This register holds the + * interrupt status flags for the device. These bits are toggle on + * write. + * + * Interrupt Enable Register (IPIER) : This register is used to enable + * interrupt sources for the device. + * Writing a '1' to a bit enables the corresponding interrupt. + * Writing a '0' to a bit disables the corresponding interrupt. + * + * IPISR/IPIER registers have the same bit definitions and are only defined + * once. + */ +#define XHI_IPIXR_RFULL_MASK 0x00000008 /* Read FIFO Full */ +#define XHI_IPIXR_WEMPTY_MASK 0x00000004 /* Write FIFO Empty */ +#define XHI_IPIXR_RDP_MASK 0x00000002 /* Read FIFO half full */ +#define XHI_IPIXR_WRP_MASK 0x00000001 /* Write FIFO half full */ +#define XHI_IPIXR_ALL_MASK 0x0000000F /* Mask of all interrupts */ + +/* Control Register (CR) */ +#define XHI_CR_SW_RESET_MASK 0x00000008 /* SW Reset Mask */ +#define XHI_CR_FIFO_CLR_MASK 0x00000004 /* FIFO Clear Mask */ +#define XHI_CR_READ_MASK 0x00000002 /* Read from ICAP to FIFO */ +#define XHI_CR_WRITE_MASK 0x00000001 /* Write from FIFO to ICAP */ + +/* Status Register (SR) */ +#define XHI_SR_CFGERR_N_MASK 0x00000100 /* Config Error Mask */ +#define XHI_SR_DALIGN_MASK 0x00000080 /* Data Alignment Mask */ +#define XHI_SR_RIP_MASK 0x00000040 /* Read back Mask */ +#define XHI_SR_IN_ABORT_N_MASK 0x00000020 /* Select Map Abort Mask */ +#define XHI_SR_DONE_MASK 0x00000001 /* Done bit Mask */ + + +#define XHI_WFO_MAX_VACANCY 1024 /* Max Write FIFO Vacancy, in words */ +#define XHI_RFO_MAX_OCCUPANCY 256 /* Max Read FIFO Occupancy, in words */ +/* The maximum amount we can request from fifo_icap_get_configuration + at once, in bytes. */ +#define XHI_MAX_READ_TRANSACTION_WORDS 0xFFF + + +/** + * fifo_icap_fifo_write: Write data to the write FIFO. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the 32-bit value to be written to the FIFO. + * + * This function will silently fail if the fifo is full. + **/ +static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, + u32 data) +{ + dev_dbg(drvdata->dev, "fifo_write: %x\n", data); + out_be32(drvdata->base_address + XHI_WF_OFFSET, data); +} + +/** + * fifo_icap_fifo_read: Read data from the Read FIFO. + * @parameter drvdata: a pointer to the drvdata. + * + * This function will silently fail if the fifo is empty. + **/ +static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) +{ + u32 data = in_be32(drvdata->base_address + XHI_RF_OFFSET); + dev_dbg(drvdata->dev, "fifo_read: %x\n", data); + return data; +} + +/** + * fifo_icap_set_read_size: Set the the size register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the size of the following read transaction, in words. + **/ +static inline void fifo_icap_set_read_size(struct hwicap_drvdata *drvdata, + u32 data) +{ + out_be32(drvdata->base_address + XHI_SZ_OFFSET, data); +} + +/** + * fifo_icap_start_config: Initiate a configuration (write) to the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_config(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_WRITE_MASK); + dev_dbg(drvdata->dev, "configuration started\n"); +} + +/** + * fifo_icap_start_readback: Initiate a readback from the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_READ_MASK); + dev_dbg(drvdata->dev, "readback started\n"); +} + +/** + * fifo_icap_busy: Return true if the ICAP is still processing a transaction. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) +{ + u32 status = in_be32(drvdata->base_address + XHI_SR_OFFSET); + dev_dbg(drvdata->dev, "Getting status = %x\n", status); + return (status & XHI_SR_DONE_MASK) ? 0 : 1; +} + +/** + * fifo_icap_write_fifo_vacancy: Query the write fifo available space. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely pushed into the write fifo. + **/ +static inline u32 fifo_icap_write_fifo_vacancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_WFV_OFFSET); +} + +/** + * fifo_icap_read_fifo_occupancy: Query the read fifo available data. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely read from the read fifo. + **/ +static inline u32 fifo_icap_read_fifo_occupancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_RFO_OFFSET); +} + +/** + * fifo_icap_set_configuration: Send configuration data to the ICAP. + * @parameter drvdata: a pointer to the drvdata. + * @parameter frame_buffer: a pointer to the data to be written to the + * ICAP device. + * @parameter num_words: the number of words (32 bit) to write to the ICAP + * device. + + * This function writes the given user data to the Write FIFO in + * polled mode and starts the transfer of the data to + * the ICAP device. + **/ +int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 write_fifo_vacancy = 0; + u32 retries = 0; + u32 remaining_words; + + dev_dbg(drvdata->dev, "fifo_set_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Read/Write + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + /* + * Set up the buffer pointer and the words to be transferred. + */ + remaining_words = num_words; + + while (remaining_words > 0) { + /* + * Wait until we have some data in the fifo. + */ + while (write_fifo_vacancy == 0) { + write_fifo_vacancy = + fifo_icap_write_fifo_vacancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + /* + * Write data into the Write FIFO. + */ + while ((write_fifo_vacancy != 0) && + (remaining_words > 0)) { + fifo_icap_fifo_write(drvdata, *frame_buffer); + + remaining_words--; + write_fifo_vacancy--; + frame_buffer++; + } + /* Start pushing whatever is in the FIFO into the ICAP. */ + fifo_icap_start_config(drvdata); + } + + /* Wait until the write has finished. */ + while (fifo_icap_busy(drvdata)) { + retries++; + if (retries > XHI_MAX_RETRIES) + break; + } + + dev_dbg(drvdata->dev, "done fifo_set_configuration\n"); + + /* + * If the requested number of words have not been read from + * the device then indicate failure. + */ + if (remaining_words != 0) + return -EIO; + + return 0; +} + +/** + * fifo_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + * + * This function reads the specified number of words from the ICAP device in + * the polled mode. + */ +int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 read_fifo_occupancy = 0; + u32 retries = 0; + u32 *data = frame_buffer; + u32 remaining_words; + u32 words_to_read; + + dev_dbg(drvdata->dev, "fifo_get_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Write/Read + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + remaining_words = num_words; + + while (remaining_words > 0) { + words_to_read = remaining_words; + /* The hardware has a limit on the number of words + that can be read at one time. */ + if (words_to_read > XHI_MAX_READ_TRANSACTION_WORDS) + words_to_read = XHI_MAX_READ_TRANSACTION_WORDS; + + remaining_words -= words_to_read; + + fifo_icap_set_read_size(drvdata, words_to_read); + fifo_icap_start_readback(drvdata); + + while (words_to_read > 0) { + /* Wait until we have some data in the fifo. */ + while (read_fifo_occupancy == 0) { + read_fifo_occupancy = + fifo_icap_read_fifo_occupancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + if (read_fifo_occupancy > words_to_read) + read_fifo_occupancy = words_to_read; + + words_to_read -= read_fifo_occupancy; + + /* Read the data from the Read FIFO. */ + while (read_fifo_occupancy != 0) { + *data++ = fifo_icap_fifo_read(drvdata); + read_fifo_occupancy--; + } + } + } + + dev_dbg(drvdata->dev, "done fifo_get_configuration\n"); + + return 0; +} + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * This function forces the software reset of the complete HWICAP device. + * All the registers will return to the default value and the FIFO is also + * flushed as a part of this software reset. + */ +void fifo_icap_reset(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Reset the device by setting/clearing the RESET bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_SW_RESET_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_SW_RESET_MASK)); + +} + +/** + * fifo_icap_flush_fifo: This function flushes the FIFOs in the device. + * @parameter drvdata: a pointer to the drvdata. + */ +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Flush the FIFO by setting/clearing the FIFO Clear bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_FIFO_CLR_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_FIFO_CLR_MASK)); +} + diff --git a/drivers/char/xilinx_hwicap/fifo_icap.h b/drivers/char/xilinx_hwicap/fifo_icap.h new file mode 100644 index 0000000..4d3068d --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_FIFO_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_FIFO_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +/* Reads integers from the device into the storage buffer. */ +int fifo_icap_get_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +/* Writes integers to the device from the storage buffer. */ +int fifo_icap_set_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +void fifo_icap_reset(struct hwicap_drvdata *drvdata); +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata); + +#endif diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c new file mode 100644 index 0000000..24f6aef --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -0,0 +1,904 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/* + * This is the code behind /dev/xilinx_icap -- it allows a user-space + * application to use the Xilinx ICAP subsystem. + * + * The following operations are possible: + * + * open open the port and initialize for access. + * release release port + * write Write a bitstream to the configuration processor. + * read Read a data stream from the configuration processor. + * + * After being opened, the port is initialized and accessed to avoid a + * corrupted first read which may occur with some hardware. The port + * is left in a desynched state, requiring that a synch sequence be + * transmitted before any valid configuration data. A user will have + * exclusive access to the device while it remains open, and the state + * of the ICAP cannot be guaranteed after the device is closed. Note + * that a complete reset of the core and the state of the ICAP cannot + * be performed on many versions of the cores, hence users of this + * device should avoid making inconsistent accesses to the device. In + * particular, accessing the read interface, without first generating + * a write containing a readback packet can leave the ICAP in an + * inaccessible state. + * + * Note that in order to use the read interface, it is first necessary + * to write a request packet to the write interface. i.e., it is not + * possible to simply readback the bitstream (or any configuration + * bits) from a device without specifically requesting them first. + * The code to craft such packets is intended to be part of the + * user-space application code that uses this device. The simplest + * way to use this interface is simply: + * + * cp foo.bit /dev/xilinx_icap + * + * Note that unless foo.bit is an appropriately constructed partial + * bitstream, this has a high likelyhood of overwriting the design + * currently programmed in the FPGA. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <asm/semaphore.h> +#include <linux/sysctl.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#ifdef CONFIG_OF +/* For open firmware. */ +#include <linux/of_device.h> +#include <linux/of_platform.h> +#endif + +#include "xilinx_hwicap.h" +#include "buffer_icap.h" +#include "fifo_icap.h" + +#define DRIVER_NAME "xilinx_icap" + +#define HWICAP_REGS (0x10000) + +/* dynamically allocate device number */ +static int xhwicap_major; +static int xhwicap_minor; +#define HWICAP_DEVICES 1 + +module_param(xhwicap_major, int, S_IRUGO); +module_param(xhwicap_minor, int, S_IRUGO); + +/* An array, which is set to true when the device is registered. */ +static bool probed_devices[HWICAP_DEVICES]; + +static struct class *icap_class; + +#define UNIMPLEMENTED 0xFFFF + +static const struct config_registers v2_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = 11, + .KEY = 12, + .CBC = 13, + .IDCODE = 14, + .AXSS = UNIMPLEMENTED, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; + +static const struct config_registers v4_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; +static const struct config_registers v5_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = 14, + .CSOB = 15, + .WBSTAR = 16, + .TIMER = 17, + .BOOTSTS = 18, + .CTL_1 = 19, +}; + +/** + * hwicap_command_desync: Send a DESYNC command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command desynchronizes the ICAP After this command, a + * bitstream containing a NULL packet, followed by a SYNCH packet is + * required before the ICAP will recognize commands. + */ +int hwicap_command_desync(struct hwicap_drvdata *drvdata) +{ + u32 buffer[4]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_DESYNCH; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); +} + +/** + * hwicap_command_capture: Send a CAPTURE command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command captures all of the flip flop states so they will be + * available during readback. One can use this command instead of + * enabling the CAPTURE block in the design. + */ +int hwicap_command_capture(struct hwicap_drvdata *drvdata) +{ + u32 buffer[7]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_GCAPTURE; + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_DUMMY_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data + * present in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); + +} + +/** + * hwicap_get_configuration_register: Query a configuration register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter reg: a constant which represents the configuration + * register value to be returned. + * Examples: XHI_IDCODE, XHI_FLR. + * @parameter RegData: returns the value of the register. + * + * Sends a query packet to the ICAP and then receives the response. + * The icap is left in Synched state. + */ +int hwicap_get_configuration_register(struct hwicap_drvdata *drvdata, + u32 reg, u32 *RegData) +{ + int status; + u32 buffer[6]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_read(reg) | 1; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + status = drvdata->config->set_configuration(drvdata, + &buffer[0], index); + if (status) + return status; + + /* + * Read the configuration register + */ + status = drvdata->config->get_configuration(drvdata, RegData, 1); + if (status) + return status; + + return 0; +} + +int hwicap_initialize_hwicap(struct hwicap_drvdata *drvdata) +{ + int status; + u32 idcode; + + dev_dbg(drvdata->dev, "initializing\n"); + + /* Abort any current transaction, to make sure we have the + * ICAP in a good state. */ + dev_dbg(drvdata->dev, "Reset...\n"); + drvdata->config->reset(drvdata); + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + /* Attempt to read the IDCODE from ICAP. This + * may not be returned correctly, due to the design of the + * hardware. + */ + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); + status = hwicap_get_configuration_register( + drvdata, drvdata->config_regs->IDCODE, &idcode); + dev_dbg(drvdata->dev, "IDCODE = %x\n", idcode); + if (status) + return status; + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + return 0; +} + +static ssize_t +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t bytes_to_read = 0; + u32 *kbuf; + u32 words; + u32 bytes_remaining; + int status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->read_buffer_in_use) { + /* If there are leftover bytes in the buffer, just */ + /* return them and don't try to read more from the */ + /* ICAP device. */ + bytes_to_read = + (count < drvdata->read_buffer_in_use) ? count : + drvdata->read_buffer_in_use; + + /* Return the data currently in the read buffer. */ + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { + status = -EFAULT; + goto error; + } + drvdata->read_buffer_in_use -= bytes_to_read; + memcpy(drvdata->read_buffer + bytes_to_read, + drvdata->read_buffer, 4 - bytes_to_read); + } else { + /* Get new data from the ICAP, and return was was requested. */ + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + /* The ICAP device is only able to read complete */ + /* words. If a number of bytes that do not correspond */ + /* to complete words is requested, then we read enough */ + /* words to get the required number of bytes, and then */ + /* save the remaining bytes for the next read. */ + + /* Determine the number of words to read, rounding up */ + /* if necessary. */ + words = ((count + 3) >> 2); + bytes_to_read = words << 2; + + if (bytes_to_read > PAGE_SIZE) + bytes_to_read = PAGE_SIZE; + + /* Ensure we only read a complete number of words. */ + bytes_remaining = bytes_to_read & 3; + bytes_to_read &= ~3; + words = bytes_to_read >> 2; + + status = drvdata->config->get_configuration(drvdata, + kbuf, words); + + /* If we didn't read correctly, then bail out. */ + if (status) { + free_page((unsigned long)kbuf); + goto error; + } + + /* If we fail to return the data to the user, then bail out. */ + if (copy_to_user(buf, kbuf, bytes_to_read)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + memcpy(kbuf, drvdata->read_buffer, bytes_remaining); + drvdata->read_buffer_in_use = bytes_remaining; + free_page((unsigned long)kbuf); + } + status = bytes_to_read; + error: + up(&drvdata->sem); + return status; +} + +static ssize_t +hwicap_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t written = 0; + ssize_t left = count; + u32 *kbuf; + ssize_t len; + ssize_t status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + left += drvdata->write_buffer_in_use; + + /* Only write multiples of 4 bytes. */ + if (left < 4) { + status = 0; + goto error; + } + + kbuf = (u32 *) __get_free_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + while (left > 3) { + /* only write multiples of 4 bytes, so there might */ + /* be as many as 3 bytes left (at the end). */ + len = left; + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + len &= ~3; + + if (drvdata->write_buffer_in_use) { + memcpy(kbuf, drvdata->write_buffer, + drvdata->write_buffer_in_use); + if (copy_from_user( + (((char *)kbuf) + (drvdata->write_buffer_in_use)), + buf + written, + len - (drvdata->write_buffer_in_use))) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } else { + if (copy_from_user(kbuf, buf + written, len)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } + + status = drvdata->config->set_configuration(drvdata, + kbuf, len >> 2); + + if (status) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + if (drvdata->write_buffer_in_use) { + len -= drvdata->write_buffer_in_use; + left -= drvdata->write_buffer_in_use; + drvdata->write_buffer_in_use = 0; + } + written += len; + left -= len; + } + if ((left > 0) && (left < 4)) { + if (!copy_from_user(drvdata->write_buffer, + buf + written, left)) { + drvdata->write_buffer_in_use = left; + written += left; + left = 0; + } + } + + free_page((unsigned long)kbuf); + status = written; + error: + up(&drvdata->sem); + return status; +} + +static int hwicap_open(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata; + int status; + + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->is_open) { + status = -EBUSY; + goto error; + } + + status = hwicap_initialize_hwicap(drvdata); + if (status) { + dev_err(drvdata->dev, "Failed to open file"); + goto error; + } + + file->private_data = drvdata; + drvdata->write_buffer_in_use = 0; + drvdata->read_buffer_in_use = 0; + drvdata->is_open = 1; + + error: + up(&drvdata->sem); + return status; +} + +static int hwicap_release(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata = file->private_data; + int i; + int status = 0; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->write_buffer_in_use) { + /* Flush write buffer. */ + for (i = drvdata->write_buffer_in_use; i < 4; i++) + drvdata->write_buffer[i] = 0; + + status = drvdata->config->set_configuration(drvdata, + (u32 *) drvdata->write_buffer, 1); + if (status) + goto error; + } + + status = hwicap_command_desync(drvdata); + if (status) + goto error; + + error: + drvdata->is_open = 0; + up(&drvdata->sem); + return status; +} + +static struct file_operations hwicap_fops = { + .owner = THIS_MODULE, + .write = hwicap_write, + .read = hwicap_read, + .open = hwicap_open, + .release = hwicap_release, +}; + +static int __devinit hwicap_setup(struct device *dev, int id, + const struct resource *regs_res, + const struct hwicap_driver_config *config, + const struct config_registers *config_regs) +{ + dev_t devt; + struct hwicap_drvdata *drvdata = NULL; + int retval = 0; + + dev_info(dev, "Xilinx icap port driver\n"); + + if (id < 0) { + for (id = 0; id < HWICAP_DEVICES; id++) + if (!probed_devices[id]) + break; + } + if (id < 0 || id >= HWICAP_DEVICES) { + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); + return -EINVAL; + } + if (probed_devices[id]) { + dev_err(dev, "cannot assign to %s%i; it is already in use\n", + DRIVER_NAME, id); + return -EBUSY; + } + + probed_devices[id] = 1; + + devt = MKDEV(xhwicap_major, xhwicap_minor + id); + + drvdata = kmalloc(sizeof(struct hwicap_drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + return -ENOMEM; + } + memset((void *)drvdata, 0, sizeof(struct hwicap_drvdata)); + dev_set_drvdata(dev, (void *)drvdata); + + if (!regs_res) { + dev_err(dev, "Couldn't get registers resource\n"); + retval = -EFAULT; + goto failed1; + } + + drvdata->mem_start = regs_res->start; + drvdata->mem_end = regs_res->end; + drvdata->mem_size = regs_res->end - regs_res->start + 1; + + if (!request_mem_region(drvdata->mem_start, + drvdata->mem_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at %p\n", + (void *)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + drvdata->devt = devt; + drvdata->dev = dev; + drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); + if (!drvdata->base_address) { + dev_err(dev, "ioremap() failed\n"); + goto failed2; + } + + drvdata->config = config; + drvdata->config_regs = config_regs; + + init_MUTEX(&drvdata->sem); + drvdata->is_open = 0; + + dev_info(dev, "ioremap %lx to %p with size %x\n", + (unsigned long int)drvdata->mem_start, + drvdata->base_address, drvdata->mem_size); + + cdev_init(&drvdata->cdev, &hwicap_fops); + drvdata->cdev.owner = THIS_MODULE; + retval = cdev_add(&drvdata->cdev, devt, 1); + if (retval) { + dev_err(dev, "cdev_add() failed\n"); + goto failed3; + } + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ + class_device_create(icap_class, NULL, devt, NULL, DRIVER_NAME); + return 0; /* success */ + + failed3: + iounmap(drvdata->base_address); + + failed2: + release_mem_region(regs_res->start, drvdata->mem_size); + + failed1: + kfree(drvdata); + + return retval; +} + +static struct hwicap_driver_config buffer_icap_config = { + .get_configuration = buffer_icap_get_configuration, + .set_configuration = buffer_icap_set_configuration, + .reset = buffer_icap_reset, +}; + +static struct hwicap_driver_config fifo_icap_config = { + .get_configuration = fifo_icap_get_configuration, + .set_configuration = fifo_icap_set_configuration, + .reset = fifo_icap_reset, +}; + +static int __devexit hwicap_remove(struct device *dev) +{ + struct hwicap_drvdata *drvdata; + + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); + + if (!drvdata) + return 0; + + class_device_destroy(icap_class, drvdata->devt); + cdev_del(&drvdata->cdev); + iounmap(drvdata->base_address); + release_mem_region(drvdata->mem_start, drvdata->mem_size); + kfree(drvdata); + dev_set_drvdata(dev, NULL); + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; + + return 0; /* success */ +} + +static int __devinit hwicap_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct config_registers *regs; + const char *family; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = pdev->dev.platform_data; + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + + return hwicap_setup(&pdev->dev, pdev->id, res, + &buffer_icap_config, regs); +} + +static int __devexit hwicap_drv_remove(struct platform_device *pdev) +{ + return hwicap_remove(&pdev->dev); +} + +static struct platform_driver hwicap_platform_driver = { + .probe = hwicap_drv_probe, + .remove = hwicap_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +#if defined(CONFIG_OF) +static int __devinit +hwicap_of_probe(struct of_device *op, const struct of_device_id *match) +{ + struct resource res; + const unsigned int *id; + const char *family; + int rc; + const struct hwicap_driver_config *config = match->data; + const struct config_registers *regs; + + dev_dbg(&op->dev, "hwicap_of_probe(%p, %p)\n", op, match); + + rc = of_address_to_resource(op->node, 0, &res); + if (rc) { + dev_err(&op->dev, "invalid address\n"); + return rc; + } + + id = of_get_property(op->node, "port-number", NULL); + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = of_get_property(op->node, "xlnx,family", NULL); + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + return hwicap_setup(&op->dev, id ? *id : -1, &res, config, + regs); +} + +static int __devexit hwicap_of_remove(struct of_device *op) +{ + return hwicap_remove(&op->dev); +} + +/* Match table for of_platform binding */ +static const struct of_device_id __devinit hwicap_of_match[] = { + { .compatible = "xlnx,opb-hwicap-1.00.b", .data = &buffer_icap_config}, + { .compatible = "xlnx,xps-hwicap-1.00.a", .data = &fifo_icap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, hwicap_of_match); + +static struct of_platform_driver hwicap_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = hwicap_of_match, + .probe = hwicap_of_probe, + .remove = __devexit_p(hwicap_of_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +/* Registration helpers to keep the number of #ifdefs to a minimum */ +static inline int __devinit hwicap_of_register(void) +{ + pr_debug("hwicap: calling of_register_platform_driver()\n"); + return of_register_platform_driver(&hwicap_of_driver); +} + +static inline void __devexit hwicap_of_unregister(void) +{ + of_unregister_platform_driver(&hwicap_of_driver); +} +#else /* CONFIG_OF */ +/* CONFIG_OF not enabled; do nothing helpers */ +static inline int __devinit hwicap_of_register(void) { return 0; } +static inline void __devexit hwicap_of_unregister(void) { } +#endif /* CONFIG_OF */ + +static int __devinit hwicap_module_init(void) +{ + dev_t devt; + int retval; + + icap_class = class_create(THIS_MODULE, "xilinx_config"); + + if (xhwicap_major) { + devt = MKDEV(xhwicap_major, xhwicap_minor); + retval = register_chrdev_region( + devt, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + } else { + retval = alloc_chrdev_region(&devt, + xhwicap_minor, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + xhwicap_major = MAJOR(devt); + } + + retval = platform_driver_register(&hwicap_platform_driver); + + if (retval) + goto failed1; + + retval = hwicap_of_register(); + + if (retval) + goto failed2; + + return retval; + + failed2: + platform_driver_unregister(&hwicap_platform_driver); + + failed1: + unregister_chrdev_region(devt, HWICAP_DEVICES); + + return retval; +} + +static void __devexit hwicap_module_cleanup(void) +{ + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); + + class_destroy(icap_class); + + platform_driver_unregister(&hwicap_platform_driver); + + hwicap_of_unregister(); + + unregister_chrdev_region(devt, HWICAP_DEVICES); +} + +module_init(hwicap_module_init); +module_exit(hwicap_module_cleanup); + +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h new file mode 100644 index 0000000..b6b47d0 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2007 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ +#define XILINX_HWICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +struct hwicap_drvdata { + u32 write_buffer_in_use; /* Always in [0,3] */ + u8 write_buffer[4]; + u32 read_buffer_in_use; /* Always in [0,3] */ + u8 read_buffer[4]; + u32 mem_start; /* phys. address of the control registers */ + u32 mem_end; /* phys. address of the control registers */ + u32 mem_size; + void __iomem *base_address;/* virt. address of the control registers */ + + struct device *dev; + struct cdev cdev; /* Char device structure */ + dev_t devt; + + const struct hwicap_driver_config *config; + const struct config_registers *config_regs; + void *private_data; + bool is_open; + bool is_accessing; +}; + +struct hwicap_driver_config { + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + void (*reset)(struct hwicap_drvdata *drvdata); +}; + +/* Number of times to poll the done regsiter */ +#define XHI_MAX_RETRIES 10 + +/************ Constant Definitions *************/ + +#define XHI_PAD_FRAMES 0x1 + +/* Mask for calculating configuration packet headers */ +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL +#define XHI_TYPE_MASK 0x7 +#define XHI_REGISTER_MASK 0xF +#define XHI_OP_MASK 0x3 + +#define XHI_TYPE_SHIFT 29 +#define XHI_REGISTER_SHIFT 13 +#define XHI_OP_SHIFT 27 + +#define XHI_TYPE_1 1 +#define XHI_TYPE_2 2 +#define XHI_OP_WRITE 2 +#define XHI_OP_READ 1 + +/* Address Block Types */ +#define XHI_FAR_CLB_BLOCK 0 +#define XHI_FAR_BRAM_BLOCK 1 +#define XHI_FAR_BRAM_INT_BLOCK 2 + +struct config_registers { + u32 CRC; + u32 FAR; + u32 FDRI; + u32 FDRO; + u32 CMD; + u32 CTL; + u32 MASK; + u32 STAT; + u32 LOUT; + u32 COR; + u32 MFWR; + u32 FLR; + u32 KEY; + u32 CBC; + u32 IDCODE; + u32 AXSS; + u32 C0R_1; + u32 CSOB; + u32 WBSTAR; + u32 TIMER; + u32 BOOTSTS; + u32 CTL_1; +}; + +/* Configuration Commands */ +#define XHI_CMD_NULL 0 +#define XHI_CMD_WCFG 1 +#define XHI_CMD_MFW 2 +#define XHI_CMD_DGHIGH 3 +#define XHI_CMD_RCFG 4 +#define XHI_CMD_START 5 +#define XHI_CMD_RCAP 6 +#define XHI_CMD_RCRC 7 +#define XHI_CMD_AGHIGH 8 +#define XHI_CMD_SWITCH 9 +#define XHI_CMD_GRESTORE 10 +#define XHI_CMD_SHUTDOWN 11 +#define XHI_CMD_GCAPTURE 12 +#define XHI_CMD_DESYNCH 13 +#define XHI_CMD_IPROG 15 /* Only in Virtex5 */ +#define XHI_CMD_CRCC 16 /* Only in Virtex5 */ +#define XHI_CMD_LTIMER 17 /* Only in Virtex5 */ + +/* Packet constants */ +#define XHI_SYNC_PACKET 0xAA995566UL +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_READ << XHI_OP_SHIFT)) + +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_WRITE << XHI_OP_SHIFT)) + +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF + +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL +#define XHI_TYPE_1_HEADER_BYTES 4 +#define XHI_TYPE_2_HEADER_BYTES 8 + +/* Constant to use for CRC check when CRC has been disabled */ +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL + +/** + * hwicap_type_1_read: Generates a Type 1 read packet header. + * @parameter: Register is the address of the register to be read back. + * + * Generates a Type 1 read packet header, which is used to indirectly + * read registers in the configuration logic. This packet must then + * be sent through the icap device, and a return packet received with + * the information. + **/ +static inline u32 hwicap_type_1_read(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_READ << XHI_OP_SHIFT); +} + +/** + * hwicap_type_1_write: Generates a Type 1 write packet header + * @parameter: Register is the address of the register to be read back. + **/ +static inline u32 hwicap_type_1_write(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_WRITE << XHI_OP_SHIFT); +} + +#endif -- 1.5.3.4-dirty [-- Attachment #2: Type: text/html, Size: 140146 bytes --] ^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 1/2] [POWERPC] Xilinx: hwicap driver [not found] ` <1201900363-25230-1-git-send-email-stephen.neuendorffer@xilinx.com> 2008-02-05 0:38 ` Stephen Neuendorffer @ 2008-02-05 17:24 ` Stephen Neuendorffer [not found] ` <1202232250-24971-1-git-send-email-stephen.neuendorffer@xilinx.com> 2 siblings, 0 replies; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-05 17:24 UTC (permalink / raw) To: linuxppc-dev, grant.likely, jacmet, ntl This includes code for new fifo-based xps_hwicap in addition to the older opb_hwicap, which has a significantly different interface. The common code between the two drivers is largely shared. Significant differences exists between this driver and what is supported in the EDK drivers. In particular, most of the architecture-specific code for reconfiguring individual FPGA resources has been removed. This functionality is likely better provided in a user-space support library. In addition, read and write access is supported. In addition, although the xps_hwicap cores support interrupt-driver mode, this driver only supports polled operation, in order to make the code simpler, and since the interrupt processing overhead is likely to slow down the throughput under Linux. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Fixed to add mutexes, and a few style issues. Acked-by: Grant Likely <grant.likely@secretlab.ca> The final update to xilinx_hwicap.h was missing. --- I will get this right eventually. :) --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 1 + drivers/char/xilinx_hwicap/Makefile | 7 + drivers/char/xilinx_hwicap/buffer_icap.c | 380 ++++++++++++ drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ drivers/char/xilinx_hwicap/xilinx_hwicap.c | 904 ++++++++++++++++++++++++++++ drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ 9 files changed, 1992 insertions(+), 0 deletions(-) create mode 100644 drivers/char/xilinx_hwicap/Makefile create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ef1ed5d..157ae2a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -831,6 +831,13 @@ config DTLK To compile this driver as a module, choose M here: the module will be called dtlk. +config XILINX_HWICAP + tristate "Xilinx HWICAP Support" + depends on XILINX_VIRTEX + help + This option enables support for Xilinx Internal Configuration + Access Port (ICAP) driver. + config R3964 tristate "Siemens R3964 line discipline" ---help--- diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d5..3a278a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ ifeq ($(CONFIG_GENERIC_NVRAM),y) obj-$(CONFIG_NVRAM) += generic_nvram.o else diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile new file mode 100644 index 0000000..5491cbc --- /dev/null +++ b/drivers/char/xilinx_hwicap/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Xilinx OPB hwicap driver +# + +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o + +xilinx_hwicap_m-y := xilinx_hwicap.o fifo_icap.o buffer_icap.o diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c new file mode 100644 index 0000000..dfea2bd --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.c @@ -0,0 +1,380 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "buffer_icap.h" + +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ +#define XHI_MAX_BUFFER_BYTES 2048 +#define XHI_MAX_BUFFER_INTS (XHI_MAX_BUFFER_BYTES >> 2) + +/* File access and error constants */ +#define XHI_DEVICE_READ_ERROR -1 +#define XHI_DEVICE_WRITE_ERROR -2 +#define XHI_BUFFER_OVERFLOW_ERROR -3 + +#define XHI_DEVICE_READ 0x1 +#define XHI_DEVICE_WRITE 0x0 + +/* Constants for checking transfer status */ +#define XHI_CYCLE_DONE 0 +#define XHI_CYCLE_EXECUTING 1 + +/* buffer_icap register offsets */ + +/* Size of transfer, read & write */ +#define XHI_SIZE_REG_OFFSET 0x800L +/* offset into bram, read & write */ +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L +/* Read not Configure, direction of transfer. Write only */ +#define XHI_RNC_REG_OFFSET 0x808L +/* Indicates transfer complete. Read only */ +#define XHI_STATUS_REG_OFFSET 0x80CL + +/* Constants for setting the RNC register */ +#define XHI_CONFIGURE 0x0UL +#define XHI_READBACK 0x1UL + +/* Constants for the Done register */ +#define XHI_NOT_FINISHED 0x0UL +#define XHI_FINISHED 0x1UL + +#define XHI_BUFFER_START 0 + +/** + * buffer_icap_get_status: Get the contents of the status register. + * @parameter base_address: is the base address of the device + * + * The status register contains the ICAP status and the done bit. + * + * D8 - cfgerr + * D7 - dalign + * D6 - rip + * D5 - in_abort_l + * D4 - Always 1 + * D3 - Always 1 + * D2 - Always 1 + * D1 - Always 1 + * D0 - Done bit + **/ +static inline u32 buffer_icap_get_status(void __iomem *base_address) +{ + return in_be32(base_address + XHI_STATUS_REG_OFFSET); +} + +/** + * buffer_icap_get_bram: Reads data from the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset from which the data should be read. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline u32 buffer_icap_get_bram(void __iomem *base_address, + u32 offset) +{ + return in_be32(base_address + (offset << 2)); +} + +/** + * buffer_icap_busy: Return true if the icap device is busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_busy(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_NOT_FINISHED; +} + +/** + * buffer_icap_busy: Return true if the icap device is not busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_done(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_FINISHED; +} + +/** + * buffer_icap_set_size: Set the size register. + * @parameter base_address: is the base address of the device + * @parameter data: The size in bytes. + * + * The size register holds the number of 8 bit bytes to transfer between + * bram and the icap (or icap to bram). + **/ +static inline void buffer_icap_set_size(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_SIZE_REG_OFFSET, data); +} + +/** + * buffer_icap_mSetoffsetReg: Set the bram offset register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The bram offset register holds the starting bram address to transfer + * data from during configuration or write data to during readback. + **/ +static inline void buffer_icap_set_offset(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_BRAM_OFFSET_REG_OFFSET, data); +} + +/** + * buffer_icap_set_rnc: Set the RNC (Readback not Configure) register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The RNC register determines the direction of the data transfer. It + * controls whether a configuration or readback take place. Writing to + * this register initiates the transfer. A value of 1 initiates a + * readback while writing a value of 0 initiates a configuration. + **/ +static inline void buffer_icap_set_rnc(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_RNC_REG_OFFSET, data); +} + +/** + * buffer_icap_set_bram: Write data to the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset at which the data should be written. + * @parameter data: The value to be written to the bram offset. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline void buffer_icap_set_bram(void __iomem *base_address, + u32 offset, u32 data) +{ + out_be32(base_address + (offset << 2), data); +} + +/** + * buffer_icap_device_read: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, (count << 2)); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_READBACK); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, count << 2); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * Writing to the status register resets the ICAP logic in an internal + * version of the core. For the version of the core published in EDK, + * this is a noop. + **/ +void buffer_icap_reset(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_STATUS_REG_OFFSET, 0xFEFE); +} + +/** + * buffer_icap_set_configuration: Load a partial bitstream from system memory. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Kernel address of the partial bitstream. + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 num_writes = 0; + bool dirty = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = 0; i < size; i++) { + + /* Copy data to bram */ + buffer_icap_set_bram(base_address, buffer_count, data[i]); + dirty = 1; + + if (buffer_count < XHI_MAX_BUFFER_INTS - 1) { + buffer_count++; + continue; + } + + /* Write data to ICAP */ + status = buffer_icap_device_write( + drvdata, + XHI_BUFFER_START, + XHI_MAX_BUFFER_INTS); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + num_writes++; + dirty = 0; + } + + /* Write unwritten data to ICAP */ + if (dirty) { + /* Write data to ICAP */ + status = buffer_icap_device_write(drvdata, XHI_BUFFER_START, + buffer_count); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + } + return status; + } + + return 0; +}; + +/** + * buffer_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 read_count = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = XHI_MAX_BUFFER_INTS; i < size; i++) { + if (buffer_count == XHI_MAX_BUFFER_INTS) { + u32 words_remaining = size - i; + u32 words_to_read = + words_remaining < + XHI_MAX_BUFFER_INTS ? words_remaining : + XHI_MAX_BUFFER_INTS; + + /* Read data from ICAP */ + status = buffer_icap_device_read( + drvdata, + XHI_BUFFER_START, + words_to_read); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + read_count++; + } + + /* Copy data from bram */ + data[i] = buffer_icap_get_bram(base_address, buffer_count); + buffer_count++; + } + + return 0; +}; diff --git a/drivers/char/xilinx_hwicap/buffer_icap.h b/drivers/char/xilinx_hwicap/buffer_icap.h new file mode 100644 index 0000000..0318495 --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_BUFFER_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_BUFFER_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +void buffer_icap_reset(struct hwicap_drvdata *drvdata); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c new file mode 100644 index 0000000..0988314 --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "fifo_icap.h" + +/* Register offsets for the XHwIcap device. */ +#define XHI_GIER_OFFSET 0x1C /* Device Global Interrupt Enable Reg */ +#define XHI_IPISR_OFFSET 0x20 /* Interrupt Status Register */ +#define XHI_IPIER_OFFSET 0x28 /* Interrupt Enable Register */ +#define XHI_WF_OFFSET 0x100 /* Write FIFO */ +#define XHI_RF_OFFSET 0x104 /* Read FIFO */ +#define XHI_SZ_OFFSET 0x108 /* Size Register */ +#define XHI_CR_OFFSET 0x10C /* Control Register */ +#define XHI_SR_OFFSET 0x110 /* Status Register */ +#define XHI_WFV_OFFSET 0x114 /* Write FIFO Vacancy Register */ +#define XHI_RFO_OFFSET 0x118 /* Read FIFO Occupancy Register */ + +/* Device Global Interrupt Enable Register (GIER) bit definitions */ + +#define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ + +/** + * HwIcap Device Interrupt Status/Enable Registers + * + * Interrupt Status Register (IPISR) : This register holds the + * interrupt status flags for the device. These bits are toggle on + * write. + * + * Interrupt Enable Register (IPIER) : This register is used to enable + * interrupt sources for the device. + * Writing a '1' to a bit enables the corresponding interrupt. + * Writing a '0' to a bit disables the corresponding interrupt. + * + * IPISR/IPIER registers have the same bit definitions and are only defined + * once. + */ +#define XHI_IPIXR_RFULL_MASK 0x00000008 /* Read FIFO Full */ +#define XHI_IPIXR_WEMPTY_MASK 0x00000004 /* Write FIFO Empty */ +#define XHI_IPIXR_RDP_MASK 0x00000002 /* Read FIFO half full */ +#define XHI_IPIXR_WRP_MASK 0x00000001 /* Write FIFO half full */ +#define XHI_IPIXR_ALL_MASK 0x0000000F /* Mask of all interrupts */ + +/* Control Register (CR) */ +#define XHI_CR_SW_RESET_MASK 0x00000008 /* SW Reset Mask */ +#define XHI_CR_FIFO_CLR_MASK 0x00000004 /* FIFO Clear Mask */ +#define XHI_CR_READ_MASK 0x00000002 /* Read from ICAP to FIFO */ +#define XHI_CR_WRITE_MASK 0x00000001 /* Write from FIFO to ICAP */ + +/* Status Register (SR) */ +#define XHI_SR_CFGERR_N_MASK 0x00000100 /* Config Error Mask */ +#define XHI_SR_DALIGN_MASK 0x00000080 /* Data Alignment Mask */ +#define XHI_SR_RIP_MASK 0x00000040 /* Read back Mask */ +#define XHI_SR_IN_ABORT_N_MASK 0x00000020 /* Select Map Abort Mask */ +#define XHI_SR_DONE_MASK 0x00000001 /* Done bit Mask */ + + +#define XHI_WFO_MAX_VACANCY 1024 /* Max Write FIFO Vacancy, in words */ +#define XHI_RFO_MAX_OCCUPANCY 256 /* Max Read FIFO Occupancy, in words */ +/* The maximum amount we can request from fifo_icap_get_configuration + at once, in bytes. */ +#define XHI_MAX_READ_TRANSACTION_WORDS 0xFFF + + +/** + * fifo_icap_fifo_write: Write data to the write FIFO. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the 32-bit value to be written to the FIFO. + * + * This function will silently fail if the fifo is full. + **/ +static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, + u32 data) +{ + dev_dbg(drvdata->dev, "fifo_write: %x\n", data); + out_be32(drvdata->base_address + XHI_WF_OFFSET, data); +} + +/** + * fifo_icap_fifo_read: Read data from the Read FIFO. + * @parameter drvdata: a pointer to the drvdata. + * + * This function will silently fail if the fifo is empty. + **/ +static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) +{ + u32 data = in_be32(drvdata->base_address + XHI_RF_OFFSET); + dev_dbg(drvdata->dev, "fifo_read: %x\n", data); + return data; +} + +/** + * fifo_icap_set_read_size: Set the the size register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the size of the following read transaction, in words. + **/ +static inline void fifo_icap_set_read_size(struct hwicap_drvdata *drvdata, + u32 data) +{ + out_be32(drvdata->base_address + XHI_SZ_OFFSET, data); +} + +/** + * fifo_icap_start_config: Initiate a configuration (write) to the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_config(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_WRITE_MASK); + dev_dbg(drvdata->dev, "configuration started\n"); +} + +/** + * fifo_icap_start_readback: Initiate a readback from the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_READ_MASK); + dev_dbg(drvdata->dev, "readback started\n"); +} + +/** + * fifo_icap_busy: Return true if the ICAP is still processing a transaction. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) +{ + u32 status = in_be32(drvdata->base_address + XHI_SR_OFFSET); + dev_dbg(drvdata->dev, "Getting status = %x\n", status); + return (status & XHI_SR_DONE_MASK) ? 0 : 1; +} + +/** + * fifo_icap_write_fifo_vacancy: Query the write fifo available space. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely pushed into the write fifo. + **/ +static inline u32 fifo_icap_write_fifo_vacancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_WFV_OFFSET); +} + +/** + * fifo_icap_read_fifo_occupancy: Query the read fifo available data. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely read from the read fifo. + **/ +static inline u32 fifo_icap_read_fifo_occupancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_RFO_OFFSET); +} + +/** + * fifo_icap_set_configuration: Send configuration data to the ICAP. + * @parameter drvdata: a pointer to the drvdata. + * @parameter frame_buffer: a pointer to the data to be written to the + * ICAP device. + * @parameter num_words: the number of words (32 bit) to write to the ICAP + * device. + + * This function writes the given user data to the Write FIFO in + * polled mode and starts the transfer of the data to + * the ICAP device. + **/ +int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 write_fifo_vacancy = 0; + u32 retries = 0; + u32 remaining_words; + + dev_dbg(drvdata->dev, "fifo_set_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Read/Write + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + /* + * Set up the buffer pointer and the words to be transferred. + */ + remaining_words = num_words; + + while (remaining_words > 0) { + /* + * Wait until we have some data in the fifo. + */ + while (write_fifo_vacancy == 0) { + write_fifo_vacancy = + fifo_icap_write_fifo_vacancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + /* + * Write data into the Write FIFO. + */ + while ((write_fifo_vacancy != 0) && + (remaining_words > 0)) { + fifo_icap_fifo_write(drvdata, *frame_buffer); + + remaining_words--; + write_fifo_vacancy--; + frame_buffer++; + } + /* Start pushing whatever is in the FIFO into the ICAP. */ + fifo_icap_start_config(drvdata); + } + + /* Wait until the write has finished. */ + while (fifo_icap_busy(drvdata)) { + retries++; + if (retries > XHI_MAX_RETRIES) + break; + } + + dev_dbg(drvdata->dev, "done fifo_set_configuration\n"); + + /* + * If the requested number of words have not been read from + * the device then indicate failure. + */ + if (remaining_words != 0) + return -EIO; + + return 0; +} + +/** + * fifo_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + * + * This function reads the specified number of words from the ICAP device in + * the polled mode. + */ +int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 read_fifo_occupancy = 0; + u32 retries = 0; + u32 *data = frame_buffer; + u32 remaining_words; + u32 words_to_read; + + dev_dbg(drvdata->dev, "fifo_get_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Write/Read + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + remaining_words = num_words; + + while (remaining_words > 0) { + words_to_read = remaining_words; + /* The hardware has a limit on the number of words + that can be read at one time. */ + if (words_to_read > XHI_MAX_READ_TRANSACTION_WORDS) + words_to_read = XHI_MAX_READ_TRANSACTION_WORDS; + + remaining_words -= words_to_read; + + fifo_icap_set_read_size(drvdata, words_to_read); + fifo_icap_start_readback(drvdata); + + while (words_to_read > 0) { + /* Wait until we have some data in the fifo. */ + while (read_fifo_occupancy == 0) { + read_fifo_occupancy = + fifo_icap_read_fifo_occupancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + if (read_fifo_occupancy > words_to_read) + read_fifo_occupancy = words_to_read; + + words_to_read -= read_fifo_occupancy; + + /* Read the data from the Read FIFO. */ + while (read_fifo_occupancy != 0) { + *data++ = fifo_icap_fifo_read(drvdata); + read_fifo_occupancy--; + } + } + } + + dev_dbg(drvdata->dev, "done fifo_get_configuration\n"); + + return 0; +} + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * This function forces the software reset of the complete HWICAP device. + * All the registers will return to the default value and the FIFO is also + * flushed as a part of this software reset. + */ +void fifo_icap_reset(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Reset the device by setting/clearing the RESET bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_SW_RESET_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_SW_RESET_MASK)); + +} + +/** + * fifo_icap_flush_fifo: This function flushes the FIFOs in the device. + * @parameter drvdata: a pointer to the drvdata. + */ +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Flush the FIFO by setting/clearing the FIFO Clear bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_FIFO_CLR_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_FIFO_CLR_MASK)); +} + diff --git a/drivers/char/xilinx_hwicap/fifo_icap.h b/drivers/char/xilinx_hwicap/fifo_icap.h new file mode 100644 index 0000000..4d3068d --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_FIFO_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_FIFO_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +/* Reads integers from the device into the storage buffer. */ +int fifo_icap_get_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +/* Writes integers to the device from the storage buffer. */ +int fifo_icap_set_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +void fifo_icap_reset(struct hwicap_drvdata *drvdata); +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata); + +#endif diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c new file mode 100644 index 0000000..24f6aef --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -0,0 +1,904 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/* + * This is the code behind /dev/xilinx_icap -- it allows a user-space + * application to use the Xilinx ICAP subsystem. + * + * The following operations are possible: + * + * open open the port and initialize for access. + * release release port + * write Write a bitstream to the configuration processor. + * read Read a data stream from the configuration processor. + * + * After being opened, the port is initialized and accessed to avoid a + * corrupted first read which may occur with some hardware. The port + * is left in a desynched state, requiring that a synch sequence be + * transmitted before any valid configuration data. A user will have + * exclusive access to the device while it remains open, and the state + * of the ICAP cannot be guaranteed after the device is closed. Note + * that a complete reset of the core and the state of the ICAP cannot + * be performed on many versions of the cores, hence users of this + * device should avoid making inconsistent accesses to the device. In + * particular, accessing the read interface, without first generating + * a write containing a readback packet can leave the ICAP in an + * inaccessible state. + * + * Note that in order to use the read interface, it is first necessary + * to write a request packet to the write interface. i.e., it is not + * possible to simply readback the bitstream (or any configuration + * bits) from a device without specifically requesting them first. + * The code to craft such packets is intended to be part of the + * user-space application code that uses this device. The simplest + * way to use this interface is simply: + * + * cp foo.bit /dev/xilinx_icap + * + * Note that unless foo.bit is an appropriately constructed partial + * bitstream, this has a high likelyhood of overwriting the design + * currently programmed in the FPGA. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <asm/semaphore.h> +#include <linux/sysctl.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#ifdef CONFIG_OF +/* For open firmware. */ +#include <linux/of_device.h> +#include <linux/of_platform.h> +#endif + +#include "xilinx_hwicap.h" +#include "buffer_icap.h" +#include "fifo_icap.h" + +#define DRIVER_NAME "xilinx_icap" + +#define HWICAP_REGS (0x10000) + +/* dynamically allocate device number */ +static int xhwicap_major; +static int xhwicap_minor; +#define HWICAP_DEVICES 1 + +module_param(xhwicap_major, int, S_IRUGO); +module_param(xhwicap_minor, int, S_IRUGO); + +/* An array, which is set to true when the device is registered. */ +static bool probed_devices[HWICAP_DEVICES]; + +static struct class *icap_class; + +#define UNIMPLEMENTED 0xFFFF + +static const struct config_registers v2_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = 11, + .KEY = 12, + .CBC = 13, + .IDCODE = 14, + .AXSS = UNIMPLEMENTED, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; + +static const struct config_registers v4_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; +static const struct config_registers v5_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = 14, + .CSOB = 15, + .WBSTAR = 16, + .TIMER = 17, + .BOOTSTS = 18, + .CTL_1 = 19, +}; + +/** + * hwicap_command_desync: Send a DESYNC command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command desynchronizes the ICAP After this command, a + * bitstream containing a NULL packet, followed by a SYNCH packet is + * required before the ICAP will recognize commands. + */ +int hwicap_command_desync(struct hwicap_drvdata *drvdata) +{ + u32 buffer[4]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_DESYNCH; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); +} + +/** + * hwicap_command_capture: Send a CAPTURE command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command captures all of the flip flop states so they will be + * available during readback. One can use this command instead of + * enabling the CAPTURE block in the design. + */ +int hwicap_command_capture(struct hwicap_drvdata *drvdata) +{ + u32 buffer[7]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_GCAPTURE; + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_DUMMY_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data + * present in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); + +} + +/** + * hwicap_get_configuration_register: Query a configuration register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter reg: a constant which represents the configuration + * register value to be returned. + * Examples: XHI_IDCODE, XHI_FLR. + * @parameter RegData: returns the value of the register. + * + * Sends a query packet to the ICAP and then receives the response. + * The icap is left in Synched state. + */ +int hwicap_get_configuration_register(struct hwicap_drvdata *drvdata, + u32 reg, u32 *RegData) +{ + int status; + u32 buffer[6]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_read(reg) | 1; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + status = drvdata->config->set_configuration(drvdata, + &buffer[0], index); + if (status) + return status; + + /* + * Read the configuration register + */ + status = drvdata->config->get_configuration(drvdata, RegData, 1); + if (status) + return status; + + return 0; +} + +int hwicap_initialize_hwicap(struct hwicap_drvdata *drvdata) +{ + int status; + u32 idcode; + + dev_dbg(drvdata->dev, "initializing\n"); + + /* Abort any current transaction, to make sure we have the + * ICAP in a good state. */ + dev_dbg(drvdata->dev, "Reset...\n"); + drvdata->config->reset(drvdata); + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + /* Attempt to read the IDCODE from ICAP. This + * may not be returned correctly, due to the design of the + * hardware. + */ + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); + status = hwicap_get_configuration_register( + drvdata, drvdata->config_regs->IDCODE, &idcode); + dev_dbg(drvdata->dev, "IDCODE = %x\n", idcode); + if (status) + return status; + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + return 0; +} + +static ssize_t +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t bytes_to_read = 0; + u32 *kbuf; + u32 words; + u32 bytes_remaining; + int status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->read_buffer_in_use) { + /* If there are leftover bytes in the buffer, just */ + /* return them and don't try to read more from the */ + /* ICAP device. */ + bytes_to_read = + (count < drvdata->read_buffer_in_use) ? count : + drvdata->read_buffer_in_use; + + /* Return the data currently in the read buffer. */ + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { + status = -EFAULT; + goto error; + } + drvdata->read_buffer_in_use -= bytes_to_read; + memcpy(drvdata->read_buffer + bytes_to_read, + drvdata->read_buffer, 4 - bytes_to_read); + } else { + /* Get new data from the ICAP, and return was was requested. */ + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + /* The ICAP device is only able to read complete */ + /* words. If a number of bytes that do not correspond */ + /* to complete words is requested, then we read enough */ + /* words to get the required number of bytes, and then */ + /* save the remaining bytes for the next read. */ + + /* Determine the number of words to read, rounding up */ + /* if necessary. */ + words = ((count + 3) >> 2); + bytes_to_read = words << 2; + + if (bytes_to_read > PAGE_SIZE) + bytes_to_read = PAGE_SIZE; + + /* Ensure we only read a complete number of words. */ + bytes_remaining = bytes_to_read & 3; + bytes_to_read &= ~3; + words = bytes_to_read >> 2; + + status = drvdata->config->get_configuration(drvdata, + kbuf, words); + + /* If we didn't read correctly, then bail out. */ + if (status) { + free_page((unsigned long)kbuf); + goto error; + } + + /* If we fail to return the data to the user, then bail out. */ + if (copy_to_user(buf, kbuf, bytes_to_read)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + memcpy(kbuf, drvdata->read_buffer, bytes_remaining); + drvdata->read_buffer_in_use = bytes_remaining; + free_page((unsigned long)kbuf); + } + status = bytes_to_read; + error: + up(&drvdata->sem); + return status; +} + +static ssize_t +hwicap_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t written = 0; + ssize_t left = count; + u32 *kbuf; + ssize_t len; + ssize_t status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + left += drvdata->write_buffer_in_use; + + /* Only write multiples of 4 bytes. */ + if (left < 4) { + status = 0; + goto error; + } + + kbuf = (u32 *) __get_free_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + while (left > 3) { + /* only write multiples of 4 bytes, so there might */ + /* be as many as 3 bytes left (at the end). */ + len = left; + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + len &= ~3; + + if (drvdata->write_buffer_in_use) { + memcpy(kbuf, drvdata->write_buffer, + drvdata->write_buffer_in_use); + if (copy_from_user( + (((char *)kbuf) + (drvdata->write_buffer_in_use)), + buf + written, + len - (drvdata->write_buffer_in_use))) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } else { + if (copy_from_user(kbuf, buf + written, len)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } + + status = drvdata->config->set_configuration(drvdata, + kbuf, len >> 2); + + if (status) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + if (drvdata->write_buffer_in_use) { + len -= drvdata->write_buffer_in_use; + left -= drvdata->write_buffer_in_use; + drvdata->write_buffer_in_use = 0; + } + written += len; + left -= len; + } + if ((left > 0) && (left < 4)) { + if (!copy_from_user(drvdata->write_buffer, + buf + written, left)) { + drvdata->write_buffer_in_use = left; + written += left; + left = 0; + } + } + + free_page((unsigned long)kbuf); + status = written; + error: + up(&drvdata->sem); + return status; +} + +static int hwicap_open(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata; + int status; + + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->is_open) { + status = -EBUSY; + goto error; + } + + status = hwicap_initialize_hwicap(drvdata); + if (status) { + dev_err(drvdata->dev, "Failed to open file"); + goto error; + } + + file->private_data = drvdata; + drvdata->write_buffer_in_use = 0; + drvdata->read_buffer_in_use = 0; + drvdata->is_open = 1; + + error: + up(&drvdata->sem); + return status; +} + +static int hwicap_release(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata = file->private_data; + int i; + int status = 0; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->write_buffer_in_use) { + /* Flush write buffer. */ + for (i = drvdata->write_buffer_in_use; i < 4; i++) + drvdata->write_buffer[i] = 0; + + status = drvdata->config->set_configuration(drvdata, + (u32 *) drvdata->write_buffer, 1); + if (status) + goto error; + } + + status = hwicap_command_desync(drvdata); + if (status) + goto error; + + error: + drvdata->is_open = 0; + up(&drvdata->sem); + return status; +} + +static struct file_operations hwicap_fops = { + .owner = THIS_MODULE, + .write = hwicap_write, + .read = hwicap_read, + .open = hwicap_open, + .release = hwicap_release, +}; + +static int __devinit hwicap_setup(struct device *dev, int id, + const struct resource *regs_res, + const struct hwicap_driver_config *config, + const struct config_registers *config_regs) +{ + dev_t devt; + struct hwicap_drvdata *drvdata = NULL; + int retval = 0; + + dev_info(dev, "Xilinx icap port driver\n"); + + if (id < 0) { + for (id = 0; id < HWICAP_DEVICES; id++) + if (!probed_devices[id]) + break; + } + if (id < 0 || id >= HWICAP_DEVICES) { + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); + return -EINVAL; + } + if (probed_devices[id]) { + dev_err(dev, "cannot assign to %s%i; it is already in use\n", + DRIVER_NAME, id); + return -EBUSY; + } + + probed_devices[id] = 1; + + devt = MKDEV(xhwicap_major, xhwicap_minor + id); + + drvdata = kmalloc(sizeof(struct hwicap_drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + return -ENOMEM; + } + memset((void *)drvdata, 0, sizeof(struct hwicap_drvdata)); + dev_set_drvdata(dev, (void *)drvdata); + + if (!regs_res) { + dev_err(dev, "Couldn't get registers resource\n"); + retval = -EFAULT; + goto failed1; + } + + drvdata->mem_start = regs_res->start; + drvdata->mem_end = regs_res->end; + drvdata->mem_size = regs_res->end - regs_res->start + 1; + + if (!request_mem_region(drvdata->mem_start, + drvdata->mem_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at %p\n", + (void *)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + drvdata->devt = devt; + drvdata->dev = dev; + drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); + if (!drvdata->base_address) { + dev_err(dev, "ioremap() failed\n"); + goto failed2; + } + + drvdata->config = config; + drvdata->config_regs = config_regs; + + init_MUTEX(&drvdata->sem); + drvdata->is_open = 0; + + dev_info(dev, "ioremap %lx to %p with size %x\n", + (unsigned long int)drvdata->mem_start, + drvdata->base_address, drvdata->mem_size); + + cdev_init(&drvdata->cdev, &hwicap_fops); + drvdata->cdev.owner = THIS_MODULE; + retval = cdev_add(&drvdata->cdev, devt, 1); + if (retval) { + dev_err(dev, "cdev_add() failed\n"); + goto failed3; + } + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ + class_device_create(icap_class, NULL, devt, NULL, DRIVER_NAME); + return 0; /* success */ + + failed3: + iounmap(drvdata->base_address); + + failed2: + release_mem_region(regs_res->start, drvdata->mem_size); + + failed1: + kfree(drvdata); + + return retval; +} + +static struct hwicap_driver_config buffer_icap_config = { + .get_configuration = buffer_icap_get_configuration, + .set_configuration = buffer_icap_set_configuration, + .reset = buffer_icap_reset, +}; + +static struct hwicap_driver_config fifo_icap_config = { + .get_configuration = fifo_icap_get_configuration, + .set_configuration = fifo_icap_set_configuration, + .reset = fifo_icap_reset, +}; + +static int __devexit hwicap_remove(struct device *dev) +{ + struct hwicap_drvdata *drvdata; + + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); + + if (!drvdata) + return 0; + + class_device_destroy(icap_class, drvdata->devt); + cdev_del(&drvdata->cdev); + iounmap(drvdata->base_address); + release_mem_region(drvdata->mem_start, drvdata->mem_size); + kfree(drvdata); + dev_set_drvdata(dev, NULL); + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; + + return 0; /* success */ +} + +static int __devinit hwicap_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct config_registers *regs; + const char *family; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = pdev->dev.platform_data; + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + + return hwicap_setup(&pdev->dev, pdev->id, res, + &buffer_icap_config, regs); +} + +static int __devexit hwicap_drv_remove(struct platform_device *pdev) +{ + return hwicap_remove(&pdev->dev); +} + +static struct platform_driver hwicap_platform_driver = { + .probe = hwicap_drv_probe, + .remove = hwicap_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +#if defined(CONFIG_OF) +static int __devinit +hwicap_of_probe(struct of_device *op, const struct of_device_id *match) +{ + struct resource res; + const unsigned int *id; + const char *family; + int rc; + const struct hwicap_driver_config *config = match->data; + const struct config_registers *regs; + + dev_dbg(&op->dev, "hwicap_of_probe(%p, %p)\n", op, match); + + rc = of_address_to_resource(op->node, 0, &res); + if (rc) { + dev_err(&op->dev, "invalid address\n"); + return rc; + } + + id = of_get_property(op->node, "port-number", NULL); + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = of_get_property(op->node, "xlnx,family", NULL); + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + return hwicap_setup(&op->dev, id ? *id : -1, &res, config, + regs); +} + +static int __devexit hwicap_of_remove(struct of_device *op) +{ + return hwicap_remove(&op->dev); +} + +/* Match table for of_platform binding */ +static const struct of_device_id __devinit hwicap_of_match[] = { + { .compatible = "xlnx,opb-hwicap-1.00.b", .data = &buffer_icap_config}, + { .compatible = "xlnx,xps-hwicap-1.00.a", .data = &fifo_icap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, hwicap_of_match); + +static struct of_platform_driver hwicap_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = hwicap_of_match, + .probe = hwicap_of_probe, + .remove = __devexit_p(hwicap_of_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +/* Registration helpers to keep the number of #ifdefs to a minimum */ +static inline int __devinit hwicap_of_register(void) +{ + pr_debug("hwicap: calling of_register_platform_driver()\n"); + return of_register_platform_driver(&hwicap_of_driver); +} + +static inline void __devexit hwicap_of_unregister(void) +{ + of_unregister_platform_driver(&hwicap_of_driver); +} +#else /* CONFIG_OF */ +/* CONFIG_OF not enabled; do nothing helpers */ +static inline int __devinit hwicap_of_register(void) { return 0; } +static inline void __devexit hwicap_of_unregister(void) { } +#endif /* CONFIG_OF */ + +static int __devinit hwicap_module_init(void) +{ + dev_t devt; + int retval; + + icap_class = class_create(THIS_MODULE, "xilinx_config"); + + if (xhwicap_major) { + devt = MKDEV(xhwicap_major, xhwicap_minor); + retval = register_chrdev_region( + devt, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + } else { + retval = alloc_chrdev_region(&devt, + xhwicap_minor, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + xhwicap_major = MAJOR(devt); + } + + retval = platform_driver_register(&hwicap_platform_driver); + + if (retval) + goto failed1; + + retval = hwicap_of_register(); + + if (retval) + goto failed2; + + return retval; + + failed2: + platform_driver_unregister(&hwicap_platform_driver); + + failed1: + unregister_chrdev_region(devt, HWICAP_DEVICES); + + return retval; +} + +static void __devexit hwicap_module_cleanup(void) +{ + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); + + class_destroy(icap_class); + + platform_driver_unregister(&hwicap_platform_driver); + + hwicap_of_unregister(); + + unregister_chrdev_region(devt, HWICAP_DEVICES); +} + +module_init(hwicap_module_init); +module_exit(hwicap_module_cleanup); + +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h new file mode 100644 index 0000000..ae771ca --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2007 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ +#define XILINX_HWICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +struct hwicap_drvdata { + u32 write_buffer_in_use; /* Always in [0,3] */ + u8 write_buffer[4]; + u32 read_buffer_in_use; /* Always in [0,3] */ + u8 read_buffer[4]; + u32 mem_start; /* phys. address of the control registers */ + u32 mem_end; /* phys. address of the control registers */ + u32 mem_size; + void __iomem *base_address;/* virt. address of the control registers */ + + struct device *dev; + struct cdev cdev; /* Char device structure */ + dev_t devt; + + const struct hwicap_driver_config *config; + const struct config_registers *config_regs; + void *private_data; + bool is_open; + struct semaphore sem; +}; + +struct hwicap_driver_config { + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + void (*reset)(struct hwicap_drvdata *drvdata); +}; + +/* Number of times to poll the done regsiter */ +#define XHI_MAX_RETRIES 10 + +/************ Constant Definitions *************/ + +#define XHI_PAD_FRAMES 0x1 + +/* Mask for calculating configuration packet headers */ +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL +#define XHI_TYPE_MASK 0x7 +#define XHI_REGISTER_MASK 0xF +#define XHI_OP_MASK 0x3 + +#define XHI_TYPE_SHIFT 29 +#define XHI_REGISTER_SHIFT 13 +#define XHI_OP_SHIFT 27 + +#define XHI_TYPE_1 1 +#define XHI_TYPE_2 2 +#define XHI_OP_WRITE 2 +#define XHI_OP_READ 1 + +/* Address Block Types */ +#define XHI_FAR_CLB_BLOCK 0 +#define XHI_FAR_BRAM_BLOCK 1 +#define XHI_FAR_BRAM_INT_BLOCK 2 + +struct config_registers { + u32 CRC; + u32 FAR; + u32 FDRI; + u32 FDRO; + u32 CMD; + u32 CTL; + u32 MASK; + u32 STAT; + u32 LOUT; + u32 COR; + u32 MFWR; + u32 FLR; + u32 KEY; + u32 CBC; + u32 IDCODE; + u32 AXSS; + u32 C0R_1; + u32 CSOB; + u32 WBSTAR; + u32 TIMER; + u32 BOOTSTS; + u32 CTL_1; +}; + +/* Configuration Commands */ +#define XHI_CMD_NULL 0 +#define XHI_CMD_WCFG 1 +#define XHI_CMD_MFW 2 +#define XHI_CMD_DGHIGH 3 +#define XHI_CMD_RCFG 4 +#define XHI_CMD_START 5 +#define XHI_CMD_RCAP 6 +#define XHI_CMD_RCRC 7 +#define XHI_CMD_AGHIGH 8 +#define XHI_CMD_SWITCH 9 +#define XHI_CMD_GRESTORE 10 +#define XHI_CMD_SHUTDOWN 11 +#define XHI_CMD_GCAPTURE 12 +#define XHI_CMD_DESYNCH 13 +#define XHI_CMD_IPROG 15 /* Only in Virtex5 */ +#define XHI_CMD_CRCC 16 /* Only in Virtex5 */ +#define XHI_CMD_LTIMER 17 /* Only in Virtex5 */ + +/* Packet constants */ +#define XHI_SYNC_PACKET 0xAA995566UL +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_READ << XHI_OP_SHIFT)) + +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_WRITE << XHI_OP_SHIFT)) + +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF + +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL +#define XHI_TYPE_1_HEADER_BYTES 4 +#define XHI_TYPE_2_HEADER_BYTES 8 + +/* Constant to use for CRC check when CRC has been disabled */ +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL + +/** + * hwicap_type_1_read: Generates a Type 1 read packet header. + * @parameter: Register is the address of the register to be read back. + * + * Generates a Type 1 read packet header, which is used to indirectly + * read registers in the configuration logic. This packet must then + * be sent through the icap device, and a return packet received with + * the information. + **/ +static inline u32 hwicap_type_1_read(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_READ << XHI_OP_SHIFT); +} + +/** + * hwicap_type_1_write: Generates a Type 1 write packet header + * @parameter: Register is the address of the register to be read back. + **/ +static inline u32 hwicap_type_1_write(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_WRITE << XHI_OP_SHIFT); +} + +#endif -- 1.5.3.4-dirty ^ permalink raw reply related [flat|nested] 21+ messages in thread
[parent not found: <1202232250-24971-1-git-send-email-stephen.neuendorffer@xilinx.com>]
* [PATCH 2/2] [POWERPC] Xilinx: hwicap: update booting-without-of.txt [not found] ` <1202232250-24971-1-git-send-email-stephen.neuendorffer@xilinx.com> @ 2008-02-05 17:24 ` Stephen Neuendorffer 0 siblings, 0 replies; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-05 17:24 UTC (permalink / raw) To: linuxppc-dev, grant.likely, jacmet, ntl The ICAP device in Xilinx FPGAs differs slightly between different FPGAs. The driver needs an additional attribute in the device tree to distinguish this. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> --- Documentation/powerpc/booting-without-of.txt | 14 ++++++++++++++ 1 files changed, 14 insertions(+), 0 deletions(-) diff --git a/Documentation/powerpc/booting-without-of.txt b/Documentation/powerpc/booting-without-of.txt index e9a3cb1..035e28f 100644 --- a/Documentation/powerpc/booting-without-of.txt +++ b/Documentation/powerpc/booting-without-of.txt @@ -2513,6 +2513,20 @@ platforms are moved over to use the flattened-device-tree model. Requred properties: - current-speed : Baud rate of uartlite + + v) Xilinx hwicap + + Xilinx hwicap devices provide access to the configuration logic + of the FPGA through the Internal Configuration Access Port + (ICAP). The ICAP enables partial reconfiguration of the FPGA, + readback of the configuration information, and some control over + 'warm boots' of the FPGA fabric. + + Required properties: + - xlnx,family : The family of the FPGA, necessary since the + capabilities of the underlying ICAP hardware + differ between different families. May be + 'virtex2p', 'virtex4', or 'virtex5'. More devices will be defined as this spec matures. -- 1.5.3.4-dirty ^ permalink raw reply related [flat|nested] 21+ messages in thread
[parent not found: <47AB6552.7040503@gmail.com>]
* [PATCH] [POWERPC] Xilinx: hwicap driver [not found] <47AB6552.7040503@gmail.com> @ 2008-02-08 2:17 ` Stephen Neuendorffer 2008-02-08 9:10 ` Jiri Slaby 2008-02-08 16:49 ` Randy Dunlap 0 siblings, 2 replies; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-08 2:17 UTC (permalink / raw) To: grant.likely, linuxppc-dev, jirislaby, linux-kernel This includes code for new fifo-based xps_hwicap in addition to the older opb_hwicap, which has a significantly different interface. The common code between the two drivers is largely shared. Significant differences exists between this driver and what is supported in the EDK drivers. In particular, most of the architecture-specific code for reconfiguring individual FPGA resources has been removed. This functionality is likely better provided in a user-space support library. In addition, read and write access is supported. In addition, although the xps_hwicap cores support interrupt-driver mode, this driver only supports polled operation, in order to make the code simpler, and since the interrupt processing overhead is likely to slow down the throughput under Linux. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Fixed to add mutexes, and a few style issues. Acked-by: Grant Likely <grant.likely@secretlab.ca> The final update to xilinx_hwicap.h was missing. fix some missing __user tags and incorrect section tags. convert semaphores to mutexes. make probed_devices re-entrancy and error condition safe. fix some backwards memcpys. some other minor cleanups. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 1 + drivers/char/xilinx_hwicap/Makefile | 7 + drivers/char/xilinx_hwicap/buffer_icap.c | 380 ++++++++++++ drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ drivers/char/xilinx_hwicap/xilinx_hwicap.c | 923 ++++++++++++++++++++++++++++ drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ 9 files changed, 2011 insertions(+), 0 deletions(-) create mode 100644 drivers/char/xilinx_hwicap/Makefile create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ef1ed5d..157ae2a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -831,6 +831,13 @@ config DTLK To compile this driver as a module, choose M here: the module will be called dtlk. +config XILINX_HWICAP + tristate "Xilinx HWICAP Support" + depends on XILINX_VIRTEX + help + This option enables support for Xilinx Internal Configuration + Access Port (ICAP) driver. + config R3964 tristate "Siemens R3964 line discipline" ---help--- diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d5..3a278a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ ifeq ($(CONFIG_GENERIC_NVRAM),y) obj-$(CONFIG_NVRAM) += generic_nvram.o else diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile new file mode 100644 index 0000000..5491cbc --- /dev/null +++ b/drivers/char/xilinx_hwicap/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Xilinx OPB hwicap driver +# + +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o + +xilinx_hwicap_m-y := xilinx_hwicap.o fifo_icap.o buffer_icap.o diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c new file mode 100644 index 0000000..dfea2bd --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.c @@ -0,0 +1,380 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "buffer_icap.h" + +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ +#define XHI_MAX_BUFFER_BYTES 2048 +#define XHI_MAX_BUFFER_INTS (XHI_MAX_BUFFER_BYTES >> 2) + +/* File access and error constants */ +#define XHI_DEVICE_READ_ERROR -1 +#define XHI_DEVICE_WRITE_ERROR -2 +#define XHI_BUFFER_OVERFLOW_ERROR -3 + +#define XHI_DEVICE_READ 0x1 +#define XHI_DEVICE_WRITE 0x0 + +/* Constants for checking transfer status */ +#define XHI_CYCLE_DONE 0 +#define XHI_CYCLE_EXECUTING 1 + +/* buffer_icap register offsets */ + +/* Size of transfer, read & write */ +#define XHI_SIZE_REG_OFFSET 0x800L +/* offset into bram, read & write */ +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L +/* Read not Configure, direction of transfer. Write only */ +#define XHI_RNC_REG_OFFSET 0x808L +/* Indicates transfer complete. Read only */ +#define XHI_STATUS_REG_OFFSET 0x80CL + +/* Constants for setting the RNC register */ +#define XHI_CONFIGURE 0x0UL +#define XHI_READBACK 0x1UL + +/* Constants for the Done register */ +#define XHI_NOT_FINISHED 0x0UL +#define XHI_FINISHED 0x1UL + +#define XHI_BUFFER_START 0 + +/** + * buffer_icap_get_status: Get the contents of the status register. + * @parameter base_address: is the base address of the device + * + * The status register contains the ICAP status and the done bit. + * + * D8 - cfgerr + * D7 - dalign + * D6 - rip + * D5 - in_abort_l + * D4 - Always 1 + * D3 - Always 1 + * D2 - Always 1 + * D1 - Always 1 + * D0 - Done bit + **/ +static inline u32 buffer_icap_get_status(void __iomem *base_address) +{ + return in_be32(base_address + XHI_STATUS_REG_OFFSET); +} + +/** + * buffer_icap_get_bram: Reads data from the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset from which the data should be read. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline u32 buffer_icap_get_bram(void __iomem *base_address, + u32 offset) +{ + return in_be32(base_address + (offset << 2)); +} + +/** + * buffer_icap_busy: Return true if the icap device is busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_busy(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_NOT_FINISHED; +} + +/** + * buffer_icap_busy: Return true if the icap device is not busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_done(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_FINISHED; +} + +/** + * buffer_icap_set_size: Set the size register. + * @parameter base_address: is the base address of the device + * @parameter data: The size in bytes. + * + * The size register holds the number of 8 bit bytes to transfer between + * bram and the icap (or icap to bram). + **/ +static inline void buffer_icap_set_size(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_SIZE_REG_OFFSET, data); +} + +/** + * buffer_icap_mSetoffsetReg: Set the bram offset register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The bram offset register holds the starting bram address to transfer + * data from during configuration or write data to during readback. + **/ +static inline void buffer_icap_set_offset(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_BRAM_OFFSET_REG_OFFSET, data); +} + +/** + * buffer_icap_set_rnc: Set the RNC (Readback not Configure) register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The RNC register determines the direction of the data transfer. It + * controls whether a configuration or readback take place. Writing to + * this register initiates the transfer. A value of 1 initiates a + * readback while writing a value of 0 initiates a configuration. + **/ +static inline void buffer_icap_set_rnc(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_RNC_REG_OFFSET, data); +} + +/** + * buffer_icap_set_bram: Write data to the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset at which the data should be written. + * @parameter data: The value to be written to the bram offset. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline void buffer_icap_set_bram(void __iomem *base_address, + u32 offset, u32 data) +{ + out_be32(base_address + (offset << 2), data); +} + +/** + * buffer_icap_device_read: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, (count << 2)); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_READBACK); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, count << 2); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * Writing to the status register resets the ICAP logic in an internal + * version of the core. For the version of the core published in EDK, + * this is a noop. + **/ +void buffer_icap_reset(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_STATUS_REG_OFFSET, 0xFEFE); +} + +/** + * buffer_icap_set_configuration: Load a partial bitstream from system memory. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Kernel address of the partial bitstream. + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 num_writes = 0; + bool dirty = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = 0; i < size; i++) { + + /* Copy data to bram */ + buffer_icap_set_bram(base_address, buffer_count, data[i]); + dirty = 1; + + if (buffer_count < XHI_MAX_BUFFER_INTS - 1) { + buffer_count++; + continue; + } + + /* Write data to ICAP */ + status = buffer_icap_device_write( + drvdata, + XHI_BUFFER_START, + XHI_MAX_BUFFER_INTS); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + num_writes++; + dirty = 0; + } + + /* Write unwritten data to ICAP */ + if (dirty) { + /* Write data to ICAP */ + status = buffer_icap_device_write(drvdata, XHI_BUFFER_START, + buffer_count); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + } + return status; + } + + return 0; +}; + +/** + * buffer_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 read_count = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = XHI_MAX_BUFFER_INTS; i < size; i++) { + if (buffer_count == XHI_MAX_BUFFER_INTS) { + u32 words_remaining = size - i; + u32 words_to_read = + words_remaining < + XHI_MAX_BUFFER_INTS ? words_remaining : + XHI_MAX_BUFFER_INTS; + + /* Read data from ICAP */ + status = buffer_icap_device_read( + drvdata, + XHI_BUFFER_START, + words_to_read); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + read_count++; + } + + /* Copy data from bram */ + data[i] = buffer_icap_get_bram(base_address, buffer_count); + buffer_count++; + } + + return 0; +}; diff --git a/drivers/char/xilinx_hwicap/buffer_icap.h b/drivers/char/xilinx_hwicap/buffer_icap.h new file mode 100644 index 0000000..0318495 --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_BUFFER_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_BUFFER_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +void buffer_icap_reset(struct hwicap_drvdata *drvdata); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c new file mode 100644 index 0000000..0988314 --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "fifo_icap.h" + +/* Register offsets for the XHwIcap device. */ +#define XHI_GIER_OFFSET 0x1C /* Device Global Interrupt Enable Reg */ +#define XHI_IPISR_OFFSET 0x20 /* Interrupt Status Register */ +#define XHI_IPIER_OFFSET 0x28 /* Interrupt Enable Register */ +#define XHI_WF_OFFSET 0x100 /* Write FIFO */ +#define XHI_RF_OFFSET 0x104 /* Read FIFO */ +#define XHI_SZ_OFFSET 0x108 /* Size Register */ +#define XHI_CR_OFFSET 0x10C /* Control Register */ +#define XHI_SR_OFFSET 0x110 /* Status Register */ +#define XHI_WFV_OFFSET 0x114 /* Write FIFO Vacancy Register */ +#define XHI_RFO_OFFSET 0x118 /* Read FIFO Occupancy Register */ + +/* Device Global Interrupt Enable Register (GIER) bit definitions */ + +#define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ + +/** + * HwIcap Device Interrupt Status/Enable Registers + * + * Interrupt Status Register (IPISR) : This register holds the + * interrupt status flags for the device. These bits are toggle on + * write. + * + * Interrupt Enable Register (IPIER) : This register is used to enable + * interrupt sources for the device. + * Writing a '1' to a bit enables the corresponding interrupt. + * Writing a '0' to a bit disables the corresponding interrupt. + * + * IPISR/IPIER registers have the same bit definitions and are only defined + * once. + */ +#define XHI_IPIXR_RFULL_MASK 0x00000008 /* Read FIFO Full */ +#define XHI_IPIXR_WEMPTY_MASK 0x00000004 /* Write FIFO Empty */ +#define XHI_IPIXR_RDP_MASK 0x00000002 /* Read FIFO half full */ +#define XHI_IPIXR_WRP_MASK 0x00000001 /* Write FIFO half full */ +#define XHI_IPIXR_ALL_MASK 0x0000000F /* Mask of all interrupts */ + +/* Control Register (CR) */ +#define XHI_CR_SW_RESET_MASK 0x00000008 /* SW Reset Mask */ +#define XHI_CR_FIFO_CLR_MASK 0x00000004 /* FIFO Clear Mask */ +#define XHI_CR_READ_MASK 0x00000002 /* Read from ICAP to FIFO */ +#define XHI_CR_WRITE_MASK 0x00000001 /* Write from FIFO to ICAP */ + +/* Status Register (SR) */ +#define XHI_SR_CFGERR_N_MASK 0x00000100 /* Config Error Mask */ +#define XHI_SR_DALIGN_MASK 0x00000080 /* Data Alignment Mask */ +#define XHI_SR_RIP_MASK 0x00000040 /* Read back Mask */ +#define XHI_SR_IN_ABORT_N_MASK 0x00000020 /* Select Map Abort Mask */ +#define XHI_SR_DONE_MASK 0x00000001 /* Done bit Mask */ + + +#define XHI_WFO_MAX_VACANCY 1024 /* Max Write FIFO Vacancy, in words */ +#define XHI_RFO_MAX_OCCUPANCY 256 /* Max Read FIFO Occupancy, in words */ +/* The maximum amount we can request from fifo_icap_get_configuration + at once, in bytes. */ +#define XHI_MAX_READ_TRANSACTION_WORDS 0xFFF + + +/** + * fifo_icap_fifo_write: Write data to the write FIFO. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the 32-bit value to be written to the FIFO. + * + * This function will silently fail if the fifo is full. + **/ +static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, + u32 data) +{ + dev_dbg(drvdata->dev, "fifo_write: %x\n", data); + out_be32(drvdata->base_address + XHI_WF_OFFSET, data); +} + +/** + * fifo_icap_fifo_read: Read data from the Read FIFO. + * @parameter drvdata: a pointer to the drvdata. + * + * This function will silently fail if the fifo is empty. + **/ +static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) +{ + u32 data = in_be32(drvdata->base_address + XHI_RF_OFFSET); + dev_dbg(drvdata->dev, "fifo_read: %x\n", data); + return data; +} + +/** + * fifo_icap_set_read_size: Set the the size register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the size of the following read transaction, in words. + **/ +static inline void fifo_icap_set_read_size(struct hwicap_drvdata *drvdata, + u32 data) +{ + out_be32(drvdata->base_address + XHI_SZ_OFFSET, data); +} + +/** + * fifo_icap_start_config: Initiate a configuration (write) to the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_config(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_WRITE_MASK); + dev_dbg(drvdata->dev, "configuration started\n"); +} + +/** + * fifo_icap_start_readback: Initiate a readback from the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_READ_MASK); + dev_dbg(drvdata->dev, "readback started\n"); +} + +/** + * fifo_icap_busy: Return true if the ICAP is still processing a transaction. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) +{ + u32 status = in_be32(drvdata->base_address + XHI_SR_OFFSET); + dev_dbg(drvdata->dev, "Getting status = %x\n", status); + return (status & XHI_SR_DONE_MASK) ? 0 : 1; +} + +/** + * fifo_icap_write_fifo_vacancy: Query the write fifo available space. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely pushed into the write fifo. + **/ +static inline u32 fifo_icap_write_fifo_vacancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_WFV_OFFSET); +} + +/** + * fifo_icap_read_fifo_occupancy: Query the read fifo available data. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely read from the read fifo. + **/ +static inline u32 fifo_icap_read_fifo_occupancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_RFO_OFFSET); +} + +/** + * fifo_icap_set_configuration: Send configuration data to the ICAP. + * @parameter drvdata: a pointer to the drvdata. + * @parameter frame_buffer: a pointer to the data to be written to the + * ICAP device. + * @parameter num_words: the number of words (32 bit) to write to the ICAP + * device. + + * This function writes the given user data to the Write FIFO in + * polled mode and starts the transfer of the data to + * the ICAP device. + **/ +int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 write_fifo_vacancy = 0; + u32 retries = 0; + u32 remaining_words; + + dev_dbg(drvdata->dev, "fifo_set_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Read/Write + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + /* + * Set up the buffer pointer and the words to be transferred. + */ + remaining_words = num_words; + + while (remaining_words > 0) { + /* + * Wait until we have some data in the fifo. + */ + while (write_fifo_vacancy == 0) { + write_fifo_vacancy = + fifo_icap_write_fifo_vacancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + /* + * Write data into the Write FIFO. + */ + while ((write_fifo_vacancy != 0) && + (remaining_words > 0)) { + fifo_icap_fifo_write(drvdata, *frame_buffer); + + remaining_words--; + write_fifo_vacancy--; + frame_buffer++; + } + /* Start pushing whatever is in the FIFO into the ICAP. */ + fifo_icap_start_config(drvdata); + } + + /* Wait until the write has finished. */ + while (fifo_icap_busy(drvdata)) { + retries++; + if (retries > XHI_MAX_RETRIES) + break; + } + + dev_dbg(drvdata->dev, "done fifo_set_configuration\n"); + + /* + * If the requested number of words have not been read from + * the device then indicate failure. + */ + if (remaining_words != 0) + return -EIO; + + return 0; +} + +/** + * fifo_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + * + * This function reads the specified number of words from the ICAP device in + * the polled mode. + */ +int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 read_fifo_occupancy = 0; + u32 retries = 0; + u32 *data = frame_buffer; + u32 remaining_words; + u32 words_to_read; + + dev_dbg(drvdata->dev, "fifo_get_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Write/Read + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + remaining_words = num_words; + + while (remaining_words > 0) { + words_to_read = remaining_words; + /* The hardware has a limit on the number of words + that can be read at one time. */ + if (words_to_read > XHI_MAX_READ_TRANSACTION_WORDS) + words_to_read = XHI_MAX_READ_TRANSACTION_WORDS; + + remaining_words -= words_to_read; + + fifo_icap_set_read_size(drvdata, words_to_read); + fifo_icap_start_readback(drvdata); + + while (words_to_read > 0) { + /* Wait until we have some data in the fifo. */ + while (read_fifo_occupancy == 0) { + read_fifo_occupancy = + fifo_icap_read_fifo_occupancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + if (read_fifo_occupancy > words_to_read) + read_fifo_occupancy = words_to_read; + + words_to_read -= read_fifo_occupancy; + + /* Read the data from the Read FIFO. */ + while (read_fifo_occupancy != 0) { + *data++ = fifo_icap_fifo_read(drvdata); + read_fifo_occupancy--; + } + } + } + + dev_dbg(drvdata->dev, "done fifo_get_configuration\n"); + + return 0; +} + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * This function forces the software reset of the complete HWICAP device. + * All the registers will return to the default value and the FIFO is also + * flushed as a part of this software reset. + */ +void fifo_icap_reset(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Reset the device by setting/clearing the RESET bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_SW_RESET_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_SW_RESET_MASK)); + +} + +/** + * fifo_icap_flush_fifo: This function flushes the FIFOs in the device. + * @parameter drvdata: a pointer to the drvdata. + */ +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Flush the FIFO by setting/clearing the FIFO Clear bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_FIFO_CLR_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_FIFO_CLR_MASK)); +} + diff --git a/drivers/char/xilinx_hwicap/fifo_icap.h b/drivers/char/xilinx_hwicap/fifo_icap.h new file mode 100644 index 0000000..4d3068d --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_FIFO_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_FIFO_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +/* Reads integers from the device into the storage buffer. */ +int fifo_icap_get_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +/* Writes integers to the device from the storage buffer. */ +int fifo_icap_set_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +void fifo_icap_reset(struct hwicap_drvdata *drvdata); +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata); + +#endif diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c new file mode 100644 index 0000000..2caac31 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -0,0 +1,923 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/* + * This is the code behind /dev/xilinx_icap -- it allows a user-space + * application to use the Xilinx ICAP subsystem. + * + * The following operations are possible: + * + * open open the port and initialize for access. + * release release port + * write Write a bitstream to the configuration processor. + * read Read a data stream from the configuration processor. + * + * After being opened, the port is initialized and accessed to avoid a + * corrupted first read which may occur with some hardware. The port + * is left in a desynched state, requiring that a synch sequence be + * transmitted before any valid configuration data. A user will have + * exclusive access to the device while it remains open, and the state + * of the ICAP cannot be guaranteed after the device is closed. Note + * that a complete reset of the core and the state of the ICAP cannot + * be performed on many versions of the cores, hence users of this + * device should avoid making inconsistent accesses to the device. In + * particular, accessing the read interface, without first generating + * a write containing a readback packet can leave the ICAP in an + * inaccessible state. + * + * Note that in order to use the read interface, it is first necessary + * to write a request packet to the write interface. i.e., it is not + * possible to simply readback the bitstream (or any configuration + * bits) from a device without specifically requesting them first. + * The code to craft such packets is intended to be part of the + * user-space application code that uses this device. The simplest + * way to use this interface is simply: + * + * cp foo.bit /dev/xilinx_icap + * + * Note that unless foo.bit is an appropriately constructed partial + * bitstream, this has a high likelyhood of overwriting the design + * currently programmed in the FPGA. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <linux/mutex.h> +#include <linux/sysctl.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#ifdef CONFIG_OF +/* For open firmware. */ +#include <linux/of_device.h> +#include <linux/of_platform.h> +#endif + +#include "xilinx_hwicap.h" +#include "buffer_icap.h" +#include "fifo_icap.h" + +#define DRIVER_NAME "xilinx_icap" + +#define HWICAP_REGS (0x10000) + +/* dynamically allocate device number */ +static int xhwicap_major; +static int xhwicap_minor; +#define HWICAP_DEVICES 1 + +module_param(xhwicap_major, int, S_IRUGO); +module_param(xhwicap_minor, int, S_IRUGO); + +/* An array, which is set to true when the device is registered. */ +static bool probed_devices[HWICAP_DEVICES]; +static struct mutex icap_sem; + +static struct class *icap_class; + +#define UNIMPLEMENTED 0xFFFF + +static const struct config_registers v2_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = 11, + .KEY = 12, + .CBC = 13, + .IDCODE = 14, + .AXSS = UNIMPLEMENTED, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; + +static const struct config_registers v4_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; +static const struct config_registers v5_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = 14, + .CSOB = 15, + .WBSTAR = 16, + .TIMER = 17, + .BOOTSTS = 18, + .CTL_1 = 19, +}; + +/** + * hwicap_command_desync: Send a DESYNC command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command desynchronizes the ICAP After this command, a + * bitstream containing a NULL packet, followed by a SYNCH packet is + * required before the ICAP will recognize commands. + */ +static int hwicap_command_desync(struct hwicap_drvdata *drvdata) +{ + u32 buffer[4]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_DESYNCH; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); +} + +/** + * hwicap_command_capture: Send a CAPTURE command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command captures all of the flip flop states so they will be + * available during readback. One can use this command instead of + * enabling the CAPTURE block in the design. + */ +static int hwicap_command_capture(struct hwicap_drvdata *drvdata) +{ + u32 buffer[7]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_GCAPTURE; + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_DUMMY_PACKET; + + /* + * Write the data to the FIFO and initiate the transfer of data + * present in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); + +} + +/** + * hwicap_get_configuration_register: Query a configuration register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter reg: a constant which represents the configuration + * register value to be returned. + * Examples: XHI_IDCODE, XHI_FLR. + * @parameter RegData: returns the value of the register. + * + * Sends a query packet to the ICAP and then receives the response. + * The icap is left in Synched state. + */ +static int hwicap_get_configuration_register(struct hwicap_drvdata *drvdata, + u32 reg, u32 *RegData) +{ + int status; + u32 buffer[6]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_read(reg) | 1; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + status = drvdata->config->set_configuration(drvdata, + &buffer[0], index); + if (status) + return status; + + /* + * Read the configuration register + */ + status = drvdata->config->get_configuration(drvdata, RegData, 1); + if (status) + return status; + + return 0; +} + +static int hwicap_initialize_hwicap(struct hwicap_drvdata *drvdata) +{ + int status; + u32 idcode; + + dev_dbg(drvdata->dev, "initializing\n"); + + /* Abort any current transaction, to make sure we have the + * ICAP in a good state. */ + dev_dbg(drvdata->dev, "Reset...\n"); + drvdata->config->reset(drvdata); + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + /* Attempt to read the IDCODE from ICAP. This + * may not be returned correctly, due to the design of the + * hardware. + */ + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); + status = hwicap_get_configuration_register( + drvdata, drvdata->config_regs->IDCODE, &idcode); + dev_dbg(drvdata->dev, "IDCODE = %x\n", idcode); + if (status) + return status; + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + return 0; +} + +static ssize_t +hwicap_read(struct file *file, __user char *buf, size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t bytes_to_read = 0; + u32 *kbuf; + u32 words; + u32 bytes_remaining; + int status; + + status = mutex_lock_interruptible(&drvdata->sem); + if (status) + return status; + + if (drvdata->read_buffer_in_use) { + /* If there are leftover bytes in the buffer, just */ + /* return them and don't try to read more from the */ + /* ICAP device. */ + bytes_to_read = + (count < drvdata->read_buffer_in_use) ? count : + drvdata->read_buffer_in_use; + + /* Return the data currently in the read buffer. */ + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { + status = -EFAULT; + goto error; + } + drvdata->read_buffer_in_use -= bytes_to_read; + memmove(drvdata->read_buffer, + drvdata->read_buffer + bytes_to_read, + 4 - bytes_to_read); + } else { + /* Get new data from the ICAP, and return was was requested. */ + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + /* The ICAP device is only able to read complete */ + /* words. If a number of bytes that do not correspond */ + /* to complete words is requested, then we read enough */ + /* words to get the required number of bytes, and then */ + /* save the remaining bytes for the next read. */ + + /* Determine the number of words to read, rounding up */ + /* if necessary. */ + words = ((count + 3) >> 2); + bytes_to_read = words << 2; + + if (bytes_to_read > PAGE_SIZE) + bytes_to_read = PAGE_SIZE; + + /* Ensure we only read a complete number of words. */ + bytes_remaining = bytes_to_read & 3; + bytes_to_read &= ~3; + words = bytes_to_read >> 2; + + status = drvdata->config->get_configuration(drvdata, + kbuf, words); + + /* If we didn't read correctly, then bail out. */ + if (status) { + free_page((unsigned long)kbuf); + goto error; + } + + /* If we fail to return the data to the user, then bail out. */ + if (copy_to_user(buf, kbuf, bytes_to_read)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + memcpy(drvdata->read_buffer, + kbuf, + bytes_remaining); + drvdata->read_buffer_in_use = bytes_remaining; + free_page((unsigned long)kbuf); + } + status = bytes_to_read; + error: + mutex_unlock(&drvdata->sem); + return status; +} + +static ssize_t +hwicap_write(struct file *file, const __user char *buf, + size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t written = 0; + ssize_t left = count; + u32 *kbuf; + ssize_t len; + ssize_t status; + + status = mutex_lock_interruptible(&drvdata->sem); + if (status) + return status; + + left += drvdata->write_buffer_in_use; + + /* Only write multiples of 4 bytes. */ + if (left < 4) { + status = 0; + goto error; + } + + kbuf = (u32 *) __get_free_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + while (left > 3) { + /* only write multiples of 4 bytes, so there might */ + /* be as many as 3 bytes left (at the end). */ + len = left; + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + len &= ~3; + + if (drvdata->write_buffer_in_use) { + memcpy(kbuf, drvdata->write_buffer, + drvdata->write_buffer_in_use); + if (copy_from_user( + (((char *)kbuf) + drvdata->write_buffer_in_use), + buf + written, + len - (drvdata->write_buffer_in_use))) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } else { + if (copy_from_user(kbuf, buf + written, len)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } + + status = drvdata->config->set_configuration(drvdata, + kbuf, len >> 2); + + if (status) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + if (drvdata->write_buffer_in_use) { + len -= drvdata->write_buffer_in_use; + left -= drvdata->write_buffer_in_use; + drvdata->write_buffer_in_use = 0; + } + written += len; + left -= len; + } + if ((left > 0) && (left < 4)) { + if (!copy_from_user(drvdata->write_buffer, + buf + written, left)) { + drvdata->write_buffer_in_use = left; + written += left; + left = 0; + } + } + + free_page((unsigned long)kbuf); + status = written; + error: + mutex_unlock(&drvdata->sem); + return status; +} + +static int hwicap_open(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata; + int status; + + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); + + status = mutex_lock_interruptible(&drvdata->sem); + if (status) + return status; + + if (drvdata->is_open) { + status = -EBUSY; + goto error; + } + + status = hwicap_initialize_hwicap(drvdata); + if (status) { + dev_err(drvdata->dev, "Failed to open file"); + goto error; + } + + file->private_data = drvdata; + drvdata->write_buffer_in_use = 0; + drvdata->read_buffer_in_use = 0; + drvdata->is_open = 1; + + error: + mutex_unlock(&drvdata->sem); + return status; +} + +static int hwicap_release(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata = file->private_data; + int i; + int status = 0; + + mutex_lock(&drvdata->sem); + + if (drvdata->write_buffer_in_use) { + /* Flush write buffer. */ + for (i = drvdata->write_buffer_in_use; i < 4; i++) + drvdata->write_buffer[i] = 0; + + status = drvdata->config->set_configuration(drvdata, + (u32 *) drvdata->write_buffer, 1); + if (status) + goto error; + } + + status = hwicap_command_desync(drvdata); + if (status) + goto error; + + error: + drvdata->is_open = 0; + mutex_unlock(&drvdata->sem); + return status; +} + +static struct file_operations hwicap_fops = { + .owner = THIS_MODULE, + .write = hwicap_write, + .read = hwicap_read, + .open = hwicap_open, + .release = hwicap_release, +}; + +static int __devinit hwicap_setup(struct device *dev, int id, + const struct resource *regs_res, + const struct hwicap_driver_config *config, + const struct config_registers *config_regs) +{ + dev_t devt; + struct hwicap_drvdata *drvdata = NULL; + int retval = 0; + + dev_info(dev, "Xilinx icap port driver\n"); + + mutex_lock(&icap_sem); + + if (id < 0) { + for (id = 0; id < HWICAP_DEVICES; id++) + if (!probed_devices[id]) + break; + } + if (id < 0 || id >= HWICAP_DEVICES) { + mutex_unlock(&icap_sem); + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); + return -EINVAL; + } + if (probed_devices[id]) { + mutex_unlock(&icap_sem); + dev_err(dev, "cannot assign to %s%i; it is already in use\n", + DRIVER_NAME, id); + return -EBUSY; + } + + probed_devices[id] = 1; + mutex_unlock(&icap_sem); + + devt = MKDEV(xhwicap_major, xhwicap_minor + id); + + drvdata = kzalloc(sizeof(struct hwicap_drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + retval = -ENOMEM; + goto failed0; + } + dev_set_drvdata(dev, (void *)drvdata); + + if (!regs_res) { + dev_err(dev, "Couldn't get registers resource\n"); + retval = -EFAULT; + goto failed1; + } + + drvdata->mem_start = regs_res->start; + drvdata->mem_end = regs_res->end; + drvdata->mem_size = regs_res->end - regs_res->start + 1; + + if (!request_mem_region(drvdata->mem_start, + drvdata->mem_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at %p\n", + (void *)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + drvdata->devt = devt; + drvdata->dev = dev; + drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); + if (!drvdata->base_address) { + dev_err(dev, "ioremap() failed\n"); + goto failed2; + } + + drvdata->config = config; + drvdata->config_regs = config_regs; + + mutex_init(&drvdata->sem); + drvdata->is_open = 0; + + dev_info(dev, "ioremap %lx to %p with size %x\n", + (unsigned long int)drvdata->mem_start, + drvdata->base_address, drvdata->mem_size); + + cdev_init(&drvdata->cdev, &hwicap_fops); + drvdata->cdev.owner = THIS_MODULE; + retval = cdev_add(&drvdata->cdev, devt, 1); + if (retval) { + dev_err(dev, "cdev_add() failed\n"); + goto failed3; + } + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ + device_create(icap_class, dev, devt, "%s%d", DRIVER_NAME, id); + return 0; /* success */ + + failed3: + iounmap(drvdata->base_address); + + failed2: + release_mem_region(regs_res->start, drvdata->mem_size); + + failed1: + kfree(drvdata); + + failed0: + mutex_lock(&icap_sem); + probed_devices[id] = 0; + mutex_unlock(&icap_sem); + + return retval; +} + +static struct hwicap_driver_config buffer_icap_config = { + .get_configuration = buffer_icap_get_configuration, + .set_configuration = buffer_icap_set_configuration, + .reset = buffer_icap_reset, +}; + +static struct hwicap_driver_config fifo_icap_config = { + .get_configuration = fifo_icap_get_configuration, + .set_configuration = fifo_icap_set_configuration, + .reset = fifo_icap_reset, +}; + +static int __devexit hwicap_remove(struct device *dev) +{ + struct hwicap_drvdata *drvdata; + + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); + + if (!drvdata) + return 0; + + device_destroy(icap_class, drvdata->devt); + cdev_del(&drvdata->cdev); + iounmap(drvdata->base_address); + release_mem_region(drvdata->mem_start, drvdata->mem_size); + kfree(drvdata); + dev_set_drvdata(dev, NULL); + + mutex_lock(&icap_sem); + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; + mutex_unlock(&icap_sem); + return 0; /* success */ +} + +static int __devinit hwicap_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct config_registers *regs; + const char *family; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = pdev->dev.platform_data; + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + + return hwicap_setup(&pdev->dev, pdev->id, res, + &buffer_icap_config, regs); +} + +static int __devexit hwicap_drv_remove(struct platform_device *pdev) +{ + return hwicap_remove(&pdev->dev); +} + +static struct platform_driver hwicap_platform_driver = { + .probe = hwicap_drv_probe, + .remove = hwicap_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +#if defined(CONFIG_OF) +static int __devinit +hwicap_of_probe(struct of_device *op, const struct of_device_id *match) +{ + struct resource res; + const unsigned int *id; + const char *family; + int rc; + const struct hwicap_driver_config *config = match->data; + const struct config_registers *regs; + + dev_dbg(&op->dev, "hwicap_of_probe(%p, %p)\n", op, match); + + rc = of_address_to_resource(op->node, 0, &res); + if (rc) { + dev_err(&op->dev, "invalid address\n"); + return rc; + } + + id = of_get_property(op->node, "port-number", NULL); + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = of_get_property(op->node, "xlnx,family", NULL); + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + return hwicap_setup(&op->dev, id ? *id : -1, &res, config, + regs); +} + +static int __devexit hwicap_of_remove(struct of_device *op) +{ + return hwicap_remove(&op->dev); +} + +/* Match table for of_platform binding */ +static const struct of_device_id __devinit hwicap_of_match[] = { + { .compatible = "xlnx,opb-hwicap-1.00.b", .data = &buffer_icap_config}, + { .compatible = "xlnx,xps-hwicap-1.00.a", .data = &fifo_icap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, hwicap_of_match); + +static struct of_platform_driver hwicap_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = hwicap_of_match, + .probe = hwicap_of_probe, + .remove = __devexit_p(hwicap_of_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +/* Registration helpers to keep the number of #ifdefs to a minimum */ +static inline int __init hwicap_of_register(void) +{ + pr_debug("hwicap: calling of_register_platform_driver()\n"); + return of_register_platform_driver(&hwicap_of_driver); +} + +static inline void __exit hwicap_of_unregister(void) +{ + of_unregister_platform_driver(&hwicap_of_driver); +} +#else /* CONFIG_OF */ +/* CONFIG_OF not enabled; do nothing helpers */ +static inline int __init hwicap_of_register(void) { return 0; } +static inline void __exit hwicap_of_unregister(void) { } +#endif /* CONFIG_OF */ + +static int __init hwicap_module_init(void) +{ + dev_t devt; + int retval; + + icap_class = class_create(THIS_MODULE, "xilinx_config"); + mutex_init(&icap_sem); + + if (xhwicap_major) { + devt = MKDEV(xhwicap_major, xhwicap_minor); + retval = register_chrdev_region( + devt, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + } else { + retval = alloc_chrdev_region(&devt, + xhwicap_minor, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + xhwicap_major = MAJOR(devt); + } + + retval = platform_driver_register(&hwicap_platform_driver); + + if (retval) + goto failed1; + + retval = hwicap_of_register(); + + if (retval) + goto failed2; + + return retval; + + failed2: + platform_driver_unregister(&hwicap_platform_driver); + + failed1: + unregister_chrdev_region(devt, HWICAP_DEVICES); + + return retval; +} + +static void __exit hwicap_module_cleanup(void) +{ + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); + + class_destroy(icap_class); + + platform_driver_unregister(&hwicap_platform_driver); + + hwicap_of_unregister(); + + unregister_chrdev_region(devt, HWICAP_DEVICES); +} + +module_init(hwicap_module_init); +module_exit(hwicap_module_cleanup); + +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h new file mode 100644 index 0000000..5718679 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2007 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ +#define XILINX_HWICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +struct hwicap_drvdata { + u32 write_buffer_in_use; /* Always in [0,3] */ + u8 write_buffer[4]; + u32 read_buffer_in_use; /* Always in [0,3] */ + u8 read_buffer[4]; + resource_size_t mem_start;/* phys. address of the control registers */ + resource_size_t mem_end; /* phys. address of the control registers */ + resource_size_t mem_size; + void __iomem *base_address;/* virt. address of the control registers */ + + struct device *dev; + struct cdev cdev; /* Char device structure */ + dev_t devt; + + const struct hwicap_driver_config *config; + const struct config_registers *config_regs; + void *private_data; + bool is_open; + struct mutex sem; +}; + +struct hwicap_driver_config { + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + void (*reset)(struct hwicap_drvdata *drvdata); +}; + +/* Number of times to poll the done regsiter */ +#define XHI_MAX_RETRIES 10 + +/************ Constant Definitions *************/ + +#define XHI_PAD_FRAMES 0x1 + +/* Mask for calculating configuration packet headers */ +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL +#define XHI_TYPE_MASK 0x7 +#define XHI_REGISTER_MASK 0xF +#define XHI_OP_MASK 0x3 + +#define XHI_TYPE_SHIFT 29 +#define XHI_REGISTER_SHIFT 13 +#define XHI_OP_SHIFT 27 + +#define XHI_TYPE_1 1 +#define XHI_TYPE_2 2 +#define XHI_OP_WRITE 2 +#define XHI_OP_READ 1 + +/* Address Block Types */ +#define XHI_FAR_CLB_BLOCK 0 +#define XHI_FAR_BRAM_BLOCK 1 +#define XHI_FAR_BRAM_INT_BLOCK 2 + +struct config_registers { + u32 CRC; + u32 FAR; + u32 FDRI; + u32 FDRO; + u32 CMD; + u32 CTL; + u32 MASK; + u32 STAT; + u32 LOUT; + u32 COR; + u32 MFWR; + u32 FLR; + u32 KEY; + u32 CBC; + u32 IDCODE; + u32 AXSS; + u32 C0R_1; + u32 CSOB; + u32 WBSTAR; + u32 TIMER; + u32 BOOTSTS; + u32 CTL_1; +}; + +/* Configuration Commands */ +#define XHI_CMD_NULL 0 +#define XHI_CMD_WCFG 1 +#define XHI_CMD_MFW 2 +#define XHI_CMD_DGHIGH 3 +#define XHI_CMD_RCFG 4 +#define XHI_CMD_START 5 +#define XHI_CMD_RCAP 6 +#define XHI_CMD_RCRC 7 +#define XHI_CMD_AGHIGH 8 +#define XHI_CMD_SWITCH 9 +#define XHI_CMD_GRESTORE 10 +#define XHI_CMD_SHUTDOWN 11 +#define XHI_CMD_GCAPTURE 12 +#define XHI_CMD_DESYNCH 13 +#define XHI_CMD_IPROG 15 /* Only in Virtex5 */ +#define XHI_CMD_CRCC 16 /* Only in Virtex5 */ +#define XHI_CMD_LTIMER 17 /* Only in Virtex5 */ + +/* Packet constants */ +#define XHI_SYNC_PACKET 0xAA995566UL +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_READ << XHI_OP_SHIFT)) + +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_WRITE << XHI_OP_SHIFT)) + +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF + +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL +#define XHI_TYPE_1_HEADER_BYTES 4 +#define XHI_TYPE_2_HEADER_BYTES 8 + +/* Constant to use for CRC check when CRC has been disabled */ +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL + +/** + * hwicap_type_1_read: Generates a Type 1 read packet header. + * @parameter: Register is the address of the register to be read back. + * + * Generates a Type 1 read packet header, which is used to indirectly + * read registers in the configuration logic. This packet must then + * be sent through the icap device, and a return packet received with + * the information. + **/ +static inline u32 hwicap_type_1_read(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_READ << XHI_OP_SHIFT); +} + +/** + * hwicap_type_1_write: Generates a Type 1 write packet header + * @parameter: Register is the address of the register to be read back. + **/ +static inline u32 hwicap_type_1_write(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_WRITE << XHI_OP_SHIFT); +} + +#endif -- 1.5.3.4-dirty ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-02-08 2:17 ` [PATCH] [POWERPC] Xilinx: hwicap driver Stephen Neuendorffer @ 2008-02-08 9:10 ` Jiri Slaby 2008-02-08 16:49 ` Randy Dunlap 1 sibling, 0 replies; 21+ messages in thread From: Jiri Slaby @ 2008-02-08 9:10 UTC (permalink / raw) To: Stephen Neuendorffer; +Cc: linuxppc-dev, linux-kernel On 02/08/2008 03:17 AM, Stephen Neuendorffer wrote: > This includes code for new fifo-based xps_hwicap in addition to the > older opb_hwicap, which has a significantly different interface. The > common code between the two drivers is largely shared. > > Significant differences exists between this driver and what is > supported in the EDK drivers. In particular, most of the > architecture-specific code for reconfiguring individual FPGA resources > has been removed. This functionality is likely better provided in a > user-space support library. In addition, read and write access is > supported. In addition, although the xps_hwicap cores support > interrupt-driver mode, this driver only supports polled operation, in > order to make the code simpler, and since the interrupt processing > overhead is likely to slow down the throughput under Linux. > > Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> > > Fixed to add mutexes, and a few style issues. > > Acked-by: Grant Likely <grant.likely@secretlab.ca> > > The final update to xilinx_hwicap.h was missing. > > fix some missing __user tags and incorrect section tags. > convert semaphores to mutexes. > make probed_devices re-entrancy and error condition safe. > fix some backwards memcpys. > some other minor cleanups. Looks good to me. > Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> > --- [...] > diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c > new file mode 100644 > index 0000000..2caac31 > --- /dev/null > +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c > @@ -0,0 +1,923 @@ [...] > +module_param(xhwicap_major, int, S_IRUGO); > +module_param(xhwicap_minor, int, S_IRUGO); > + > +/* An array, which is set to true when the device is registered. */ > +static bool probed_devices[HWICAP_DEVICES]; > +static struct mutex icap_sem; Just a sideway note, static DEFINE_MUTEX(icap_sem); and you don't need to runtime init it then. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-02-08 2:17 ` [PATCH] [POWERPC] Xilinx: hwicap driver Stephen Neuendorffer 2008-02-08 9:10 ` Jiri Slaby @ 2008-02-08 16:49 ` Randy Dunlap 1 sibling, 0 replies; 21+ messages in thread From: Randy Dunlap @ 2008-02-08 16:49 UTC (permalink / raw) To: Stephen Neuendorffer; +Cc: linuxppc-dev, jirislaby, linux-kernel On Thu, 7 Feb 2008 18:17:41 -0800 Stephen Neuendorffer wrote: > drivers/char/Kconfig | 7 + > drivers/char/Makefile | 1 + > drivers/char/xilinx_hwicap/Makefile | 7 + > drivers/char/xilinx_hwicap/buffer_icap.c | 380 ++++++++++++ > drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ > drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ > drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ > drivers/char/xilinx_hwicap/xilinx_hwicap.c | 923 ++++++++++++++++++++++++++++ > drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ > 9 files changed, 2011 insertions(+), 0 deletions(-) > create mode 100644 drivers/char/xilinx_hwicap/Makefile > create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c > create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h > create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c > create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h > create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c > create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h > > diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c > new file mode 100644 > index 0000000..dfea2bd > --- /dev/null > +++ b/drivers/char/xilinx_hwicap/buffer_icap.c > @@ -0,0 +1,380 @@ > +/** > + * buffer_icap_get_status: Get the contents of the status register. > + * @parameter base_address: is the base address of the device > + * Hi, For this function and many others in these source files: Please see Documentation/kernel-doc-nano-HOWTO.txt for the correct kernel-doc notation format. If you have questions or need help, please ask. Hints: a. function name & short description: separate name & description with '-' b. parameters are listed as: @base_address: (without "parameter") > + * The status register contains the ICAP status and the done bit. > + * > + * D8 - cfgerr > + * D7 - dalign > + * D6 - rip > + * D5 - in_abort_l > + * D4 - Always 1 > + * D3 - Always 1 > + * D2 - Always 1 > + * D1 - Always 1 > + * D0 - Done bit > + **/ > +static inline u32 buffer_icap_get_status(void __iomem *base_address) > +{ > + return in_be32(base_address + XHI_STATUS_REG_OFFSET); > +} > diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h > new file mode 100644 > index 0000000..5718679 > --- /dev/null > +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h > @@ -0,0 +1,193 @@ > +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ > +#define XILINX_HWICAP_H_ /* by using protection macros */ > + > +#include <linux/types.h> > +#include <linux/cdev.h> > +#include <linux/version.h> > +#include <linux/platform_device.h> > + > +#include <asm/io.h> > + > +struct hwicap_drvdata { BTW, you can also use kernel-doc for structs, unions, & enums. > + u32 write_buffer_in_use; /* Always in [0,3] */ > + u8 write_buffer[4]; > + u32 read_buffer_in_use; /* Always in [0,3] */ > + u8 read_buffer[4]; > + resource_size_t mem_start;/* phys. address of the control registers */ > + resource_size_t mem_end; /* phys. address of the control registers */ > + resource_size_t mem_size; > + void __iomem *base_address;/* virt. address of the control registers */ > + > + struct device *dev; > + struct cdev cdev; /* Char device structure */ > + dev_t devt; > + > + const struct hwicap_driver_config *config; > + const struct config_registers *config_regs; > + void *private_data; > + bool is_open; > + struct mutex sem; > +}; > + > +struct hwicap_driver_config { > + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, > + u32 size); > + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, > + u32 size); > + void (*reset)(struct hwicap_drvdata *drvdata); > +}; > + > +/* Number of times to poll the done regsiter */ > +#define XHI_MAX_RETRIES 10 --- ~Randy ^ permalink raw reply [flat|nested] 21+ messages in thread
[parent not found: <1201805233-15112-1-git-send-email-stephen.neuendorffer@xilinx.com>]
* [PATCH] [POWERPC] Xilinx: hwicap driver [not found] <1201805233-15112-1-git-send-email-stephen.neuendorffer@xilinx.com> @ 2008-02-01 1:02 ` Stephen Neuendorffer 2008-02-01 5:21 ` Grant Likely [not found] ` <1201827769-7439-1-git-send-email-stephen.neuendorffer@xilinx.com> 1 sibling, 1 reply; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-01 1:02 UTC (permalink / raw) To: linuxppc-dev, grant.likely, jacmet This includes code for new fifo-based xps_hwicap in addition to the older opb_hwicap, which has a significantly different interface. The common code between the two drivers is largely shared. Significant differences exists between this driver and what is supported in the EDK drivers. In particular, most of the architecture-specific code for reconfiguring individual FPGA resources has been removed. This functionality is likely better provided in a user-space support library. In addition, read and write access is supported. In addition, although the xps_hwicap cores support interrupt-driver mode, this driver only supports polled operation, in order to make the code simpler, and since the interrupt processing overhead is likely to slow down the throughput under Linux. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Fixed to add spinlocks, and a few style issues. --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 1 + drivers/char/xilinx_hwicap/Makefile | 7 + drivers/char/xilinx_hwicap/buffer_icap.c | 380 ++++++++++++ drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ drivers/char/xilinx_hwicap/xilinx_hwicap.c | 912 ++++++++++++++++++++++++++++ drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ 9 files changed, 2000 insertions(+), 0 deletions(-) create mode 100644 drivers/char/xilinx_hwicap/Makefile create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ef1ed5d..157ae2a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -831,6 +831,13 @@ config DTLK To compile this driver as a module, choose M here: the module will be called dtlk. +config XILINX_HWICAP + tristate "Xilinx HWICAP Support" + depends on XILINX_VIRTEX + help + This option enables support for Xilinx Internal Configuration + Access Port (ICAP) driver. + config R3964 tristate "Siemens R3964 line discipline" ---help--- diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d5..3a278a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ ifeq ($(CONFIG_GENERIC_NVRAM),y) obj-$(CONFIG_NVRAM) += generic_nvram.o else diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile new file mode 100644 index 0000000..5491cbc --- /dev/null +++ b/drivers/char/xilinx_hwicap/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Xilinx OPB hwicap driver +# + +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o + +xilinx_hwicap_m-y := xilinx_hwicap.o fifo_icap.o buffer_icap.o diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c new file mode 100644 index 0000000..dfea2bd --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.c @@ -0,0 +1,380 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "buffer_icap.h" + +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ +#define XHI_MAX_BUFFER_BYTES 2048 +#define XHI_MAX_BUFFER_INTS (XHI_MAX_BUFFER_BYTES >> 2) + +/* File access and error constants */ +#define XHI_DEVICE_READ_ERROR -1 +#define XHI_DEVICE_WRITE_ERROR -2 +#define XHI_BUFFER_OVERFLOW_ERROR -3 + +#define XHI_DEVICE_READ 0x1 +#define XHI_DEVICE_WRITE 0x0 + +/* Constants for checking transfer status */ +#define XHI_CYCLE_DONE 0 +#define XHI_CYCLE_EXECUTING 1 + +/* buffer_icap register offsets */ + +/* Size of transfer, read & write */ +#define XHI_SIZE_REG_OFFSET 0x800L +/* offset into bram, read & write */ +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L +/* Read not Configure, direction of transfer. Write only */ +#define XHI_RNC_REG_OFFSET 0x808L +/* Indicates transfer complete. Read only */ +#define XHI_STATUS_REG_OFFSET 0x80CL + +/* Constants for setting the RNC register */ +#define XHI_CONFIGURE 0x0UL +#define XHI_READBACK 0x1UL + +/* Constants for the Done register */ +#define XHI_NOT_FINISHED 0x0UL +#define XHI_FINISHED 0x1UL + +#define XHI_BUFFER_START 0 + +/** + * buffer_icap_get_status: Get the contents of the status register. + * @parameter base_address: is the base address of the device + * + * The status register contains the ICAP status and the done bit. + * + * D8 - cfgerr + * D7 - dalign + * D6 - rip + * D5 - in_abort_l + * D4 - Always 1 + * D3 - Always 1 + * D2 - Always 1 + * D1 - Always 1 + * D0 - Done bit + **/ +static inline u32 buffer_icap_get_status(void __iomem *base_address) +{ + return in_be32(base_address + XHI_STATUS_REG_OFFSET); +} + +/** + * buffer_icap_get_bram: Reads data from the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset from which the data should be read. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline u32 buffer_icap_get_bram(void __iomem *base_address, + u32 offset) +{ + return in_be32(base_address + (offset << 2)); +} + +/** + * buffer_icap_busy: Return true if the icap device is busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_busy(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_NOT_FINISHED; +} + +/** + * buffer_icap_busy: Return true if the icap device is not busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_done(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_FINISHED; +} + +/** + * buffer_icap_set_size: Set the size register. + * @parameter base_address: is the base address of the device + * @parameter data: The size in bytes. + * + * The size register holds the number of 8 bit bytes to transfer between + * bram and the icap (or icap to bram). + **/ +static inline void buffer_icap_set_size(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_SIZE_REG_OFFSET, data); +} + +/** + * buffer_icap_mSetoffsetReg: Set the bram offset register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The bram offset register holds the starting bram address to transfer + * data from during configuration or write data to during readback. + **/ +static inline void buffer_icap_set_offset(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_BRAM_OFFSET_REG_OFFSET, data); +} + +/** + * buffer_icap_set_rnc: Set the RNC (Readback not Configure) register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The RNC register determines the direction of the data transfer. It + * controls whether a configuration or readback take place. Writing to + * this register initiates the transfer. A value of 1 initiates a + * readback while writing a value of 0 initiates a configuration. + **/ +static inline void buffer_icap_set_rnc(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_RNC_REG_OFFSET, data); +} + +/** + * buffer_icap_set_bram: Write data to the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset at which the data should be written. + * @parameter data: The value to be written to the bram offset. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline void buffer_icap_set_bram(void __iomem *base_address, + u32 offset, u32 data) +{ + out_be32(base_address + (offset << 2), data); +} + +/** + * buffer_icap_device_read: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, (count << 2)); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_READBACK); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, count << 2); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * Writing to the status register resets the ICAP logic in an internal + * version of the core. For the version of the core published in EDK, + * this is a noop. + **/ +void buffer_icap_reset(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_STATUS_REG_OFFSET, 0xFEFE); +} + +/** + * buffer_icap_set_configuration: Load a partial bitstream from system memory. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Kernel address of the partial bitstream. + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 num_writes = 0; + bool dirty = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = 0; i < size; i++) { + + /* Copy data to bram */ + buffer_icap_set_bram(base_address, buffer_count, data[i]); + dirty = 1; + + if (buffer_count < XHI_MAX_BUFFER_INTS - 1) { + buffer_count++; + continue; + } + + /* Write data to ICAP */ + status = buffer_icap_device_write( + drvdata, + XHI_BUFFER_START, + XHI_MAX_BUFFER_INTS); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + num_writes++; + dirty = 0; + } + + /* Write unwritten data to ICAP */ + if (dirty) { + /* Write data to ICAP */ + status = buffer_icap_device_write(drvdata, XHI_BUFFER_START, + buffer_count); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + } + return status; + } + + return 0; +}; + +/** + * buffer_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 read_count = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = XHI_MAX_BUFFER_INTS; i < size; i++) { + if (buffer_count == XHI_MAX_BUFFER_INTS) { + u32 words_remaining = size - i; + u32 words_to_read = + words_remaining < + XHI_MAX_BUFFER_INTS ? words_remaining : + XHI_MAX_BUFFER_INTS; + + /* Read data from ICAP */ + status = buffer_icap_device_read( + drvdata, + XHI_BUFFER_START, + words_to_read); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + read_count++; + } + + /* Copy data from bram */ + data[i] = buffer_icap_get_bram(base_address, buffer_count); + buffer_count++; + } + + return 0; +}; diff --git a/drivers/char/xilinx_hwicap/buffer_icap.h b/drivers/char/xilinx_hwicap/buffer_icap.h new file mode 100644 index 0000000..0318495 --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_BUFFER_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_BUFFER_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +void buffer_icap_reset(struct hwicap_drvdata *drvdata); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c new file mode 100644 index 0000000..0988314 --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "fifo_icap.h" + +/* Register offsets for the XHwIcap device. */ +#define XHI_GIER_OFFSET 0x1C /* Device Global Interrupt Enable Reg */ +#define XHI_IPISR_OFFSET 0x20 /* Interrupt Status Register */ +#define XHI_IPIER_OFFSET 0x28 /* Interrupt Enable Register */ +#define XHI_WF_OFFSET 0x100 /* Write FIFO */ +#define XHI_RF_OFFSET 0x104 /* Read FIFO */ +#define XHI_SZ_OFFSET 0x108 /* Size Register */ +#define XHI_CR_OFFSET 0x10C /* Control Register */ +#define XHI_SR_OFFSET 0x110 /* Status Register */ +#define XHI_WFV_OFFSET 0x114 /* Write FIFO Vacancy Register */ +#define XHI_RFO_OFFSET 0x118 /* Read FIFO Occupancy Register */ + +/* Device Global Interrupt Enable Register (GIER) bit definitions */ + +#define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ + +/** + * HwIcap Device Interrupt Status/Enable Registers + * + * Interrupt Status Register (IPISR) : This register holds the + * interrupt status flags for the device. These bits are toggle on + * write. + * + * Interrupt Enable Register (IPIER) : This register is used to enable + * interrupt sources for the device. + * Writing a '1' to a bit enables the corresponding interrupt. + * Writing a '0' to a bit disables the corresponding interrupt. + * + * IPISR/IPIER registers have the same bit definitions and are only defined + * once. + */ +#define XHI_IPIXR_RFULL_MASK 0x00000008 /* Read FIFO Full */ +#define XHI_IPIXR_WEMPTY_MASK 0x00000004 /* Write FIFO Empty */ +#define XHI_IPIXR_RDP_MASK 0x00000002 /* Read FIFO half full */ +#define XHI_IPIXR_WRP_MASK 0x00000001 /* Write FIFO half full */ +#define XHI_IPIXR_ALL_MASK 0x0000000F /* Mask of all interrupts */ + +/* Control Register (CR) */ +#define XHI_CR_SW_RESET_MASK 0x00000008 /* SW Reset Mask */ +#define XHI_CR_FIFO_CLR_MASK 0x00000004 /* FIFO Clear Mask */ +#define XHI_CR_READ_MASK 0x00000002 /* Read from ICAP to FIFO */ +#define XHI_CR_WRITE_MASK 0x00000001 /* Write from FIFO to ICAP */ + +/* Status Register (SR) */ +#define XHI_SR_CFGERR_N_MASK 0x00000100 /* Config Error Mask */ +#define XHI_SR_DALIGN_MASK 0x00000080 /* Data Alignment Mask */ +#define XHI_SR_RIP_MASK 0x00000040 /* Read back Mask */ +#define XHI_SR_IN_ABORT_N_MASK 0x00000020 /* Select Map Abort Mask */ +#define XHI_SR_DONE_MASK 0x00000001 /* Done bit Mask */ + + +#define XHI_WFO_MAX_VACANCY 1024 /* Max Write FIFO Vacancy, in words */ +#define XHI_RFO_MAX_OCCUPANCY 256 /* Max Read FIFO Occupancy, in words */ +/* The maximum amount we can request from fifo_icap_get_configuration + at once, in bytes. */ +#define XHI_MAX_READ_TRANSACTION_WORDS 0xFFF + + +/** + * fifo_icap_fifo_write: Write data to the write FIFO. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the 32-bit value to be written to the FIFO. + * + * This function will silently fail if the fifo is full. + **/ +static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, + u32 data) +{ + dev_dbg(drvdata->dev, "fifo_write: %x\n", data); + out_be32(drvdata->base_address + XHI_WF_OFFSET, data); +} + +/** + * fifo_icap_fifo_read: Read data from the Read FIFO. + * @parameter drvdata: a pointer to the drvdata. + * + * This function will silently fail if the fifo is empty. + **/ +static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) +{ + u32 data = in_be32(drvdata->base_address + XHI_RF_OFFSET); + dev_dbg(drvdata->dev, "fifo_read: %x\n", data); + return data; +} + +/** + * fifo_icap_set_read_size: Set the the size register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the size of the following read transaction, in words. + **/ +static inline void fifo_icap_set_read_size(struct hwicap_drvdata *drvdata, + u32 data) +{ + out_be32(drvdata->base_address + XHI_SZ_OFFSET, data); +} + +/** + * fifo_icap_start_config: Initiate a configuration (write) to the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_config(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_WRITE_MASK); + dev_dbg(drvdata->dev, "configuration started\n"); +} + +/** + * fifo_icap_start_readback: Initiate a readback from the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_READ_MASK); + dev_dbg(drvdata->dev, "readback started\n"); +} + +/** + * fifo_icap_busy: Return true if the ICAP is still processing a transaction. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) +{ + u32 status = in_be32(drvdata->base_address + XHI_SR_OFFSET); + dev_dbg(drvdata->dev, "Getting status = %x\n", status); + return (status & XHI_SR_DONE_MASK) ? 0 : 1; +} + +/** + * fifo_icap_write_fifo_vacancy: Query the write fifo available space. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely pushed into the write fifo. + **/ +static inline u32 fifo_icap_write_fifo_vacancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_WFV_OFFSET); +} + +/** + * fifo_icap_read_fifo_occupancy: Query the read fifo available data. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely read from the read fifo. + **/ +static inline u32 fifo_icap_read_fifo_occupancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_RFO_OFFSET); +} + +/** + * fifo_icap_set_configuration: Send configuration data to the ICAP. + * @parameter drvdata: a pointer to the drvdata. + * @parameter frame_buffer: a pointer to the data to be written to the + * ICAP device. + * @parameter num_words: the number of words (32 bit) to write to the ICAP + * device. + + * This function writes the given user data to the Write FIFO in + * polled mode and starts the transfer of the data to + * the ICAP device. + **/ +int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 write_fifo_vacancy = 0; + u32 retries = 0; + u32 remaining_words; + + dev_dbg(drvdata->dev, "fifo_set_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Read/Write + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + /* + * Set up the buffer pointer and the words to be transferred. + */ + remaining_words = num_words; + + while (remaining_words > 0) { + /* + * Wait until we have some data in the fifo. + */ + while (write_fifo_vacancy == 0) { + write_fifo_vacancy = + fifo_icap_write_fifo_vacancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + /* + * Write data into the Write FIFO. + */ + while ((write_fifo_vacancy != 0) && + (remaining_words > 0)) { + fifo_icap_fifo_write(drvdata, *frame_buffer); + + remaining_words--; + write_fifo_vacancy--; + frame_buffer++; + } + /* Start pushing whatever is in the FIFO into the ICAP. */ + fifo_icap_start_config(drvdata); + } + + /* Wait until the write has finished. */ + while (fifo_icap_busy(drvdata)) { + retries++; + if (retries > XHI_MAX_RETRIES) + break; + } + + dev_dbg(drvdata->dev, "done fifo_set_configuration\n"); + + /* + * If the requested number of words have not been read from + * the device then indicate failure. + */ + if (remaining_words != 0) + return -EIO; + + return 0; +} + +/** + * fifo_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + * + * This function reads the specified number of words from the ICAP device in + * the polled mode. + */ +int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 read_fifo_occupancy = 0; + u32 retries = 0; + u32 *data = frame_buffer; + u32 remaining_words; + u32 words_to_read; + + dev_dbg(drvdata->dev, "fifo_get_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Write/Read + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + remaining_words = num_words; + + while (remaining_words > 0) { + words_to_read = remaining_words; + /* The hardware has a limit on the number of words + that can be read at one time. */ + if (words_to_read > XHI_MAX_READ_TRANSACTION_WORDS) + words_to_read = XHI_MAX_READ_TRANSACTION_WORDS; + + remaining_words -= words_to_read; + + fifo_icap_set_read_size(drvdata, words_to_read); + fifo_icap_start_readback(drvdata); + + while (words_to_read > 0) { + /* Wait until we have some data in the fifo. */ + while (read_fifo_occupancy == 0) { + read_fifo_occupancy = + fifo_icap_read_fifo_occupancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + if (read_fifo_occupancy > words_to_read) + read_fifo_occupancy = words_to_read; + + words_to_read -= read_fifo_occupancy; + + /* Read the data from the Read FIFO. */ + while (read_fifo_occupancy != 0) { + *data++ = fifo_icap_fifo_read(drvdata); + read_fifo_occupancy--; + } + } + } + + dev_dbg(drvdata->dev, "done fifo_get_configuration\n"); + + return 0; +} + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * This function forces the software reset of the complete HWICAP device. + * All the registers will return to the default value and the FIFO is also + * flushed as a part of this software reset. + */ +void fifo_icap_reset(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Reset the device by setting/clearing the RESET bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_SW_RESET_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_SW_RESET_MASK)); + +} + +/** + * fifo_icap_flush_fifo: This function flushes the FIFOs in the device. + * @parameter drvdata: a pointer to the drvdata. + */ +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Flush the FIFO by setting/clearing the FIFO Clear bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_FIFO_CLR_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_FIFO_CLR_MASK)); +} + diff --git a/drivers/char/xilinx_hwicap/fifo_icap.h b/drivers/char/xilinx_hwicap/fifo_icap.h new file mode 100644 index 0000000..4d3068d --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_FIFO_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_FIFO_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +/* Reads integers from the device into the storage buffer. */ +int fifo_icap_get_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +/* Writes integers to the device from the storage buffer. */ +int fifo_icap_set_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +void fifo_icap_reset(struct hwicap_drvdata *drvdata); +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata); + +#endif diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c new file mode 100644 index 0000000..a55e6e1 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -0,0 +1,912 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/* + * This is the code behind /dev/xilinx_icap -- it allows a user-space + * application to use the Xilinx ICAP subsystem. + * + * The following operations are possible: + * + * open open the port and initialize for access. + * release release port + * write Write a bitstream to the configuration processor. + * read Read a data stream from the configuration processor. + * + * After being opened, the port is initialized and accessed to avoid a + * corrupted first read which may occur with some hardware. The port + * is left in a desynched state, requiring that a synch sequence be + * transmitted before any valid configuration data. A user will have + * exclusive access to the device while it remains open, and the state + * of the ICAP cannot be guaranteed after the device is closed. Note + * that a complete reset of the core and the state of the ICAP cannot + * be performed on many versions of the cores, hence users of this + * device should avoid making inconsistent accesses to the device. In + * particular, accessing the read interface, without first generating + * a write containing a readback packet can leave the ICAP in an + * inaccessible state. + * + * Note that in order to use the read interface, it is first necessary + * to write a request packet to the write interface. i.e., it is not + * possible to simply readback the bitstream (or any configuration + * bits) from a device without specifically requesting them first. + * The code to craft such packets is intended to be part of the + * user-space application code that uses this device. The simplest + * way to use this interface is simply: + * + * cp foo.bit /dev/xilinx_icap + * + * Note that unless foo.bit is an appropriately constructed partial + * bitstream, this has a high likelyhood of overwriting the design + * currently programmed in the FPGA. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <linux/spinlock.h> +#include <linux/sysctl.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#ifdef CONFIG_OF +/* For open firmware. */ +#include <linux/of_device.h> +#include <linux/of_platform.h> +#endif + +#include "xilinx_hwicap.h" +#include "buffer_icap.h" +#include "fifo_icap.h" + +#define DRIVER_NAME "xilinx_icap" + +#define HWICAP_REGS (0x10000) + +/* dynamically allocate device number */ +static int xhwicap_major; +static int xhwicap_minor; +#define HWICAP_DEVICES 1 + +module_param(xhwicap_major, int, S_IRUGO); +module_param(xhwicap_minor, int, S_IRUGO); + +/* An array, which is set to true when the device is registered. */ +static bool probed_devices[HWICAP_DEVICES]; + +static spinlock_t hwicap_spinlock = SPIN_LOCK_UNLOCKED; + +static struct class *icap_class; + +#define UNIMPLEMENTED 0xFFFF + +static const struct config_registers v2_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = 11, + .KEY = 12, + .CBC = 13, + .IDCODE = 14, + .AXSS = UNIMPLEMENTED, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; + +static const struct config_registers v4_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; +static const struct config_registers v5_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = 14, + .CSOB = 15, + .WBSTAR = 16, + .TIMER = 17, + .BOOTSTS = 18, + .CTL_1 = 19, +}; + +/** + * hwicap_command_desync: Send a DESYNC command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command desynchronizes the ICAP After this command, a + * bitstream containing a NULL packet, followed by a SYNCH packet is + * required before the ICAP will recognize commands. + */ +int hwicap_command_desync(struct hwicap_drvdata *drvdata) +{ + u32 buffer[4]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_DESYNCH; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); +} + +/** + * hwicap_command_capture: Send a CAPTURE command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command captures all of the flip flop states so they will be + * available during readback. One can use this command instead of + * enabling the CAPTURE block in the design. + */ +int hwicap_command_capture(struct hwicap_drvdata *drvdata) +{ + u32 buffer[7]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_GCAPTURE; + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_DUMMY_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data + * present in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); + +} + +/** + * hwicap_get_configuration_register: Query a configuration register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter reg: a constant which represents the configuration + * register value to be returned. + * Examples: XHI_IDCODE, XHI_FLR. + * @parameter RegData: returns the value of the register. + * + * Sends a query packet to the ICAP and then receives the response. + * The icap is left in Synched state. + */ +int hwicap_get_configuration_register(struct hwicap_drvdata *drvdata, + u32 reg, u32 *RegData) +{ + int status; + u32 buffer[6]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_read(reg) | 1; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + status = drvdata->config->set_configuration(drvdata, + &buffer[0], index); + if (status) + return status; + + /* + * Read the configuration register + */ + status = drvdata->config->get_configuration(drvdata, RegData, 1); + if (status) + return status; + + return 0; +} + +int hwicap_initialize_hwicap(struct hwicap_drvdata *drvdata) +{ + int status; + u32 idcode; + + dev_dbg(drvdata->dev, "initializing\n"); + + /* Abort any current transaction, to make sure we have the + * ICAP in a good state. */ + dev_dbg(drvdata->dev, "Reset...\n"); + drvdata->config->reset(drvdata); + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + /* Attempt to read the IDCODE from ICAP. This + * may not be returned correctly, due to the design of the + * hardware. + */ + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); + status = hwicap_get_configuration_register( + drvdata, drvdata->config_regs->IDCODE, &idcode); + dev_dbg(drvdata->dev, "IDCODE = %x\n", idcode); + if (status) + return status; + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + return 0; +} + +static ssize_t +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t bytes_to_read = 0; + u32 *kbuf; + u32 words; + u32 bytes_remaining; + int status; + + spin_lock(&hwicap_spinlock); + if (drvdata->is_accessing) + return -EBUSY; + drvdata->is_accessing = 1; + spin_unlock(&hwicap_spinlock); + + if (drvdata->read_buffer_in_use) { + /* If there are leftover bytes in the buffer, just */ + /* return them and don't try to read more from the */ + /* ICAP device. */ + bytes_to_read = + (count < drvdata->read_buffer_in_use) ? count : + drvdata->read_buffer_in_use; + + /* Return the data currently in the read buffer. */ + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { + status = -EFAULT; + goto error; + } + drvdata->read_buffer_in_use -= bytes_to_read; + memcpy(drvdata->read_buffer + bytes_to_read, + drvdata->read_buffer, 4 - bytes_to_read); + } else { + /* Get new data from the ICAP, and return was was requested. */ + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + /* The ICAP device is only able to read complete */ + /* words. If a number of bytes that do not correspond */ + /* to complete words is requested, then we read enough */ + /* words to get the required number of bytes, and then */ + /* save the remaining bytes for the next read. */ + + /* Determine the number of words to read, rounding up */ + /* if necessary. */ + words = ((count + 3) >> 2); + bytes_to_read = words << 2; + + if (bytes_to_read > PAGE_SIZE) + bytes_to_read = PAGE_SIZE; + + /* Ensure we only read a complete number of words. */ + bytes_remaining = bytes_to_read & 3; + bytes_to_read &= ~3; + words = bytes_to_read >> 2; + + status = drvdata->config->get_configuration(drvdata, + kbuf, words); + + /* If we didn't read correctly, then bail out. */ + if (status) { + free_page((unsigned long)kbuf); + goto error; + } + + /* If we fail to return the data to the user, then bail out. */ + if (copy_to_user(buf, kbuf, bytes_to_read)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + memcpy(kbuf, drvdata->read_buffer, bytes_remaining); + drvdata->read_buffer_in_use = bytes_remaining; + free_page((unsigned long)kbuf); + } + status = bytes_to_read; + error: + spin_lock(&hwicap_spinlock); + drvdata->is_accessing = 0; + spin_unlock(&hwicap_spinlock); + return status; +} + +static ssize_t +hwicap_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t written = 0; + ssize_t left = count; + u32 *kbuf; + ssize_t len; + ssize_t status; + + spin_lock(&hwicap_spinlock); + if (drvdata->is_accessing) + return -EBUSY; + drvdata->is_accessing = 1; + spin_unlock(&hwicap_spinlock); + + left += drvdata->write_buffer_in_use; + + /* Only write multiples of 4 bytes. */ + if (left < 4) { + status = 0; + goto error; + } + + kbuf = (u32 *) __get_free_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + while (left > 3) { + /* only write multiples of 4 bytes, so there might */ + /* be as many as 3 bytes left (at the end). */ + len = left; + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + len &= ~3; + + if (drvdata->write_buffer_in_use) { + memcpy(kbuf, drvdata->write_buffer, + drvdata->write_buffer_in_use); + if (copy_from_user( + (((char *)kbuf) + (drvdata->write_buffer_in_use)), + buf + written, + len - (drvdata->write_buffer_in_use))) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } else { + if (copy_from_user(kbuf, buf + written, len)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } + + status = drvdata->config->set_configuration(drvdata, + kbuf, len >> 2); + + if (status) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + if (drvdata->write_buffer_in_use) { + len -= drvdata->write_buffer_in_use; + left -= drvdata->write_buffer_in_use; + drvdata->write_buffer_in_use = 0; + } + written += len; + left -= len; + } + if ((left > 0) && (left < 4)) { + if (!copy_from_user(drvdata->write_buffer, + buf + written, left)) { + drvdata->write_buffer_in_use = left; + written += left; + left = 0; + } + } + + free_page((unsigned long)kbuf); + status = written; + error: + spin_lock(&hwicap_spinlock); + drvdata->is_accessing = 0; + spin_unlock(&hwicap_spinlock); + return status; +} + +static int hwicap_open(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata; + int status; + + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); + + spin_lock(&hwicap_spinlock); + if (drvdata->is_open) + return -EBUSY; + drvdata->is_open = 1; + drvdata->is_accessing = 0; + spin_unlock(&hwicap_spinlock); + + status = hwicap_initialize_hwicap(drvdata); + if (status) { + dev_err(drvdata->dev, "Failed to open file"); + return status; + } + + file->private_data = drvdata; + drvdata->write_buffer_in_use = 0; + drvdata->read_buffer_in_use = 0; + + return 0; +} + +static int hwicap_release(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata = file->private_data; + int i; + int status = 0; + + if (drvdata->write_buffer_in_use) { + /* Flush write buffer. */ + for (i = drvdata->write_buffer_in_use; i < 4; i++) + drvdata->write_buffer[i] = 0; + + status = drvdata->config->set_configuration(drvdata, + (u32 *) drvdata->write_buffer, 1); + if (status) + goto error; + } + + status = hwicap_command_desync(drvdata); + if (status) + goto error; + + error: + spin_lock(&hwicap_spinlock); + drvdata->is_open = 0; + spin_unlock(&hwicap_spinlock); + return status; +} + +static struct file_operations hwicap_fops = { + .owner = THIS_MODULE, + .write = hwicap_write, + .read = hwicap_read, + .open = hwicap_open, + .release = hwicap_release, +}; + +static int __devinit hwicap_setup(struct device *dev, int id, + const struct resource *regs_res, + const struct hwicap_driver_config *config, + const struct config_registers *config_regs) +{ + dev_t devt; + struct hwicap_drvdata *drvdata = NULL; + int retval = 0; + + dev_info(dev, "Xilinx icap port driver\n"); + + if (id < 0) { + for (id = 0; id < HWICAP_DEVICES; id++) + if (!probed_devices[id]) + break; + } + if (id < 0 || id >= HWICAP_DEVICES) { + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); + return -EINVAL; + } + if (probed_devices[id]) { + dev_err(dev, "cannot assign to %s%i; it is already in use\n", + DRIVER_NAME, id); + return -EBUSY; + } + + probed_devices[id] = 1; + + devt = MKDEV(xhwicap_major, xhwicap_minor + id); + + drvdata = kmalloc(sizeof(struct hwicap_drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + return -ENOMEM; + } + memset((void *)drvdata, 0, sizeof(struct hwicap_drvdata)); + dev_set_drvdata(dev, (void *)drvdata); + + if (!regs_res) { + dev_err(dev, "Couldn't get registers resource\n"); + retval = -EFAULT; + goto failed1; + } + + drvdata->mem_start = regs_res->start; + drvdata->mem_end = regs_res->end; + drvdata->mem_size = regs_res->end - regs_res->start + 1; + + if (!request_mem_region(drvdata->mem_start, + drvdata->mem_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at %p\n", + (void *)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + drvdata->devt = devt; + drvdata->dev = dev; + drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); + if (!drvdata->base_address) { + dev_err(dev, "ioremap() failed\n"); + goto failed2; + } + + drvdata->config = config; + drvdata->config_regs = config_regs; + + spin_lock(&hwicap_spinlock); + drvdata->is_open = 0; + drvdata->is_accessing = 0; + spin_unlock(&hwicap_spinlock); + + dev_info(dev, "ioremap %lx to %p with size %x\n", + (unsigned long int)drvdata->mem_start, + drvdata->base_address, drvdata->mem_size); + + cdev_init(&drvdata->cdev, &hwicap_fops); + drvdata->cdev.owner = THIS_MODULE; + retval = cdev_add(&drvdata->cdev, devt, 1); + if (retval) { + dev_err(dev, "cdev_add() failed\n"); + goto failed3; + } + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ + class_device_create(icap_class, NULL, devt, NULL, DRIVER_NAME); + return 0; /* success */ + + failed3: + iounmap(drvdata->base_address); + + failed2: + release_mem_region(regs_res->start, drvdata->mem_size); + + failed1: + kfree(drvdata); + + return retval; +} + +static struct hwicap_driver_config buffer_icap_config = { + .get_configuration = buffer_icap_get_configuration, + .set_configuration = buffer_icap_set_configuration, + .reset = buffer_icap_reset, +}; + +static struct hwicap_driver_config fifo_icap_config = { + .get_configuration = fifo_icap_get_configuration, + .set_configuration = fifo_icap_set_configuration, + .reset = fifo_icap_reset, +}; + +static int __devexit hwicap_remove(struct device *dev) +{ + struct hwicap_drvdata *drvdata; + + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); + + if (!drvdata) + return 0; + + class_device_destroy(icap_class, drvdata->devt); + cdev_del(&drvdata->cdev); + iounmap(drvdata->base_address); + release_mem_region(drvdata->mem_start, drvdata->mem_size); + kfree(drvdata); + dev_set_drvdata(dev, NULL); + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; + + return 0; /* success */ +} + +static int __devinit hwicap_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct config_registers *regs; + const char *family; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = pdev->dev.platform_data; + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + + return hwicap_setup(&pdev->dev, pdev->id, res, + &buffer_icap_config, regs); +} + +static int __devexit hwicap_drv_remove(struct platform_device *pdev) +{ + return hwicap_remove(&pdev->dev); +} + +static struct platform_driver hwicap_platform_driver = { + .probe = hwicap_drv_probe, + .remove = hwicap_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +#if defined(CONFIG_OF) +static int __devinit +hwicap_of_probe(struct of_device *op, const struct of_device_id *match) +{ + struct resource res; + const unsigned int *id; + const char *family; + int rc; + const struct hwicap_driver_config *config = match->data; + const struct config_registers *regs; + + dev_dbg(&op->dev, "hwicap_of_probe(%p, %p)\n", op, match); + + rc = of_address_to_resource(op->node, 0, &res); + if (rc) { + dev_err(&op->dev, "invalid address\n"); + return rc; + } + + id = of_get_property(op->node, "port-number", NULL); + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = of_get_property(op->node, "xlnx,family", NULL); + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + return hwicap_setup(&op->dev, id ? *id : -1, &res, config, + regs); +} + +static int __devexit hwicap_of_remove(struct of_device *op) +{ + return hwicap_remove(&op->dev); +} + +/* Match table for of_platform binding */ +static const struct of_device_id __devinit hwicap_of_match[] = { + { .compatible = "xlnx,opb-hwicap-1.00.b", .data = &buffer_icap_config}, + { .compatible = "xlnx,xps-hwicap-1.00.a", .data = &fifo_icap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, hwicap_of_match); + +static struct of_platform_driver hwicap_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = hwicap_of_match, + .probe = hwicap_of_probe, + .remove = __devexit_p(hwicap_of_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +/* Registration helpers to keep the number of #ifdefs to a minimum */ +static inline int __devinit hwicap_of_register(void) +{ + pr_debug("hwicap: calling of_register_platform_driver()\n"); + return of_register_platform_driver(&hwicap_of_driver); +} + +static inline void __devexit hwicap_of_unregister(void) +{ + of_unregister_platform_driver(&hwicap_of_driver); +} +#else /* CONFIG_OF */ +/* CONFIG_OF not enabled; do nothing helpers */ +static inline int __devinit hwicap_of_register(void) { return 0; } +static inline void __devexit hwicap_of_unregister(void) { } +#endif /* CONFIG_OF */ + +static int __devinit hwicap_module_init(void) +{ + dev_t devt; + int retval; + + icap_class = class_create(THIS_MODULE, "xilinx_config"); + + if (xhwicap_major) { + devt = MKDEV(xhwicap_major, xhwicap_minor); + retval = register_chrdev_region( + devt, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + } else { + retval = alloc_chrdev_region(&devt, + xhwicap_minor, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + xhwicap_major = MAJOR(devt); + } + + retval = platform_driver_register(&hwicap_platform_driver); + + if (retval) + goto failed1; + + retval = hwicap_of_register(); + + if (retval) + goto failed2; + + return retval; + + failed2: + platform_driver_unregister(&hwicap_platform_driver); + + failed1: + unregister_chrdev_region(devt, HWICAP_DEVICES); + + return retval; +} + +static void __devexit hwicap_module_cleanup(void) +{ + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); + + class_destroy(icap_class); + + platform_driver_unregister(&hwicap_platform_driver); + + hwicap_of_unregister(); + + unregister_chrdev_region(devt, HWICAP_DEVICES); +} + +module_init(hwicap_module_init); +module_exit(hwicap_module_cleanup); + +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h new file mode 100644 index 0000000..b6b47d0 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2007 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ +#define XILINX_HWICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +struct hwicap_drvdata { + u32 write_buffer_in_use; /* Always in [0,3] */ + u8 write_buffer[4]; + u32 read_buffer_in_use; /* Always in [0,3] */ + u8 read_buffer[4]; + u32 mem_start; /* phys. address of the control registers */ + u32 mem_end; /* phys. address of the control registers */ + u32 mem_size; + void __iomem *base_address;/* virt. address of the control registers */ + + struct device *dev; + struct cdev cdev; /* Char device structure */ + dev_t devt; + + const struct hwicap_driver_config *config; + const struct config_registers *config_regs; + void *private_data; + bool is_open; + bool is_accessing; +}; + +struct hwicap_driver_config { + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + void (*reset)(struct hwicap_drvdata *drvdata); +}; + +/* Number of times to poll the done regsiter */ +#define XHI_MAX_RETRIES 10 + +/************ Constant Definitions *************/ + +#define XHI_PAD_FRAMES 0x1 + +/* Mask for calculating configuration packet headers */ +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL +#define XHI_TYPE_MASK 0x7 +#define XHI_REGISTER_MASK 0xF +#define XHI_OP_MASK 0x3 + +#define XHI_TYPE_SHIFT 29 +#define XHI_REGISTER_SHIFT 13 +#define XHI_OP_SHIFT 27 + +#define XHI_TYPE_1 1 +#define XHI_TYPE_2 2 +#define XHI_OP_WRITE 2 +#define XHI_OP_READ 1 + +/* Address Block Types */ +#define XHI_FAR_CLB_BLOCK 0 +#define XHI_FAR_BRAM_BLOCK 1 +#define XHI_FAR_BRAM_INT_BLOCK 2 + +struct config_registers { + u32 CRC; + u32 FAR; + u32 FDRI; + u32 FDRO; + u32 CMD; + u32 CTL; + u32 MASK; + u32 STAT; + u32 LOUT; + u32 COR; + u32 MFWR; + u32 FLR; + u32 KEY; + u32 CBC; + u32 IDCODE; + u32 AXSS; + u32 C0R_1; + u32 CSOB; + u32 WBSTAR; + u32 TIMER; + u32 BOOTSTS; + u32 CTL_1; +}; + +/* Configuration Commands */ +#define XHI_CMD_NULL 0 +#define XHI_CMD_WCFG 1 +#define XHI_CMD_MFW 2 +#define XHI_CMD_DGHIGH 3 +#define XHI_CMD_RCFG 4 +#define XHI_CMD_START 5 +#define XHI_CMD_RCAP 6 +#define XHI_CMD_RCRC 7 +#define XHI_CMD_AGHIGH 8 +#define XHI_CMD_SWITCH 9 +#define XHI_CMD_GRESTORE 10 +#define XHI_CMD_SHUTDOWN 11 +#define XHI_CMD_GCAPTURE 12 +#define XHI_CMD_DESYNCH 13 +#define XHI_CMD_IPROG 15 /* Only in Virtex5 */ +#define XHI_CMD_CRCC 16 /* Only in Virtex5 */ +#define XHI_CMD_LTIMER 17 /* Only in Virtex5 */ + +/* Packet constants */ +#define XHI_SYNC_PACKET 0xAA995566UL +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_READ << XHI_OP_SHIFT)) + +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_WRITE << XHI_OP_SHIFT)) + +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF + +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL +#define XHI_TYPE_1_HEADER_BYTES 4 +#define XHI_TYPE_2_HEADER_BYTES 8 + +/* Constant to use for CRC check when CRC has been disabled */ +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL + +/** + * hwicap_type_1_read: Generates a Type 1 read packet header. + * @parameter: Register is the address of the register to be read back. + * + * Generates a Type 1 read packet header, which is used to indirectly + * read registers in the configuration logic. This packet must then + * be sent through the icap device, and a return packet received with + * the information. + **/ +static inline u32 hwicap_type_1_read(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_READ << XHI_OP_SHIFT); +} + +/** + * hwicap_type_1_write: Generates a Type 1 write packet header + * @parameter: Register is the address of the register to be read back. + **/ +static inline u32 hwicap_type_1_write(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_WRITE << XHI_OP_SHIFT); +} + +#endif -- 1.5.3.4-dirty ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-02-01 1:02 ` Stephen Neuendorffer @ 2008-02-01 5:21 ` Grant Likely 0 siblings, 0 replies; 21+ messages in thread From: Grant Likely @ 2008-02-01 5:21 UTC (permalink / raw) To: Stephen Neuendorffer; +Cc: linuxppc-dev On 1/31/08, Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> wrote: > This includes code for new fifo-based xps_hwicap in addition to the > older opb_hwicap, which has a significantly different interface. The > common code between the two drivers is largely shared. > > Significant differences exists between this driver and what is > supported in the EDK drivers. In particular, most of the > architecture-specific code for reconfiguring individual FPGA resources > has been removed. This functionality is likely better provided in a > user-space support library. In addition, read and write access is > supported. In addition, although the xps_hwicap cores support > interrupt-driver mode, this driver only supports polled operation, in > order to make the code simpler, and since the interrupt processing > overhead is likely to slow down the throughput under Linux. > > Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> > > Fixed to add spinlocks, and a few style issues. You're probably better off using a semaphore, or more specifically a MUTEX, to protect the whole fop instead of a spin lock and the flags. The semantics are simpler if do it with a mutex and the function can sleep while holding it. Cheers, g. ^ permalink raw reply [flat|nested] 21+ messages in thread
[parent not found: <1201827769-7439-1-git-send-email-stephen.neuendorffer@xilinx.com>]
* RE: [PATCH] [POWERPC] Xilinx: hwicap driver [not found] ` <1201827769-7439-1-git-send-email-stephen.neuendorffer@xilinx.com> @ 2008-02-01 5:01 ` Stephen Neuendorffer 2008-02-01 5:56 ` Nathan Lynch 2008-02-01 18:22 ` Stephen Neuendorffer 1 sibling, 1 reply; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-01 5:01 UTC (permalink / raw) To: Stephen Neuendorffer, linuxppc-dev, grant.likely, jacmet [-- Attachment #1: Type: text/plain, Size: 67836 bytes --] Nevermind... I posted this too fast and screwed up the spinlocks. -----Original Message----- From: Stephen Neuendorffer [mailto:stephen.neuendorffer@xilinx.com] Sent: Thu 1/31/2008 5:02 PM To: linuxppc-dev@ozlabs.org; grant.likely@secretlab.ca; jacmet@sunsite.dk Cc: Stephen Neuendorffer Subject: [PATCH] [POWERPC] Xilinx: hwicap driver This includes code for new fifo-based xps_hwicap in addition to the older opb_hwicap, which has a significantly different interface. The common code between the two drivers is largely shared. Significant differences exists between this driver and what is supported in the EDK drivers. In particular, most of the architecture-specific code for reconfiguring individual FPGA resources has been removed. This functionality is likely better provided in a user-space support library. In addition, read and write access is supported. In addition, although the xps_hwicap cores support interrupt-driver mode, this driver only supports polled operation, in order to make the code simpler, and since the interrupt processing overhead is likely to slow down the throughput under Linux. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Fixed to add spinlocks, and a few style issues. --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 1 + drivers/char/xilinx_hwicap/Makefile | 7 + drivers/char/xilinx_hwicap/buffer_icap.c | 380 ++++++++++++ drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ drivers/char/xilinx_hwicap/xilinx_hwicap.c | 912 ++++++++++++++++++++++++++++ drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ 9 files changed, 2000 insertions(+), 0 deletions(-) create mode 100644 drivers/char/xilinx_hwicap/Makefile create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ef1ed5d..157ae2a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -831,6 +831,13 @@ config DTLK To compile this driver as a module, choose M here: the module will be called dtlk. +config XILINX_HWICAP + tristate "Xilinx HWICAP Support" + depends on XILINX_VIRTEX + help + This option enables support for Xilinx Internal Configuration + Access Port (ICAP) driver. + config R3964 tristate "Siemens R3964 line discipline" ---help--- diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d5..3a278a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ ifeq ($(CONFIG_GENERIC_NVRAM),y) obj-$(CONFIG_NVRAM) += generic_nvram.o else diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile new file mode 100644 index 0000000..5491cbc --- /dev/null +++ b/drivers/char/xilinx_hwicap/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Xilinx OPB hwicap driver +# + +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o + +xilinx_hwicap_m-y := xilinx_hwicap.o fifo_icap.o buffer_icap.o diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c new file mode 100644 index 0000000..dfea2bd --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.c @@ -0,0 +1,380 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "buffer_icap.h" + +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ +#define XHI_MAX_BUFFER_BYTES 2048 +#define XHI_MAX_BUFFER_INTS (XHI_MAX_BUFFER_BYTES >> 2) + +/* File access and error constants */ +#define XHI_DEVICE_READ_ERROR -1 +#define XHI_DEVICE_WRITE_ERROR -2 +#define XHI_BUFFER_OVERFLOW_ERROR -3 + +#define XHI_DEVICE_READ 0x1 +#define XHI_DEVICE_WRITE 0x0 + +/* Constants for checking transfer status */ +#define XHI_CYCLE_DONE 0 +#define XHI_CYCLE_EXECUTING 1 + +/* buffer_icap register offsets */ + +/* Size of transfer, read & write */ +#define XHI_SIZE_REG_OFFSET 0x800L +/* offset into bram, read & write */ +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L +/* Read not Configure, direction of transfer. Write only */ +#define XHI_RNC_REG_OFFSET 0x808L +/* Indicates transfer complete. Read only */ +#define XHI_STATUS_REG_OFFSET 0x80CL + +/* Constants for setting the RNC register */ +#define XHI_CONFIGURE 0x0UL +#define XHI_READBACK 0x1UL + +/* Constants for the Done register */ +#define XHI_NOT_FINISHED 0x0UL +#define XHI_FINISHED 0x1UL + +#define XHI_BUFFER_START 0 + +/** + * buffer_icap_get_status: Get the contents of the status register. + * @parameter base_address: is the base address of the device + * + * The status register contains the ICAP status and the done bit. + * + * D8 - cfgerr + * D7 - dalign + * D6 - rip + * D5 - in_abort_l + * D4 - Always 1 + * D3 - Always 1 + * D2 - Always 1 + * D1 - Always 1 + * D0 - Done bit + **/ +static inline u32 buffer_icap_get_status(void __iomem *base_address) +{ + return in_be32(base_address + XHI_STATUS_REG_OFFSET); +} + +/** + * buffer_icap_get_bram: Reads data from the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset from which the data should be read. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline u32 buffer_icap_get_bram(void __iomem *base_address, + u32 offset) +{ + return in_be32(base_address + (offset << 2)); +} + +/** + * buffer_icap_busy: Return true if the icap device is busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_busy(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_NOT_FINISHED; +} + +/** + * buffer_icap_busy: Return true if the icap device is not busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_done(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_FINISHED; +} + +/** + * buffer_icap_set_size: Set the size register. + * @parameter base_address: is the base address of the device + * @parameter data: The size in bytes. + * + * The size register holds the number of 8 bit bytes to transfer between + * bram and the icap (or icap to bram). + **/ +static inline void buffer_icap_set_size(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_SIZE_REG_OFFSET, data); +} + +/** + * buffer_icap_mSetoffsetReg: Set the bram offset register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The bram offset register holds the starting bram address to transfer + * data from during configuration or write data to during readback. + **/ +static inline void buffer_icap_set_offset(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_BRAM_OFFSET_REG_OFFSET, data); +} + +/** + * buffer_icap_set_rnc: Set the RNC (Readback not Configure) register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The RNC register determines the direction of the data transfer. It + * controls whether a configuration or readback take place. Writing to + * this register initiates the transfer. A value of 1 initiates a + * readback while writing a value of 0 initiates a configuration. + **/ +static inline void buffer_icap_set_rnc(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_RNC_REG_OFFSET, data); +} + +/** + * buffer_icap_set_bram: Write data to the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset at which the data should be written. + * @parameter data: The value to be written to the bram offset. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline void buffer_icap_set_bram(void __iomem *base_address, + u32 offset, u32 data) +{ + out_be32(base_address + (offset << 2), data); +} + +/** + * buffer_icap_device_read: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, (count << 2)); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_READBACK); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, count << 2); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * Writing to the status register resets the ICAP logic in an internal + * version of the core. For the version of the core published in EDK, + * this is a noop. + **/ +void buffer_icap_reset(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_STATUS_REG_OFFSET, 0xFEFE); +} + +/** + * buffer_icap_set_configuration: Load a partial bitstream from system memory. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Kernel address of the partial bitstream. + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 num_writes = 0; + bool dirty = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = 0; i < size; i++) { + + /* Copy data to bram */ + buffer_icap_set_bram(base_address, buffer_count, data[i]); + dirty = 1; + + if (buffer_count < XHI_MAX_BUFFER_INTS - 1) { + buffer_count++; + continue; + } + + /* Write data to ICAP */ + status = buffer_icap_device_write( + drvdata, + XHI_BUFFER_START, + XHI_MAX_BUFFER_INTS); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + num_writes++; + dirty = 0; + } + + /* Write unwritten data to ICAP */ + if (dirty) { + /* Write data to ICAP */ + status = buffer_icap_device_write(drvdata, XHI_BUFFER_START, + buffer_count); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + } + return status; + } + + return 0; +}; + +/** + * buffer_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 read_count = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = XHI_MAX_BUFFER_INTS; i < size; i++) { + if (buffer_count == XHI_MAX_BUFFER_INTS) { + u32 words_remaining = size - i; + u32 words_to_read = + words_remaining < + XHI_MAX_BUFFER_INTS ? words_remaining : + XHI_MAX_BUFFER_INTS; + + /* Read data from ICAP */ + status = buffer_icap_device_read( + drvdata, + XHI_BUFFER_START, + words_to_read); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + read_count++; + } + + /* Copy data from bram */ + data[i] = buffer_icap_get_bram(base_address, buffer_count); + buffer_count++; + } + + return 0; +}; diff --git a/drivers/char/xilinx_hwicap/buffer_icap.h b/drivers/char/xilinx_hwicap/buffer_icap.h new file mode 100644 index 0000000..0318495 --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_BUFFER_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_BUFFER_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +void buffer_icap_reset(struct hwicap_drvdata *drvdata); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c new file mode 100644 index 0000000..0988314 --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "fifo_icap.h" + +/* Register offsets for the XHwIcap device. */ +#define XHI_GIER_OFFSET 0x1C /* Device Global Interrupt Enable Reg */ +#define XHI_IPISR_OFFSET 0x20 /* Interrupt Status Register */ +#define XHI_IPIER_OFFSET 0x28 /* Interrupt Enable Register */ +#define XHI_WF_OFFSET 0x100 /* Write FIFO */ +#define XHI_RF_OFFSET 0x104 /* Read FIFO */ +#define XHI_SZ_OFFSET 0x108 /* Size Register */ +#define XHI_CR_OFFSET 0x10C /* Control Register */ +#define XHI_SR_OFFSET 0x110 /* Status Register */ +#define XHI_WFV_OFFSET 0x114 /* Write FIFO Vacancy Register */ +#define XHI_RFO_OFFSET 0x118 /* Read FIFO Occupancy Register */ + +/* Device Global Interrupt Enable Register (GIER) bit definitions */ + +#define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ + +/** + * HwIcap Device Interrupt Status/Enable Registers + * + * Interrupt Status Register (IPISR) : This register holds the + * interrupt status flags for the device. These bits are toggle on + * write. + * + * Interrupt Enable Register (IPIER) : This register is used to enable + * interrupt sources for the device. + * Writing a '1' to a bit enables the corresponding interrupt. + * Writing a '0' to a bit disables the corresponding interrupt. + * + * IPISR/IPIER registers have the same bit definitions and are only defined + * once. + */ +#define XHI_IPIXR_RFULL_MASK 0x00000008 /* Read FIFO Full */ +#define XHI_IPIXR_WEMPTY_MASK 0x00000004 /* Write FIFO Empty */ +#define XHI_IPIXR_RDP_MASK 0x00000002 /* Read FIFO half full */ +#define XHI_IPIXR_WRP_MASK 0x00000001 /* Write FIFO half full */ +#define XHI_IPIXR_ALL_MASK 0x0000000F /* Mask of all interrupts */ + +/* Control Register (CR) */ +#define XHI_CR_SW_RESET_MASK 0x00000008 /* SW Reset Mask */ +#define XHI_CR_FIFO_CLR_MASK 0x00000004 /* FIFO Clear Mask */ +#define XHI_CR_READ_MASK 0x00000002 /* Read from ICAP to FIFO */ +#define XHI_CR_WRITE_MASK 0x00000001 /* Write from FIFO to ICAP */ + +/* Status Register (SR) */ +#define XHI_SR_CFGERR_N_MASK 0x00000100 /* Config Error Mask */ +#define XHI_SR_DALIGN_MASK 0x00000080 /* Data Alignment Mask */ +#define XHI_SR_RIP_MASK 0x00000040 /* Read back Mask */ +#define XHI_SR_IN_ABORT_N_MASK 0x00000020 /* Select Map Abort Mask */ +#define XHI_SR_DONE_MASK 0x00000001 /* Done bit Mask */ + + +#define XHI_WFO_MAX_VACANCY 1024 /* Max Write FIFO Vacancy, in words */ +#define XHI_RFO_MAX_OCCUPANCY 256 /* Max Read FIFO Occupancy, in words */ +/* The maximum amount we can request from fifo_icap_get_configuration + at once, in bytes. */ +#define XHI_MAX_READ_TRANSACTION_WORDS 0xFFF + + +/** + * fifo_icap_fifo_write: Write data to the write FIFO. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the 32-bit value to be written to the FIFO. + * + * This function will silently fail if the fifo is full. + **/ +static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, + u32 data) +{ + dev_dbg(drvdata->dev, "fifo_write: %x\n", data); + out_be32(drvdata->base_address + XHI_WF_OFFSET, data); +} + +/** + * fifo_icap_fifo_read: Read data from the Read FIFO. + * @parameter drvdata: a pointer to the drvdata. + * + * This function will silently fail if the fifo is empty. + **/ +static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) +{ + u32 data = in_be32(drvdata->base_address + XHI_RF_OFFSET); + dev_dbg(drvdata->dev, "fifo_read: %x\n", data); + return data; +} + +/** + * fifo_icap_set_read_size: Set the the size register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the size of the following read transaction, in words. + **/ +static inline void fifo_icap_set_read_size(struct hwicap_drvdata *drvdata, + u32 data) +{ + out_be32(drvdata->base_address + XHI_SZ_OFFSET, data); +} + +/** + * fifo_icap_start_config: Initiate a configuration (write) to the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_config(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_WRITE_MASK); + dev_dbg(drvdata->dev, "configuration started\n"); +} + +/** + * fifo_icap_start_readback: Initiate a readback from the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_READ_MASK); + dev_dbg(drvdata->dev, "readback started\n"); +} + +/** + * fifo_icap_busy: Return true if the ICAP is still processing a transaction. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) +{ + u32 status = in_be32(drvdata->base_address + XHI_SR_OFFSET); + dev_dbg(drvdata->dev, "Getting status = %x\n", status); + return (status & XHI_SR_DONE_MASK) ? 0 : 1; +} + +/** + * fifo_icap_write_fifo_vacancy: Query the write fifo available space. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely pushed into the write fifo. + **/ +static inline u32 fifo_icap_write_fifo_vacancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_WFV_OFFSET); +} + +/** + * fifo_icap_read_fifo_occupancy: Query the read fifo available data. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely read from the read fifo. + **/ +static inline u32 fifo_icap_read_fifo_occupancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_RFO_OFFSET); +} + +/** + * fifo_icap_set_configuration: Send configuration data to the ICAP. + * @parameter drvdata: a pointer to the drvdata. + * @parameter frame_buffer: a pointer to the data to be written to the + * ICAP device. + * @parameter num_words: the number of words (32 bit) to write to the ICAP + * device. + + * This function writes the given user data to the Write FIFO in + * polled mode and starts the transfer of the data to + * the ICAP device. + **/ +int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 write_fifo_vacancy = 0; + u32 retries = 0; + u32 remaining_words; + + dev_dbg(drvdata->dev, "fifo_set_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Read/Write + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + /* + * Set up the buffer pointer and the words to be transferred. + */ + remaining_words = num_words; + + while (remaining_words > 0) { + /* + * Wait until we have some data in the fifo. + */ + while (write_fifo_vacancy == 0) { + write_fifo_vacancy = + fifo_icap_write_fifo_vacancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + /* + * Write data into the Write FIFO. + */ + while ((write_fifo_vacancy != 0) && + (remaining_words > 0)) { + fifo_icap_fifo_write(drvdata, *frame_buffer); + + remaining_words--; + write_fifo_vacancy--; + frame_buffer++; + } + /* Start pushing whatever is in the FIFO into the ICAP. */ + fifo_icap_start_config(drvdata); + } + + /* Wait until the write has finished. */ + while (fifo_icap_busy(drvdata)) { + retries++; + if (retries > XHI_MAX_RETRIES) + break; + } + + dev_dbg(drvdata->dev, "done fifo_set_configuration\n"); + + /* + * If the requested number of words have not been read from + * the device then indicate failure. + */ + if (remaining_words != 0) + return -EIO; + + return 0; +} + +/** + * fifo_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + * + * This function reads the specified number of words from the ICAP device in + * the polled mode. + */ +int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 read_fifo_occupancy = 0; + u32 retries = 0; + u32 *data = frame_buffer; + u32 remaining_words; + u32 words_to_read; + + dev_dbg(drvdata->dev, "fifo_get_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Write/Read + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + remaining_words = num_words; + + while (remaining_words > 0) { + words_to_read = remaining_words; + /* The hardware has a limit on the number of words + that can be read at one time. */ + if (words_to_read > XHI_MAX_READ_TRANSACTION_WORDS) + words_to_read = XHI_MAX_READ_TRANSACTION_WORDS; + + remaining_words -= words_to_read; + + fifo_icap_set_read_size(drvdata, words_to_read); + fifo_icap_start_readback(drvdata); + + while (words_to_read > 0) { + /* Wait until we have some data in the fifo. */ + while (read_fifo_occupancy == 0) { + read_fifo_occupancy = + fifo_icap_read_fifo_occupancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + if (read_fifo_occupancy > words_to_read) + read_fifo_occupancy = words_to_read; + + words_to_read -= read_fifo_occupancy; + + /* Read the data from the Read FIFO. */ + while (read_fifo_occupancy != 0) { + *data++ = fifo_icap_fifo_read(drvdata); + read_fifo_occupancy--; + } + } + } + + dev_dbg(drvdata->dev, "done fifo_get_configuration\n"); + + return 0; +} + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * This function forces the software reset of the complete HWICAP device. + * All the registers will return to the default value and the FIFO is also + * flushed as a part of this software reset. + */ +void fifo_icap_reset(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Reset the device by setting/clearing the RESET bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_SW_RESET_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_SW_RESET_MASK)); + +} + +/** + * fifo_icap_flush_fifo: This function flushes the FIFOs in the device. + * @parameter drvdata: a pointer to the drvdata. + */ +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Flush the FIFO by setting/clearing the FIFO Clear bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_FIFO_CLR_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_FIFO_CLR_MASK)); +} + diff --git a/drivers/char/xilinx_hwicap/fifo_icap.h b/drivers/char/xilinx_hwicap/fifo_icap.h new file mode 100644 index 0000000..4d3068d --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_FIFO_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_FIFO_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +/* Reads integers from the device into the storage buffer. */ +int fifo_icap_get_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +/* Writes integers to the device from the storage buffer. */ +int fifo_icap_set_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +void fifo_icap_reset(struct hwicap_drvdata *drvdata); +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata); + +#endif diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c new file mode 100644 index 0000000..a55e6e1 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -0,0 +1,912 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/* + * This is the code behind /dev/xilinx_icap -- it allows a user-space + * application to use the Xilinx ICAP subsystem. + * + * The following operations are possible: + * + * open open the port and initialize for access. + * release release port + * write Write a bitstream to the configuration processor. + * read Read a data stream from the configuration processor. + * + * After being opened, the port is initialized and accessed to avoid a + * corrupted first read which may occur with some hardware. The port + * is left in a desynched state, requiring that a synch sequence be + * transmitted before any valid configuration data. A user will have + * exclusive access to the device while it remains open, and the state + * of the ICAP cannot be guaranteed after the device is closed. Note + * that a complete reset of the core and the state of the ICAP cannot + * be performed on many versions of the cores, hence users of this + * device should avoid making inconsistent accesses to the device. In + * particular, accessing the read interface, without first generating + * a write containing a readback packet can leave the ICAP in an + * inaccessible state. + * + * Note that in order to use the read interface, it is first necessary + * to write a request packet to the write interface. i.e., it is not + * possible to simply readback the bitstream (or any configuration + * bits) from a device without specifically requesting them first. + * The code to craft such packets is intended to be part of the + * user-space application code that uses this device. The simplest + * way to use this interface is simply: + * + * cp foo.bit /dev/xilinx_icap + * + * Note that unless foo.bit is an appropriately constructed partial + * bitstream, this has a high likelyhood of overwriting the design + * currently programmed in the FPGA. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <linux/spinlock.h> +#include <linux/sysctl.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#ifdef CONFIG_OF +/* For open firmware. */ +#include <linux/of_device.h> +#include <linux/of_platform.h> +#endif + +#include "xilinx_hwicap.h" +#include "buffer_icap.h" +#include "fifo_icap.h" + +#define DRIVER_NAME "xilinx_icap" + +#define HWICAP_REGS (0x10000) + +/* dynamically allocate device number */ +static int xhwicap_major; +static int xhwicap_minor; +#define HWICAP_DEVICES 1 + +module_param(xhwicap_major, int, S_IRUGO); +module_param(xhwicap_minor, int, S_IRUGO); + +/* An array, which is set to true when the device is registered. */ +static bool probed_devices[HWICAP_DEVICES]; + +static spinlock_t hwicap_spinlock = SPIN_LOCK_UNLOCKED; + +static struct class *icap_class; + +#define UNIMPLEMENTED 0xFFFF + +static const struct config_registers v2_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = 11, + .KEY = 12, + .CBC = 13, + .IDCODE = 14, + .AXSS = UNIMPLEMENTED, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; + +static const struct config_registers v4_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; +static const struct config_registers v5_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = 14, + .CSOB = 15, + .WBSTAR = 16, + .TIMER = 17, + .BOOTSTS = 18, + .CTL_1 = 19, +}; + +/** + * hwicap_command_desync: Send a DESYNC command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command desynchronizes the ICAP After this command, a + * bitstream containing a NULL packet, followed by a SYNCH packet is + * required before the ICAP will recognize commands. + */ +int hwicap_command_desync(struct hwicap_drvdata *drvdata) +{ + u32 buffer[4]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_DESYNCH; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); +} + +/** + * hwicap_command_capture: Send a CAPTURE command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command captures all of the flip flop states so they will be + * available during readback. One can use this command instead of + * enabling the CAPTURE block in the design. + */ +int hwicap_command_capture(struct hwicap_drvdata *drvdata) +{ + u32 buffer[7]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_GCAPTURE; + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_DUMMY_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data + * present in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); + +} + +/** + * hwicap_get_configuration_register: Query a configuration register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter reg: a constant which represents the configuration + * register value to be returned. + * Examples: XHI_IDCODE, XHI_FLR. + * @parameter RegData: returns the value of the register. + * + * Sends a query packet to the ICAP and then receives the response. + * The icap is left in Synched state. + */ +int hwicap_get_configuration_register(struct hwicap_drvdata *drvdata, + u32 reg, u32 *RegData) +{ + int status; + u32 buffer[6]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_read(reg) | 1; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + status = drvdata->config->set_configuration(drvdata, + &buffer[0], index); + if (status) + return status; + + /* + * Read the configuration register + */ + status = drvdata->config->get_configuration(drvdata, RegData, 1); + if (status) + return status; + + return 0; +} + +int hwicap_initialize_hwicap(struct hwicap_drvdata *drvdata) +{ + int status; + u32 idcode; + + dev_dbg(drvdata->dev, "initializing\n"); + + /* Abort any current transaction, to make sure we have the + * ICAP in a good state. */ + dev_dbg(drvdata->dev, "Reset...\n"); + drvdata->config->reset(drvdata); + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + /* Attempt to read the IDCODE from ICAP. This + * may not be returned correctly, due to the design of the + * hardware. + */ + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); + status = hwicap_get_configuration_register( + drvdata, drvdata->config_regs->IDCODE, &idcode); + dev_dbg(drvdata->dev, "IDCODE = %x\n", idcode); + if (status) + return status; + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + return 0; +} + +static ssize_t +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t bytes_to_read = 0; + u32 *kbuf; + u32 words; + u32 bytes_remaining; + int status; + + spin_lock(&hwicap_spinlock); + if (drvdata->is_accessing) + return -EBUSY; + drvdata->is_accessing = 1; + spin_unlock(&hwicap_spinlock); + + if (drvdata->read_buffer_in_use) { + /* If there are leftover bytes in the buffer, just */ + /* return them and don't try to read more from the */ + /* ICAP device. */ + bytes_to_read = + (count < drvdata->read_buffer_in_use) ? count : + drvdata->read_buffer_in_use; + + /* Return the data currently in the read buffer. */ + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { + status = -EFAULT; + goto error; + } + drvdata->read_buffer_in_use -= bytes_to_read; + memcpy(drvdata->read_buffer + bytes_to_read, + drvdata->read_buffer, 4 - bytes_to_read); + } else { + /* Get new data from the ICAP, and return was was requested. */ + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + /* The ICAP device is only able to read complete */ + /* words. If a number of bytes that do not correspond */ + /* to complete words is requested, then we read enough */ + /* words to get the required number of bytes, and then */ + /* save the remaining bytes for the next read. */ + + /* Determine the number of words to read, rounding up */ + /* if necessary. */ + words = ((count + 3) >> 2); + bytes_to_read = words << 2; + + if (bytes_to_read > PAGE_SIZE) + bytes_to_read = PAGE_SIZE; + + /* Ensure we only read a complete number of words. */ + bytes_remaining = bytes_to_read & 3; + bytes_to_read &= ~3; + words = bytes_to_read >> 2; + + status = drvdata->config->get_configuration(drvdata, + kbuf, words); + + /* If we didn't read correctly, then bail out. */ + if (status) { + free_page((unsigned long)kbuf); + goto error; + } + + /* If we fail to return the data to the user, then bail out. */ + if (copy_to_user(buf, kbuf, bytes_to_read)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + memcpy(kbuf, drvdata->read_buffer, bytes_remaining); + drvdata->read_buffer_in_use = bytes_remaining; + free_page((unsigned long)kbuf); + } + status = bytes_to_read; + error: + spin_lock(&hwicap_spinlock); + drvdata->is_accessing = 0; + spin_unlock(&hwicap_spinlock); + return status; +} + +static ssize_t +hwicap_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t written = 0; + ssize_t left = count; + u32 *kbuf; + ssize_t len; + ssize_t status; + + spin_lock(&hwicap_spinlock); + if (drvdata->is_accessing) + return -EBUSY; + drvdata->is_accessing = 1; + spin_unlock(&hwicap_spinlock); + + left += drvdata->write_buffer_in_use; + + /* Only write multiples of 4 bytes. */ + if (left < 4) { + status = 0; + goto error; + } + + kbuf = (u32 *) __get_free_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + while (left > 3) { + /* only write multiples of 4 bytes, so there might */ + /* be as many as 3 bytes left (at the end). */ + len = left; + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + len &= ~3; + + if (drvdata->write_buffer_in_use) { + memcpy(kbuf, drvdata->write_buffer, + drvdata->write_buffer_in_use); + if (copy_from_user( + (((char *)kbuf) + (drvdata->write_buffer_in_use)), + buf + written, + len - (drvdata->write_buffer_in_use))) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } else { + if (copy_from_user(kbuf, buf + written, len)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } + + status = drvdata->config->set_configuration(drvdata, + kbuf, len >> 2); + + if (status) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + if (drvdata->write_buffer_in_use) { + len -= drvdata->write_buffer_in_use; + left -= drvdata->write_buffer_in_use; + drvdata->write_buffer_in_use = 0; + } + written += len; + left -= len; + } + if ((left > 0) && (left < 4)) { + if (!copy_from_user(drvdata->write_buffer, + buf + written, left)) { + drvdata->write_buffer_in_use = left; + written += left; + left = 0; + } + } + + free_page((unsigned long)kbuf); + status = written; + error: + spin_lock(&hwicap_spinlock); + drvdata->is_accessing = 0; + spin_unlock(&hwicap_spinlock); + return status; +} + +static int hwicap_open(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata; + int status; + + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); + + spin_lock(&hwicap_spinlock); + if (drvdata->is_open) + return -EBUSY; + drvdata->is_open = 1; + drvdata->is_accessing = 0; + spin_unlock(&hwicap_spinlock); + + status = hwicap_initialize_hwicap(drvdata); + if (status) { + dev_err(drvdata->dev, "Failed to open file"); + return status; + } + + file->private_data = drvdata; + drvdata->write_buffer_in_use = 0; + drvdata->read_buffer_in_use = 0; + + return 0; +} + +static int hwicap_release(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata = file->private_data; + int i; + int status = 0; + + if (drvdata->write_buffer_in_use) { + /* Flush write buffer. */ + for (i = drvdata->write_buffer_in_use; i < 4; i++) + drvdata->write_buffer[i] = 0; + + status = drvdata->config->set_configuration(drvdata, + (u32 *) drvdata->write_buffer, 1); + if (status) + goto error; + } + + status = hwicap_command_desync(drvdata); + if (status) + goto error; + + error: + spin_lock(&hwicap_spinlock); + drvdata->is_open = 0; + spin_unlock(&hwicap_spinlock); + return status; +} + +static struct file_operations hwicap_fops = { + .owner = THIS_MODULE, + .write = hwicap_write, + .read = hwicap_read, + .open = hwicap_open, + .release = hwicap_release, +}; + +static int __devinit hwicap_setup(struct device *dev, int id, + const struct resource *regs_res, + const struct hwicap_driver_config *config, + const struct config_registers *config_regs) +{ + dev_t devt; + struct hwicap_drvdata *drvdata = NULL; + int retval = 0; + + dev_info(dev, "Xilinx icap port driver\n"); + + if (id < 0) { + for (id = 0; id < HWICAP_DEVICES; id++) + if (!probed_devices[id]) + break; + } + if (id < 0 || id >= HWICAP_DEVICES) { + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); + return -EINVAL; + } + if (probed_devices[id]) { + dev_err(dev, "cannot assign to %s%i; it is already in use\n", + DRIVER_NAME, id); + return -EBUSY; + } + + probed_devices[id] = 1; + + devt = MKDEV(xhwicap_major, xhwicap_minor + id); + + drvdata = kmalloc(sizeof(struct hwicap_drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + return -ENOMEM; + } + memset((void *)drvdata, 0, sizeof(struct hwicap_drvdata)); + dev_set_drvdata(dev, (void *)drvdata); + + if (!regs_res) { + dev_err(dev, "Couldn't get registers resource\n"); + retval = -EFAULT; + goto failed1; + } + + drvdata->mem_start = regs_res->start; + drvdata->mem_end = regs_res->end; + drvdata->mem_size = regs_res->end - regs_res->start + 1; + + if (!request_mem_region(drvdata->mem_start, + drvdata->mem_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at %p\n", + (void *)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + drvdata->devt = devt; + drvdata->dev = dev; + drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); + if (!drvdata->base_address) { + dev_err(dev, "ioremap() failed\n"); + goto failed2; + } + + drvdata->config = config; + drvdata->config_regs = config_regs; + + spin_lock(&hwicap_spinlock); + drvdata->is_open = 0; + drvdata->is_accessing = 0; + spin_unlock(&hwicap_spinlock); + + dev_info(dev, "ioremap %lx to %p with size %x\n", + (unsigned long int)drvdata->mem_start, + drvdata->base_address, drvdata->mem_size); + + cdev_init(&drvdata->cdev, &hwicap_fops); + drvdata->cdev.owner = THIS_MODULE; + retval = cdev_add(&drvdata->cdev, devt, 1); + if (retval) { + dev_err(dev, "cdev_add() failed\n"); + goto failed3; + } + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ + class_device_create(icap_class, NULL, devt, NULL, DRIVER_NAME); + return 0; /* success */ + + failed3: + iounmap(drvdata->base_address); + + failed2: + release_mem_region(regs_res->start, drvdata->mem_size); + + failed1: + kfree(drvdata); + + return retval; +} + +static struct hwicap_driver_config buffer_icap_config = { + .get_configuration = buffer_icap_get_configuration, + .set_configuration = buffer_icap_set_configuration, + .reset = buffer_icap_reset, +}; + +static struct hwicap_driver_config fifo_icap_config = { + .get_configuration = fifo_icap_get_configuration, + .set_configuration = fifo_icap_set_configuration, + .reset = fifo_icap_reset, +}; + +static int __devexit hwicap_remove(struct device *dev) +{ + struct hwicap_drvdata *drvdata; + + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); + + if (!drvdata) + return 0; + + class_device_destroy(icap_class, drvdata->devt); + cdev_del(&drvdata->cdev); + iounmap(drvdata->base_address); + release_mem_region(drvdata->mem_start, drvdata->mem_size); + kfree(drvdata); + dev_set_drvdata(dev, NULL); + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; + + return 0; /* success */ +} + +static int __devinit hwicap_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct config_registers *regs; + const char *family; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = pdev->dev.platform_data; + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + + return hwicap_setup(&pdev->dev, pdev->id, res, + &buffer_icap_config, regs); +} + +static int __devexit hwicap_drv_remove(struct platform_device *pdev) +{ + return hwicap_remove(&pdev->dev); +} + +static struct platform_driver hwicap_platform_driver = { + .probe = hwicap_drv_probe, + .remove = hwicap_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +#if defined(CONFIG_OF) +static int __devinit +hwicap_of_probe(struct of_device *op, const struct of_device_id *match) +{ + struct resource res; + const unsigned int *id; + const char *family; + int rc; + const struct hwicap_driver_config *config = match->data; + const struct config_registers *regs; + + dev_dbg(&op->dev, "hwicap_of_probe(%p, %p)\n", op, match); + + rc = of_address_to_resource(op->node, 0, &res); + if (rc) { + dev_err(&op->dev, "invalid address\n"); + return rc; + } + + id = of_get_property(op->node, "port-number", NULL); + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = of_get_property(op->node, "xlnx,family", NULL); + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + return hwicap_setup(&op->dev, id ? *id : -1, &res, config, + regs); +} + +static int __devexit hwicap_of_remove(struct of_device *op) +{ + return hwicap_remove(&op->dev); +} + +/* Match table for of_platform binding */ +static const struct of_device_id __devinit hwicap_of_match[] = { + { .compatible = "xlnx,opb-hwicap-1.00.b", .data = &buffer_icap_config}, + { .compatible = "xlnx,xps-hwicap-1.00.a", .data = &fifo_icap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, hwicap_of_match); + +static struct of_platform_driver hwicap_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = hwicap_of_match, + .probe = hwicap_of_probe, + .remove = __devexit_p(hwicap_of_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +/* Registration helpers to keep the number of #ifdefs to a minimum */ +static inline int __devinit hwicap_of_register(void) +{ + pr_debug("hwicap: calling of_register_platform_driver()\n"); + return of_register_platform_driver(&hwicap_of_driver); +} + +static inline void __devexit hwicap_of_unregister(void) +{ + of_unregister_platform_driver(&hwicap_of_driver); +} +#else /* CONFIG_OF */ +/* CONFIG_OF not enabled; do nothing helpers */ +static inline int __devinit hwicap_of_register(void) { return 0; } +static inline void __devexit hwicap_of_unregister(void) { } +#endif /* CONFIG_OF */ + +static int __devinit hwicap_module_init(void) +{ + dev_t devt; + int retval; + + icap_class = class_create(THIS_MODULE, "xilinx_config"); + + if (xhwicap_major) { + devt = MKDEV(xhwicap_major, xhwicap_minor); + retval = register_chrdev_region( + devt, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + } else { + retval = alloc_chrdev_region(&devt, + xhwicap_minor, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + xhwicap_major = MAJOR(devt); + } + + retval = platform_driver_register(&hwicap_platform_driver); + + if (retval) + goto failed1; + + retval = hwicap_of_register(); + + if (retval) + goto failed2; + + return retval; + + failed2: + platform_driver_unregister(&hwicap_platform_driver); + + failed1: + unregister_chrdev_region(devt, HWICAP_DEVICES); + + return retval; +} + +static void __devexit hwicap_module_cleanup(void) +{ + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); + + class_destroy(icap_class); + + platform_driver_unregister(&hwicap_platform_driver); + + hwicap_of_unregister(); + + unregister_chrdev_region(devt, HWICAP_DEVICES); +} + +module_init(hwicap_module_init); +module_exit(hwicap_module_cleanup); + +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h new file mode 100644 index 0000000..b6b47d0 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2007 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ +#define XILINX_HWICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +struct hwicap_drvdata { + u32 write_buffer_in_use; /* Always in [0,3] */ + u8 write_buffer[4]; + u32 read_buffer_in_use; /* Always in [0,3] */ + u8 read_buffer[4]; + u32 mem_start; /* phys. address of the control registers */ + u32 mem_end; /* phys. address of the control registers */ + u32 mem_size; + void __iomem *base_address;/* virt. address of the control registers */ + + struct device *dev; + struct cdev cdev; /* Char device structure */ + dev_t devt; + + const struct hwicap_driver_config *config; + const struct config_registers *config_regs; + void *private_data; + bool is_open; + bool is_accessing; +}; + +struct hwicap_driver_config { + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + void (*reset)(struct hwicap_drvdata *drvdata); +}; + +/* Number of times to poll the done regsiter */ +#define XHI_MAX_RETRIES 10 + +/************ Constant Definitions *************/ + +#define XHI_PAD_FRAMES 0x1 + +/* Mask for calculating configuration packet headers */ +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL +#define XHI_TYPE_MASK 0x7 +#define XHI_REGISTER_MASK 0xF +#define XHI_OP_MASK 0x3 + +#define XHI_TYPE_SHIFT 29 +#define XHI_REGISTER_SHIFT 13 +#define XHI_OP_SHIFT 27 + +#define XHI_TYPE_1 1 +#define XHI_TYPE_2 2 +#define XHI_OP_WRITE 2 +#define XHI_OP_READ 1 + +/* Address Block Types */ +#define XHI_FAR_CLB_BLOCK 0 +#define XHI_FAR_BRAM_BLOCK 1 +#define XHI_FAR_BRAM_INT_BLOCK 2 + +struct config_registers { + u32 CRC; + u32 FAR; + u32 FDRI; + u32 FDRO; + u32 CMD; + u32 CTL; + u32 MASK; + u32 STAT; + u32 LOUT; + u32 COR; + u32 MFWR; + u32 FLR; + u32 KEY; + u32 CBC; + u32 IDCODE; + u32 AXSS; + u32 C0R_1; + u32 CSOB; + u32 WBSTAR; + u32 TIMER; + u32 BOOTSTS; + u32 CTL_1; +}; + +/* Configuration Commands */ +#define XHI_CMD_NULL 0 +#define XHI_CMD_WCFG 1 +#define XHI_CMD_MFW 2 +#define XHI_CMD_DGHIGH 3 +#define XHI_CMD_RCFG 4 +#define XHI_CMD_START 5 +#define XHI_CMD_RCAP 6 +#define XHI_CMD_RCRC 7 +#define XHI_CMD_AGHIGH 8 +#define XHI_CMD_SWITCH 9 +#define XHI_CMD_GRESTORE 10 +#define XHI_CMD_SHUTDOWN 11 +#define XHI_CMD_GCAPTURE 12 +#define XHI_CMD_DESYNCH 13 +#define XHI_CMD_IPROG 15 /* Only in Virtex5 */ +#define XHI_CMD_CRCC 16 /* Only in Virtex5 */ +#define XHI_CMD_LTIMER 17 /* Only in Virtex5 */ + +/* Packet constants */ +#define XHI_SYNC_PACKET 0xAA995566UL +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_READ << XHI_OP_SHIFT)) + +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_WRITE << XHI_OP_SHIFT)) + +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF + +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL +#define XHI_TYPE_1_HEADER_BYTES 4 +#define XHI_TYPE_2_HEADER_BYTES 8 + +/* Constant to use for CRC check when CRC has been disabled */ +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL + +/** + * hwicap_type_1_read: Generates a Type 1 read packet header. + * @parameter: Register is the address of the register to be read back. + * + * Generates a Type 1 read packet header, which is used to indirectly + * read registers in the configuration logic. This packet must then + * be sent through the icap device, and a return packet received with + * the information. + **/ +static inline u32 hwicap_type_1_read(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_READ << XHI_OP_SHIFT); +} + +/** + * hwicap_type_1_write: Generates a Type 1 write packet header + * @parameter: Register is the address of the register to be read back. + **/ +static inline u32 hwicap_type_1_write(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_WRITE << XHI_OP_SHIFT); +} + +#endif -- 1.5.3.4-dirty [-- Attachment #2: Type: text/html, Size: 140593 bytes --] ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-02-01 5:01 ` Stephen Neuendorffer @ 2008-02-01 5:56 ` Nathan Lynch 2008-02-01 17:31 ` Stephen Neuendorffer 0 siblings, 1 reply; 21+ messages in thread From: Nathan Lynch @ 2008-02-01 5:56 UTC (permalink / raw) To: Stephen Neuendorffer; +Cc: linuxppc-dev Stephen Neuendorffer wrote: > > +static ssize_t > +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) > +{ > + struct hwicap_drvdata *drvdata = file->private_data; > + ssize_t bytes_to_read = 0; > + u32 *kbuf; > + u32 words; > + u32 bytes_remaining; > + int status; > + > + spin_lock(&hwicap_spinlock); > + if (drvdata->is_accessing) > + return -EBUSY; Need to unlock before returning (several places in this patch). ^ permalink raw reply [flat|nested] 21+ messages in thread
* RE: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-02-01 5:56 ` Nathan Lynch @ 2008-02-01 17:31 ` Stephen Neuendorffer 0 siblings, 0 replies; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-01 17:31 UTC (permalink / raw) To: Nathan Lynch; +Cc: linuxppc-dev Thanks, I realized that on the way home.. That's what I get for trying to finish something quickly before leaving.. :) Steve > -----Original Message----- > From: Nathan Lynch [mailto:ntl@pobox.com] > Sent: Thursday, January 31, 2008 9:56 PM > To: Stephen Neuendorffer > Cc: linuxppc-dev@ozlabs.org; grant.likely@secretlab.ca; jacmet@sunsite.dk > Subject: Re: [PATCH] [POWERPC] Xilinx: hwicap driver >=20 > Stephen Neuendorffer wrote: > > > > +static ssize_t > > +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) > > +{ > > + struct hwicap_drvdata *drvdata =3D file->private_data; > > + ssize_t bytes_to_read =3D 0; > > + u32 *kbuf; > > + u32 words; > > + u32 bytes_remaining; > > + int status; > > + > > + spin_lock(&hwicap_spinlock); > > + if (drvdata->is_accessing) > > + return -EBUSY; >=20 > Need to unlock before returning (several places in this patch). >=20 ^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH] [POWERPC] Xilinx: hwicap driver [not found] ` <1201827769-7439-1-git-send-email-stephen.neuendorffer@xilinx.com> 2008-02-01 5:01 ` Stephen Neuendorffer @ 2008-02-01 18:22 ` Stephen Neuendorffer 2008-02-01 18:42 ` Grant Likely 1 sibling, 1 reply; 21+ messages in thread From: Stephen Neuendorffer @ 2008-02-01 18:22 UTC (permalink / raw) To: linuxppc-dev, grant.likely, jacmet, ntl This includes code for new fifo-based xps_hwicap in addition to the older opb_hwicap, which has a significantly different interface. The common code between the two drivers is largely shared. Significant differences exists between this driver and what is supported in the EDK drivers. In particular, most of the architecture-specific code for reconfiguring individual FPGA resources has been removed. This functionality is likely better provided in a user-space support library. In addition, read and write access is supported. In addition, although the xps_hwicap cores support interrupt-driver mode, this driver only supports polled operation, in order to make the code simpler, and since the interrupt processing overhead is likely to slow down the throughput under Linux. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Fixed to add mutexes, and a few style issues. --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 1 + drivers/char/xilinx_hwicap/Makefile | 7 + drivers/char/xilinx_hwicap/buffer_icap.c | 380 ++++++++++++ drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ drivers/char/xilinx_hwicap/xilinx_hwicap.c | 900 ++++++++++++++++++++++++++++ drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ 9 files changed, 1988 insertions(+), 0 deletions(-) create mode 100644 drivers/char/xilinx_hwicap/Makefile create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ef1ed5d..157ae2a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -831,6 +831,13 @@ config DTLK To compile this driver as a module, choose M here: the module will be called dtlk. +config XILINX_HWICAP + tristate "Xilinx HWICAP Support" + depends on XILINX_VIRTEX + help + This option enables support for Xilinx Internal Configuration + Access Port (ICAP) driver. + config R3964 tristate "Siemens R3964 line discipline" ---help--- diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d5..3a278a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ ifeq ($(CONFIG_GENERIC_NVRAM),y) obj-$(CONFIG_NVRAM) += generic_nvram.o else diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile new file mode 100644 index 0000000..5491cbc --- /dev/null +++ b/drivers/char/xilinx_hwicap/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Xilinx OPB hwicap driver +# + +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o + +xilinx_hwicap_m-y := xilinx_hwicap.o fifo_icap.o buffer_icap.o diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c new file mode 100644 index 0000000..dfea2bd --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.c @@ -0,0 +1,380 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "buffer_icap.h" + +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ +#define XHI_MAX_BUFFER_BYTES 2048 +#define XHI_MAX_BUFFER_INTS (XHI_MAX_BUFFER_BYTES >> 2) + +/* File access and error constants */ +#define XHI_DEVICE_READ_ERROR -1 +#define XHI_DEVICE_WRITE_ERROR -2 +#define XHI_BUFFER_OVERFLOW_ERROR -3 + +#define XHI_DEVICE_READ 0x1 +#define XHI_DEVICE_WRITE 0x0 + +/* Constants for checking transfer status */ +#define XHI_CYCLE_DONE 0 +#define XHI_CYCLE_EXECUTING 1 + +/* buffer_icap register offsets */ + +/* Size of transfer, read & write */ +#define XHI_SIZE_REG_OFFSET 0x800L +/* offset into bram, read & write */ +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L +/* Read not Configure, direction of transfer. Write only */ +#define XHI_RNC_REG_OFFSET 0x808L +/* Indicates transfer complete. Read only */ +#define XHI_STATUS_REG_OFFSET 0x80CL + +/* Constants for setting the RNC register */ +#define XHI_CONFIGURE 0x0UL +#define XHI_READBACK 0x1UL + +/* Constants for the Done register */ +#define XHI_NOT_FINISHED 0x0UL +#define XHI_FINISHED 0x1UL + +#define XHI_BUFFER_START 0 + +/** + * buffer_icap_get_status: Get the contents of the status register. + * @parameter base_address: is the base address of the device + * + * The status register contains the ICAP status and the done bit. + * + * D8 - cfgerr + * D7 - dalign + * D6 - rip + * D5 - in_abort_l + * D4 - Always 1 + * D3 - Always 1 + * D2 - Always 1 + * D1 - Always 1 + * D0 - Done bit + **/ +static inline u32 buffer_icap_get_status(void __iomem *base_address) +{ + return in_be32(base_address + XHI_STATUS_REG_OFFSET); +} + +/** + * buffer_icap_get_bram: Reads data from the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset from which the data should be read. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline u32 buffer_icap_get_bram(void __iomem *base_address, + u32 offset) +{ + return in_be32(base_address + (offset << 2)); +} + +/** + * buffer_icap_busy: Return true if the icap device is busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_busy(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_NOT_FINISHED; +} + +/** + * buffer_icap_busy: Return true if the icap device is not busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_done(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_FINISHED; +} + +/** + * buffer_icap_set_size: Set the size register. + * @parameter base_address: is the base address of the device + * @parameter data: The size in bytes. + * + * The size register holds the number of 8 bit bytes to transfer between + * bram and the icap (or icap to bram). + **/ +static inline void buffer_icap_set_size(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_SIZE_REG_OFFSET, data); +} + +/** + * buffer_icap_mSetoffsetReg: Set the bram offset register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The bram offset register holds the starting bram address to transfer + * data from during configuration or write data to during readback. + **/ +static inline void buffer_icap_set_offset(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_BRAM_OFFSET_REG_OFFSET, data); +} + +/** + * buffer_icap_set_rnc: Set the RNC (Readback not Configure) register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The RNC register determines the direction of the data transfer. It + * controls whether a configuration or readback take place. Writing to + * this register initiates the transfer. A value of 1 initiates a + * readback while writing a value of 0 initiates a configuration. + **/ +static inline void buffer_icap_set_rnc(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_RNC_REG_OFFSET, data); +} + +/** + * buffer_icap_set_bram: Write data to the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset at which the data should be written. + * @parameter data: The value to be written to the bram offset. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline void buffer_icap_set_bram(void __iomem *base_address, + u32 offset, u32 data) +{ + out_be32(base_address + (offset << 2), data); +} + +/** + * buffer_icap_device_read: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, (count << 2)); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_READBACK); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) > XHI_MAX_BUFFER_INTS) + return -EINVAL; + + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, count << 2); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + return 0; + +}; + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * Writing to the status register resets the ICAP logic in an internal + * version of the core. For the version of the core published in EDK, + * this is a noop. + **/ +void buffer_icap_reset(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_STATUS_REG_OFFSET, 0xFEFE); +} + +/** + * buffer_icap_set_configuration: Load a partial bitstream from system memory. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Kernel address of the partial bitstream. + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 num_writes = 0; + bool dirty = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = 0; i < size; i++) { + + /* Copy data to bram */ + buffer_icap_set_bram(base_address, buffer_count, data[i]); + dirty = 1; + + if (buffer_count < XHI_MAX_BUFFER_INTS - 1) { + buffer_count++; + continue; + } + + /* Write data to ICAP */ + status = buffer_icap_device_write( + drvdata, + XHI_BUFFER_START, + XHI_MAX_BUFFER_INTS); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + num_writes++; + dirty = 0; + } + + /* Write unwritten data to ICAP */ + if (dirty) { + /* Write data to ICAP */ + status = buffer_icap_device_write(drvdata, XHI_BUFFER_START, + buffer_count); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + } + return status; + } + + return 0; +}; + +/** + * buffer_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 read_count = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = XHI_MAX_BUFFER_INTS; i < size; i++) { + if (buffer_count == XHI_MAX_BUFFER_INTS) { + u32 words_remaining = size - i; + u32 words_to_read = + words_remaining < + XHI_MAX_BUFFER_INTS ? words_remaining : + XHI_MAX_BUFFER_INTS; + + /* Read data from ICAP */ + status = buffer_icap_device_read( + drvdata, + XHI_BUFFER_START, + words_to_read); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + read_count++; + } + + /* Copy data from bram */ + data[i] = buffer_icap_get_bram(base_address, buffer_count); + buffer_count++; + } + + return 0; +}; diff --git a/drivers/char/xilinx_hwicap/buffer_icap.h b/drivers/char/xilinx_hwicap/buffer_icap.h new file mode 100644 index 0000000..0318495 --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_BUFFER_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_BUFFER_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +void buffer_icap_reset(struct hwicap_drvdata *drvdata); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c new file mode 100644 index 0000000..0988314 --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "fifo_icap.h" + +/* Register offsets for the XHwIcap device. */ +#define XHI_GIER_OFFSET 0x1C /* Device Global Interrupt Enable Reg */ +#define XHI_IPISR_OFFSET 0x20 /* Interrupt Status Register */ +#define XHI_IPIER_OFFSET 0x28 /* Interrupt Enable Register */ +#define XHI_WF_OFFSET 0x100 /* Write FIFO */ +#define XHI_RF_OFFSET 0x104 /* Read FIFO */ +#define XHI_SZ_OFFSET 0x108 /* Size Register */ +#define XHI_CR_OFFSET 0x10C /* Control Register */ +#define XHI_SR_OFFSET 0x110 /* Status Register */ +#define XHI_WFV_OFFSET 0x114 /* Write FIFO Vacancy Register */ +#define XHI_RFO_OFFSET 0x118 /* Read FIFO Occupancy Register */ + +/* Device Global Interrupt Enable Register (GIER) bit definitions */ + +#define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ + +/** + * HwIcap Device Interrupt Status/Enable Registers + * + * Interrupt Status Register (IPISR) : This register holds the + * interrupt status flags for the device. These bits are toggle on + * write. + * + * Interrupt Enable Register (IPIER) : This register is used to enable + * interrupt sources for the device. + * Writing a '1' to a bit enables the corresponding interrupt. + * Writing a '0' to a bit disables the corresponding interrupt. + * + * IPISR/IPIER registers have the same bit definitions and are only defined + * once. + */ +#define XHI_IPIXR_RFULL_MASK 0x00000008 /* Read FIFO Full */ +#define XHI_IPIXR_WEMPTY_MASK 0x00000004 /* Write FIFO Empty */ +#define XHI_IPIXR_RDP_MASK 0x00000002 /* Read FIFO half full */ +#define XHI_IPIXR_WRP_MASK 0x00000001 /* Write FIFO half full */ +#define XHI_IPIXR_ALL_MASK 0x0000000F /* Mask of all interrupts */ + +/* Control Register (CR) */ +#define XHI_CR_SW_RESET_MASK 0x00000008 /* SW Reset Mask */ +#define XHI_CR_FIFO_CLR_MASK 0x00000004 /* FIFO Clear Mask */ +#define XHI_CR_READ_MASK 0x00000002 /* Read from ICAP to FIFO */ +#define XHI_CR_WRITE_MASK 0x00000001 /* Write from FIFO to ICAP */ + +/* Status Register (SR) */ +#define XHI_SR_CFGERR_N_MASK 0x00000100 /* Config Error Mask */ +#define XHI_SR_DALIGN_MASK 0x00000080 /* Data Alignment Mask */ +#define XHI_SR_RIP_MASK 0x00000040 /* Read back Mask */ +#define XHI_SR_IN_ABORT_N_MASK 0x00000020 /* Select Map Abort Mask */ +#define XHI_SR_DONE_MASK 0x00000001 /* Done bit Mask */ + + +#define XHI_WFO_MAX_VACANCY 1024 /* Max Write FIFO Vacancy, in words */ +#define XHI_RFO_MAX_OCCUPANCY 256 /* Max Read FIFO Occupancy, in words */ +/* The maximum amount we can request from fifo_icap_get_configuration + at once, in bytes. */ +#define XHI_MAX_READ_TRANSACTION_WORDS 0xFFF + + +/** + * fifo_icap_fifo_write: Write data to the write FIFO. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the 32-bit value to be written to the FIFO. + * + * This function will silently fail if the fifo is full. + **/ +static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, + u32 data) +{ + dev_dbg(drvdata->dev, "fifo_write: %x\n", data); + out_be32(drvdata->base_address + XHI_WF_OFFSET, data); +} + +/** + * fifo_icap_fifo_read: Read data from the Read FIFO. + * @parameter drvdata: a pointer to the drvdata. + * + * This function will silently fail if the fifo is empty. + **/ +static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) +{ + u32 data = in_be32(drvdata->base_address + XHI_RF_OFFSET); + dev_dbg(drvdata->dev, "fifo_read: %x\n", data); + return data; +} + +/** + * fifo_icap_set_read_size: Set the the size register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the size of the following read transaction, in words. + **/ +static inline void fifo_icap_set_read_size(struct hwicap_drvdata *drvdata, + u32 data) +{ + out_be32(drvdata->base_address + XHI_SZ_OFFSET, data); +} + +/** + * fifo_icap_start_config: Initiate a configuration (write) to the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_config(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_WRITE_MASK); + dev_dbg(drvdata->dev, "configuration started\n"); +} + +/** + * fifo_icap_start_readback: Initiate a readback from the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_READ_MASK); + dev_dbg(drvdata->dev, "readback started\n"); +} + +/** + * fifo_icap_busy: Return true if the ICAP is still processing a transaction. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) +{ + u32 status = in_be32(drvdata->base_address + XHI_SR_OFFSET); + dev_dbg(drvdata->dev, "Getting status = %x\n", status); + return (status & XHI_SR_DONE_MASK) ? 0 : 1; +} + +/** + * fifo_icap_write_fifo_vacancy: Query the write fifo available space. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely pushed into the write fifo. + **/ +static inline u32 fifo_icap_write_fifo_vacancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_WFV_OFFSET); +} + +/** + * fifo_icap_read_fifo_occupancy: Query the read fifo available data. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely read from the read fifo. + **/ +static inline u32 fifo_icap_read_fifo_occupancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_RFO_OFFSET); +} + +/** + * fifo_icap_set_configuration: Send configuration data to the ICAP. + * @parameter drvdata: a pointer to the drvdata. + * @parameter frame_buffer: a pointer to the data to be written to the + * ICAP device. + * @parameter num_words: the number of words (32 bit) to write to the ICAP + * device. + + * This function writes the given user data to the Write FIFO in + * polled mode and starts the transfer of the data to + * the ICAP device. + **/ +int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 write_fifo_vacancy = 0; + u32 retries = 0; + u32 remaining_words; + + dev_dbg(drvdata->dev, "fifo_set_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Read/Write + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + /* + * Set up the buffer pointer and the words to be transferred. + */ + remaining_words = num_words; + + while (remaining_words > 0) { + /* + * Wait until we have some data in the fifo. + */ + while (write_fifo_vacancy == 0) { + write_fifo_vacancy = + fifo_icap_write_fifo_vacancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + /* + * Write data into the Write FIFO. + */ + while ((write_fifo_vacancy != 0) && + (remaining_words > 0)) { + fifo_icap_fifo_write(drvdata, *frame_buffer); + + remaining_words--; + write_fifo_vacancy--; + frame_buffer++; + } + /* Start pushing whatever is in the FIFO into the ICAP. */ + fifo_icap_start_config(drvdata); + } + + /* Wait until the write has finished. */ + while (fifo_icap_busy(drvdata)) { + retries++; + if (retries > XHI_MAX_RETRIES) + break; + } + + dev_dbg(drvdata->dev, "done fifo_set_configuration\n"); + + /* + * If the requested number of words have not been read from + * the device then indicate failure. + */ + if (remaining_words != 0) + return -EIO; + + return 0; +} + +/** + * fifo_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + * + * This function reads the specified number of words from the ICAP device in + * the polled mode. + */ +int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 read_fifo_occupancy = 0; + u32 retries = 0; + u32 *data = frame_buffer; + u32 remaining_words; + u32 words_to_read; + + dev_dbg(drvdata->dev, "fifo_get_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Write/Read + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + remaining_words = num_words; + + while (remaining_words > 0) { + words_to_read = remaining_words; + /* The hardware has a limit on the number of words + that can be read at one time. */ + if (words_to_read > XHI_MAX_READ_TRANSACTION_WORDS) + words_to_read = XHI_MAX_READ_TRANSACTION_WORDS; + + remaining_words -= words_to_read; + + fifo_icap_set_read_size(drvdata, words_to_read); + fifo_icap_start_readback(drvdata); + + while (words_to_read > 0) { + /* Wait until we have some data in the fifo. */ + while (read_fifo_occupancy == 0) { + read_fifo_occupancy = + fifo_icap_read_fifo_occupancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + if (read_fifo_occupancy > words_to_read) + read_fifo_occupancy = words_to_read; + + words_to_read -= read_fifo_occupancy; + + /* Read the data from the Read FIFO. */ + while (read_fifo_occupancy != 0) { + *data++ = fifo_icap_fifo_read(drvdata); + read_fifo_occupancy--; + } + } + } + + dev_dbg(drvdata->dev, "done fifo_get_configuration\n"); + + return 0; +} + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * This function forces the software reset of the complete HWICAP device. + * All the registers will return to the default value and the FIFO is also + * flushed as a part of this software reset. + */ +void fifo_icap_reset(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Reset the device by setting/clearing the RESET bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_SW_RESET_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_SW_RESET_MASK)); + +} + +/** + * fifo_icap_flush_fifo: This function flushes the FIFOs in the device. + * @parameter drvdata: a pointer to the drvdata. + */ +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Flush the FIFO by setting/clearing the FIFO Clear bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_FIFO_CLR_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_FIFO_CLR_MASK)); +} + diff --git a/drivers/char/xilinx_hwicap/fifo_icap.h b/drivers/char/xilinx_hwicap/fifo_icap.h new file mode 100644 index 0000000..4d3068d --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_FIFO_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_FIFO_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +/* Reads integers from the device into the storage buffer. */ +int fifo_icap_get_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +/* Writes integers to the device from the storage buffer. */ +int fifo_icap_set_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +void fifo_icap_reset(struct hwicap_drvdata *drvdata); +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata); + +#endif diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c new file mode 100644 index 0000000..83f2d0b --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -0,0 +1,900 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/* + * This is the code behind /dev/xilinx_icap -- it allows a user-space + * application to use the Xilinx ICAP subsystem. + * + * The following operations are possible: + * + * open open the port and initialize for access. + * release release port + * write Write a bitstream to the configuration processor. + * read Read a data stream from the configuration processor. + * + * After being opened, the port is initialized and accessed to avoid a + * corrupted first read which may occur with some hardware. The port + * is left in a desynched state, requiring that a synch sequence be + * transmitted before any valid configuration data. A user will have + * exclusive access to the device while it remains open, and the state + * of the ICAP cannot be guaranteed after the device is closed. Note + * that a complete reset of the core and the state of the ICAP cannot + * be performed on many versions of the cores, hence users of this + * device should avoid making inconsistent accesses to the device. In + * particular, accessing the read interface, without first generating + * a write containing a readback packet can leave the ICAP in an + * inaccessible state. + * + * Note that in order to use the read interface, it is first necessary + * to write a request packet to the write interface. i.e., it is not + * possible to simply readback the bitstream (or any configuration + * bits) from a device without specifically requesting them first. + * The code to craft such packets is intended to be part of the + * user-space application code that uses this device. The simplest + * way to use this interface is simply: + * + * cp foo.bit /dev/xilinx_icap + * + * Note that unless foo.bit is an appropriately constructed partial + * bitstream, this has a high likelyhood of overwriting the design + * currently programmed in the FPGA. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <asm/semaphore.h> +#include <linux/sysctl.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#ifdef CONFIG_OF +/* For open firmware. */ +#include <linux/of_device.h> +#include <linux/of_platform.h> +#endif + +#include "xilinx_hwicap.h" +#include "buffer_icap.h" +#include "fifo_icap.h" + +#define DRIVER_NAME "xilinx_icap" + +#define HWICAP_REGS (0x10000) + +/* dynamically allocate device number */ +static int xhwicap_major; +static int xhwicap_minor; +#define HWICAP_DEVICES 1 + +module_param(xhwicap_major, int, S_IRUGO); +module_param(xhwicap_minor, int, S_IRUGO); + +/* An array, which is set to true when the device is registered. */ +static bool probed_devices[HWICAP_DEVICES]; + +static struct class *icap_class; + +#define UNIMPLEMENTED 0xFFFF + +static const struct config_registers v2_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = 11, + .KEY = 12, + .CBC = 13, + .IDCODE = 14, + .AXSS = UNIMPLEMENTED, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; + +static const struct config_registers v4_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; +static const struct config_registers v5_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = 14, + .CSOB = 15, + .WBSTAR = 16, + .TIMER = 17, + .BOOTSTS = 18, + .CTL_1 = 19, +}; + +/** + * hwicap_command_desync: Send a DESYNC command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command desynchronizes the ICAP After this command, a + * bitstream containing a NULL packet, followed by a SYNCH packet is + * required before the ICAP will recognize commands. + */ +int hwicap_command_desync(struct hwicap_drvdata *drvdata) +{ + u32 buffer[4]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_DESYNCH; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); +} + +/** + * hwicap_command_capture: Send a CAPTURE command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command captures all of the flip flop states so they will be + * available during readback. One can use this command instead of + * enabling the CAPTURE block in the design. + */ +int hwicap_command_capture(struct hwicap_drvdata *drvdata) +{ + u32 buffer[7]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_GCAPTURE; + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_DUMMY_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data + * present in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); + +} + +/** + * hwicap_get_configuration_register: Query a configuration register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter reg: a constant which represents the configuration + * register value to be returned. + * Examples: XHI_IDCODE, XHI_FLR. + * @parameter RegData: returns the value of the register. + * + * Sends a query packet to the ICAP and then receives the response. + * The icap is left in Synched state. + */ +int hwicap_get_configuration_register(struct hwicap_drvdata *drvdata, + u32 reg, u32 *RegData) +{ + int status; + u32 buffer[6]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_read(reg) | 1; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + status = drvdata->config->set_configuration(drvdata, + &buffer[0], index); + if (status) + return status; + + /* + * Read the configuration register + */ + status = drvdata->config->get_configuration(drvdata, RegData, 1); + if (status) + return status; + + return 0; +} + +int hwicap_initialize_hwicap(struct hwicap_drvdata *drvdata) +{ + int status; + u32 idcode; + + dev_dbg(drvdata->dev, "initializing\n"); + + /* Abort any current transaction, to make sure we have the + * ICAP in a good state. */ + dev_dbg(drvdata->dev, "Reset...\n"); + drvdata->config->reset(drvdata); + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + /* Attempt to read the IDCODE from ICAP. This + * may not be returned correctly, due to the design of the + * hardware. + */ + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); + status = hwicap_get_configuration_register( + drvdata, drvdata->config_regs->IDCODE, &idcode); + dev_dbg(drvdata->dev, "IDCODE = %x\n", idcode); + if (status) + return status; + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + return 0; +} + +static ssize_t +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t bytes_to_read = 0; + u32 *kbuf; + u32 words; + u32 bytes_remaining; + int status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->read_buffer_in_use) { + /* If there are leftover bytes in the buffer, just */ + /* return them and don't try to read more from the */ + /* ICAP device. */ + bytes_to_read = + (count < drvdata->read_buffer_in_use) ? count : + drvdata->read_buffer_in_use; + + /* Return the data currently in the read buffer. */ + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { + status = -EFAULT; + goto error; + } + drvdata->read_buffer_in_use -= bytes_to_read; + memcpy(drvdata->read_buffer + bytes_to_read, + drvdata->read_buffer, 4 - bytes_to_read); + } else { + /* Get new data from the ICAP, and return was was requested. */ + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + /* The ICAP device is only able to read complete */ + /* words. If a number of bytes that do not correspond */ + /* to complete words is requested, then we read enough */ + /* words to get the required number of bytes, and then */ + /* save the remaining bytes for the next read. */ + + /* Determine the number of words to read, rounding up */ + /* if necessary. */ + words = ((count + 3) >> 2); + bytes_to_read = words << 2; + + if (bytes_to_read > PAGE_SIZE) + bytes_to_read = PAGE_SIZE; + + /* Ensure we only read a complete number of words. */ + bytes_remaining = bytes_to_read & 3; + bytes_to_read &= ~3; + words = bytes_to_read >> 2; + + status = drvdata->config->get_configuration(drvdata, + kbuf, words); + + /* If we didn't read correctly, then bail out. */ + if (status) { + free_page((unsigned long)kbuf); + goto error; + } + + /* If we fail to return the data to the user, then bail out. */ + if (copy_to_user(buf, kbuf, bytes_to_read)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + memcpy(kbuf, drvdata->read_buffer, bytes_remaining); + drvdata->read_buffer_in_use = bytes_remaining; + free_page((unsigned long)kbuf); + } + status = bytes_to_read; + error: + up(&drvdata->sem); + return status; +} + +static ssize_t +hwicap_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t written = 0; + ssize_t left = count; + u32 *kbuf; + ssize_t len; + ssize_t status; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + left += drvdata->write_buffer_in_use; + + /* Only write multiples of 4 bytes. */ + if (left < 4) { + status = 0; + goto error; + } + + kbuf = (u32 *) __get_free_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + while (left > 3) { + /* only write multiples of 4 bytes, so there might */ + /* be as many as 3 bytes left (at the end). */ + len = left; + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + len &= ~3; + + if (drvdata->write_buffer_in_use) { + memcpy(kbuf, drvdata->write_buffer, + drvdata->write_buffer_in_use); + if (copy_from_user( + (((char *)kbuf) + (drvdata->write_buffer_in_use)), + buf + written, + len - (drvdata->write_buffer_in_use))) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } else { + if (copy_from_user(kbuf, buf + written, len)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } + + status = drvdata->config->set_configuration(drvdata, + kbuf, len >> 2); + + if (status) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + if (drvdata->write_buffer_in_use) { + len -= drvdata->write_buffer_in_use; + left -= drvdata->write_buffer_in_use; + drvdata->write_buffer_in_use = 0; + } + written += len; + left -= len; + } + if ((left > 0) && (left < 4)) { + if (!copy_from_user(drvdata->write_buffer, + buf + written, left)) { + drvdata->write_buffer_in_use = left; + written += left; + left = 0; + } + } + + free_page((unsigned long)kbuf); + status = written; + error: + up(&drvdata->sem); + return status; +} + +static int hwicap_open(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata; + int status; + + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + drvdata->is_open = 1; + + status = hwicap_initialize_hwicap(drvdata); + if (status) { + dev_err(drvdata->dev, "Failed to open file"); + return status; + } + + file->private_data = drvdata; + drvdata->write_buffer_in_use = 0; + drvdata->read_buffer_in_use = 0; + + up(&drvdata->sem); + + return 0; +} + +static int hwicap_release(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata = file->private_data; + int i; + int status = 0; + + if (down_interruptible(&drvdata->sem)) + return -ERESTARTSYS; + + if (drvdata->write_buffer_in_use) { + /* Flush write buffer. */ + for (i = drvdata->write_buffer_in_use; i < 4; i++) + drvdata->write_buffer[i] = 0; + + status = drvdata->config->set_configuration(drvdata, + (u32 *) drvdata->write_buffer, 1); + if (status) + goto error; + } + + status = hwicap_command_desync(drvdata); + if (status) + goto error; + + error: + drvdata->is_open = 0; + up(&drvdata->sem); + return status; +} + +static struct file_operations hwicap_fops = { + .owner = THIS_MODULE, + .write = hwicap_write, + .read = hwicap_read, + .open = hwicap_open, + .release = hwicap_release, +}; + +static int __devinit hwicap_setup(struct device *dev, int id, + const struct resource *regs_res, + const struct hwicap_driver_config *config, + const struct config_registers *config_regs) +{ + dev_t devt; + struct hwicap_drvdata *drvdata = NULL; + int retval = 0; + + dev_info(dev, "Xilinx icap port driver\n"); + + if (id < 0) { + for (id = 0; id < HWICAP_DEVICES; id++) + if (!probed_devices[id]) + break; + } + if (id < 0 || id >= HWICAP_DEVICES) { + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); + return -EINVAL; + } + if (probed_devices[id]) { + dev_err(dev, "cannot assign to %s%i; it is already in use\n", + DRIVER_NAME, id); + return -EBUSY; + } + + probed_devices[id] = 1; + + devt = MKDEV(xhwicap_major, xhwicap_minor + id); + + drvdata = kmalloc(sizeof(struct hwicap_drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + return -ENOMEM; + } + memset((void *)drvdata, 0, sizeof(struct hwicap_drvdata)); + dev_set_drvdata(dev, (void *)drvdata); + + if (!regs_res) { + dev_err(dev, "Couldn't get registers resource\n"); + retval = -EFAULT; + goto failed1; + } + + drvdata->mem_start = regs_res->start; + drvdata->mem_end = regs_res->end; + drvdata->mem_size = regs_res->end - regs_res->start + 1; + + if (!request_mem_region(drvdata->mem_start, + drvdata->mem_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at %p\n", + (void *)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + drvdata->devt = devt; + drvdata->dev = dev; + drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); + if (!drvdata->base_address) { + dev_err(dev, "ioremap() failed\n"); + goto failed2; + } + + drvdata->config = config; + drvdata->config_regs = config_regs; + + init_MUTEX(&drvdata->sem); + drvdata->is_open = 0; + + dev_info(dev, "ioremap %lx to %p with size %x\n", + (unsigned long int)drvdata->mem_start, + drvdata->base_address, drvdata->mem_size); + + cdev_init(&drvdata->cdev, &hwicap_fops); + drvdata->cdev.owner = THIS_MODULE; + retval = cdev_add(&drvdata->cdev, devt, 1); + if (retval) { + dev_err(dev, "cdev_add() failed\n"); + goto failed3; + } + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ + class_device_create(icap_class, NULL, devt, NULL, DRIVER_NAME); + return 0; /* success */ + + failed3: + iounmap(drvdata->base_address); + + failed2: + release_mem_region(regs_res->start, drvdata->mem_size); + + failed1: + kfree(drvdata); + + return retval; +} + +static struct hwicap_driver_config buffer_icap_config = { + .get_configuration = buffer_icap_get_configuration, + .set_configuration = buffer_icap_set_configuration, + .reset = buffer_icap_reset, +}; + +static struct hwicap_driver_config fifo_icap_config = { + .get_configuration = fifo_icap_get_configuration, + .set_configuration = fifo_icap_set_configuration, + .reset = fifo_icap_reset, +}; + +static int __devexit hwicap_remove(struct device *dev) +{ + struct hwicap_drvdata *drvdata; + + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); + + if (!drvdata) + return 0; + + class_device_destroy(icap_class, drvdata->devt); + cdev_del(&drvdata->cdev); + iounmap(drvdata->base_address); + release_mem_region(drvdata->mem_start, drvdata->mem_size); + kfree(drvdata); + dev_set_drvdata(dev, NULL); + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; + + return 0; /* success */ +} + +static int __devinit hwicap_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct config_registers *regs; + const char *family; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = pdev->dev.platform_data; + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + + return hwicap_setup(&pdev->dev, pdev->id, res, + &buffer_icap_config, regs); +} + +static int __devexit hwicap_drv_remove(struct platform_device *pdev) +{ + return hwicap_remove(&pdev->dev); +} + +static struct platform_driver hwicap_platform_driver = { + .probe = hwicap_drv_probe, + .remove = hwicap_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +#if defined(CONFIG_OF) +static int __devinit +hwicap_of_probe(struct of_device *op, const struct of_device_id *match) +{ + struct resource res; + const unsigned int *id; + const char *family; + int rc; + const struct hwicap_driver_config *config = match->data; + const struct config_registers *regs; + + dev_dbg(&op->dev, "hwicap_of_probe(%p, %p)\n", op, match); + + rc = of_address_to_resource(op->node, 0, &res); + if (rc) { + dev_err(&op->dev, "invalid address\n"); + return rc; + } + + id = of_get_property(op->node, "port-number", NULL); + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = of_get_property(op->node, "xlnx,family", NULL); + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + return hwicap_setup(&op->dev, id ? *id : -1, &res, config, + regs); +} + +static int __devexit hwicap_of_remove(struct of_device *op) +{ + return hwicap_remove(&op->dev); +} + +/* Match table for of_platform binding */ +static const struct of_device_id __devinit hwicap_of_match[] = { + { .compatible = "xlnx,opb-hwicap-1.00.b", .data = &buffer_icap_config}, + { .compatible = "xlnx,xps-hwicap-1.00.a", .data = &fifo_icap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, hwicap_of_match); + +static struct of_platform_driver hwicap_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = hwicap_of_match, + .probe = hwicap_of_probe, + .remove = __devexit_p(hwicap_of_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +/* Registration helpers to keep the number of #ifdefs to a minimum */ +static inline int __devinit hwicap_of_register(void) +{ + pr_debug("hwicap: calling of_register_platform_driver()\n"); + return of_register_platform_driver(&hwicap_of_driver); +} + +static inline void __devexit hwicap_of_unregister(void) +{ + of_unregister_platform_driver(&hwicap_of_driver); +} +#else /* CONFIG_OF */ +/* CONFIG_OF not enabled; do nothing helpers */ +static inline int __devinit hwicap_of_register(void) { return 0; } +static inline void __devexit hwicap_of_unregister(void) { } +#endif /* CONFIG_OF */ + +static int __devinit hwicap_module_init(void) +{ + dev_t devt; + int retval; + + icap_class = class_create(THIS_MODULE, "xilinx_config"); + + if (xhwicap_major) { + devt = MKDEV(xhwicap_major, xhwicap_minor); + retval = register_chrdev_region( + devt, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + } else { + retval = alloc_chrdev_region(&devt, + xhwicap_minor, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + xhwicap_major = MAJOR(devt); + } + + retval = platform_driver_register(&hwicap_platform_driver); + + if (retval) + goto failed1; + + retval = hwicap_of_register(); + + if (retval) + goto failed2; + + return retval; + + failed2: + platform_driver_unregister(&hwicap_platform_driver); + + failed1: + unregister_chrdev_region(devt, HWICAP_DEVICES); + + return retval; +} + +static void __devexit hwicap_module_cleanup(void) +{ + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); + + class_destroy(icap_class); + + platform_driver_unregister(&hwicap_platform_driver); + + hwicap_of_unregister(); + + unregister_chrdev_region(devt, HWICAP_DEVICES); +} + +module_init(hwicap_module_init); +module_exit(hwicap_module_cleanup); + +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h new file mode 100644 index 0000000..b6b47d0 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2007 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ +#define XILINX_HWICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +struct hwicap_drvdata { + u32 write_buffer_in_use; /* Always in [0,3] */ + u8 write_buffer[4]; + u32 read_buffer_in_use; /* Always in [0,3] */ + u8 read_buffer[4]; + u32 mem_start; /* phys. address of the control registers */ + u32 mem_end; /* phys. address of the control registers */ + u32 mem_size; + void __iomem *base_address;/* virt. address of the control registers */ + + struct device *dev; + struct cdev cdev; /* Char device structure */ + dev_t devt; + + const struct hwicap_driver_config *config; + const struct config_registers *config_regs; + void *private_data; + bool is_open; + bool is_accessing; +}; + +struct hwicap_driver_config { + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + void (*reset)(struct hwicap_drvdata *drvdata); +}; + +/* Number of times to poll the done regsiter */ +#define XHI_MAX_RETRIES 10 + +/************ Constant Definitions *************/ + +#define XHI_PAD_FRAMES 0x1 + +/* Mask for calculating configuration packet headers */ +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL +#define XHI_TYPE_MASK 0x7 +#define XHI_REGISTER_MASK 0xF +#define XHI_OP_MASK 0x3 + +#define XHI_TYPE_SHIFT 29 +#define XHI_REGISTER_SHIFT 13 +#define XHI_OP_SHIFT 27 + +#define XHI_TYPE_1 1 +#define XHI_TYPE_2 2 +#define XHI_OP_WRITE 2 +#define XHI_OP_READ 1 + +/* Address Block Types */ +#define XHI_FAR_CLB_BLOCK 0 +#define XHI_FAR_BRAM_BLOCK 1 +#define XHI_FAR_BRAM_INT_BLOCK 2 + +struct config_registers { + u32 CRC; + u32 FAR; + u32 FDRI; + u32 FDRO; + u32 CMD; + u32 CTL; + u32 MASK; + u32 STAT; + u32 LOUT; + u32 COR; + u32 MFWR; + u32 FLR; + u32 KEY; + u32 CBC; + u32 IDCODE; + u32 AXSS; + u32 C0R_1; + u32 CSOB; + u32 WBSTAR; + u32 TIMER; + u32 BOOTSTS; + u32 CTL_1; +}; + +/* Configuration Commands */ +#define XHI_CMD_NULL 0 +#define XHI_CMD_WCFG 1 +#define XHI_CMD_MFW 2 +#define XHI_CMD_DGHIGH 3 +#define XHI_CMD_RCFG 4 +#define XHI_CMD_START 5 +#define XHI_CMD_RCAP 6 +#define XHI_CMD_RCRC 7 +#define XHI_CMD_AGHIGH 8 +#define XHI_CMD_SWITCH 9 +#define XHI_CMD_GRESTORE 10 +#define XHI_CMD_SHUTDOWN 11 +#define XHI_CMD_GCAPTURE 12 +#define XHI_CMD_DESYNCH 13 +#define XHI_CMD_IPROG 15 /* Only in Virtex5 */ +#define XHI_CMD_CRCC 16 /* Only in Virtex5 */ +#define XHI_CMD_LTIMER 17 /* Only in Virtex5 */ + +/* Packet constants */ +#define XHI_SYNC_PACKET 0xAA995566UL +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_READ << XHI_OP_SHIFT)) + +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_WRITE << XHI_OP_SHIFT)) + +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF + +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL +#define XHI_TYPE_1_HEADER_BYTES 4 +#define XHI_TYPE_2_HEADER_BYTES 8 + +/* Constant to use for CRC check when CRC has been disabled */ +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL + +/** + * hwicap_type_1_read: Generates a Type 1 read packet header. + * @parameter: Register is the address of the register to be read back. + * + * Generates a Type 1 read packet header, which is used to indirectly + * read registers in the configuration logic. This packet must then + * be sent through the icap device, and a return packet received with + * the information. + **/ +static inline u32 hwicap_type_1_read(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_READ << XHI_OP_SHIFT); +} + +/** + * hwicap_type_1_write: Generates a Type 1 write packet header + * @parameter: Register is the address of the register to be read back. + **/ +static inline u32 hwicap_type_1_write(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_WRITE << XHI_OP_SHIFT); +} + +#endif -- 1.5.3.4-dirty ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-02-01 18:22 ` Stephen Neuendorffer @ 2008-02-01 18:42 ` Grant Likely 0 siblings, 0 replies; 21+ messages in thread From: Grant Likely @ 2008-02-01 18:42 UTC (permalink / raw) To: Stephen Neuendorffer; +Cc: linuxppc-dev, ntl On 2/1/08, Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> wrote: > diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c > new file mode 100644 > index 0000000..83f2d0b > --- /dev/null > +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c > +static int hwicap_open(struct inode *inode, struct file *file) > +{ > + struct hwicap_drvdata *drvdata; > + int status; > + > + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); > + > + if (down_interruptible(&drvdata->sem)) > + return -ERESTARTSYS; > + > + drvdata->is_open = 1; It doesn't look like this flag is used anywhere anymore. > + > + status = hwicap_initialize_hwicap(drvdata); > + if (status) { > + dev_err(drvdata->dev, "Failed to open file"); > + return status; returning without up() > + } > + > + file->private_data = drvdata; > + drvdata->write_buffer_in_use = 0; > + drvdata->read_buffer_in_use = 0; > + > + up(&drvdata->sem); > + > + return 0; > +} > + -- Grant Likely, B.Sc., P.Eng. Secret Lab Technologies Ltd. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] [XILINX][HWICAP] Xilinx Internal Configuration Access Port device driver. @ 2007-12-05 12:20 Peter Korsgaard 2008-01-31 18:47 ` [PATCH] [POWERPC] Xilinx: hwicap driver Stephen Neuendorffer 0 siblings, 1 reply; 21+ messages in thread From: Peter Korsgaard @ 2007-12-05 12:20 UTC (permalink / raw) To: Stephen Neuendorffer; +Cc: linuxppc-dev >>>>> "Stephen" == Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> writes: Hi, Stephen> Supports static platform_device and static device tree configuration. Stephen> This is a standalone driver that does not depend on EDK generated Stephen> files. However, it is also somewhat different in functionality from Stephen> the standard EDK driver. Stephen> 1) The EDK driver doesn't support readback, which this driver does. Stephen> 2) The EDK driver supports fine granularity reading and writing, which Stephen> this driver does not. The fine granularity support is heavily Stephen> architecture independent, which makes it difficult to make the driver Stephen> forward compatible. The fine granularity support is also complex and Stephen> probably better handled in user space anyway. Stephen> Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Stephen> --- Stephen> Grant, Stephen> No comments last time... It would be nice if this merged Stephen> with 2.6.25, I think. Stephen> Steve I never used the icap stuff, but here goes .. Stephen> drivers/char/Kconfig | 5 + Stephen> drivers/char/Makefile | 1 + Stephen> drivers/char/xilinx_hwicap/Makefile | 7 + Stephen> drivers/char/xilinx_hwicap/xhwicap_srp.c | 414 ++++++++++++++++++++ Stephen> drivers/char/xilinx_hwicap/xilinx_hwicap.c | 565 ++++++++++++++++++++++++++++ Stephen> drivers/char/xilinx_hwicap/xilinx_hwicap.h | 539 ++++++++++++++++++++++++++ Stephen> 6 files changed, 1531 insertions(+), 0 deletions(-) Stephen> create mode 100644 drivers/char/xilinx_hwicap/Makefile Stephen> create mode 100644 drivers/char/xilinx_hwicap/xhwicap_srp.c Stephen> create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c Stephen> create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h > diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig > index bf18d75..72295cc 100644 > --- a/drivers/char/Kconfig > +++ b/drivers/char/Kconfig > @@ -573,6 +573,11 @@ config HVC_DRIVER > It will automatically be selected if one of the back-end console drivers > is selected. Please don't put the HWICAP option in the middle of the HVC options. > +config XILINX_HWICAP > + tristate "Xilinx OPB HWICAP Support" > + depends on XILINX_VIRTEX > + help > + This option enables support for Xilinx Internal Configuration Access Port (ICAP) driver. Line too long. > > config HVC_CONSOLE > bool "pSeries Hypervisor Virtual Console support" > diff --git a/drivers/char/Makefile b/drivers/char/Makefile > index 07304d5..8cfcbb0 100644 > --- a/drivers/char/Makefile > +++ b/drivers/char/Makefile > @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o > obj-$(CONFIG_SGI_DS1286) += ds1286.o > obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o > obj-$(CONFIG_DS1302) += ds1302.o > +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ > ifeq ($(CONFIG_GENERIC_NVRAM),y) > obj-$(CONFIG_NVRAM) += generic_nvram.o > else > diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile > new file mode 100644 > index 0000000..818f4e1 > --- /dev/null > +++ b/drivers/char/xilinx_hwicap/Makefile > @@ -0,0 +1,7 @@ > +# > +# Makefile for the Xilinx OPB hwicap driver > +# > + > +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o > + > +xilinx_hwicap_m-y := xilinx_hwicap.o xhwicap_srp.o Those files are both quite small, couldn't you merge them and get rid of the global symbols and the xilinx_hwicap directory? > diff --git a/drivers/char/xilinx_hwicap/xhwicap_srp.c b/drivers/char/xilinx_hwicap/xhwicap_srp.c > new file mode 100644 > index 0000000..388eefe > --- /dev/null > +++ b/drivers/char/xilinx_hwicap/xhwicap_srp.c > @@ -0,0 +1,414 @@ > +/***************************************************************************** > + * > + * Author: Xilinx, Inc. > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License as published by the > + * Free Software Foundation; either version 2 of the License, or (at your > + * option) any later version. > + * > + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" > + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND > + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, > + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, > + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION > + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, > + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE > + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY > + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE > + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR > + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF > + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS > + * FOR A PARTICULAR PURPOSE. > + * > + * Xilinx products are not intended for use in life support appliances, > + * devices, or systems. Use in such applications is expressly prohibited. > + * > + * (c) Copyright 2003-2007 Xilinx Inc. > + * All rights reserved. > + * > + * You should have received a copy of the GNU General Public License along > + * with this program; if not, write to the Free Software Foundation, Inc., > + * 675 Mass Ave, Cambridge, MA 02139, USA. > + * > + * ****************************************************************************/ Could you please you a smaller / standard GPL header instead? > + > +#include "xilinx_hwicap.h" > + > +#define XHI_BUFFER_START 0 > + > +/****************************************************************************/ > +/** > + * > + * Stores data in the storage buffer at the specified address. > + * > + * @param InstancePtr - a pointer to the XHwIcap instance to be worked on. > + * > + * @param Address - bram word address > + * > + * @param Data - data to be stored at address > + * > + * @return None. > + * > + * @note None. > + * > +*****************************************************************************/ Please use std kerneldoc format. > +void XHwIcap_StorageBufferWrite(struct xhwicap_drvdata *InstancePtr, > + u32 Address, u32 Data) No CamelCase, Uppercase parameters. > +{ > + /* Write data to storage buffer. */ > + XHwIcap_mSetBram(InstancePtr->baseAddress, Address, Data); > +} > + > +/****************************************************************************/ > +/** > + * > + * Read data from the specified address in the storage buffer.. > + * > + * @param InstancePtr - a pointer to the XHwIcap instance to be worked on. > + * > + * @param Address - bram word address > + * > + * @return Data. > + * > + * @note None. > + * > +*****************************************************************************/ > +u32 XHwIcap_StorageBufferRead(struct xhwicap_drvdata *InstancePtr, u32 Address) > +{ > + u32 Data; > + > + /* Read data from address. Multiply Address by 4 since 4 bytes per > + * word.*/ > + Data = XHwIcap_mGetBram(InstancePtr->baseAddress, Address); > + return Data; > + > +} > + > +/****************************************************************************/ > +/** > + * > + * Reads bytes from the device (ICAP) and puts it in the storage buffer. > + * > + * @param InstancePtr - a pointer to the XHwIcap instance to be worked on. > + * > + * @param Offset - The storage buffer start address. > + * > + * @param NumInts - The number of words (32 bit) to read from the > + * device (ICAP). > + * > + *@return int - 0 or -EBUSY or -EINVAL > + * > + * @note None. > + * > +*****************************************************************************/ > +int XHwIcap_DeviceRead(struct xhwicap_drvdata *InstancePtr, u32 Offset, > + u32 NumInts) > +{ > + > + s32 Retries = 0; > + > + if (XHwIcap_mGetDoneReg(InstancePtr->baseAddress) == XHI_NOT_FINISHED) { > + return -EBUSY; > + } No curly brackets around single statement. In general, please run the patch through checkpatch.pl and fixup stuff. > + > + if ((Offset + NumInts) <= XHI_MAX_BUFFER_INTS) { > + /* setSize NumInts*4 to get bytes. */ > + XHwIcap_mSetSizeReg((InstancePtr->baseAddress), (NumInts << 2)); > + XHwIcap_mSetOffsetReg((InstancePtr->baseAddress), Offset); > + XHwIcap_mSetRncReg((InstancePtr->baseAddress), XHI_READBACK); > + > + while (XHwIcap_mGetDoneReg(InstancePtr->baseAddress) == > + XHI_NOT_FINISHED) { > + Retries++; > + if (Retries > XHI_MAX_RETRIES) { > + return -EBUSY; > + } > + } > + } else { > + return -EINVAL; > + } > + return 0; > + > +}; > + > +/****************************************************************************/ > +/** > + * > + * Writes bytes from the storage buffer and puts it in the device (ICAP). > + * > + * @param InstancePtr - a pointer to the XHwIcap instance to be worked on. > + * > + * @param Offset - The storage buffer start address. > + * > + * @param NumInts - The number of words (32 bit) to read from the > + * device (ICAP). > + * > + *@return int - 0 or -EBUSY or -EINVAL > + * > + * @note None. > + * > +*****************************************************************************/ > +int XHwIcap_DeviceWrite(struct xhwicap_drvdata *InstancePtr, u32 Offset, > + u32 NumInts) > +{ > + > + s32 Retries = 0; > + > + if (XHwIcap_mGetDoneReg(InstancePtr->baseAddress) == XHI_NOT_FINISHED) { > + return -EBUSY; > + } > + > + if ((Offset + NumInts) <= XHI_MAX_BUFFER_INTS) { > + /* setSize NumInts*4 to get bytes. */ > + XHwIcap_mSetSizeReg((InstancePtr->baseAddress), NumInts << 2); > + XHwIcap_mSetOffsetReg((InstancePtr->baseAddress), Offset); > + XHwIcap_mSetRncReg((InstancePtr->baseAddress), XHI_CONFIGURE); > + > + while (XHwIcap_mGetDoneReg(InstancePtr->baseAddress) == > + XHI_NOT_FINISHED) { > + Retries++; > + if (Retries > XHI_MAX_RETRIES) { > + return -EBUSY; > + } > + } > + } else { > + return -EINVAL; > + } > + return 0; > + > +}; > + > +/****************************************************************************/ > +/** > + * > + * Sends a DESYNC command to the ICAP port. > + * > + * @param InstancePtr - a pointer to the XHwIcap instance to be worked on. > + * > + *@return int - 0 or -EBUSY or -EINVAL > + * > + * @note None. > + * > +*****************************************************************************/ > +int XHwIcap_CommandDesync(struct xhwicap_drvdata *InstancePtr) > +{ > + int status; > + > + XHwIcap_StorageBufferWrite(InstancePtr, 0, > + (XHwIcap_Type1Write(XHI_CMD) | 1)); > + XHwIcap_StorageBufferWrite(InstancePtr, 1, XHI_CMD_DESYNCH); > + XHwIcap_StorageBufferWrite(InstancePtr, 2, XHI_NOOP_PACKET); > + XHwIcap_StorageBufferWrite(InstancePtr, 3, XHI_NOOP_PACKET); > + > + /* send four words */ > + status = XHwIcap_DeviceWrite(InstancePtr, 0, 4); > + if (status) { > + return status; > + } > + > + return 0; > +} > + > +/****************************************************************************/ > +/** > + * > + * Sends a CAPTURE command to the ICAP port. This command caputres all > + * of the flip flop states so they will be available during readback. > + * One can use this command instead of enabling the CAPTURE block in the > + * design. > + * > + * @param InstancePtr - a pointer to the XHwIcap instance to be worked on. > + * > + * @return int - 0 or -EBUSY or -EINVAL > + * > + * @note None. > + * > +*****************************************************************************/ > +int XHwIcap_CommandCapture(struct xhwicap_drvdata *InstancePtr) > +{ > + int status; > + > + /* DUMMY and SYNC */ > + XHwIcap_StorageBufferWrite(InstancePtr, 0, XHI_DUMMY_PACKET); > + XHwIcap_StorageBufferWrite(InstancePtr, 1, XHI_SYNC_PACKET); > + XHwIcap_StorageBufferWrite(InstancePtr, 2, > + (XHwIcap_Type1Write(XHI_CMD) | 1)); > + XHwIcap_StorageBufferWrite(InstancePtr, 3, XHI_CMD_GCAPTURE); > + XHwIcap_StorageBufferWrite(InstancePtr, 4, XHI_DUMMY_PACKET); > + XHwIcap_StorageBufferWrite(InstancePtr, 5, XHI_DUMMY_PACKET); > + > + /* send six words */ > + status = XHwIcap_DeviceWrite(InstancePtr, 0, 6); > + if (status) { /* send six words */ > + return status; > + } > + > + return 0; > +} > + > +/****************************************************************************/ > +/** > + * > + * This function returns the value of the specified configuration > + * register. > + * > + * @param InstancePtr - a pointer to the XHwIcap instance to be worked > + * on. > + * > + * @param ConfigReg - A constant which represents the configuration > + * register value to be returned. Constants specified in xhwicap_i.h. Examples: > + * XHI_IDCODE, XHI_FLR. > + * > + * @return The value of the specified configuration register. > + * > + * > +*****************************************************************************/ > + > +u32 XHwIcap_GetConfigReg(struct xhwicap_drvdata *InstancePtr, u32 ConfigReg) > +{ > + u32 Packet; > + int status; > + > + /* Write bitstream to bram */ > + Packet = XHwIcap_Type1Read(ConfigReg) | 1; > + XHwIcap_StorageBufferWrite(InstancePtr, 0, XHI_DUMMY_PACKET); > + XHwIcap_StorageBufferWrite(InstancePtr, 1, XHI_SYNC_PACKET); > + XHwIcap_StorageBufferWrite(InstancePtr, 2, Packet); > + XHwIcap_StorageBufferWrite(InstancePtr, 3, XHI_NOOP_PACKET); > + XHwIcap_StorageBufferWrite(InstancePtr, 4, XHI_NOOP_PACKET); > + > + /* Transfer Bitstream from Bram to ICAP */ > + status = XHwIcap_DeviceWrite(InstancePtr, 0, 5); > + if (status) { > + return status; > + } > + > + /* Now readback one word into bram position > + * XHI_EX_BITSTREAM_LENGTH*/ > + status = XHwIcap_DeviceRead(InstancePtr, 5, 1); > + if (status) { > + return status; > + } > + > + /* Return the Register value */ > + return XHwIcap_StorageBufferRead(InstancePtr, 5); > +} > + > +/**************************************************************************** > + * > + * Loads a partial bitstream from system memory. > + * > + * @param InstancePtr - a pointer to the XHwIcap instance to be worked on. > + * > + * @param Data - Address of the data representing the partial bitstream > + * > + * @param Size - the size of the partial bitstream in 32 bit words. > + * > + * @return 0, -EFBIG or -EINVAL. > + * > + * @note None. > + * > +*****************************************************************************/ > +int XHwIcap_SetConfiguration(struct xhwicap_drvdata *InstancePtr, u32 *Data, > + u32 Size) > +{ > + int status; > + s32 BufferCount = 0; > + s32 NumWrites = 0; > + bool Dirty = 0; > + u32 I; > + > + /* Loop through all the data */ > + for (I = 0, BufferCount = 0; I < Size; I++) { > + > + /* Copy data to bram */ > + XHwIcap_StorageBufferWrite(InstancePtr, BufferCount, Data[I]); > + Dirty = 1; > + > + if (BufferCount == XHI_MAX_BUFFER_INTS - 1) { > + /* Write data to ICAP */ > + status = XHwIcap_DeviceWrite(InstancePtr, XHI_BUFFER_START, > + XHI_MAX_BUFFER_INTS); > + if (status != 0) { > + /* abort. */ > + XHwIcap_mReset(InstancePtr->baseAddress); return status; > + } > + > + BufferCount = 0; > + NumWrites++; > + Dirty = 0; > + } else { > + BufferCount++; > + } > + } > + > + /* Write unwritten data to ICAP */ > + if (Dirty) { > + /* Write data to ICAP */ > + status = XHwIcap_DeviceWrite(InstancePtr, XHI_BUFFER_START, > + BufferCount); > + if (status != 0) { > + /* abort. */ > + XHwIcap_mReset(InstancePtr->baseAddress); > + } > + return status; > + } > + > + return 0; > +}; > + > +/**************************************************************************** > + * > + * Reads Configuration Data from the device. > + * > + * @param InstancePtr - a pointer to the XHwIcap instance to be worked on. > + * > + * @param Data - Address of the data representing the partial bitstream > + * > + * @param Size - the size of the partial bitstream in 32 bit words. > + * > + * @return 0, -EFBIG or -EINVAL. > + * > + * @note None. > + * > +*****************************************************************************/ > +int XHwIcap_GetConfiguration(struct xhwicap_drvdata *InstancePtr, u32 *Data, > + u32 Size) > +{ > + int status; > + s32 BufferCount = 0; > + s32 NumReads = 0; > + u32 I; > + > + /* Loop through all the data */ > + for (I = 0, BufferCount = XHI_MAX_BUFFER_INTS; I < Size; I++) { > + if (BufferCount == XHI_MAX_BUFFER_INTS) { > + u32 intsRemaining = Size - I; > + u32 intsToRead = > + intsRemaining < > + XHI_MAX_BUFFER_INTS ? intsRemaining : > + XHI_MAX_BUFFER_INTS; > + > + /* Read data from ICAP */ > + > + status = > + XHwIcap_DeviceRead(InstancePtr, XHI_BUFFER_START, > + intsToRead); > + if (status != 0) { > + /* abort. */ > + XHwIcap_mReset(InstancePtr->baseAddress); > + return status; > + } > + > + BufferCount = 0; > + NumReads++; > + } > + > + /* Copy data from bram */ > + Data[I] = XHwIcap_StorageBufferRead(InstancePtr, BufferCount); > + BufferCount++; > + } > + > + return 0; > +}; > diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c > new file mode 100644 > index 0000000..d42538e > --- /dev/null > +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c > @@ -0,0 +1,565 @@ > +/***************************************************************************** > + * > + * Author: Xilinx, Inc. > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License as published by the > + * Free Software Foundation; either version 2 of the License, or (at your > + * option) any later version. > + * > + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" > + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND > + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, > + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, > + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION > + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, > + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE > + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY > + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE > + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR > + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF > + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS > + * FOR A PARTICULAR PURPOSE. > + * > + * Xilinx products are not intended for use in life support appliances, > + * devices, or systems. Use in such applications is expressly prohibited. > + * > + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group > + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group > + * (c) Copyright 2007 Xilinx Inc. > + * All rights reserved. > + * > + * You should have received a copy of the GNU General Public License along > + * with this program; if not, write to the Free Software Foundation, Inc., > + * 675 Mass Ave, Cambridge, MA 02139, USA. > + * > + *****************************************************************************/ > + > +/* > + * xilinx_hwicap.c > + * > + * This is the code behind /dev/xilinx_icap/'x' -- it allows a user-space > + * application to use the Xilinx ICAP subsystem. > + * > + * A /dev/xilinx_icap/'x' device node represents an arbitrary device > + * on port 'x'. The following operations are possible: > + * > + * open do nothing, set up default IEEE 1284 protocol to be COMPAT > + * release release port and unregister device (if necessary) > + * write Write a bitstream to the configuration processor. > + * read Read a data stream from the configuration processor. > + * > + * Note that in order to use the read interface, it is first necessary > + * to write a request packet to the write interface. i.e., it is not > + * possible to simply readback the bitstream (or any configuration > + * bits) from a device without specifically requesting them first. > + * The code to craft such packets is intended to be part of the > + * user-space application code that uses this device. The simplest > + * way to use this interface is simply: > + * > + * cp foo.bit /dev/xilinx_icap > + * > + * Note that unless foo.bit is an appropriately constructed partial > + * bitstream, this has a high likelyhood of overwriting the design > + * currently programmed in the FPGA. > + */ > + > +#include <linux/version.h> > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/types.h> > +#include <linux/ioport.h> > +#include <linux/interrupt.h> > +#include <linux/fcntl.h> > +#include <linux/init.h> > +#include <linux/poll.h> > +#include <linux/proc_fs.h> > +#include <linux/spinlock.h> > +#include <linux/sysctl.h> > +#include <linux/version.h> > +#include <linux/fs.h> > +#include <linux/cdev.h> > +#include <linux/platform_device.h> > + > +#include <asm/io.h> > +#include <asm/uaccess.h> > +#include <asm/system.h> > + > +#ifdef CONFIG_OF > +/* For open firmware. */ > +#include <linux/of_device.h> > +#include <linux/of_platform.h> > +#endif > + > +#include "xilinx_hwicap.h" > + > +#define DRIVER_NAME "xilinx_icap" > + > +#define XHWICAP_REGS (0x10000) > + > +/* dynamically allocate device number */ > +static int xhwicap_major = 0; > +static int xhwicap_minor = 0; > +static int xhwicap_no_minors = 4; > +module_param(xhwicap_major, int, S_IRUGO); > +module_param(xhwicap_minor, int, S_IRUGO); > +module_param(xhwicap_no_minors, int, S_IRUGO); Why don't you request official major/minor numbers? (Documentation/devices.txt) > + > +static struct class *icap_class; > + > +int xhwicap_initialize_xhwicap(struct xhwicap_drvdata *drvdata) > +{ > + > + u32 DeviceIdCode; > + u32 Packet; > + int Status; > + > + dev_dbg(drvdata->dev, "Reset...\n"); > + > + /* Abort any current transaction, to make sure we have the ICAP in */ > + /* a good state. */ > + XHwIcap_mReset(drvdata->baseAddress); > + > + /* Read the IDCODE from ICAP if specified. */ > + { > + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); > + > + /* Write bitstream to bram */ > + Packet = XHwIcap_Type1Read(XHI_IDCODE) | 1; > + XHwIcap_StorageBufferWrite(drvdata, 0, XHI_DUMMY_PACKET); > + XHwIcap_StorageBufferWrite(drvdata, 1, XHI_SYNC_PACKET); > + XHwIcap_StorageBufferWrite(drvdata, 2, Packet); > + XHwIcap_StorageBufferWrite(drvdata, 3, XHI_NOOP_PACKET); > + XHwIcap_StorageBufferWrite(drvdata, 4, XHI_NOOP_PACKET); > + > + /* Transfer Bitstream from Bram to ICAP */ > + Status = XHwIcap_DeviceWrite(drvdata, 0, 5); > + if (Status) { > + return Status; > + } > + > + /* Now readback one word into bram position > + * XHI_EX_BITSTREAM_LENGTH*/ > + Status = XHwIcap_DeviceRead(drvdata, 5, 1); > + if (Status) { > + return Status; > + } > + > + /* Return the Register value */ > + DeviceIdCode = XHwIcap_StorageBufferRead(drvdata, 5); > + > + /* Mask out the version section of the DeviceIdCode */ > + DeviceIdCode = DeviceIdCode & 0x0FFFFFFF; > + > + dev_dbg(drvdata->dev, "Desync...\n"); > + Status = XHwIcap_CommandDesync(drvdata); > + > + if (Status) { > + return Status; > + } > + } > + > + /* Abort any current transaction, to make sure we have the ICAP in */ > + /* a good state. */ > + XHwIcap_mReset(drvdata->baseAddress); > + > + dev_info(drvdata->dev, "Device IDCODE = %x\n", DeviceIdCode); > + > + return 0; > +} > + > +static ssize_t > +xhwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) > +{ > + struct xhwicap_drvdata *drvdata = file->private_data; > + ssize_t bytes_to_read = 0; > + u32 *kbuf; > + u32 words; > + u32 bytes_remaining; > + int Status; > + > + if (drvdata->read_buffer_in_use) { > + /* If there are leftover bytes in the buffer, just */ > + /* return them and don't try to read more from the */ > + /* ICAP device. */ > + bytes_to_read = > + (count < > + drvdata->read_buffer_in_use) ? count : drvdata-> > + read_buffer_in_use; > + > + /* Return the data currently in the read buffer. */ > + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { > + return -EFAULT; > + } > + drvdata->read_buffer_in_use -= bytes_to_read; > + memcpy(drvdata->read_buffer + bytes_to_read, > + drvdata->read_buffer, 4 - bytes_to_read); > + } else { > + /* Get new data from the ICAP, and return was was requested. */ > + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); > + if (!kbuf) > + return -ENOMEM; > + > + /* The ICAP device is only able to read complete */ > + /* words. If a number of bytes that do not correspond */ > + /* to complete words is requested, then we read enough */ > + /* words to get the required number of bytes, and then */ > + /* save the remaining bytes for the next read. */ > + > + /* Determine the number of words to read, rounding up */ > + /* if necessary. */ > + words = ((count + 3) >> 2); > + bytes_to_read = words << 2; > + > + if (bytes_to_read > PAGE_SIZE) { > + bytes_to_read = PAGE_SIZE; > + } > + /* Ensure we only read a complete number of words. */ > + /* BUG: should be count & 3? */ > + bytes_remaining = bytes_to_read & 3; > + bytes_to_read &= ~3; > + words = bytes_to_read >> 2; > + > + Status = XHwIcap_GetConfiguration(drvdata, kbuf, words); > + /* If we didn't read correctly, then bail out. */ > + if (Status) { > + free_page((unsigned long)kbuf); > + return -EFAULT; > + } > + /* If we fail to return the data to the user, then bail out. */ > + if (copy_to_user(buf, kbuf, bytes_to_read)) { > + free_page((unsigned long)kbuf); > + return -EFAULT; > + } > + memcpy(kbuf, drvdata->read_buffer, bytes_remaining); > + drvdata->read_buffer_in_use = bytes_remaining; > + free_page((unsigned long)kbuf); > + } > + return bytes_to_read; > +} > + > +static ssize_t xhwicap_write(struct file *file, const char *buf, > + size_t count, loff_t *ppos) > +{ > + struct xhwicap_drvdata *drvdata = file->private_data; > + ssize_t written = 0; > + ssize_t left = count; > + u32 *kbuf; > + int len; > + int Status; > + > + left += drvdata->write_buffer_in_use; > + > + /* only write multiples of 4 bytes. */ > + if (left < 4) > + return 0; > + > + kbuf = (u32 *) __get_free_page(GFP_KERNEL); > + if (!kbuf) > + return -ENOMEM; > + > + while (left > 3) { > + /* only write multiples of 4 bytes, so there might */ > + /* be as many as 3 bytes left (at the end). */ > + len = left; > + > + if (len > PAGE_SIZE) > + len = PAGE_SIZE; > + len &= ~3; > + > + if (drvdata->write_buffer_in_use) { > + memcpy(kbuf, drvdata->write_buffer, > + drvdata->write_buffer_in_use); > + if (copy_from_user > + ((((char *)kbuf) + (drvdata->write_buffer_in_use)), > + buf + written, > + len - (drvdata->write_buffer_in_use))) { > + free_page((unsigned long)kbuf); > + return -EFAULT; > + } > + } else { > + if (copy_from_user(kbuf, buf + written, len)) { > + free_page((unsigned long)kbuf); > + return -EFAULT; > + } > + } > + > + Status = XHwIcap_SetConfiguration(drvdata, kbuf, len >> 2); > + > + if (Status) { > + free_page((unsigned long)kbuf); > + return -EFAULT; > + } > + if (drvdata->write_buffer_in_use) { > + len -= drvdata->write_buffer_in_use; > + left -= drvdata->write_buffer_in_use; > + drvdata->write_buffer_in_use = 0; > + } > + written += len; > + left -= len; > + } > + if ((left > 0) && (left < 4)) { > + if (!copy_from_user(drvdata->write_buffer, buf + written, left)) { > + drvdata->write_buffer_in_use = left; > + written += left; > + left = 0; > + } > + } > + > + free_page((unsigned long)kbuf); > + return written; > +} > + > +static int xhwicap_open(struct inode *inode, struct file *file) > +{ > + struct xhwicap_drvdata *drvdata; > + int status; > + > + drvdata = container_of(inode->i_cdev, struct xhwicap_drvdata, cdev); > + > + status = xhwicap_initialize_xhwicap(drvdata); > + if (status) { > + dev_err(drvdata->dev, "Failed to open file"); > + return -status; > + } > + > + drvdata->flags = 0; > + file->private_data = drvdata; > + drvdata->write_buffer_in_use = 0; > + drvdata->read_buffer_in_use = 0; > + > + return 0; > +} > + > +static int xhwicap_release(struct inode *inode, struct file *file) > +{ > + struct xhwicap_drvdata *drvdata = file->private_data; > + int i; > + int Status; > + > + if (drvdata->write_buffer_in_use) { > + /* Flush write buffer. */ > + for (i = drvdata->write_buffer_in_use; i < 4; i++) { > + drvdata->write_buffer[i] = 0; > + } > + Status = > + XHwIcap_SetConfiguration(drvdata, > + (u32 *) drvdata->write_buffer, 1); > + if (Status) { > + return Status; > + } > + } > + > + Status = XHwIcap_CommandDesync(drvdata); > + if (Status) { > + return Status; > + } > + > + return 0; > +} > + > +static struct file_operations xhwicap_fops = { > + .owner = THIS_MODULE, > + .write = xhwicap_write, > + .read = xhwicap_read, > + .open = xhwicap_open, > + .release = xhwicap_release, > +}; > + > +static int __init xhwicap_drv_probe(struct device *dev) > +{ > + dev_t devt; > + struct platform_device *pdev = to_platform_device(dev); > + struct xhwicap_drvdata *drvdata = NULL; > + struct resource *regs_res; > + int retval = 0; > + > + if (!dev) { > + return -EINVAL; > + } > + > + dev_info(dev, "Xilinx icap port driver\n"); > + > + devt = MKDEV(xhwicap_major, xhwicap_minor + pdev->id); > + > + drvdata = kmalloc(sizeof(struct xhwicap_drvdata), GFP_KERNEL); > + if (!drvdata) { > + dev_err(dev, "Couldn't allocate device private record\n"); > + return -ENOMEM; > + } > + memset((void *)drvdata, 0, sizeof(struct xhwicap_drvdata)); > + dev_set_drvdata(dev, (void *)drvdata); > + > + /* Map the control registers in */ > + regs_res = platform_get_resource(to_platform_device(dev), > + IORESOURCE_MEM, 0); > + if (!regs_res) { > + dev_err(dev, "Couldn't get registers resource\n"); > + retval = -EFAULT; > + goto failed1; > + } > + > + drvdata->mem_start = regs_res->start; > + drvdata->mem_end = regs_res->end; > + drvdata->mem_size = regs_res->end - regs_res->start + 1; > + > + if (drvdata->mem_size < XHWICAP_REGS) { > + dev_err(dev, "Couldn't get registers resource\n"); > + retval = -EFAULT; > + goto failed1; > + } > + > + if (!request_mem_region(drvdata->mem_start, drvdata->mem_size, DRIVER_NAME)) { > + dev_err(dev, "Couldn't lock memory region at %p\n", > + (void *)regs_res->start); > + retval = -EBUSY; > + goto failed1; > + } > + > + drvdata->devt = devt; > + drvdata->dev = dev; > + drvdata->baseAddress = ioremap(drvdata->mem_start, drvdata->mem_size); > + if (!drvdata->baseAddress) { > + dev_err(dev, "ioremap() failed\n"); > + goto failed2; > + } > + > + dev_info(dev, "ioremap %lx to %p with size %x\n", > + (unsigned long int)drvdata->mem_start, > + drvdata->baseAddress, drvdata->mem_size); > + > + cdev_init(&drvdata->cdev, &xhwicap_fops); > + drvdata->cdev.owner = THIS_MODULE; > + retval = cdev_add(&drvdata->cdev, devt, 1); > + if (retval) { > + dev_err(dev, "cdev_add() failed\n"); > + goto failed3; > + } > + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ > + class_device_create(icap_class, NULL, devt, NULL, DRIVER_NAME); > + return 0; /* success */ > + > + failed3: > + iounmap(drvdata->baseAddress); > + > + failed2: > + release_mem_region(regs_res->start, drvdata->mem_size); > + > + failed1: > + kfree(drvdata); > + > + return retval; > +} > + > +static int __exit xhwicap_drv_remove(struct device *dev) > +{ > + struct xhwicap_drvdata *drvdata; > + > + if (!dev) > + return -EINVAL; > + > + drvdata = (struct xhwicap_drvdata *)dev_get_drvdata(dev); > + > + class_device_destroy(icap_class, drvdata->devt); > + cdev_del(&drvdata->cdev); > + iounmap(drvdata->baseAddress); > + release_mem_region(drvdata->mem_start, drvdata->mem_size); > + kfree(drvdata); > + dev_set_drvdata(dev, NULL); > + > + return 0; /* success */ > +} > + > +static struct device_driver xhwicap_module_driver = { > + .name = DRIVER_NAME, > + .bus = &platform_bus_type, > + > + .probe = xhwicap_drv_probe, > + .remove = xhwicap_drv_remove, > +}; Please use struct platform_driver instead. > + > +static int __init xhwicap_module_init(void) > +{ > + dev_t devt; > + int retval; > + > + icap_class = class_create(THIS_MODULE, "xilinx_config"); What's that for? > + > + if (xhwicap_major) { > + devt = MKDEV(xhwicap_major, xhwicap_minor); > + retval = register_chrdev_region(devt, xhwicap_no_minors, > + DRIVER_NAME); > + } else { > + retval = > + alloc_chrdev_region(&devt, xhwicap_minor, xhwicap_no_minors, > + DRIVER_NAME); > + xhwicap_major = MAJOR(devt); > + } > + if (retval < 0) { > + xhwicap_major = 0; > + return retval; > + } > + > + retval = driver_register(&xhwicap_module_driver); > + > + if (retval) { > + unregister_chrdev_region(devt, xhwicap_no_minors); > + } > + > + return retval; > +} > + > +static void __exit xhwicap_module_cleanup(void) > +{ > + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); > + > + class_destroy(icap_class); > + > + driver_unregister(&xhwicap_module_driver); > + > + unregister_chrdev_region(devt, xhwicap_no_minors); > +} > + > +module_init(xhwicap_module_init); > +module_exit(xhwicap_module_cleanup); > + > +#ifdef CONFIG_OF > + > +static int __init xilinx_hwicap_of_init(void) > +{ > + struct device_node *np; > + unsigned int i; > + struct platform_device *pdev; > + int ret; > + > + for (np = NULL, i = 0; > + (np = of_find_compatible_node(np, NULL, "xlnx,opb-hwicap")) != NULL; > + i++) { > + struct resource r; > + > + memset(&r, 0, sizeof(r)); > + > + ret = of_address_to_resource(np, 0, &r); > + if (ret) > + goto err; > + pdev = > + platform_device_register_simple(DRIVER_NAME, i, &r, 1); > + > + if (IS_ERR(pdev)) { > + ret = PTR_ERR(pdev); > + goto err; > + } > + } > + > + return 0; > +err: > + return ret; > +} > + > +module_init(xilinx_hwicap_of_init); > + > +#endif > + > +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); > +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h > new file mode 100644 > index 0000000..80e3fe0 > --- /dev/null > +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h > @@ -0,0 +1,539 @@ > +/***************************************************************************** > + * > + * Author: Xilinx, Inc. > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License as published by the > + * Free Software Foundation; either version 2 of the License, or (at your > + * option) any later version. > + * > + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" > + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND > + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, > + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, > + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION > + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, > + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE > + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY > + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE > + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR > + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF > + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS > + * FOR A PARTICULAR PURPOSE. > + * > + * Xilinx products are not intended for use in life support appliances, > + * devices, or systems. Use in such applications is expressly prohibited. > + * > + * (c) Copyright 2003-2007 Xilinx Inc. > + * All rights reserved. > + * > + * You should have received a copy of the GNU General Public License along > + * with this program; if not, write to the Free Software Foundation, Inc., > + * 675 Mass Ave, Cambridge, MA 02139, USA. > + * > + *****************************************************************************/ > + > +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ > +#define XILINX_HWICAP_H_ /* by using protection macros */ > + > +#include <linux/types.h> > +#include <linux/cdev.h> > +#include <linux/version.h> > +#include <linux/platform_device.h> > + > +#include <asm/io.h> > + > +struct xhwicap_drvdata { > + u32 flags; > + u32 write_buffer_in_use; /* Always in [0,3] */ > + u8 write_buffer[4]; > + u32 read_buffer_in_use; /* Always in [0,3] */ > + u8 read_buffer[4]; > + u32 mem_start; /* phys. address of the control registers */ > + u32 mem_end; /* phys. address of the control registers */ > + u32 mem_size; > + void __iomem *baseAddress;/* virt. address of the control registers */ > + > + struct device *dev; > + struct cdev cdev; /* Char device structure */ > + dev_t devt; > +}; > + > +/***************************** Include Files ********************************/ > + > +#define virtex2 0 > +#define virtex4 1 > + > +#ifdef CONFIG_XILINX_VIRTEX_4_FX > +#define XHI_FAMILY virtex4 > +#else > +#define XHI_FAMILY virtex2 > +#endif So having a single kernel with v2p/v4 support is not an option? > + > +/************************** Constant Definitions ****************************/ > + > +#define XHI_PAD_FRAMES 0x1 > + > +/* Mask for calculating configuration packet headers */ > +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL > +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL > +#define XHI_TYPE_MASK 0x7 > +#define XHI_REGISTER_MASK 0xF > +#define XHI_OP_MASK 0x3 > + > +#define XHI_TYPE_SHIFT 29 > +#define XHI_REGISTER_SHIFT 13 > +#define XHI_OP_SHIFT 27 > + > +#define XHI_TYPE_1 1 > +#define XHI_TYPE_2 2 > +#define XHI_OP_WRITE 2 > +#define XHI_OP_READ 1 > + > +/* Address Block Types */ > +#define XHI_FAR_CLB_BLOCK 0 > +#define XHI_FAR_BRAM_BLOCK 1 > +#define XHI_FAR_BRAM_INT_BLOCK 2 > + > +/* Addresses of the Configuration Registers */ > +#define XHI_CRC 0 > +#define XHI_FAR 1 > +#define XHI_FDRI 2 > +#define XHI_FDRO 3 > +#define XHI_CMD 4 > +#define XHI_CTL 5 > +#define XHI_MASK 6 > +#define XHI_STAT 7 > +#define XHI_LOUT 8 > +#define XHI_COR 9 > +#define XHI_MFWR 10 > + > +#if XHI_FAMILY == virtex4 > + > +#define XHI_CBC 11 > +#define XHI_IDCODE 12 > +#define XHI_AXSS 13 > +#define XHI_NUM_REGISTERS 14 > + > +#else > + > +#define XHI_FLR 11 > +#define XHI_KEY 12 > +#define XHI_CBC 13 > +#define XHI_IDCODE 14 > +#define XHI_NUM_REGISTERS 15 > + > +#endif > + > +/* Configuration Commands */ > +#define XHI_CMD_NULL 0 > +#define XHI_CMD_WCFG 1 > +#define XHI_CMD_MFW 2 > +#define XHI_CMD_DGHIGH 3 > +#define XHI_CMD_RCFG 4 > +#define XHI_CMD_START 5 > +#define XHI_CMD_RCAP 6 > +#define XHI_CMD_RCRC 7 > +#define XHI_CMD_AGHIGH 8 > +#define XHI_CMD_SWITCH 9 > +#define XHI_CMD_GRESTORE 10 > +#define XHI_CMD_SHUTDOWN 11 > +#define XHI_CMD_GCAPTURE 12 > +#define XHI_CMD_DESYNCH 13 > + > +/* Packet constants */ > +#define XHI_SYNC_PACKET 0xAA995566UL > +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL > +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) > +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ > + (XHI_OP_READ << XHI_OP_SHIFT)) > + > +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ > + (XHI_OP_WRITE << XHI_OP_SHIFT)) > + > +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF > + > +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL > +#define XHI_TYPE_1_HEADER_BYTES 4 > +#define XHI_TYPE_2_HEADER_BYTES 8 > + > +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ > +#define XHI_MAX_BUFFER_BYTES 2048 > +#define XHI_MAX_BUFFER_INTS 512 > + > +/* Number of frames in different tile types */ > +#if XHI_FAMILY == virtex4 > + > +#define XHI_GCLK_FRAMES 3 > +#define XHI_IOB_FRAMES 30 > +#define XHI_DSP_FRAMES 21 > +#define XHI_CLB_FRAMES 22 > +#define XHI_BRAM_FRAMES 64 > +#define XHI_BRAM_INT_FRAMES 20 > + > +#else > + > +#define XHI_GCLK_FRAMES 4 > +#define XHI_IOB_FRAMES 4 > +#define XHI_IOI_FRAMES 22 > +#define XHI_CLB_FRAMES 22 > +#define XHI_BRAM_FRAMES 64 > +#define XHI_BRAM_INT_FRAMES 22 > + > +#endif > + > +/* Device Resources */ > +#define CLB 0 > +#define DSP 1 > +#define BRAM 2 > +#define BRAM_INT 3 > +#define IOB 4 > +#define IOI 5 > +#define CLK 6 > +#define MGT 7 > + > +#define BLOCKTYPE0 0 > +#define BLOCKTYPE1 1 > +#define BLOCKTYPE2 2 > + > +/* The number of words reserved for the header in the storage buffer. */ > +/* MAY CHANGE FOR V4 */ > +#define XHI_HEADER_BUFFER_WORDS 20 > +#define XHI_HEADER_BUFFER_BYTES (XHI_HEADER_BUFFER_WORDS << 2) > + > +/* CLB major frames start at 3 for the first column (since we are using > + * column numbers that start at 1, when the column is added to this offset, > + * that first one will be 3 as required. */ > +#define XHI_CLB_MAJOR_FRAME_OFFSET 2 > + > +/* File access and error constants */ > +#define XHI_DEVICE_READ_ERROR -1 > +#define XHI_DEVICE_WRITE_ERROR -2 > +#define XHI_BUFFER_OVERFLOW_ERROR -3 > + > +#define XHI_DEVICE_READ 0x1 > +#define XHI_DEVICE_WRITE 0x0 > + > +/* Constants for checking transfer status */ > +#define XHI_CYCLE_DONE 0 > +#define XHI_CYCLE_EXECUTING 1 > + > +/* Constant to use for CRC check when CRC has been disabled */ > +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL > + > +/* Major Row Offset */ > +#define XHI_CLB_MAJOR_ROW_OFFSET 96+(32*XHI_HEADER_BUFFER_WORDS)-1 > + > +/* Number of times to poll the done regsiter */ > +#define XHI_MAX_RETRIES 1000 > + > +/************************** Constant Definitions ****************************/ > + > +/* XHwIcap register offsets */ > + > +/* Size of transfer, read & write */ > +#define XHI_SIZE_REG_OFFSET 0x800L > +/* Offset into bram, read & write */ > +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L > +/* Read not Configure, direction of transfer. Write only */ > +#define XHI_RNC_REG_OFFSET 0x808L > +/* Indicates transfer complete. Read only */ > +#define XHI_STATUS_REG_OFFSET 0x80CL > + > +/* Constants for setting the RNC register */ > +#define XHI_CONFIGURE 0x0UL > +#define XHI_READBACK 0x1UL > + > +/* Constants for the Done register */ > +#define XHI_NOT_FINISHED 0x0UL > +#define XHI_FINISHED 0x1UL > + > +/**************************** Type Definitions ******************************/ > + > +/***************** Macros (Inline Functions) Definitions ********************/ > + > +/****************************************************************************/ > +/** > +* > +* Get the contents of the size register. > +* > +* The size register holds the number of 32 bit words to transfer between > +* bram and the icap (or icap to bram). > +* > +* @param BaseAddress is the base address of the device > +* > +* @return A 32-bit value representing the contents of the size > +* register. > +* > +* @note > +* > +* u32 XHwIcap_mGetSizeReg(u32 BaseAddress); > +* > +*****************************************************************************/ > +#define XHwIcap_mGetSizeReg(BaseAddress) \ > + (in_be32((u32 *)((BaseAddress) + XHI_SIZE_REG_OFFSET))) > + > +/****************************************************************************/ Why not a single getter with a offset/register parameter instead of all these? And use inline functions instead of macros. > +/** > +* > +* Get the contents of the bram offset register. > +* > +* The bram offset register holds the starting bram address to transfer > +* data from during configuration or write data to during readback. > +* > +* @param BaseAddress is the base address of the device > +* > +* @return A 32-bit value representing the contents of the bram offset > +* register. > +* > +* @note > +* > +* u32 XHwIcap_mGetOffsetReg(u32 BaseAddress); > +* > +*****************************************************************************/ > +#define XHwIcap_mGetOffsetReg(BaseAddress) \ > + (in_be32((u32 *)((BaseAddress + XHI_BRAM_OFFSET_REG_OFFSET)))) > + > +/****************************************************************************/ > +/** > +* > +* Get the contents of the done register. > +* > +* The done register is set to zero during configuration or readback. > +* When the current configuration or readback completes the done register > +* is set to one. > +* > +* @param BaseAddress is the base address of the device > +* > +* @return A 32-bit value with bit 1 representing done or not > +* > +* @note > +* > +* u32 XHwIcap_mGetDoneReg(u32 BaseAddress); > +* > +*****************************************************************************/ > + > +#define XHwIcap_mGetDoneReg(BaseAddress) \ > + (in_be32((u32 *)((BaseAddress + XHI_STATUS_REG_OFFSET))) & 1) > + > +/****************************************************************************/ > +/** > +* > +* Get the contents of the status register. > +* > +* The status register contains the ICAP status and the done bit. > +* > +* D8 - cfgerr > +* D7 - dalign > +* D6 - rip > +* D5 - in_abort_l > +* D4 - Always 1 > +* D3 - Always 1 > +* D2 - Always 1 > +* D1 - Always 1 > +* D0 - Done bit > +* > +* @param BaseAddress is the base address of the device > +* > +* @return A 32-bit value representing the contents of the status register > +* > +* @note > +* > +* u32 XHwIcap_mGetStatusReg(u32 BaseAddress); > +* > +*****************************************************************************/ > + > +#define XHwIcap_mGetStatusReg(BaseAddress) \ > + (in_be32((u32 *)((BaseAddress + XHI_STATUS_REG_OFFSET)))) > + > +#define XHwIcap_mReset(BaseAddress) \ > + (out_be32((u32 *)((BaseAddress + XHI_STATUS_REG_OFFSET)), 0xFEFE)) > + > +/****************************************************************************/ > +/** > +* Reads data from the storage buffer bram. > +* > +* A bram is used as a configuration memory cache. One frame of data can > +* be stored in this "storage buffer". > +* > +* @param BaseAddress - contains the base address of the component. > +* > +* @param Offset - The offset into which the data should be read. > +* > +* @return The value of the specified offset in the bram. > +* > +* @note > +* > +* u32 XHwIcap_mGetBram(u32 BaseAddress, u32 Offset); > +* > +*****************************************************************************/ > +#define XHwIcap_mGetBram(BaseAddress, Offset) \ > + (in_be32((u32 *)((BaseAddress+(Offset<<2))))) > + > +/****************************************************************************/ > +/** > +* Set the size register. > +* > +* The size register holds the number of 8 bit bytes to transfer between > +* bram and the icap (or icap to bram). > +* > +* @param BaseAddress - contains the base address of the device. > +* > +* @param Data - The size in bytes. > +* > +* @return None. > +* > +* @note > +* > +* void XHwIcap_mSetSizeReg(u32 BaseAddress, u32 Data); > +* > +*****************************************************************************/ > +#define XHwIcap_mSetSizeReg(BaseAddress, Data) \ > + (out_be32((u32 *)((BaseAddress) + XHI_SIZE_REG_OFFSET), (Data))) > + > +/****************************************************************************/ > +/** > +* Set the bram offset register. > +* > +* The bram offset register holds the starting bram address to transfer > +* data from during configuration or write data to during readback. > +* > +* @param BaseAddress contains the base address of the device. > +* > +* @param Data is the value to be written to the data register. > +* > +* @return None. > +* > +* @note > +* > +* void XHwIcap_mSetOffsetReg(u32 BaseAddress, u32 Data); > +* > +*****************************************************************************/ > +#define XHwIcap_mSetOffsetReg(BaseAddress, Data) \ > + (out_be32((u32 *)((BaseAddress) + XHI_BRAM_OFFSET_REG_OFFSET), (Data))) > + > +/****************************************************************************/ > +/** > +* Set the RNC (Readback not Configure) register. > +* > +* The RNC register determines the direction of the data transfer. It > +* controls whether a configuration or readback take place. Writing to > +* this register initiates the transfer. A value of 1 initiates a > +* readback while writing a value of 0 initiates a configuration. > +* > +* @param BaseAddress contains the base address of the device. > +* > +* @param Data is the value to be written to the data register. > +* > +* @return None. > +* > +* @note > +* > +* void XHwIcap_mSetRncReg(u32 BaseAddress, u32 Data); > +* > +*****************************************************************************/ > +#define XHwIcap_mSetRncReg(BaseAddress, Data) \ > + (out_be32((u32 *)((BaseAddress) + XHI_RNC_REG_OFFSET), (Data))) > + > +/****************************************************************************/ > +/** > +* Write data to the storage buffer bram. > +* > +* A bram is used as a configuration memory cache. One frame of data can > +* be stored in this "storage buffer". > +* > +* @param BaseAddress - contains the base address of the component. > +* > +* @param Offset - The offset into which the data should be written. > +* > +* @param Data - The value to be written to the bram offset. > +* > +* @return None. > +* > +* @note > +* > +* void XHwIcap_mSetBram(u32 BaseAddress, u32 Offset, u32 Data); > +* > +*****************************************************************************/ > +#define XHwIcap_mSetBram(BaseAddress, Offset, Data) \ > + (out_be32((u32 *)((BaseAddress+(Offset<<2))), (Data))) > + > +/****************************************************************************/ > +/** > +* > +* Generates a Type 1 packet header that reads back the requested configuration > +* register. > +* > +* @param Register is the address of the register to be read back. > +* Register constants are defined in this file. > +* > +* @return Type 1 packet header to read the specified register > +* > +* @note None. > +* > +*****************************************************************************/ > +#define XHwIcap_Type1Read(Register) \ > + ((XHI_TYPE_1 << XHI_TYPE_SHIFT) | (Register << XHI_REGISTER_SHIFT) | \ > + (XHI_OP_READ << XHI_OP_SHIFT)) > + > +/****************************************************************************/ > +/** > +* > +* Generates a Type 1 packet header that writes to the requested > +* configuration register. > +* > +* @param Register is the address of the register to be written to. > +* Register constants are defined in this file. > +* > +* @return Type 1 packet header to write the specified register > +* > +* @note None. > +* > +*****************************************************************************/ > +#define XHwIcap_Type1Write(Register) \ > + ((XHI_TYPE_1 << XHI_TYPE_SHIFT) | (Register << XHI_REGISTER_SHIFT) | \ > + (XHI_OP_WRITE << XHI_OP_SHIFT)) > + > +/************************** Function Prototypes *****************************/ > + > +/* These functions are the ones defined in the lower level > + * Self-Reconfiguration Platform (SRP) API. > + */ > + > +/* Initializes a XHwIcap instance.. */ > +int XHwIcap_Initialize(struct xhwicap_drvdata *InstancePtr, u16 DeviceId, > + u32 DeviceIdCode); > + > +/* Reads integers from the device into the storage buffer. */ > +int XHwIcap_DeviceRead(struct xhwicap_drvdata *InstancePtr, u32 Offset, > + u32 NumInts); > + > +/* Writes integers to the device from the storage buffer. */ > +int XHwIcap_DeviceWrite(struct xhwicap_drvdata *InstancePtr, u32 Offset, > + u32 NumInts); > + > +/* Writes word to the storage buffer. */ > +void XHwIcap_StorageBufferWrite(struct xhwicap_drvdata *InstancePtr, > + u32 Address, u32 Data); > + > +/* Reads word from the storage buffer. */ > +u32 XHwIcap_StorageBufferRead(struct xhwicap_drvdata *InstancePtr, u32 Address); > + > +/* Loads a partial bitstream from system memory. */ > +int XHwIcap_SetConfiguration(struct xhwicap_drvdata *InstancePtr, u32 *Data, > + u32 Size); > + > +/* Loads a partial bitstream from system memory. */ > +int XHwIcap_GetConfiguration(struct xhwicap_drvdata *InstancePtr, u32 *Data, > + u32 Size); > + > +/* Sends a DESYNC command to the ICAP */ > +int XHwIcap_CommandDesync(struct xhwicap_drvdata *InstancePtr); > + > +/* Sends a CAPTURE command to the ICAP */ > +int XHwIcap_CommandCapture(struct xhwicap_drvdata *InstancePtr); > + > +/* Returns the value of the specified configuration register */ > +u32 XHwIcap_GetConfigReg(struct xhwicap_drvdata *InstancePtr, u32 ConfigReg); > + > +#endif > -- > 1.5.3.4-dirty > -- Bye, Peter Korsgaard ^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH] [POWERPC] Xilinx: hwicap driver 2007-12-05 12:20 [PATCH] [XILINX][HWICAP] Xilinx Internal Configuration Access Port device driver Peter Korsgaard @ 2008-01-31 18:47 ` Stephen Neuendorffer 2008-01-31 19:58 ` Grant Likely 0 siblings, 1 reply; 21+ messages in thread From: Stephen Neuendorffer @ 2008-01-31 18:47 UTC (permalink / raw) To: linuxppc-dev, grant.likely, jacmet This includes code for new fifo-based xps_hwicap in addition to the older opb_hwicap, which has a significantly different interface. The common code between the two drivers is largely shared. Significant differences exists between this driver and what is supported in the EDK drivers. In particular, most of the architecture-specific code for reconfiguring individual FPGA resources has been removed. This functionality is likely better provided in a user-space support library. In addition, read and write access is supported. In addition, although the xps_hwicap cores support interrupt-driver mode, this driver only supports polled operation, in order to make the code simpler, and since the interrupt processing overhead is likely to slow down the throughput under Linux. Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 1 + drivers/char/xilinx_hwicap/Makefile | 7 + drivers/char/xilinx_hwicap/buffer_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/buffer_icap.h | 57 ++ drivers/char/xilinx_hwicap/fifo_icap.c | 381 ++++++++++++ drivers/char/xilinx_hwicap/fifo_icap.h | 62 ++ drivers/char/xilinx_hwicap/xilinx_hwicap.c | 895 ++++++++++++++++++++++++++++ drivers/char/xilinx_hwicap/xilinx_hwicap.h | 193 ++++++ 9 files changed, 1984 insertions(+), 0 deletions(-) create mode 100644 drivers/char/xilinx_hwicap/Makefile create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.c create mode 100644 drivers/char/xilinx_hwicap/buffer_icap.h create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.c create mode 100644 drivers/char/xilinx_hwicap/fifo_icap.h create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.c create mode 100644 drivers/char/xilinx_hwicap/xilinx_hwicap.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ef1ed5d..157ae2a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -831,6 +831,13 @@ config DTLK To compile this driver as a module, choose M here: the module will be called dtlk. +config XILINX_HWICAP + tristate "Xilinx HWICAP Support" + depends on XILINX_VIRTEX + help + This option enables support for Xilinx Internal Configuration + Access Port (ICAP) driver. + config R3964 tristate "Siemens R3964 line discipline" ---help--- diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d5..3a278a0 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_EFI_RTC) += efirtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap/ ifeq ($(CONFIG_GENERIC_NVRAM),y) obj-$(CONFIG_NVRAM) += generic_nvram.o else diff --git a/drivers/char/xilinx_hwicap/Makefile b/drivers/char/xilinx_hwicap/Makefile new file mode 100644 index 0000000..5491cbc --- /dev/null +++ b/drivers/char/xilinx_hwicap/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Xilinx OPB hwicap driver +# + +obj-$(CONFIG_XILINX_HWICAP) += xilinx_hwicap_m.o + +xilinx_hwicap_m-y := xilinx_hwicap.o fifo_icap.o buffer_icap.o diff --git a/drivers/char/xilinx_hwicap/buffer_icap.c b/drivers/char/xilinx_hwicap/buffer_icap.c new file mode 100644 index 0000000..ccbf30e --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "buffer_icap.h" + +/* Indicates how many bytes will fit in a buffer. (1 BRAM) */ +#define XHI_MAX_BUFFER_BYTES 2048 +#define XHI_MAX_BUFFER_INTS (XHI_MAX_BUFFER_BYTES >> 2) + +/* File access and error constants */ +#define XHI_DEVICE_READ_ERROR -1 +#define XHI_DEVICE_WRITE_ERROR -2 +#define XHI_BUFFER_OVERFLOW_ERROR -3 + +#define XHI_DEVICE_READ 0x1 +#define XHI_DEVICE_WRITE 0x0 + +/* Constants for checking transfer status */ +#define XHI_CYCLE_DONE 0 +#define XHI_CYCLE_EXECUTING 1 + +/* buffer_icap register offsets */ + +/* Size of transfer, read & write */ +#define XHI_SIZE_REG_OFFSET 0x800L +/* offset into bram, read & write */ +#define XHI_BRAM_OFFSET_REG_OFFSET 0x804L +/* Read not Configure, direction of transfer. Write only */ +#define XHI_RNC_REG_OFFSET 0x808L +/* Indicates transfer complete. Read only */ +#define XHI_STATUS_REG_OFFSET 0x80CL + +/* Constants for setting the RNC register */ +#define XHI_CONFIGURE 0x0UL +#define XHI_READBACK 0x1UL + +/* Constants for the Done register */ +#define XHI_NOT_FINISHED 0x0UL +#define XHI_FINISHED 0x1UL + +#define XHI_BUFFER_START 0 + +/** + * buffer_icap_get_status: Get the contents of the status register. + * @parameter base_address: is the base address of the device + * + * The status register contains the ICAP status and the done bit. + * + * D8 - cfgerr + * D7 - dalign + * D6 - rip + * D5 - in_abort_l + * D4 - Always 1 + * D3 - Always 1 + * D2 - Always 1 + * D1 - Always 1 + * D0 - Done bit + **/ +static inline u32 buffer_icap_get_status(void __iomem *base_address) +{ + return in_be32(base_address + XHI_STATUS_REG_OFFSET); +} + +/** + * buffer_icap_get_bram: Reads data from the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset from which the data should be read. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline u32 buffer_icap_get_bram(void __iomem *base_address, + u32 offset) +{ + return in_be32(base_address + (offset << 2)); +} + +/** + * buffer_icap_busy: Return true if the icap device is busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_busy(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_NOT_FINISHED; +} + +/** + * buffer_icap_busy: Return true if the icap device is not busy + * @parameter base_address: is the base address of the device + * + * The queries the low order bit of the status register, which + * indicates whether the current configuration or readback operation + * has completed. + **/ +static inline bool buffer_icap_done(void __iomem *base_address) +{ + return (buffer_icap_get_status(base_address) & 1) == XHI_FINISHED; +} + +/** + * buffer_icap_set_size: Set the size register. + * @parameter base_address: is the base address of the device + * @parameter data: The size in bytes. + * + * The size register holds the number of 8 bit bytes to transfer between + * bram and the icap (or icap to bram). + **/ +static inline void buffer_icap_set_size(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_SIZE_REG_OFFSET, data); +} + +/** + * buffer_icap_mSetoffsetReg: Set the bram offset register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The bram offset register holds the starting bram address to transfer + * data from during configuration or write data to during readback. + **/ +static inline void buffer_icap_set_offset(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_BRAM_OFFSET_REG_OFFSET, data); +} + +/** + * buffer_icap_set_rnc: Set the RNC (Readback not Configure) register. + * @parameter base_address: contains the base address of the device. + * @parameter data: is the value to be written to the data register. + * + * The RNC register determines the direction of the data transfer. It + * controls whether a configuration or readback take place. Writing to + * this register initiates the transfer. A value of 1 initiates a + * readback while writing a value of 0 initiates a configuration. + **/ +static inline void buffer_icap_set_rnc(void __iomem *base_address, + u32 data) +{ + out_be32(base_address + XHI_RNC_REG_OFFSET, data); +} + +/** + * buffer_icap_set_bram: Write data to the storage buffer bram. + * @parameter base_address: contains the base address of the component. + * @parameter offset: The word offset at which the data should be written. + * @parameter data: The value to be written to the bram offset. + * + * A bram is used as a configuration memory cache. One frame of data can + * be stored in this "storage buffer". + **/ +static inline void buffer_icap_set_bram(void __iomem *base_address, + u32 offset, u32 data) +{ + out_be32(base_address + (offset << 2), data); +} + +/** + * buffer_icap_device_read: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) <= XHI_MAX_BUFFER_INTS) { + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, (count << 2)); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_READBACK); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + } else { + return -EINVAL; + } + return 0; + +}; + +/** + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. + * @parameter drvdata: a pointer to the drvdata. + * @parameter offset: The storage buffer start address. + * @parameter count: The number of words (32 bit) to read from the + * device (ICAP). + **/ +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, + u32 offset, u32 count) +{ + + s32 retries = 0; + void __iomem *base_address = drvdata->base_address; + + if (buffer_icap_busy(base_address)) + return -EBUSY; + + if ((offset + count) <= XHI_MAX_BUFFER_INTS) { + /* setSize count*4 to get bytes. */ + buffer_icap_set_size(base_address, count << 2); + buffer_icap_set_offset(base_address, offset); + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); + + while (buffer_icap_busy(base_address)) { + retries++; + if (retries > XHI_MAX_RETRIES) + return -EBUSY; + } + } else { + return -EINVAL; + } + return 0; + +}; + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * Writing to the status register resets the ICAP logic in an internal + * version of the core. For the version of the core published in EDK, + * this is a noop. + **/ +void buffer_icap_reset(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_STATUS_REG_OFFSET, 0xFEFE); +} + +/** + * buffer_icap_set_configuration: Load a partial bitstream from system memory. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Kernel address of the partial bitstream. + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 num_writes = 0; + bool dirty = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = 0; i < size; i++) { + + /* Copy data to bram */ + buffer_icap_set_bram(base_address, buffer_count, data[i]); + dirty = 1; + + if (buffer_count == XHI_MAX_BUFFER_INTS - 1) { + /* Write data to ICAP */ + status = buffer_icap_device_write( + drvdata, + XHI_BUFFER_START, + XHI_MAX_BUFFER_INTS); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + num_writes++; + dirty = 0; + } else { + buffer_count++; + } + } + + /* Write unwritten data to ICAP */ + if (dirty) { + /* Write data to ICAP */ + status = buffer_icap_device_write(drvdata, XHI_BUFFER_START, + buffer_count); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + } + return status; + } + + return 0; +}; + +/** + * buffer_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + **/ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 size) +{ + int status; + s32 buffer_count = 0; + s32 read_count = 0; + u32 i; + void __iomem *base_address = drvdata->base_address; + + /* Loop through all the data */ + for (i = 0, buffer_count = XHI_MAX_BUFFER_INTS; i < size; i++) { + if (buffer_count == XHI_MAX_BUFFER_INTS) { + u32 words_remaining = size - i; + u32 words_to_read = + words_remaining < + XHI_MAX_BUFFER_INTS ? words_remaining : + XHI_MAX_BUFFER_INTS; + + /* Read data from ICAP */ + status = buffer_icap_device_read( + drvdata, + XHI_BUFFER_START, + words_to_read); + if (status != 0) { + /* abort. */ + buffer_icap_reset(drvdata); + return status; + } + + buffer_count = 0; + read_count++; + } + + /* Copy data from bram */ + data[i] = buffer_icap_get_bram(base_address, buffer_count); + buffer_count++; + } + + return 0; +}; diff --git a/drivers/char/xilinx_hwicap/buffer_icap.h b/drivers/char/xilinx_hwicap/buffer_icap.h new file mode 100644 index 0000000..0318495 --- /dev/null +++ b/drivers/char/xilinx_hwicap/buffer_icap.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_BUFFER_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_BUFFER_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +void buffer_icap_reset(struct hwicap_drvdata *drvdata); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +/* Loads a partial bitstream from system memory. */ +int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data, + u32 Size); + +#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c new file mode 100644 index 0000000..0988314 --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -0,0 +1,381 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#include "fifo_icap.h" + +/* Register offsets for the XHwIcap device. */ +#define XHI_GIER_OFFSET 0x1C /* Device Global Interrupt Enable Reg */ +#define XHI_IPISR_OFFSET 0x20 /* Interrupt Status Register */ +#define XHI_IPIER_OFFSET 0x28 /* Interrupt Enable Register */ +#define XHI_WF_OFFSET 0x100 /* Write FIFO */ +#define XHI_RF_OFFSET 0x104 /* Read FIFO */ +#define XHI_SZ_OFFSET 0x108 /* Size Register */ +#define XHI_CR_OFFSET 0x10C /* Control Register */ +#define XHI_SR_OFFSET 0x110 /* Status Register */ +#define XHI_WFV_OFFSET 0x114 /* Write FIFO Vacancy Register */ +#define XHI_RFO_OFFSET 0x118 /* Read FIFO Occupancy Register */ + +/* Device Global Interrupt Enable Register (GIER) bit definitions */ + +#define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ + +/** + * HwIcap Device Interrupt Status/Enable Registers + * + * Interrupt Status Register (IPISR) : This register holds the + * interrupt status flags for the device. These bits are toggle on + * write. + * + * Interrupt Enable Register (IPIER) : This register is used to enable + * interrupt sources for the device. + * Writing a '1' to a bit enables the corresponding interrupt. + * Writing a '0' to a bit disables the corresponding interrupt. + * + * IPISR/IPIER registers have the same bit definitions and are only defined + * once. + */ +#define XHI_IPIXR_RFULL_MASK 0x00000008 /* Read FIFO Full */ +#define XHI_IPIXR_WEMPTY_MASK 0x00000004 /* Write FIFO Empty */ +#define XHI_IPIXR_RDP_MASK 0x00000002 /* Read FIFO half full */ +#define XHI_IPIXR_WRP_MASK 0x00000001 /* Write FIFO half full */ +#define XHI_IPIXR_ALL_MASK 0x0000000F /* Mask of all interrupts */ + +/* Control Register (CR) */ +#define XHI_CR_SW_RESET_MASK 0x00000008 /* SW Reset Mask */ +#define XHI_CR_FIFO_CLR_MASK 0x00000004 /* FIFO Clear Mask */ +#define XHI_CR_READ_MASK 0x00000002 /* Read from ICAP to FIFO */ +#define XHI_CR_WRITE_MASK 0x00000001 /* Write from FIFO to ICAP */ + +/* Status Register (SR) */ +#define XHI_SR_CFGERR_N_MASK 0x00000100 /* Config Error Mask */ +#define XHI_SR_DALIGN_MASK 0x00000080 /* Data Alignment Mask */ +#define XHI_SR_RIP_MASK 0x00000040 /* Read back Mask */ +#define XHI_SR_IN_ABORT_N_MASK 0x00000020 /* Select Map Abort Mask */ +#define XHI_SR_DONE_MASK 0x00000001 /* Done bit Mask */ + + +#define XHI_WFO_MAX_VACANCY 1024 /* Max Write FIFO Vacancy, in words */ +#define XHI_RFO_MAX_OCCUPANCY 256 /* Max Read FIFO Occupancy, in words */ +/* The maximum amount we can request from fifo_icap_get_configuration + at once, in bytes. */ +#define XHI_MAX_READ_TRANSACTION_WORDS 0xFFF + + +/** + * fifo_icap_fifo_write: Write data to the write FIFO. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the 32-bit value to be written to the FIFO. + * + * This function will silently fail if the fifo is full. + **/ +static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, + u32 data) +{ + dev_dbg(drvdata->dev, "fifo_write: %x\n", data); + out_be32(drvdata->base_address + XHI_WF_OFFSET, data); +} + +/** + * fifo_icap_fifo_read: Read data from the Read FIFO. + * @parameter drvdata: a pointer to the drvdata. + * + * This function will silently fail if the fifo is empty. + **/ +static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) +{ + u32 data = in_be32(drvdata->base_address + XHI_RF_OFFSET); + dev_dbg(drvdata->dev, "fifo_read: %x\n", data); + return data; +} + +/** + * fifo_icap_set_read_size: Set the the size register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: the size of the following read transaction, in words. + **/ +static inline void fifo_icap_set_read_size(struct hwicap_drvdata *drvdata, + u32 data) +{ + out_be32(drvdata->base_address + XHI_SZ_OFFSET, data); +} + +/** + * fifo_icap_start_config: Initiate a configuration (write) to the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_config(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_WRITE_MASK); + dev_dbg(drvdata->dev, "configuration started\n"); +} + +/** + * fifo_icap_start_readback: Initiate a readback from the device. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) +{ + out_be32(drvdata->base_address + XHI_CR_OFFSET, XHI_CR_READ_MASK); + dev_dbg(drvdata->dev, "readback started\n"); +} + +/** + * fifo_icap_busy: Return true if the ICAP is still processing a transaction. + * @parameter drvdata: a pointer to the drvdata. + **/ +static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) +{ + u32 status = in_be32(drvdata->base_address + XHI_SR_OFFSET); + dev_dbg(drvdata->dev, "Getting status = %x\n", status); + return (status & XHI_SR_DONE_MASK) ? 0 : 1; +} + +/** + * fifo_icap_write_fifo_vacancy: Query the write fifo available space. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely pushed into the write fifo. + **/ +static inline u32 fifo_icap_write_fifo_vacancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_WFV_OFFSET); +} + +/** + * fifo_icap_read_fifo_occupancy: Query the read fifo available data. + * @parameter drvdata: a pointer to the drvdata. + * + * Return the number of words that can be safely read from the read fifo. + **/ +static inline u32 fifo_icap_read_fifo_occupancy( + struct hwicap_drvdata *drvdata) +{ + return in_be32(drvdata->base_address + XHI_RFO_OFFSET); +} + +/** + * fifo_icap_set_configuration: Send configuration data to the ICAP. + * @parameter drvdata: a pointer to the drvdata. + * @parameter frame_buffer: a pointer to the data to be written to the + * ICAP device. + * @parameter num_words: the number of words (32 bit) to write to the ICAP + * device. + + * This function writes the given user data to the Write FIFO in + * polled mode and starts the transfer of the data to + * the ICAP device. + **/ +int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 write_fifo_vacancy = 0; + u32 retries = 0; + u32 remaining_words; + + dev_dbg(drvdata->dev, "fifo_set_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Read/Write + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + /* + * Set up the buffer pointer and the words to be transferred. + */ + remaining_words = num_words; + + while (remaining_words > 0) { + /* + * Wait until we have some data in the fifo. + */ + while (write_fifo_vacancy == 0) { + write_fifo_vacancy = + fifo_icap_write_fifo_vacancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + /* + * Write data into the Write FIFO. + */ + while ((write_fifo_vacancy != 0) && + (remaining_words > 0)) { + fifo_icap_fifo_write(drvdata, *frame_buffer); + + remaining_words--; + write_fifo_vacancy--; + frame_buffer++; + } + /* Start pushing whatever is in the FIFO into the ICAP. */ + fifo_icap_start_config(drvdata); + } + + /* Wait until the write has finished. */ + while (fifo_icap_busy(drvdata)) { + retries++; + if (retries > XHI_MAX_RETRIES) + break; + } + + dev_dbg(drvdata->dev, "done fifo_set_configuration\n"); + + /* + * If the requested number of words have not been read from + * the device then indicate failure. + */ + if (remaining_words != 0) + return -EIO; + + return 0; +} + +/** + * fifo_icap_get_configuration: Read configuration data from the device. + * @parameter drvdata: a pointer to the drvdata. + * @parameter data: Address of the data representing the partial bitstream + * @parameter size: the size of the partial bitstream in 32 bit words. + * + * This function reads the specified number of words from the ICAP device in + * the polled mode. + */ +int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, + u32 *frame_buffer, u32 num_words) +{ + + u32 read_fifo_occupancy = 0; + u32 retries = 0; + u32 *data = frame_buffer; + u32 remaining_words; + u32 words_to_read; + + dev_dbg(drvdata->dev, "fifo_get_configuration\n"); + + /* + * Check if the ICAP device is Busy with the last Write/Read + */ + if (fifo_icap_busy(drvdata)) + return -EBUSY; + + remaining_words = num_words; + + while (remaining_words > 0) { + words_to_read = remaining_words; + /* The hardware has a limit on the number of words + that can be read at one time. */ + if (words_to_read > XHI_MAX_READ_TRANSACTION_WORDS) + words_to_read = XHI_MAX_READ_TRANSACTION_WORDS; + + remaining_words -= words_to_read; + + fifo_icap_set_read_size(drvdata, words_to_read); + fifo_icap_start_readback(drvdata); + + while (words_to_read > 0) { + /* Wait until we have some data in the fifo. */ + while (read_fifo_occupancy == 0) { + read_fifo_occupancy = + fifo_icap_read_fifo_occupancy(drvdata); + retries++; + if (retries > XHI_MAX_RETRIES) + return -EIO; + } + + if (read_fifo_occupancy > words_to_read) + read_fifo_occupancy = words_to_read; + + words_to_read -= read_fifo_occupancy; + + /* Read the data from the Read FIFO. */ + while (read_fifo_occupancy != 0) { + *data++ = fifo_icap_fifo_read(drvdata); + read_fifo_occupancy--; + } + } + } + + dev_dbg(drvdata->dev, "done fifo_get_configuration\n"); + + return 0; +} + +/** + * buffer_icap_reset: Reset the logic of the icap device. + * @parameter drvdata: a pointer to the drvdata. + * + * This function forces the software reset of the complete HWICAP device. + * All the registers will return to the default value and the FIFO is also + * flushed as a part of this software reset. + */ +void fifo_icap_reset(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Reset the device by setting/clearing the RESET bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_SW_RESET_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_SW_RESET_MASK)); + +} + +/** + * fifo_icap_flush_fifo: This function flushes the FIFOs in the device. + * @parameter drvdata: a pointer to the drvdata. + */ +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata) +{ + u32 reg_data; + /* + * Flush the FIFO by setting/clearing the FIFO Clear bit in the + * Control Register. + */ + reg_data = in_be32(drvdata->base_address + XHI_CR_OFFSET); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data | XHI_CR_FIFO_CLR_MASK); + + out_be32(drvdata->base_address + XHI_CR_OFFSET, + reg_data & (~XHI_CR_FIFO_CLR_MASK)); +} + diff --git a/drivers/char/xilinx_hwicap/fifo_icap.h b/drivers/char/xilinx_hwicap/fifo_icap.h new file mode 100644 index 0000000..4d3068d --- /dev/null +++ b/drivers/char/xilinx_hwicap/fifo_icap.h @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_FIFO_ICAP_H_ /* prevent circular inclusions */ +#define XILINX_FIFO_ICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include "xilinx_hwicap.h" + +/* Reads integers from the device into the storage buffer. */ +int fifo_icap_get_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +/* Writes integers to the device from the storage buffer. */ +int fifo_icap_set_configuration( + struct hwicap_drvdata *drvdata, + u32 *FrameBuffer, + u32 NumWords); + +void fifo_icap_reset(struct hwicap_drvdata *drvdata); +void fifo_icap_flush_fifo(struct hwicap_drvdata *drvdata); + +#endif diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c new file mode 100644 index 0000000..b351d02 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -0,0 +1,895 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2002 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2004 Xilinx Inc., Systems Engineering Group + * (c) Copyright 2007-2008 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/* + * This is the code behind /dev/xilinx_icap -- it allows a user-space + * application to use the Xilinx ICAP subsystem. + * + * The following operations are possible: + * + * open open the port and initialize for access. + * release release port + * write Write a bitstream to the configuration processor. + * read Read a data stream from the configuration processor. + * + * After being opened, the port is initialized and accessed to avoid a + * corrupted first read which may occur with some hardware. The port + * is left in a desynched state, requiring that a synch sequence be + * transmitted before any valid configuration data. A user will have + * exclusive access to the device while it remains open, and the state + * of the ICAP cannot be guaranteed after the device is closed. Note + * that a complete reset of the core and the state of the ICAP cannot + * be performed on many versions of the cores, hence users of this + * device should avoid making inconsistent accesses to the device. In + * particular, accessing the read interface, without first generating + * a write containing a readback packet can leave the ICAP in an + * inaccessible state. + * + * Note that in order to use the read interface, it is first necessary + * to write a request packet to the write interface. i.e., it is not + * possible to simply readback the bitstream (or any configuration + * bits) from a device without specifically requesting them first. + * The code to craft such packets is intended to be part of the + * user-space application code that uses this device. The simplest + * way to use this interface is simply: + * + * cp foo.bit /dev/xilinx_icap + * + * Note that unless foo.bit is an appropriately constructed partial + * bitstream, this has a high likelyhood of overwriting the design + * currently programmed in the FPGA. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/proc_fs.h> +#include <linux/spinlock.h> +#include <linux/sysctl.h> +#include <linux/version.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#ifdef CONFIG_OF +/* For open firmware. */ +#include <linux/of_device.h> +#include <linux/of_platform.h> +#endif + +#include "xilinx_hwicap.h" +#include "buffer_icap.h" +#include "fifo_icap.h" + +#define DRIVER_NAME "xilinx_icap" + +#define HWICAP_REGS (0x10000) + +/* dynamically allocate device number */ +static int xhwicap_major; +static int xhwicap_minor; +#define HWICAP_DEVICES 1 + +module_param(xhwicap_major, int, S_IRUGO); +module_param(xhwicap_minor, int, S_IRUGO); + +/* An array, which is set to true when the device is registered. */ +static bool probed_devices[HWICAP_DEVICES]; + +static struct class *icap_class; + +#define UNIMPLEMENTED 0xFFFF + +static const struct config_registers v2_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = 11, + .KEY = 12, + .CBC = 13, + .IDCODE = 14, + .AXSS = UNIMPLEMENTED, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; + +static const struct config_registers v4_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = UNIMPLEMENTED, + .CSOB = UNIMPLEMENTED, + .WBSTAR = UNIMPLEMENTED, + .TIMER = UNIMPLEMENTED, + .BOOTSTS = UNIMPLEMENTED, + .CTL_1 = UNIMPLEMENTED, +}; +static const struct config_registers v5_config_registers = { + .CRC = 0, + .FAR = 1, + .FDRI = 2, + .FDRO = 3, + .CMD = 4, + .CTL = 5, + .MASK = 6, + .STAT = 7, + .LOUT = 8, + .COR = 9, + .MFWR = 10, + .FLR = UNIMPLEMENTED, + .KEY = UNIMPLEMENTED, + .CBC = 11, + .IDCODE = 12, + .AXSS = 13, + .C0R_1 = 14, + .CSOB = 15, + .WBSTAR = 16, + .TIMER = 17, + .BOOTSTS = 18, + .CTL_1 = 19, +}; + +/** + * hwicap_command_desync: Send a DESYNC command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command desynchronizes the ICAP After this command, a + * bitstream containing a NULL packet, followed by a SYNCH packet is + * required before the ICAP will recognize commands. + */ +int hwicap_command_desync(struct hwicap_drvdata *drvdata) +{ + u32 buffer[4]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_DESYNCH; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); +} + +/** + * hwicap_command_capture: Send a CAPTURE command to the ICAP port. + * @parameter drvdata: a pointer to the drvdata. + * + * This command captures all of the flip flop states so they will be + * available during readback. One can use this command instead of + * enabling the CAPTURE block in the design. + */ +int hwicap_command_capture(struct hwicap_drvdata *drvdata) +{ + u32 buffer[7]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_write(drvdata->config_regs->CMD) | 1; + buffer[index++] = XHI_CMD_GCAPTURE; + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_DUMMY_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data + * present in the FIFO to the ICAP device. + */ + return drvdata->config->set_configuration(drvdata, + &buffer[0], index); + +} + +/** + * hwicap_get_configuration_register: Query a configuration register. + * @parameter drvdata: a pointer to the drvdata. + * @parameter reg: a constant which represents the configuration + * register value to be returned. + * Examples: XHI_IDCODE, XHI_FLR. + * @parameter RegData: returns the value of the register. + * + * Sends a query packet to the ICAP and then receives the response. + * The icap is left in Synched state. + */ +int hwicap_get_configuration_register(struct hwicap_drvdata *drvdata, + u32 reg, u32 *RegData) +{ + int status; + u32 buffer[6]; + u32 index = 0; + + /* + * Create the data to be written to the ICAP. + */ + buffer[index++] = XHI_DUMMY_PACKET; + buffer[index++] = XHI_SYNC_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = hwicap_type_1_read(reg) | 1; + buffer[index++] = XHI_NOOP_PACKET; + buffer[index++] = XHI_NOOP_PACKET; + + /* + * Write the data to the FIFO and intiate the transfer of data present + * in the FIFO to the ICAP device. + */ + status = drvdata->config->set_configuration(drvdata, + &buffer[0], index); + if (status) + return status; + + /* + * Read the configuration register + */ + status = drvdata->config->get_configuration(drvdata, RegData, 1); + if (status) + return status; + + return 0; +} + +int hwicap_initialize_hwicap(struct hwicap_drvdata *drvdata) +{ + int status; + u32 idcode; + + dev_dbg(drvdata->dev, "initializing\n"); + + /* Abort any current transaction, to make sure we have the + * ICAP in a good state. */ + dev_dbg(drvdata->dev, "Reset...\n"); + drvdata->config->reset(drvdata); + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + /* Attempt to read the IDCODE from ICAP. This + * may not be returned correctly, due to the design of the + * hardware. + */ + dev_dbg(drvdata->dev, "Reading IDCODE...\n"); + status = hwicap_get_configuration_register( + drvdata, drvdata->config_regs->IDCODE, &idcode); + dev_dbg(drvdata->dev, "IDCODE = %x\n", idcode); + if (status) + return status; + + dev_dbg(drvdata->dev, "Desync...\n"); + status = hwicap_command_desync(drvdata); + if (status) + return status; + + return 0; +} + +static ssize_t +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t bytes_to_read = 0; + u32 *kbuf; + u32 words; + u32 bytes_remaining; + int status; + + if (drvdata->is_accessing) + return -EBUSY; + + drvdata->is_accessing = 1; + + if (drvdata->read_buffer_in_use) { + /* If there are leftover bytes in the buffer, just */ + /* return them and don't try to read more from the */ + /* ICAP device. */ + bytes_to_read = + (count < drvdata->read_buffer_in_use) ? count : + drvdata->read_buffer_in_use; + + /* Return the data currently in the read buffer. */ + if (copy_to_user(buf, drvdata->read_buffer, bytes_to_read)) { + status = -EFAULT; + goto error; + } + drvdata->read_buffer_in_use -= bytes_to_read; + memcpy(drvdata->read_buffer + bytes_to_read, + drvdata->read_buffer, 4 - bytes_to_read); + } else { + /* Get new data from the ICAP, and return was was requested. */ + kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + /* The ICAP device is only able to read complete */ + /* words. If a number of bytes that do not correspond */ + /* to complete words is requested, then we read enough */ + /* words to get the required number of bytes, and then */ + /* save the remaining bytes for the next read. */ + + /* Determine the number of words to read, rounding up */ + /* if necessary. */ + words = ((count + 3) >> 2); + bytes_to_read = words << 2; + + if (bytes_to_read > PAGE_SIZE) + bytes_to_read = PAGE_SIZE; + + /* Ensure we only read a complete number of words. */ + bytes_remaining = bytes_to_read & 3; + bytes_to_read &= ~3; + words = bytes_to_read >> 2; + + status = drvdata->config->get_configuration(drvdata, + kbuf, words); + + /* If we didn't read correctly, then bail out. */ + if (status) { + free_page((unsigned long)kbuf); + goto error; + } + + /* If we fail to return the data to the user, then bail out. */ + if (copy_to_user(buf, kbuf, bytes_to_read)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + memcpy(kbuf, drvdata->read_buffer, bytes_remaining); + drvdata->read_buffer_in_use = bytes_remaining; + free_page((unsigned long)kbuf); + } + status = bytes_to_read; + error: + drvdata->is_accessing = 0; + return status; +} + +static ssize_t +hwicap_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct hwicap_drvdata *drvdata = file->private_data; + ssize_t written = 0; + ssize_t left = count; + u32 *kbuf; + ssize_t len; + ssize_t status; + + if (drvdata->is_accessing) + return -EBUSY; + + drvdata->is_accessing = 1; + + left += drvdata->write_buffer_in_use; + + /* Only write multiples of 4 bytes. */ + if (left < 4) { + status = 0; + goto error; + } + + kbuf = (u32 *) __get_free_page(GFP_KERNEL); + if (!kbuf) { + status = -ENOMEM; + goto error; + } + + while (left > 3) { + /* only write multiples of 4 bytes, so there might */ + /* be as many as 3 bytes left (at the end). */ + len = left; + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + len &= ~3; + + if (drvdata->write_buffer_in_use) { + memcpy(kbuf, drvdata->write_buffer, + drvdata->write_buffer_in_use); + if (copy_from_user( + (((char *)kbuf) + (drvdata->write_buffer_in_use)), + buf + written, + len - (drvdata->write_buffer_in_use))) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } else { + if (copy_from_user(kbuf, buf + written, len)) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + } + + status = drvdata->config->set_configuration(drvdata, + kbuf, len >> 2); + + if (status) { + free_page((unsigned long)kbuf); + status = -EFAULT; + goto error; + } + if (drvdata->write_buffer_in_use) { + len -= drvdata->write_buffer_in_use; + left -= drvdata->write_buffer_in_use; + drvdata->write_buffer_in_use = 0; + } + written += len; + left -= len; + } + if ((left > 0) && (left < 4)) { + if (!copy_from_user(drvdata->write_buffer, + buf + written, left)) { + drvdata->write_buffer_in_use = left; + written += left; + left = 0; + } + } + + free_page((unsigned long)kbuf); + status = written; + error: + drvdata->is_accessing = 0; + return status; +} + +static int hwicap_open(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata; + int status; + + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); + if (drvdata->is_open) + return -EBUSY; + + drvdata->is_open = 1; + drvdata->is_accessing = 0; + + status = hwicap_initialize_hwicap(drvdata); + if (status) { + dev_err(drvdata->dev, "Failed to open file"); + return status; + } + + file->private_data = drvdata; + drvdata->write_buffer_in_use = 0; + drvdata->read_buffer_in_use = 0; + + return 0; +} + +static int hwicap_release(struct inode *inode, struct file *file) +{ + struct hwicap_drvdata *drvdata = file->private_data; + int i; + int status = 0; + + if (drvdata->write_buffer_in_use) { + /* Flush write buffer. */ + for (i = drvdata->write_buffer_in_use; i < 4; i++) + drvdata->write_buffer[i] = 0; + + status = drvdata->config->set_configuration(drvdata, + (u32 *) drvdata->write_buffer, 1); + if (status) + goto error; + } + + status = hwicap_command_desync(drvdata); + if (status) + goto error; + + error: + drvdata->is_open = 0; + return status; +} + +static struct file_operations hwicap_fops = { + .owner = THIS_MODULE, + .write = hwicap_write, + .read = hwicap_read, + .open = hwicap_open, + .release = hwicap_release, +}; + +static int __devinit hwicap_setup(struct device *dev, int id, + const struct resource *regs_res, + const struct hwicap_driver_config *config, + const struct config_registers *config_regs) +{ + dev_t devt; + struct hwicap_drvdata *drvdata = NULL; + int retval = 0; + + dev_info(dev, "Xilinx icap port driver\n"); + + if (id < 0) { + for (id = 0; id < HWICAP_DEVICES; id++) + if (!probed_devices[id]) + break; + } + if (id < 0 || id >= HWICAP_DEVICES) { + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); + return -EINVAL; + } + if (probed_devices[id]) { + dev_err(dev, "cannot assign to %s%i; it is already in use\n", + DRIVER_NAME, id); + return -EBUSY; + } + + probed_devices[id] = 1; + + devt = MKDEV(xhwicap_major, xhwicap_minor + id); + + drvdata = kmalloc(sizeof(struct hwicap_drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(dev, "Couldn't allocate device private record\n"); + return -ENOMEM; + } + memset((void *)drvdata, 0, sizeof(struct hwicap_drvdata)); + dev_set_drvdata(dev, (void *)drvdata); + + if (!regs_res) { + dev_err(dev, "Couldn't get registers resource\n"); + retval = -EFAULT; + goto failed1; + } + + drvdata->mem_start = regs_res->start; + drvdata->mem_end = regs_res->end; + drvdata->mem_size = regs_res->end - regs_res->start + 1; + + if (!request_mem_region(drvdata->mem_start, + drvdata->mem_size, DRIVER_NAME)) { + dev_err(dev, "Couldn't lock memory region at %p\n", + (void *)regs_res->start); + retval = -EBUSY; + goto failed1; + } + + drvdata->devt = devt; + drvdata->dev = dev; + drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); + if (!drvdata->base_address) { + dev_err(dev, "ioremap() failed\n"); + goto failed2; + } + + drvdata->config = config; + drvdata->config_regs = config_regs; + drvdata->is_open = 0; + + dev_info(dev, "ioremap %lx to %p with size %x\n", + (unsigned long int)drvdata->mem_start, + drvdata->base_address, drvdata->mem_size); + + cdev_init(&drvdata->cdev, &hwicap_fops); + drvdata->cdev.owner = THIS_MODULE; + retval = cdev_add(&drvdata->cdev, devt, 1); + if (retval) { + dev_err(dev, "cdev_add() failed\n"); + goto failed3; + } + /* devfs_mk_cdev(devt, S_IFCHR|S_IRUGO|S_IWUGO, DRIVER_NAME); */ + class_device_create(icap_class, NULL, devt, NULL, DRIVER_NAME); + return 0; /* success */ + + failed3: + iounmap(drvdata->base_address); + + failed2: + release_mem_region(regs_res->start, drvdata->mem_size); + + failed1: + kfree(drvdata); + + return retval; +} + +static struct hwicap_driver_config buffer_icap_config = { + .get_configuration = buffer_icap_get_configuration, + .set_configuration = buffer_icap_set_configuration, + .reset = buffer_icap_reset, +}; + +static struct hwicap_driver_config fifo_icap_config = { + .get_configuration = fifo_icap_get_configuration, + .set_configuration = fifo_icap_set_configuration, + .reset = fifo_icap_reset, +}; + +static int __devexit hwicap_remove(struct device *dev) +{ + struct hwicap_drvdata *drvdata; + + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); + + if (drvdata) { + class_device_destroy(icap_class, drvdata->devt); + cdev_del(&drvdata->cdev); + iounmap(drvdata->base_address); + release_mem_region(drvdata->mem_start, drvdata->mem_size); + kfree(drvdata); + dev_set_drvdata(dev, NULL); + probed_devices[MINOR(dev->devt)-xhwicap_minor] = 0; + } + + return 0; /* success */ +} + +static int __devinit hwicap_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct config_registers *regs; + const char *family; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = pdev->dev.platform_data; + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + + return hwicap_setup(&pdev->dev, pdev->id, res, + &buffer_icap_config, regs); +} + +static int __devexit hwicap_drv_remove(struct platform_device *pdev) +{ + return hwicap_remove(&pdev->dev); +} + +static struct platform_driver hwicap_platform_driver = { + .probe = hwicap_drv_probe, + .remove = hwicap_drv_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +/* --------------------------------------------------------------------- + * OF bus binding + */ + +#if defined(CONFIG_OF) +static int __devinit +hwicap_of_probe(struct of_device *op, const struct of_device_id *match) +{ + struct resource res; + const unsigned int *id; + const char *family; + int rc; + const struct hwicap_driver_config *config = match->data; + const struct config_registers *regs; + + dev_dbg(&op->dev, "hwicap_of_probe(%p, %p)\n", op, match); + + rc = of_address_to_resource(op->node, 0, &res); + if (rc) { + dev_err(&op->dev, "invalid address\n"); + return rc; + } + + id = of_get_property(op->node, "port-number", NULL); + + /* It's most likely that we're using V4, if the family is not + specified */ + regs = &v4_config_registers; + family = of_get_property(op->node, "xlnx,family", NULL); + + if (family) { + if (!strcmp(family, "virtex2p")) { + regs = &v2_config_registers; + } else if (!strcmp(family, "virtex4")) { + regs = &v4_config_registers; + } else if (!strcmp(family, "virtex5")) { + regs = &v5_config_registers; + } + } + return hwicap_setup(&op->dev, id ? *id : -1, &res, config, + regs); +} + +static int __devexit hwicap_of_remove(struct of_device *op) +{ + return hwicap_remove(&op->dev); +} + +/* Match table for of_platform binding */ +static const struct of_device_id __devinit hwicap_of_match[] = { + { .compatible = "xlnx,opb-hwicap-1.00.b", .data = &buffer_icap_config}, + { .compatible = "xlnx,xps-hwicap-1.00.a", .data = &fifo_icap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, hwicap_of_match); + +static struct of_platform_driver hwicap_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = hwicap_of_match, + .probe = hwicap_of_probe, + .remove = __devexit_p(hwicap_of_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +/* Registration helpers to keep the number of #ifdefs to a minimum */ +static inline int __devinit hwicap_of_register(void) +{ + pr_debug("hwicap: calling of_register_platform_driver()\n"); + return of_register_platform_driver(&hwicap_of_driver); +} + +static inline void __devexit hwicap_of_unregister(void) +{ + of_unregister_platform_driver(&hwicap_of_driver); +} +#else /* CONFIG_OF */ +/* CONFIG_OF not enabled; do nothing helpers */ +static inline int __devinit hwicap_of_register(void) { return 0; } +static inline void __devexit hwicap_of_unregister(void) { } +#endif /* CONFIG_OF */ + +static int __devinit hwicap_module_init(void) +{ + dev_t devt; + int retval; + + icap_class = class_create(THIS_MODULE, "xilinx_config"); + + if (xhwicap_major) { + devt = MKDEV(xhwicap_major, xhwicap_minor); + retval = register_chrdev_region( + devt, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + } else { + retval = alloc_chrdev_region(&devt, + xhwicap_minor, + HWICAP_DEVICES, + DRIVER_NAME); + if (retval < 0) + return retval; + xhwicap_major = MAJOR(devt); + } + + retval = platform_driver_register(&hwicap_platform_driver); + + if (retval) + goto failed1; + + retval = hwicap_of_register(); + + if (retval) + goto failed2; + + return retval; + + failed2: + platform_driver_unregister(&hwicap_platform_driver); + + failed1: + unregister_chrdev_region(devt, HWICAP_DEVICES); + + return retval; +} + +static void __devexit hwicap_module_cleanup(void) +{ + dev_t devt = MKDEV(xhwicap_major, xhwicap_minor); + + class_destroy(icap_class); + + platform_driver_unregister(&hwicap_platform_driver); + + hwicap_of_unregister(); + + unregister_chrdev_region(devt, HWICAP_DEVICES); +} + +module_init(hwicap_module_init); +module_exit(hwicap_module_cleanup); + +MODULE_AUTHOR("Xilinx, Inc; Xilinx Research Labs Group"); +MODULE_DESCRIPTION("Xilinx ICAP Port Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.h b/drivers/char/xilinx_hwicap/xilinx_hwicap.h new file mode 100644 index 0000000..b6b47d0 --- /dev/null +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * + * Author: Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" + * AS A COURTESY TO YOU, SOLELY FOR USE IN DEVELOPING PROGRAMS AND + * SOLUTIONS FOR XILINX DEVICES. BY PROVIDING THIS DESIGN, CODE, + * OR INFORMATION AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, + * APPLICATION OR STANDARD, XILINX IS MAKING NO REPRESENTATION + * THAT THIS IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT, + * AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE + * FOR YOUR IMPLEMENTATION. XILINX EXPRESSLY DISCLAIMS ANY + * WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE + * IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR + * REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF + * INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE. + * + * Xilinx products are not intended for use in life support appliances, + * devices, or systems. Use in such applications is expressly prohibited. + * + * (c) Copyright 2003-2007 Xilinx Inc. + * All rights reserved. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +#ifndef XILINX_HWICAP_H_ /* prevent circular inclusions */ +#define XILINX_HWICAP_H_ /* by using protection macros */ + +#include <linux/types.h> +#include <linux/cdev.h> +#include <linux/version.h> +#include <linux/platform_device.h> + +#include <asm/io.h> + +struct hwicap_drvdata { + u32 write_buffer_in_use; /* Always in [0,3] */ + u8 write_buffer[4]; + u32 read_buffer_in_use; /* Always in [0,3] */ + u8 read_buffer[4]; + u32 mem_start; /* phys. address of the control registers */ + u32 mem_end; /* phys. address of the control registers */ + u32 mem_size; + void __iomem *base_address;/* virt. address of the control registers */ + + struct device *dev; + struct cdev cdev; /* Char device structure */ + dev_t devt; + + const struct hwicap_driver_config *config; + const struct config_registers *config_regs; + void *private_data; + bool is_open; + bool is_accessing; +}; + +struct hwicap_driver_config { + int (*get_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + int (*set_configuration)(struct hwicap_drvdata *drvdata, u32 *data, + u32 size); + void (*reset)(struct hwicap_drvdata *drvdata); +}; + +/* Number of times to poll the done regsiter */ +#define XHI_MAX_RETRIES 10 + +/************ Constant Definitions *************/ + +#define XHI_PAD_FRAMES 0x1 + +/* Mask for calculating configuration packet headers */ +#define XHI_WORD_COUNT_MASK_TYPE_1 0x7FFUL +#define XHI_WORD_COUNT_MASK_TYPE_2 0x1FFFFFUL +#define XHI_TYPE_MASK 0x7 +#define XHI_REGISTER_MASK 0xF +#define XHI_OP_MASK 0x3 + +#define XHI_TYPE_SHIFT 29 +#define XHI_REGISTER_SHIFT 13 +#define XHI_OP_SHIFT 27 + +#define XHI_TYPE_1 1 +#define XHI_TYPE_2 2 +#define XHI_OP_WRITE 2 +#define XHI_OP_READ 1 + +/* Address Block Types */ +#define XHI_FAR_CLB_BLOCK 0 +#define XHI_FAR_BRAM_BLOCK 1 +#define XHI_FAR_BRAM_INT_BLOCK 2 + +struct config_registers { + u32 CRC; + u32 FAR; + u32 FDRI; + u32 FDRO; + u32 CMD; + u32 CTL; + u32 MASK; + u32 STAT; + u32 LOUT; + u32 COR; + u32 MFWR; + u32 FLR; + u32 KEY; + u32 CBC; + u32 IDCODE; + u32 AXSS; + u32 C0R_1; + u32 CSOB; + u32 WBSTAR; + u32 TIMER; + u32 BOOTSTS; + u32 CTL_1; +}; + +/* Configuration Commands */ +#define XHI_CMD_NULL 0 +#define XHI_CMD_WCFG 1 +#define XHI_CMD_MFW 2 +#define XHI_CMD_DGHIGH 3 +#define XHI_CMD_RCFG 4 +#define XHI_CMD_START 5 +#define XHI_CMD_RCAP 6 +#define XHI_CMD_RCRC 7 +#define XHI_CMD_AGHIGH 8 +#define XHI_CMD_SWITCH 9 +#define XHI_CMD_GRESTORE 10 +#define XHI_CMD_SHUTDOWN 11 +#define XHI_CMD_GCAPTURE 12 +#define XHI_CMD_DESYNCH 13 +#define XHI_CMD_IPROG 15 /* Only in Virtex5 */ +#define XHI_CMD_CRCC 16 /* Only in Virtex5 */ +#define XHI_CMD_LTIMER 17 /* Only in Virtex5 */ + +/* Packet constants */ +#define XHI_SYNC_PACKET 0xAA995566UL +#define XHI_DUMMY_PACKET 0xFFFFFFFFUL +#define XHI_NOOP_PACKET (XHI_TYPE_1 << XHI_TYPE_SHIFT) +#define XHI_TYPE_2_READ ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_READ << XHI_OP_SHIFT)) + +#define XHI_TYPE_2_WRITE ((XHI_TYPE_2 << XHI_TYPE_SHIFT) | \ + (XHI_OP_WRITE << XHI_OP_SHIFT)) + +#define XHI_TYPE2_CNT_MASK 0x07FFFFFF + +#define XHI_TYPE_1_PACKET_MAX_WORDS 2047UL +#define XHI_TYPE_1_HEADER_BYTES 4 +#define XHI_TYPE_2_HEADER_BYTES 8 + +/* Constant to use for CRC check when CRC has been disabled */ +#define XHI_DISABLED_AUTO_CRC 0x0000DEFCUL + +/** + * hwicap_type_1_read: Generates a Type 1 read packet header. + * @parameter: Register is the address of the register to be read back. + * + * Generates a Type 1 read packet header, which is used to indirectly + * read registers in the configuration logic. This packet must then + * be sent through the icap device, and a return packet received with + * the information. + **/ +static inline u32 hwicap_type_1_read(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_READ << XHI_OP_SHIFT); +} + +/** + * hwicap_type_1_write: Generates a Type 1 write packet header + * @parameter: Register is the address of the register to be read back. + **/ +static inline u32 hwicap_type_1_write(u32 Register) +{ + return (XHI_TYPE_1 << XHI_TYPE_SHIFT) | + (Register << XHI_REGISTER_SHIFT) | + (XHI_OP_WRITE << XHI_OP_SHIFT); +} + +#endif -- 1.5.3.4-dirty ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-01-31 18:47 ` [PATCH] [POWERPC] Xilinx: hwicap driver Stephen Neuendorffer @ 2008-01-31 19:58 ` Grant Likely 2008-01-31 22:39 ` Stephen Neuendorffer 0 siblings, 1 reply; 21+ messages in thread From: Grant Likely @ 2008-01-31 19:58 UTC (permalink / raw) To: Stephen Neuendorffer; +Cc: linuxppc-dev On 1/31/08, Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> wrote: > This includes code for new fifo-based xps_hwicap in addition to the > older opb_hwicap, which has a significantly different interface. The > common code between the two drivers is largely shared. > > Significant differences exists between this driver and what is > supported in the EDK drivers. In particular, most of the > architecture-specific code for reconfiguring individual FPGA resources > has been removed. This functionality is likely better provided in a > user-space support library. In addition, read and write access is > supported. In addition, although the xps_hwicap cores support > interrupt-driver mode, this driver only supports polled operation, in > order to make the code simpler, and since the interrupt processing > overhead is likely to slow down the throughput under Linux. > > Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> Comments below... Also, I'm not sure if drivers/char/xilinx_hwicap is the best place for this driver. drivers/misc/ might be better (but I'm not sure; poll other people on this) Finally, buffer_icap and fifo_icap are not huge blocks of code. Will they be used by any other drivers? Can they be rolled into the xilinx_hwicap.c file? > +static int buffer_icap_device_read(struct hwicap_drvdata *drvdata, > + u32 offset, u32 count) > +{ > + > + s32 retries = 0; > + void __iomem *base_address = drvdata->base_address; > + > + if (buffer_icap_busy(base_address)) > + return -EBUSY; > + > + if ((offset + count) <= XHI_MAX_BUFFER_INTS) { > + /* setSize count*4 to get bytes. */ > + buffer_icap_set_size(base_address, (count << 2)); > + buffer_icap_set_offset(base_address, offset); > + buffer_icap_set_rnc(base_address, XHI_READBACK); > + > + while (buffer_icap_busy(base_address)) { > + retries++; > + if (retries > XHI_MAX_RETRIES) > + return -EBUSY; > + } > + } else { > + return -EINVAL; > + } Style comment: reverse the condition and bail with return -EINVAL at the test point. It will simplify the code and better match with common practice in the kernel. > + return 0; > + > +}; > + > +/** > + * buffer_icap_device_write: Transfer bytes from ICAP to the storage buffer. > + * @parameter drvdata: a pointer to the drvdata. > + * @parameter offset: The storage buffer start address. > + * @parameter count: The number of words (32 bit) to read from the > + * device (ICAP). > + **/ > +static int buffer_icap_device_write(struct hwicap_drvdata *drvdata, > + u32 offset, u32 count) > +{ > + > + s32 retries = 0; > + void __iomem *base_address = drvdata->base_address; > + > + if (buffer_icap_busy(base_address)) > + return -EBUSY; > + > + if ((offset + count) <= XHI_MAX_BUFFER_INTS) { > + /* setSize count*4 to get bytes. */ > + buffer_icap_set_size(base_address, count << 2); > + buffer_icap_set_offset(base_address, offset); > + buffer_icap_set_rnc(base_address, XHI_CONFIGURE); > + > + while (buffer_icap_busy(base_address)) { > + retries++; > + if (retries > XHI_MAX_RETRIES) > + return -EBUSY; > + } > + } else { > + return -EINVAL; > + } Ditto > +/** > + * buffer_icap_set_configuration: Load a partial bitstream from system memory. > + * @parameter drvdata: a pointer to the drvdata. > + * @parameter data: Kernel address of the partial bitstream. > + * @parameter size: the size of the partial bitstream in 32 bit words. > + **/ > +int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data, > + u32 size) > +{ > + int status; > + s32 buffer_count = 0; > + s32 num_writes = 0; > + bool dirty = 0; > + u32 i; > + void __iomem *base_address = drvdata->base_address; > + > + /* Loop through all the data */ > + for (i = 0, buffer_count = 0; i < size; i++) { > + > + /* Copy data to bram */ > + buffer_icap_set_bram(base_address, buffer_count, data[i]); > + dirty = 1; > + > + if (buffer_count == XHI_MAX_BUFFER_INTS - 1) { > + /* Write data to ICAP */ > + status = buffer_icap_device_write( > + drvdata, > + XHI_BUFFER_START, > + XHI_MAX_BUFFER_INTS); > + if (status != 0) { > + /* abort. */ > + buffer_icap_reset(drvdata); > + return status; > + } > + > + buffer_count = 0; > + num_writes++; > + dirty = 0; > + } else { > + buffer_count++; > + } Nit: Consider reversing the test condition and using 'buffer_count++; continue' > +static ssize_t > +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) > +{ > + struct hwicap_drvdata *drvdata = file->private_data; > + ssize_t bytes_to_read = 0; > + u32 *kbuf; > + u32 words; > + u32 bytes_remaining; > + int status; > + > + if (drvdata->is_accessing) > + return -EBUSY; > + > + drvdata->is_accessing = 1; Race condition, you need to use a semaphore. Otherwise it is possible to get two processes (or even two threads of the same process) in the read() hook. > +static int hwicap_open(struct inode *inode, struct file *file) > +{ > + struct hwicap_drvdata *drvdata; > + int status; > + > + drvdata = container_of(inode->i_cdev, struct hwicap_drvdata, cdev); > + if (drvdata->is_open) > + return -EBUSY; > + > + drvdata->is_open = 1; Also a race condition. Use a semaphore. Also, this isn't sufficient to prevent 2 processes from having the device open. If a process has already opened it and then calls fork(), then *both* processes will have it opened even though the open fop was only called once. (It might not matter for this particular driver as the use case is limited; but it is something you should be aware of.) > +static int __devinit hwicap_setup(struct device *dev, int id, > + const struct resource *regs_res, > + const struct hwicap_driver_config *config, > + const struct config_registers *config_regs) > +{ > + dev_t devt; > + struct hwicap_drvdata *drvdata = NULL; > + int retval = 0; > + > + dev_info(dev, "Xilinx icap port driver\n"); > + > + if (id < 0) { > + for (id = 0; id < HWICAP_DEVICES; id++) > + if (!probed_devices[id]) > + break; > + } > + if (id < 0 || id >= HWICAP_DEVICES) { > + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); > + return -EINVAL; > + } > + if (probed_devices[id]) { > + dev_err(dev, "cannot assign to %s%i; it is already in use\n", > + DRIVER_NAME, id); > + return -EBUSY; > + } > + > + probed_devices[id] = 1; Hmmm, I'm not thrilled with the fixed array of HWICAP devices. That sort of thing just ends up causing headaches down the road. Can the driver just allocate a new structure and the next available ID at probe time? (I hold my nose every time I write something like the above because I know I'm going to regret it later). sysfs/udev can provide details about which one is which. > + > +static int __devexit hwicap_remove(struct device *dev) > +{ > + struct hwicap_drvdata *drvdata; > + > + drvdata = (struct hwicap_drvdata *)dev_get_drvdata(dev); > + > + if (drvdata) { if (!drvdata) return 0; Cheers, g. -- Grant Likely, B.Sc., P.Eng. Secret Lab Technologies Ltd. ^ permalink raw reply [flat|nested] 21+ messages in thread
* RE: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-01-31 19:58 ` Grant Likely @ 2008-01-31 22:39 ` Stephen Neuendorffer 2008-01-31 23:12 ` Grant Likely 0 siblings, 1 reply; 21+ messages in thread From: Stephen Neuendorffer @ 2008-01-31 22:39 UTC (permalink / raw) To: Grant Likely; +Cc: linuxppc-dev > -----Original Message----- > From: glikely@secretlab.ca [mailto:glikely@secretlab.ca] On Behalf Of Grant Likely > Sent: Thursday, January 31, 2008 11:59 AM > To: Stephen Neuendorffer > Cc: linuxppc-dev@ozlabs.org; jacmet@sunsite.dk > Subject: Re: [PATCH] [POWERPC] Xilinx: hwicap driver >=20 > On 1/31/08, Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> wrote: > > This includes code for new fifo-based xps_hwicap in addition to the > > older opb_hwicap, which has a significantly different interface. The > > common code between the two drivers is largely shared. > > > > Significant differences exists between this driver and what is > > supported in the EDK drivers. In particular, most of the > > architecture-specific code for reconfiguring individual FPGA resources > > has been removed. This functionality is likely better provided in a > > user-space support library. In addition, read and write access is > > supported. In addition, although the xps_hwicap cores support > > interrupt-driver mode, this driver only supports polled operation, in > > order to make the code simpler, and since the interrupt processing > > overhead is likely to slow down the throughput under Linux. > > > > Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> >=20 > Comments below... >=20 > Also, I'm not sure if drivers/char/xilinx_hwicap is the best place for > this driver. drivers/misc/ might be better (but I'm not sure; poll > other people on this) I don't have strong opinions on this... If someone wants to say it should be in drivers/misc, I'm more than happy to move it. However, it doesn't seem like there is anything else that is a pure character device there. > Finally, buffer_icap and fifo_icap are not huge blocks of code. Will > they be used by any other drivers? Can they be rolled into the > xilinx_hwicap.c file? They're not huge, but they are really independent from eachother. Furthermore, I expect that future cores will have DMA capability to increase bandwidth, which would result in a dma_icap file. > Style comment: reverse the condition and bail with return -EINVAL at > the test point. It will simplify the code and better match with > common practice in the kernel. good point, I'll fix them all. > > +static ssize_t > > +hwicap_read(struct file *file, char *buf, size_t count, loff_t *ppos) > > +{ > > + struct hwicap_drvdata *drvdata =3D file->private_data; > > + ssize_t bytes_to_read =3D 0; > > + u32 *kbuf; > > + u32 words; > > + u32 bytes_remaining; > > + int status; > > + > > + if (drvdata->is_accessing) > > + return -EBUSY; > > + > > + drvdata->is_accessing =3D 1; >=20 > Race condition, you need to use a semaphore. Otherwise it is possible > to get two processes (or even two threads of the same process) in the > read() hook. good point. On thinking about this more, does the whole function need to be in a semaphore to protect against PREEMPT kernels? > > +static int hwicap_open(struct inode *inode, struct file *file) > > +{ > > + struct hwicap_drvdata *drvdata; > > + int status; > > + > > + drvdata =3D container_of(inode->i_cdev, struct = hwicap_drvdata, cdev); > > + if (drvdata->is_open) > > + return -EBUSY; > > + > > + drvdata->is_open =3D 1; >=20 > Also a race condition. Use a semaphore. >=20 > Also, this isn't sufficient to prevent 2 processes from having the > device open. If a process has already opened it and then calls > fork(), then *both* processes will have it opened even though the open > fop was only called once. (It might not matter for this particular > driver as the use case is limited; but it is something you should be > aware of.) The fork I'm somewhat less concerned about, I think. If someone calls fork(), then it's up to them to synchronize their code correctly and only call close() once. The only reason to block the open is that it's the simplest way to keep track of the state between reads and writes. > > +static int __devinit hwicap_setup(struct device *dev, int id, > > + const struct resource *regs_res, > > + const struct hwicap_driver_config *config, > > + const struct config_registers *config_regs) > > +{ > > + dev_t devt; > > + struct hwicap_drvdata *drvdata =3D NULL; > > + int retval =3D 0; > > + > > + dev_info(dev, "Xilinx icap port driver\n"); > > + > > + if (id < 0) { > > + for (id =3D 0; id < HWICAP_DEVICES; id++) > > + if (!probed_devices[id]) > > + break; > > + } > > + if (id < 0 || id >=3D HWICAP_DEVICES) { > > + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); > > + return -EINVAL; > > + } > > + if (probed_devices[id]) { > > + dev_err(dev, "cannot assign to %s%i; it is already in use\n", > > + DRIVER_NAME, id); > > + return -EBUSY; > > + } > > + > > + probed_devices[id] =3D 1; >=20 > Hmmm, I'm not thrilled with the fixed array of HWICAP devices. That > sort of thing just ends up causing headaches down the road. Can the > driver just allocate a new structure and the next available ID at > probe time? (I hold my nose every time I write something like the > above because I know I'm going to regret it later). sysfs/udev can > provide details about which one is which. Can you suggest a good driver to crib? > Cheers, > g. Thanks! ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-01-31 22:39 ` Stephen Neuendorffer @ 2008-01-31 23:12 ` Grant Likely 2008-01-31 23:51 ` Stephen Neuendorffer 0 siblings, 1 reply; 21+ messages in thread From: Grant Likely @ 2008-01-31 23:12 UTC (permalink / raw) To: Stephen Neuendorffer; +Cc: linuxppc-dev On 1/31/08, Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> wrote: > > Also, this isn't sufficient to prevent 2 processes from having the > > device open. If a process has already opened it and then calls > > fork(), then *both* processes will have it opened even though the open > > fop was only called once. (It might not matter for this particular > > driver as the use case is limited; but it is something you should be > > aware of.) > > The fork I'm somewhat less concerned about, I think. If someone calls > fork(), then it's up to them to synchronize their code correctly and > only call close() once. The only reason to block the open is that it's > the simplest way to keep track of the state between reads and writes. <interesting trivia> You're thinking of threads; not fork. When fork is called you get a whole new process which has all the same file descriptors open as the parent process. This is what the timeline would look like: p1: open(/dev/hwcap0) --- open fop called here (and struct file is allocated) p1: fork() --- p2 is created p1: do stuff p2: do stuff p1: close() p2: do more stuff p2: close() --- release fop called here (and struct file is released) Notice how close is called twice (the correct behavour) yet release is only called once? :-) That means there are 2 processes sharing the same file structure. > > > +static int __devinit hwicap_setup(struct device *dev, int id, > > > + const struct resource *regs_res, > > > + const struct hwicap_driver_config *config, > > > + const struct config_registers *config_regs) > > > +{ > > > + dev_t devt; > > > + struct hwicap_drvdata *drvdata = NULL; > > > + int retval = 0; > > > + > > > + dev_info(dev, "Xilinx icap port driver\n"); > > > + > > > + if (id < 0) { > > > + for (id = 0; id < HWICAP_DEVICES; id++) > > > + if (!probed_devices[id]) > > > + break; > > > + } > > > + if (id < 0 || id >= HWICAP_DEVICES) { > > > + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); > > > + return -EINVAL; > > > + } > > > + if (probed_devices[id]) { > > > + dev_err(dev, "cannot assign to %s%i; it is already > in use\n", > > > + DRIVER_NAME, id); > > > + return -EBUSY; > > > + } > > > + > > > + probed_devices[id] = 1; > > > > Hmmm, I'm not thrilled with the fixed array of HWICAP devices. That > > sort of thing just ends up causing headaches down the road. Can the > > driver just allocate a new structure and the next available ID at > > probe time? (I hold my nose every time I write something like the > > above because I know I'm going to regret it later). sysfs/udev can > > provide details about which one is which. > > Can you suggest a good driver to crib? What I would do is use a static struct list_head to hold a list of all instantiated devices. When you register a new device you can use list_for_each_entry to look for a free id. That way you don't have to preallocate an array for all the possible device numbers. OTOH, how many of these devices are likely to be present on any one bitstream? If it is only 1 or 2 then it might be that the overhead isn't worth the savings. I won't complain if you decide it's better the way it is now. -- Grant Likely, B.Sc., P.Eng. Secret Lab Technologies Ltd. ^ permalink raw reply [flat|nested] 21+ messages in thread
* RE: [PATCH] [POWERPC] Xilinx: hwicap driver 2008-01-31 23:12 ` Grant Likely @ 2008-01-31 23:51 ` Stephen Neuendorffer 0 siblings, 0 replies; 21+ messages in thread From: Stephen Neuendorffer @ 2008-01-31 23:51 UTC (permalink / raw) To: Grant Likely; +Cc: linuxppc-dev > -----Original Message----- > From: glikely@secretlab.ca [mailto:glikely@secretlab.ca] On Behalf Of Grant Likely > Sent: Thursday, January 31, 2008 3:13 PM > To: Stephen Neuendorffer > Cc: linuxppc-dev@ozlabs.org; jacmet@sunsite.dk > Subject: Re: [PATCH] [POWERPC] Xilinx: hwicap driver >=20 > On 1/31/08, Stephen Neuendorffer <stephen.neuendorffer@xilinx.com> wrote: > > > Also, this isn't sufficient to prevent 2 processes from having the > > > device open. If a process has already opened it and then calls > > > fork(), then *both* processes will have it opened even though the open > > > fop was only called once. (It might not matter for this particular > > > driver as the use case is limited; but it is something you should be > > > aware of.) > > > > The fork I'm somewhat less concerned about, I think. If someone calls > > fork(), then it's up to them to synchronize their code correctly and > > only call close() once. The only reason to block the open is that it's > > the simplest way to keep track of the state between reads and writes. >=20 > <interesting trivia> You're thinking of threads; not fork. When fork > is called you get a whole new process which has all the same file > descriptors open as the parent process. This is what the timeline > would look like: >=20 > p1: open(/dev/hwcap0) > --- open fop called here (and struct file is allocated) > p1: fork() > --- p2 is created > p1: do stuff > p2: do stuff > p1: close() > p2: do more stuff > p2: close() > --- release fop called here (and struct file is released) >=20 > Notice how close is called twice (the correct behavour) yet release is > only called once? :-) That means there are 2 processes sharing the > same file structure. Hmm... hadn't realized that. In most uses of fork I've seen/thought of, the return value of fork is used to identify the parent and child processes and typically the child code avoids accessing the open files in order to avoid colliding output (and relies on the implicit close() on exit). In any event, regardless of when close is called, all we really care about is the release fop... Regardless, I'll be more careful about distinguishing the close and the release in the future, though :) > > > > +static int __devinit hwicap_setup(struct device *dev, int id, > > > > + const struct resource *regs_res, > > > > + const struct hwicap_driver_config *config, > > > > + const struct config_registers *config_regs) > > > > +{ > > > > + dev_t devt; > > > > + struct hwicap_drvdata *drvdata =3D NULL; > > > > + int retval =3D 0; > > > > + > > > > + dev_info(dev, "Xilinx icap port driver\n"); > > > > + > > > > + if (id < 0) { > > > > + for (id =3D 0; id < HWICAP_DEVICES; id++) > > > > + if (!probed_devices[id]) > > > > + break; > > > > + } > > > > + if (id < 0 || id >=3D HWICAP_DEVICES) { > > > > + dev_err(dev, "%s%i too large\n", DRIVER_NAME, id); > > > > + return -EINVAL; > > > > + } > > > > + if (probed_devices[id]) { > > > > + dev_err(dev, "cannot assign to %s%i; it is already > > in use\n", > > > > + DRIVER_NAME, id); > > > > + return -EBUSY; > > > > + } > > > > + > > > > + probed_devices[id] =3D 1; > > > > > > Hmmm, I'm not thrilled with the fixed array of HWICAP devices. That > > > sort of thing just ends up causing headaches down the road. Can the > > > driver just allocate a new structure and the next available ID at > > > probe time? (I hold my nose every time I write something like the > > > above because I know I'm going to regret it later). sysfs/udev can > > > provide details about which one is which. > > > > Can you suggest a good driver to crib? >=20 > What I would do is use a static struct list_head to hold a list of all > instantiated devices. When you register a new device you can use > list_for_each_entry to look for a free id. That way you don't have to > preallocate an array for all the possible device numbers. >=20 > OTOH, how many of these devices are likely to be present on any one > bitstream? If it is only 1 or 2 then it might be that the overhead > isn't worth the savings. I won't complain if you decide it's better > the way it is now. In this case, it is highly likely that there will be only 1 in the system, and since the code is properly parameterized with respect to the number of devices that are registered, I don't think it's worth it. Steve ^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2008-02-08 18:17 UTC | newest] Thread overview: 21+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- [not found] <1201890163-12219-1-git-send-email-stephen.neuendorffer@xilinx.com> 2008-02-01 20:03 ` [PATCH] [POWERPC] Xilinx: hwicap driver Stephen Neuendorffer 2008-02-01 20:11 ` Grant Likely 2008-02-01 21:12 ` Stephen Neuendorffer [not found] ` <1201900363-25230-1-git-send-email-stephen.neuendorffer@xilinx.com> 2008-02-05 0:38 ` Stephen Neuendorffer 2008-02-05 17:24 ` [PATCH 1/2] " Stephen Neuendorffer [not found] ` <1202232250-24971-1-git-send-email-stephen.neuendorffer@xilinx.com> 2008-02-05 17:24 ` [PATCH 2/2] [POWERPC] Xilinx: hwicap: update booting-without-of.txt Stephen Neuendorffer [not found] <47AB6552.7040503@gmail.com> 2008-02-08 2:17 ` [PATCH] [POWERPC] Xilinx: hwicap driver Stephen Neuendorffer 2008-02-08 9:10 ` Jiri Slaby 2008-02-08 16:49 ` Randy Dunlap [not found] <1201805233-15112-1-git-send-email-stephen.neuendorffer@xilinx.com> 2008-02-01 1:02 ` Stephen Neuendorffer 2008-02-01 5:21 ` Grant Likely [not found] ` <1201827769-7439-1-git-send-email-stephen.neuendorffer@xilinx.com> 2008-02-01 5:01 ` Stephen Neuendorffer 2008-02-01 5:56 ` Nathan Lynch 2008-02-01 17:31 ` Stephen Neuendorffer 2008-02-01 18:22 ` Stephen Neuendorffer 2008-02-01 18:42 ` Grant Likely 2007-12-05 12:20 [PATCH] [XILINX][HWICAP] Xilinx Internal Configuration Access Port device driver Peter Korsgaard 2008-01-31 18:47 ` [PATCH] [POWERPC] Xilinx: hwicap driver Stephen Neuendorffer 2008-01-31 19:58 ` Grant Likely 2008-01-31 22:39 ` Stephen Neuendorffer 2008-01-31 23:12 ` Grant Likely 2008-01-31 23:51 ` Stephen Neuendorffer
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).