This is a small and simple driver for handling of external interrupt signal asserted on pins of Amplicon's PCIe215 board. There is already a Comedi driver subsystem in kernel which handles that (and more) board, but that framework while offering more flexibility brings also additional complexity at the cost of being generic. In some cases the simpler, more compact solution may be preferred. The purpose of this driver is therefore to handle interrupt feature of the PCIe215, while being small, simple and reliable. Signed-off-by: Piotr Gregor <piotrgregor@xxxxxxxxxxx> --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 1 + drivers/staging/pcie215/Kconfig | 7 + drivers/staging/pcie215/Makefile | 4 + drivers/staging/pcie215/README | 155 +++++ drivers/staging/pcie215/TODO | 9 + drivers/staging/pcie215/pcie215-trace.h | 51 ++ drivers/staging/pcie215/pcie215.c | 1068 +++++++++++++++++++++++++++++++ drivers/staging/pcie215/pcie215_ioctl.h | 22 + 9 files changed, 1319 insertions(+) create mode 100644 drivers/staging/pcie215/Kconfig create mode 100644 drivers/staging/pcie215/Makefile create mode 100644 drivers/staging/pcie215/README create mode 100644 drivers/staging/pcie215/TODO create mode 100644 drivers/staging/pcie215/pcie215-trace.h create mode 100644 drivers/staging/pcie215/pcie215.c create mode 100644 drivers/staging/pcie215/pcie215_ioctl.h diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index ef28a1cb64ae..d9b4156d38b8 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -112,4 +112,6 @@ source "drivers/staging/typec/Kconfig" source "drivers/staging/vboxvideo/Kconfig" +source "drivers/staging/pcie215/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 2918580bdb9e..786b2e84ae60 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -45,3 +45,4 @@ obj-$(CONFIG_GREYBUS) += greybus/ obj-$(CONFIG_BCM2835_VCHIQ) += vc04_services/ obj-$(CONFIG_CRYPTO_DEV_CCREE) += ccree/ obj-$(CONFIG_DRM_VBOXVIDEO) += vboxvideo/ +obj-$(CONFIG_PCIE215) += pcie215/ diff --git a/drivers/staging/pcie215/Kconfig b/drivers/staging/pcie215/Kconfig new file mode 100644 index 000000000000..9bb507dbb640 --- /dev/null +++ b/drivers/staging/pcie215/Kconfig @@ -0,0 +1,7 @@ +config PCIE215 + tristate "PCIe215 Driver for PCIe215 interrupt" + depends on PCI + default n + ---help--- + This driver allows user space processes to react to external interrupts signalled on pins of PCIe215. + diff --git a/drivers/staging/pcie215/Makefile b/drivers/staging/pcie215/Makefile new file mode 100644 index 000000000000..af70668a4619 --- /dev/null +++ b/drivers/staging/pcie215/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_PCIE215) += pcie215.o + +CFLAGS_pcie215.o = -I$(src) + diff --git a/drivers/staging/pcie215/README b/drivers/staging/pcie215/README new file mode 100644 index 000000000000..bce7b442216f --- /dev/null +++ b/drivers/staging/pcie215/README @@ -0,0 +1,155 @@ + +1. PCIE215 +2. IOCTL +2.1. PCIE215_IOCTL_IRQ_DISABLE +2.2. PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE +2.3. PCIE215_IOCTL_IRQ_TRIGGERS_DISABLE +3. MODULE PARAMETERS +4. COMPILATION +5. INSTALLATION AND USAGE +6. DEBUG/LOGGING +7. EXAMPLES + + +1. PCIE215 + +This is a driver for interrupts generated by Amplicon's PCIe215 +signal processing controller. It enables user space processes to react +to the external signals asserted on pins of PCIe215 board. + + +2. IOCTL + +Driver uses ioctl magic '4' and implements three ioctl calls: + +2.1. PCIE215_IOCTL_IRQ_ENABLE + +This call enables/disables interrupt generation by board's fpga, +Altera Cyclone IV. It accepts 8-bit integer argument (0 - disable, 1 - enable). + +Usage: + ioctl(fd, PCIE215_IOCTL_IRQ_ENABLE, 0); + Disables interrupts. + + ioctl(fd, PCIE215_IOCTL_IRQ_ENABLE, 1); + Enables interrupts. + +2.2. PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE + +This call enables interrupts on specific pins passed as bitmask +argument. Argument is an 8 bit unsigned integer, with appropriate bit +set to 1 to enable triggering: + + 1 Enable PPI X C0 (pin 44) + 2 Enable PPI X C3 (pin 24) + 4 Enable PPI Y C0 (pin 70) + 8 Enable PPI Y C3 (pin 11) + +Pins set to 1 in the bitmask are _added_ to current mask, +so that previously enabled pins are still enabled after +new pins have been enabled. + +2.3. PCIE215_IOCTL_IRQ_TRIGGERS_DISABLE + +This call disables interrupt triggering on specified pins. Pins set to 1 +are removed from the set of enabled pins. + + +3. MODULE PARAMETERS + +Module accepts one optional parameter: triggers. This parameter may be passed +to insmod and/or modprobe to set enabled IRQ pins at module load time. +Interruption triggering is also started in that case (if triggers != 0). +It's meaning is the same as the meaning of the argument used with ioctl +PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE and the end result is as both +PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE and PCIE215_IOCTL_IRQ_ENABLE were called. + +This parameter is exported to the sysfs and is available under +/sys/module/pcie215/parameters/triggers for reading. It is read-only +and is not updated when triggers are changed - it simply contains +the initial value of triggers bitmask. + + +4. COMPILATION + +To compile the module in kernel tree please execute +make modules SUBDIRS=drivers/staging/pcie215 +from root kernel directory. + + +5. INSTALLATION AND USAGE + +To load the module with interrupt triggering disabled + + insmod pcie215.ko + +To load the module with given triggers enabled + + insmod pcie215.ko triggers=X + +where X is an 8-bit bitmask of enabled pins for IRQ triggering. + +It is a good idea to check the dmesg after insmod + + dmesg | tail + +It should contain dump of successful configuration. + +Additional information about the driver can be found with + + modinfo pcie215 + +The simplest use case is: + + 1. Application opens driver + + fd = open(PCIE215, O_RDWR); + + 2. And specifies enabled IRQ pins + + ioctl(fd, PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE, triggers); + + 3. Enables interrupt generation by PCIe215's Altera Cyclone IV fpga + + ioctl(fd, PCIE215_IOCTL_IRQ_ENABLE, 1); + + 4. Application executes read() call and is blocked in that call + waiting for external signal being asserted on one of configured + pins (likely in loop) + + ret = read(fd, NULL, 0); + + 5. When signal on any of the enabled pins is asserted, + CPU is interrupted and driver wakes up all of the user space + processes blocked in read() call. + + Sleep is interruptible so signal queued for the process will + wake up all of the processes too. Process can understand the reason + it has been awoken by the value returned from the read() call: + it is 0 if the wake up is due to signal asserted and -1 otherwise, + errno is set to IEINTR if the wake up has been caused by (enabled) + signal. + + +6. EXAMPLES + +Load module and enable interrupt triggering on the pin 44, PPI-X-C0. + insmod pcie215.ko triggers=1 + +Load module and enable interrupt triggering on the pin 70, PPI-Y-C0. + insmod pcie215.ko triggers=4 + +Load module and enable interrupt triggering on all 4 pins. + insmod pcie215.ko triggers=15 + + +7. DEBUG/LOGGING + +If compiled with DEBUG flag module will print information on the IRQ +sources which triggered interrupt, the number of IRQs and number +of spurious IRQs if compiled with DEBUG flag. + + cd linux + make modules SUBDIRS=drivers/staging/pcie215 EXTRA_CFLAGS="-DDEBUG" + + diff --git a/drivers/staging/pcie215/TODO b/drivers/staging/pcie215/TODO new file mode 100644 index 000000000000..b0b9bb8eb718 --- /dev/null +++ b/drivers/staging/pcie215/TODO @@ -0,0 +1,9 @@ +TODO + +1. Currently the bitmask of enabled pins for interrupt triggering +is global. This means that driver can be reliably used only +by a single processes or in a scenario of cooperating processes +(if they know what they are doing). Consider enabling multiple processes +to have separate state of enabled pins. + +2. Power management needs more testing. diff --git a/drivers/staging/pcie215/pcie215-trace.h b/drivers/staging/pcie215/pcie215-trace.h new file mode 100644 index 000000000000..6baf5ffee6f9 --- /dev/null +++ b/drivers/staging/pcie215/pcie215-trace.h @@ -0,0 +1,51 @@ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM pcie215 + +#if !defined(_TRACE_PCIE215_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_PCIE215_H + +#include <linux/tracepoint.h> + +TRACE_EVENT(pcie215_event, + TP_PROTO(__u8 id), + TP_ARGS(id), + TP_STRUCT__entry( + __field(__u8, id) + ), + TP_fast_assign( + __entry->id = id; + ), + TP_printk("[%u]", __entry->id) + ); + +TRACE_EVENT(pcie215_isr, + TP_PROTO(__u8 irq_status, unsigned long long irq_n, + unsigned long long irq_spurious_n), + + TP_ARGS(irq_status, irq_n, irq_spurious_n), + + TP_STRUCT__entry( + __field(__u8, irq_status) + __field(unsigned long long, irq_n) + __field(unsigned long long, irq_spurious_n) + ), + + TP_fast_assign( + __entry->irq_status = irq_status; + __entry->irq_n = irq_n; + __entry->irq_spurious_n = irq_spurious_n; + ), + + TP_printk("IRQ# [%llu], IRQ spurious# [%llu], IRQ status [%02x]", + __entry->irq_n, __entry->irq_spurious_n, + __entry->irq_status) + ); + +#endif /* _TRACE_PCIE215_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE pcie215-trace + +#include <trace/define_trace.h> diff --git a/drivers/staging/pcie215/pcie215.c b/drivers/staging/pcie215/pcie215.c new file mode 100644 index 000000000000..de2897eda43e --- /dev/null +++ b/drivers/staging/pcie215/pcie215.c @@ -0,0 +1,1068 @@ +/* + * drivers/staging/pcie215.c + * + * Copyright(C) 2017, Piotr Gregor <piotrgregor@xxxxxxxxxxx> + * + * PCIe215 driver for interrupt + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * Overview + * + * This is a driver for interrupt generated by Amplicon's PCIe215 + * signal processing controller. It enables user space process to react + * to the external signals asserted on interrupt pins of PCIe215 board. + * + * The simplest use case is: + * + * 1. Application opens driver + * + * fd = open(PCIE215, O_RDWR); + * + * 2. And specifies pins allowed to trigger IRQ + * + * ioctl(fd, PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE, triggers); + * + * 3. Enables generation of the interrupts by Altera Cyclone + * fpga of PCIe215 + * + * ioctl(fd, PCIE215_IOCTL_IRQ_ENABLE, 1); + * + * 4. Application executes read() call and is blocked in that call + * waiting for external signal being asserted on one of configured + * (in step 2) triggers (likely in a loop) + * + * ret = read(fd, NULL, 0); + * + * 5. When signal on any of the enabled pins is asserted, + * CPU is interrupted and driver wakes up all of the user space + * processes blocked in read() call. + * + * Sleep is interruptible so signal queued for the process will + * wake up all of the processes too. Process can understand the reason + * it has been awoken by the value returned from the read() call: + * it is 0 if the wake up is due to signal asserted + * and -1 otherwise, errno is set to IEINTR + * if the wake up has been caused by (enabled) signal. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/kdev_t.h> +#include <linux/cdev.h> +#include <linux/miscdevice.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +#define CREATE_TRACE_POINTS +#include "pcie215-trace.h" + +#include "pcie215_ioctl.h" + +#define PCIE215_DRV_VERSION "1" +#define PCIE215_DRV_AUTHOR "Piotr Gregor <piotrgregor@xxxxxxxxxxx>" +#define PCIE215_DRV_DESC "Driver for Amplicon's PCIe215 Digital I/O Board" + +#define PCIE215 "pcie215" +#define PCI_DEVICE_ID_AMPLICON_PCIE215 0x12 + +/* + * Registers. + * + * PCIe215 is allocated two base address registers. We call them + * BA (Base Address Configuration Register, Bar 1) and LCR (Local Bus + * Configuration Register, Bar 0). LCR is used only to access LLIER + * (Low Level Interrupt Enable Register). PCI Bar 1 (BA) is the main + * register space. Interrupt sources are classified by the position + * in the interrupt source register and each source is maskable + * by a bit in an interrupt mask register. + */ + +/* + * PCIe215 registers (expressed as offset from base memory address). + * direction | #bits | description + */ +#define PCIE215_PORT_A_PPI_X 0x00 +/* In/Out | 8 | data to/from port A of PPI X */ + +#define PCIE215_PORT_B_PPI_X 0x08 +/* In/Out | 8 | data to/from port B of PPI X */ + +#define PCIE215_PORT_C_PPI_X 0x10 +/* In/Out | 8 | data to/from port C of PPI X */ + +#define PCIE215_PORT_CTRL_PPI_X 0x18 +/* Out | 8 | control word for PPI X */ + +#define PCIE215_PORT_A_PPI_Y 0x20 +/* In/Out | 8 | data to/from port A of PPI Y */ + +#define PCIE215_PORT_B_PPI_Y 0x28 +/* In/Out | 8 | data to/from port B of PPI Y */ + +#define PCIE215_PORT_C_PPI_Y 0x30 +/* In/Out | 8 | data to/from port C of PPI Y */ + +#define PCIE215_PORT_CTRL_PPI_Y 0x38 +/* Out | 8 | control word for PPI Y */ + +#define PCIE215_IER 0xF0 +/* In/Out | 8 | Interrupt Enable/Status Register */ + +#define PCIE215_VERSION 0x120 +/* In | 8 | Hardware Version register */ + +#define PCIE215_LLIER 0x50 +/* In/Out | 32 | Low-level Interrupt Enable Register */ + +/* + * IRQ sources bitmask (triggers of interrupts). + * Please see below and read pcie215_fops_unlocked_ioctl + * description for detailed explanation of pin bitmask. + */ +#define PCIE215_IRQ_SRC_MASK (0x0f) +/* bitmask of valid IRQ triggers */ + +/* + * IRQ triggers (bits in triggers bitmask). + */ +#define PCIE215_IRQ_SRC_PPI_X_C0 BIT(0) +/* for external signals, pin 44, INTRB */ + +#define PCIE215_IRQ_SRC_PPI_X_C3 BIT(1) +/* for external signals, pin 24, INTRA */ + +#define PCIE215_IRQ_SRC_PPI_Y_C0 BIT(2) +/* for external signals, pin 70, INTRB */ + +#define PCIE215_IRQ_SRC_PPI_Y_C3 BIT(3) +/* for external signals, pin 11, INTRA */ + +/* + * 8255 PPI Registers. + * The control register is composed of 7-bit latch circuit (D0-D6) + * and 1-bit flag (D7). + * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + * | flag | l a t c h | + */ +#define PCIE215_8255_CTRL_PORTC_LO_IO BIT(0) +/* D0 */ + +#define PCIE215_8255_CTRL_PORTB_IO BIT(1) +/* D1 (0 = Output, 1 = Input) */ + +#define PCIE215_8255_CTRL_PORTB_MODE(x) ((x & 0x1) << 2) +/* D2 */ + +#define PCIE215_8255_CTRL_PORTC_HI_IO BIT(3) +/* D3 */ + +#define PCIE215_8255_CTRL_PORTA_IO BIT(4) +/* D4 (0 = Output, 1 = Input) */ + +#define PCIE215_8255_CTRL_PORTA_MODE(x) ((x & 0x3) << 5) +/* D5 and D6 */ + +#define PCIE215_8255_CTRL_CW_FLAG BIT(7) +/* D7 */ + +/* + * 8254 Timer/Counter registers. + */ +#define PCIE215_8254_CTRL_Z1_0 0x80 +#define PCIE215_8254_CTRL_Z1_1 0x88 +#define PCIE215_8254_CTRL_Z1_2 0x90 +#define PCIE215_8254_CTRL_Z1_CTRL 0x98 +#define PCIE215_8254_CTRL_Z2_0 0xA0 +#define PCIE215_8254_CTRL_Z2_1 0xA8 +#define PCIE215_8254_CTRL_Z2_2 0xB0 +#define PCIE215_8254_CTRL_Z2_CTRL 0xB8 + +#define PCIE215_8254_CTRL_MODE_0 0 +/* mode 0 of 8254 */ + +#define PCIE215_8254_CTRL_COUNTER_SELECT(x) ((x) << 6) +/* select counter 0,1,2 */ + +#define PCIE215_8254_CTRL_RW(x) (((x) & 0x3) << 4) +/* read/write */ + +#define PCIE215_8254_CTRL_RW_LATCH PCIE215_8254_CTRL_RW(0) +#define PCIE215_8254_CTRL_RW_LSB_ONLY PCIE215_8254_CTRL_RW(1) +#define PCIE215_8254_CTRL_RW_MSB_ONLY PCIE215_8254_CTRL_RW(2) +#define PCIE215_8254_CTRL_RW_LSB_MSB PCIE215_8254_CTRL_RW(3) +#define PCIE215_8254_CTRL_BCD_16_BIT 0 +#define PCIE215_8254_CTRL_BCD_BCD 1 + +/* + * Bit flags. + * 1 if IRQ has been triggered on one of the pins some task is waitnig for, + * 0 otherwise. + */ +#define PCIE215_FLAGS_IRQ 1 + +/* + * This parameter may be passed to insmod and/or modprobe + * to set enabled IRQ triggers at module load time. Interruption + * triggering is also started in that case (if triggers != 0). + */ +static unsigned long triggers; +module_param(triggers, ulong, 0444); +MODULE_PARM_DESC(triggers, "This bit mask may be passed to insmod and/or modprobe to set enabled IRQ triggers at module load time (interrupt triggering is also started in that case). Value to trigger|pin mapping is (value:trigger|pin): 1:PPI-X-C0|44, 2:PPI-X-C3|24, 4:PPI-Y-C0|70, 8:PPI-Y-C3|11, 16:CTRZ1-OUT1-O/P|55, 32:CTRZ2-OUT1-O/P|58"); + +struct pcie215 { + struct pci_dev *pdev; + + /* synchronizes access to driver's data between ISR and ioctl */ + spinlock_t lock; + unsigned int major; + + /* assigned IRQ vector number */ + long irq; + + unsigned long irq_flags; + unsigned long long irq_n; + unsigned long long irq_spurious_n; + unsigned long long irq_missed_n; + + /* Base Address, main register space */ + unsigned long ba; + + /* Local Bus configuration register space, used to access LLIER */ + unsigned long lcr; + + /* I/O remapped memory of BA */ + void __iomem *ba_iomem; + + /* I/O remapped memory of LCR */ + void __iomem *lcr_iomem; + + /* Queue of the blocked processes waiting for external interrupt*/ + wait_queue_head_t wait_queue; + + unsigned long flags; + __u8 triggers; + + /* Configuration of PPI, stored for the reference only */ + u8 ppi_8255_config; +}; + +struct pcie215 pcie215_device; + +/** + * pcie215_ioread8 - read device memory of size 8 bits. + * @ba: Switch between Base Address and Local Address + * Configuration registers. + * @offset: Offset to chosen register space. + * + * If @ba is set to 1 then reading is performed at @offset + * from the Base Address, if ba is set to 0 then reading + * is at @offset from Local Bus. + * + * Return: The value read. + */ +static u8 pcie215_ioread8(struct pcie215 *pcie215dev, u32 offset, u8 ba) +{ + if (ba) { + if (pcie215dev->ba_iomem) + return ioread8(pcie215dev->ba_iomem + offset); + else + return inb(pcie215dev->ba + offset); + } else { + if (pcie215dev->lcr_iomem) + return ioread8(pcie215dev->lcr_iomem + offset); + else + return inb(pcie215dev->lcr + offset); + } +} + +/** + * pcie215_iowrite8 - write device memory of size 8 bits. + * @ba: Switch between Base Address and Local Address + * Configuration registers. + * @offset: Offset to chosen register space. + * + * If @ba is set to 1 then write is performed at @offset + * from the Base Address, if ba is set to 0 then write + * is at @offset from Local Bus. + */ +static void pcie215_iowrite8(struct pcie215 *pcie215dev, u8 val, u32 offset, + u8 ba) +{ + if (ba) { + if (pcie215dev->ba_iomem) + return iowrite8(val, pcie215dev->ba_iomem + offset); + else + return outb(val, pcie215dev->ba + offset); + } else { + if (pcie215dev->lcr_iomem) + return iowrite8(val, pcie215dev->lcr_iomem + offset); + else + return outb(val, pcie215dev->lcr + offset); + } +} + +/** + * pcie215_iowrite32 - write device memory of size 32 bits. + * @ba: Switch between Base Address and Local Address + * Configuration registers. + * @offset: Offset to chosen register space. + * + * If @ba is set to 1 then write is performed at @offset + * from the Base Address, if ba is set to 0 then write + * is at @offset from Local Bus. + */ +static void pcie215_iowrite32(struct pcie215 *pcie215dev, u32 val, u32 offset, + u8 ba) +{ + if (ba) { + if (pcie215dev->ba_iomem) + return iowrite32(val, pcie215dev->ba_iomem + offset); + else + return outl(val, pcie215dev->ba + offset); + } else { + if (pcie215dev->lcr_iomem) + return iowrite32(val, pcie215dev->lcr_iomem + offset); + else + return outl(val, pcie215dev->lcr + offset); + } +} + +/** + * pcie215_init_8254 - Initialise 82c54 CMOS Programmable + * Interval Timer/Counter. + * + * After power-up, the state of the 82c54 is undefined. The mode, + * count value and output of all counters are undefined. Each counter + * must be programmed before it is used. Unused counters need not + * to be programmed. + */ +static void pcie215_init_8254(struct pcie215 *pcie215dev) +{ + u8 mode = 0; + + int i = 0; + + for (; i < 3; ++i) { + mode = PCIE215_8254_CTRL_COUNTER_SELECT(i) | + PCIE215_8254_CTRL_RW_LATCH | + PCIE215_8254_CTRL_MODE_0 | + PCIE215_8254_CTRL_BCD_16_BIT; + + pcie215_iowrite8(pcie215dev, mode, + PCIE215_8254_CTRL_Z1_CTRL, 1); + pcie215_iowrite8(pcie215dev, mode, + PCIE215_8254_CTRL_Z2_CTRL, 1); + } +} + +/** + * pcie215_init_8255 - Initialise 82c55a CMOS Programmable + * Peripheral Interface. + * + * Initialise 8255 to Mode 0 for both PORT A and PORT B. + * Mode 0 is Basic Input/Output mode. No handshaking is required, + * data is simply written to or read from a specific port. + * Mode 0: + * - Two 8-bit (A,B) and two 4-bit (C) ports for each PPI + * (i.e. 4 8-bit ports in total and 4 4-bits ports in total) + * - Each of them can be individually programmed as Input/Output + * - Outputs are latched + * - Inputs are NOT latched + * This is also a default mode of PCIe215 after reset. + */ +static void pcie215_init_8255(struct pcie215 *pcie215dev) +{ + /* + * Configure PORT A and PORT B as inputs in MODE 0 (IRQ) + * (control word 0x9B) + */ + u8 config = (PCIE215_8255_CTRL_CW_FLAG | + PCIE215_8255_CTRL_PORTA_MODE(0) | + PCIE215_8255_CTRL_PORTA_IO | + PCIE215_8255_CTRL_PORTC_HI_IO | + PCIE215_8255_CTRL_PORTB_MODE(0) | + PCIE215_8255_CTRL_PORTB_IO | + PCIE215_8255_CTRL_PORTC_LO_IO); + + pcie215_iowrite8(pcie215dev, config, PCIE215_PORT_CTRL_PPI_X, 1); + pcie215_iowrite8(pcie215dev, config, PCIE215_PORT_CTRL_PPI_Y, 1); + pcie215dev->ppi_8255_config = config; +} + +/** + * pcie215_irq_enable - Enable generation of the interrupts. + * + * Lock must be taken. + */ +static void pcie215_irq_enable(struct pcie215 *pcie215dev) +{ + pcie215_iowrite32(pcie215dev, 0x80, PCIE215_LLIER, 0); +} + +/** + * pcie215_irq_disable - Disable generation of the interrupts. + * + * Lock must be taken. + */ +static void pcie215_irq_disable(struct pcie215 *pcie215dev) +{ + pcie215_iowrite32(pcie215dev, 0x00, PCIE215_LLIER, 0); +} + +/** + * pcie215_irq_triggers_enable - Enable pins to trigger IRQ. + * @enabled: Mask of pins allowed to trigger interrupt. + * This gets written to interrupt Enable/Status register. + * + * Lock must be taken. + */ +static void pcie215_irq_triggers_enable(struct pcie215 *pcie215dev, + __u8 enabled) +{ + pcie215_iowrite8(pcie215dev, enabled, PCIE215_IER, 1); +} + +/** + * pcie215_isr - Interrupt Service Routine. + * + * ISR checks for spuroius IRQ by reading the Interrupt Status/Enable + * register. It exits if none of the configured pins is in state HIGH. + * It pulls LOW asserted pins by writing 0 to corresponding bit in IER + * in loop, until all pins are LOW. Finally, it enables interrupt + * triggering on all configured pins and wakes up all processes blocked + * in a read() call to this driver. + * + * Return : IRQ_NONE if none of the configured pins is asserted, + * IRQ_HANDLED otherwise. + */ +static irqreturn_t pcie215_isr(int irq, void *dev_id) +{ + unsigned long flags; + u8 irq_status, triggered = 0; + __u8 enabled; + struct pcie215 *pcie215dev = (struct pcie215 *)dev_id; + + trace_pcie215_isr(0, pcie215dev->irq_n, pcie215dev->irq_spurious_n); + + spin_lock_irqsave(&pcie215dev->lock, flags); + + ++pcie215dev->irq_n; /* count IRQ */ + + enabled = pcie215dev->triggers; + irq_status = pcie215_ioread8(pcie215dev, PCIE215_IER, 1); + + if (!(irq_status & enabled)) { + ++pcie215dev->irq_spurious_n; + + trace_pcie215_isr(irq_status, pcie215dev->irq_n, + pcie215dev->irq_spurious_n); + +#if defined(DEBUG) + dev_info(&pcie215dev->pdev->dev, + "%s: IRQ spurious# [%llu] IRQ# [%llu] status [%02x]\n", + __func__, pcie215dev->irq_spurious_n, + pcie215dev->irq_n, irq_status); +#endif + + /* + * Reconfigure. + */ + pcie215_iowrite8(pcie215dev, pcie215dev->triggers, + PCIE215_IER, 1); + + spin_unlock_irqrestore(&pcie215dev->lock, flags); + + return IRQ_NONE; + } + + trace_pcie215_isr(irq_status, pcie215dev->irq_n, + pcie215dev->irq_spurious_n); + + /* + * Read IRQ status until all interrupt pins have been + * cleared in IRQ Enable/Status register. + * Disable already seen triggers from @enabled mask. + */ + triggered = irq_status; + enabled &= ~triggered; + pcie215_iowrite8(pcie215dev, enabled, PCIE215_IER, 1); + +#if defined(DEBUG) + dev_info(&pcie215dev->pdev->dev, + "%s: IRQ# [%llu] IRQ spurious# [%llu] status [%02x] trig [%02x] enabled [%02x]\n", + __func__, pcie215dev->irq_n, + pcie215dev->irq_spurious_n, + irq_status, triggered, enabled); +#endif + + while ((irq_status = (pcie215_ioread8(pcie215dev, PCIE215_IER, 1) + & enabled)) != 0) { + triggered |= irq_status; + enabled &= ~triggered; + pcie215_iowrite8(pcie215dev, enabled, PCIE215_IER, 1); + +#if defined(DEBUG) + dev_info(&pcie215dev->pdev->dev, + "%s: IRQ# [%llu] IRQ spurious# [%llu] status [%02x] trig [%02x] enabled [%02x]\n", + __func__, pcie215dev->irq_n, + pcie215dev->irq_spurious_n, irq_status, + triggered, enabled); +#endif + } + + /* + * Enable triggering. + */ + pcie215_iowrite8(pcie215dev, pcie215dev->triggers, PCIE215_IER, 1); + + spin_unlock_irqrestore(&pcie215dev->lock, flags); + + /* + * Set IRQ pending flag and wake up all processes waiting + * for external interrupt in interruptible state. Sync with + * user space thread to avoid reschedule and CPU bouncing + * (this is useful on UP too). + */ + set_bit(PCIE215_FLAGS_IRQ, &pcie215dev->flags); + __wake_up_sync(&pcie215dev->wait_queue, TASK_INTERRUPTIBLE, 0); + + return IRQ_HANDLED; +} + +/** + * pcie215_fops_open - Open the driver. + */ +static int pcie215_fops_open(struct inode *inode, struct file *filp) +{ + filp->private_data = &pcie215_device; + return 0; +} + +/** + * pcie215_fops_release - Close the driver. + */ +static int pcie215_fops_release(struct inode *inode, struct file *filp) +{ + filp->private_data = NULL; + return 0; +} + +/** + * pcie215_fops_unlocked_ioctl - Execute ioctl on the driver. + * @filp: file handle + * @cmd: ioctl identifier. This identifier may be one of: + * + * PCIE215_IOCTL_IRQ_ENABLE + * Turns on/off trigggring of the interrupts on the global basis, + * i.e. by writing to Altera Cyclone IV FPGA's registers. + * If turned off interrupts are not generated for enabled pins + * even if these pins are asserted. If turned on, interrupts + * are generated when enabled pins get asserted. + * + * PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE + * Configures pins for interrupt triggering. Only pins configured + * this way will wake up the blocked processes. Signals asserted + * on other pins are ignored. By default all pins are considered + * "disabled" (unless triggers=X has been passed to the insmod, + * in which case pins given in X bit mask are enabled). + * + * PCIE215_IOCTL_IRQ_TRIGGERS_DISABLE + * Disables specified pins from the mask of enabled pins. + * + * @arg: ioctl parameter. The meaning of the value of this parameter + * is considered in the context of the ioctl identifier @cmd, i.e: + * + * PCIE215_IOCTL_IRQ_ENABLE: + * @arg can be 0 for global disable + * or 1 for global enable. + * + * PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE + * @arg is a bit mask of pins allowed for interrupt + * triggering. Enabling new pins _adds_ them + * to the current mask (history is kept). + * Bit 0 is PPI-X-C0 (pin 44) + * Bit 1 is PPI-X-C3 (pin 24) + * Bit 2 is PPI-Y-C0 (pin 70) + * Bit 3 is PPI-Y-C3 (pin 11) + * Bit 4 is CTRZ1-OUT1-O/P (pin 55) - not used + * Bit 5 is CTRZ2-OUT1-O/P (pin 58) - not used + * + * PCIE215_IOCTL_IRQ_TRIGGERS_DISABLE + * @arg is a bit mask of pins removed from the bitmask + * of pins allowed for interrupt triggering. Disabling + * pins _subtructs_ them from the current mask. + * Previously enabled pins which are not specified + * in the @arg remain enabled. + * + * Return: 0 if ioctl successful, error code otherwise. + */ +static long pcie215_fops_unlocked_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + unsigned long flags; + __u8 triggers = 0; + struct pcie215 *pcie215dev = filp->private_data; + + if (!pcie215dev) + return -ENODEV; + + if (_IOC_TYPE(cmd) != PCIE215_IOCTL_MAGIC) + return -ENOTTY; + + switch (cmd) { + case PCIE215_IOCTL_IRQ_ENABLE: + if (arg) { + /* Enable interrupt triggering. */ + spin_lock_irqsave(&pcie215dev->lock, flags); + pcie215_irq_enable(pcie215dev); + spin_unlock_irqrestore(&pcie215dev->lock, flags); + } else { + /* Disable interrupt triggering. */ + spin_lock_irqsave(&pcie215dev->lock, flags); + pcie215_irq_disable(pcie215dev); + clear_bit(PCIE215_FLAGS_IRQ, &pcie215dev->flags); + spin_unlock_irqrestore(&pcie215dev->lock, flags); + } + break; + + case PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE: + + /* Enable interrupt triggering for specified pins. */ + spin_lock_irqsave(&pcie215dev->lock, flags); + triggers = arg & PCIE215_IRQ_SRC_MASK; + pcie215dev->triggers |= triggers; + pcie215_irq_triggers_enable(pcie215dev, pcie215dev->triggers); + spin_unlock_irqrestore(&pcie215dev->lock, flags); + break; + + case PCIE215_IOCTL_IRQ_TRIGGERS_DISABLE: + + /* Disable interrupt triggering for specified pins. */ + spin_lock_irqsave(&pcie215dev->lock, flags); + + /* Update enabled pins. */ + triggers = arg & PCIE215_IRQ_SRC_MASK; + pcie215dev->triggers &= ~triggers; + + /* And reconfigure hardware. */ + pcie215_irq_triggers_enable(pcie215dev, pcie215dev->triggers); + spin_unlock_irqrestore(&pcie215dev->lock, flags); + break; + + default: + return -ENOTTY; + } + + return 0; +} + +static long pcie215_fops_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + return pcie215_fops_unlocked_ioctl(filp, cmd, arg); +} + +/** + * pcie215_fops_read - Put process to sleep until signal is asserted. + * + * Return: 0 if interrupted due to physical signal being asserted + * on configured pin(s), -ERESTARTSYS if interrupted due to software + * signal delivered to the process. + */ +static ssize_t pcie215_fops_read(struct file *filp, char __user *buf, + size_t bytes_n, loff_t *offset) +{ + struct pcie215 *pcie215dev = filp->private_data; + + trace_pcie215_event(0); + + /* + * Clear IRQ pending flag + */ + clear_bit(PCIE215_FLAGS_IRQ, &pcie215dev->flags); + + /* + * Put process to sleep until any interrupt is triggered + * due to the signal being asserted on configured pin(s). + * Allow to be interrupted by signals. + */ + return wait_event_interruptible(pcie215dev->wait_queue, + test_bit(PCIE215_FLAGS_IRQ, + &pcie215dev->flags)); +} + +static const struct file_operations pcie215_fops = { + .owner = THIS_MODULE, + .open = pcie215_fops_open, + .release = pcie215_fops_release, + .unlocked_ioctl = pcie215_fops_unlocked_ioctl, + .compat_ioctl = pcie215_fops_compat_ioctl, + .read = pcie215_fops_read, +}; + +static struct miscdevice pcie215_misc_dev_t = { + .name = PCIE215, + .fops = &pcie215_fops, + .parent = NULL, + .this_device = NULL, + .nodename = NULL, + .mode = 0666, +}; + +/** + * pcie215_config_print - Dump driver's configuration. + */ +static void pcie215_config_print(struct pcie215 *pcie215dev) +{ + u8 pin; + struct pci_dev *pdev = pcie215dev->pdev; + + pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pin); + + dev_info(&pdev->dev, "Attached pcie215 to PCI [%s]: interrupt [%ld] pin [%u] major [%u]", + pci_name(pdev), pcie215dev->irq, pin, pcie215dev->major); + + if (pcie215dev->ba_iomem) + dev_info(&pdev->dev, "Base Address Configuration Register [%p] (I/O memory)", + pcie215dev->ba_iomem); + else + dev_info(&pdev->dev, "Base Address Configuration Register [%08lx]", + pcie215dev->ba); + + if (pcie215dev->lcr_iomem) + dev_info(&pdev->dev, "Local Bus Configuration Register [%p] (I/O memory)", + pcie215dev->lcr_iomem); + else + dev_info(&pdev->dev, "Local Bus Configuration Register [%08lx]", + pcie215dev->lcr); + + dev_info(&pdev->dev, "8255 configured as [%02x]", + pcie215dev->ppi_8255_config); +} + +/** + * pcie215_probe - Driver's and hardware initialization. + * + * This function performs initialization of the PCIe215 board + * and of the pcie215 driver. Apart from standard steps required + * to initialise PCIe hardware driver does these additional steps: + * + * - Initialise 8255 PPI + * - Initialise 8254 Timer/Counter + * - Disable generation of the interrupts + * - If triggers=X was passed to the insmod, then: + * 1. Enable interrupt triggering on pins + * given in a bit mask X + * 2. If X > 0 then enable IRQ generation + * on a global basis + * + * Return : 0 on success, negative error code otherwise. + */ +static int pcie215_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + int err = 0; + struct pcie215 *pcie215dev = &pcie215_device; + unsigned int bar = 0; + + dev_info(&pdev->dev, "%s: Probing...\n", __func__); + + if (!pdev) + return -ENODEV; + + init_waitqueue_head(&pcie215dev->wait_queue); + spin_lock_init(&pcie215dev->lock); + + pci_set_drvdata(pdev, pcie215dev); + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "%s: pci_enable_device failed: %d\n", + __func__, err); + return err; + } + + err = pci_request_regions(pdev, "pcie215"); + if (err) { + dev_err(&pdev->dev, "%s: pci_request_regions failed: %d\n", + __func__, err); + goto err_disable; + } + + if (!pci_is_pcie(pdev)) { + dev_err(&pdev->dev, "Aborting - cannot find PCI Express capability\n"); + err = -ENODEV; + goto err_disable; + } + + /* + * Map Local Bus register space. + */ + bar = 0; + if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) { + pcie215dev->lcr_iomem = pci_ioremap_bar(pdev, bar); + if (!pcie215dev->lcr_iomem) { + dev_err(&pdev->dev, "%s: pci_ioremap failed, can't remap Local Bus configuration register space: %d\n", + __func__, err); + err = -ENOMEM; + goto err_release_regions; + } + } else { + pcie215dev->lcr = pci_resource_start(pdev, bar); + } + + /* + * Map Base Address register space. + */ + bar = 1; + if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) { + pcie215dev->ba_iomem = pci_ioremap_bar(pdev, bar); + if (!pcie215dev->ba_iomem) { + dev_err(&pdev->dev, "%s: pci_ioremap failed, can't remap Base Address configuration register space: %d\n", + __func__, err); + err = -ENOMEM; + goto err_release_regions; + } + } else { + pcie215dev->ba = pci_resource_start(pdev, bar); + } + + /* Initialise 8255 PPI. */ + pcie215_init_8255(pcie215dev); + + /* Initialise 8254 Timer/Counter. */ + pcie215_init_8254(pcie215dev); + + if (!pdev->irq) { + dev_err(&pdev->dev, "%s: No IRQ assigned to the device\n", + __func__); + err = -ENODEV; + goto err_release_regions; + } + + if (!pci_find_capability(pdev, PCI_CAP_ID_PM)) { + dev_err(&pdev->dev, "%s: No support for power managemet on this device\n", + __func__); + } + + pcie215dev->irq = pdev->irq; + pcie215dev->pdev = pdev; + + /* Disable PCIe interrupts generation. */ + pcie215_irq_disable(pcie215dev); + + err = request_irq(pcie215dev->irq, pcie215_isr, IRQF_SHARED, PCIE215, + pcie215dev); + if (err) { + dev_err(&pdev->dev, "%s: Failed to register ISR\n", + __func__); + err = -ENODEV; + goto err_release_regions; + } + + /* Request dynamic major number. */ + err = misc_register(&pcie215_misc_dev_t); + if (err) { + dev_err(&pdev->dev, "%s: Err, can't register character device", + __func__); + err = -ENODEV; + goto err_release_regions; + } + + pcie215dev->major = MAJOR(pcie215_misc_dev_t.this_device->devt); + + /* Dump the config. */ + pcie215_config_print(pcie215dev); + + if (triggers) { + /* + * If the triggers parameter has been given on the insmod + * commandline or in the modprobe config, then enable triggers. + */ + triggers &= PCIE215_IRQ_SRC_MASK; + pcie215dev->triggers = triggers; + pcie215_irq_triggers_enable(pcie215dev, triggers); + pcie215_irq_enable(pcie215dev); + + dev_info(&pdev->dev, "%s: Enabled IRQ generation and triggers [%02x]", + __func__, pcie215dev->triggers); + } + + return err; + +err_release_regions: + + if (pcie215dev->ba_iomem) { + pci_iounmap(pdev, pcie215dev->ba_iomem); + pcie215dev->ba_iomem = NULL; + } + + if (pcie215dev->lcr_iomem) { + pci_iounmap(pdev, pcie215dev->lcr_iomem); + pcie215dev->lcr_iomem = NULL; + } + + pci_release_regions(pdev); + +err_disable: + + pci_disable_device(pdev); + + return err; +} + +/** + * pcie215_remove - Driver's and hardware deinitialisation. + * + * This function performs deinitialisation of the PCIe215 board + * and of the pcie215 driver. Apart from standard steps required + * to deinitialize PCIe hardware driver does these additional steps: + * + * - Disable interrupts generation + */ +static void pcie215_remove(struct pci_dev *pdev) +{ + struct pcie215 *pcie215dev = pci_get_drvdata(pdev); + + dev_info(&pdev->dev, "%s: Removing... IRQ# [%llu] IRQ spurious# [%llu] major [%u]\n", + __func__, pcie215dev->irq_n, + pcie215dev->irq_spurious_n, pcie215dev->major); + + if (!pdev || !pcie215dev) + return; + + /* + * Disable all IRQ sources and interrupts generation. + */ + pcie215_irq_disable(pcie215dev); + + if (pcie215dev && pcie215dev->irq) { + free_irq(pcie215dev->irq, pcie215dev); + pcie215dev->irq = 0; + } + + if (pcie215dev->ba_iomem) { + pci_iounmap(pdev, pcie215dev->ba_iomem); + pcie215dev->ba_iomem = NULL; + } + + if (pcie215dev->lcr_iomem) { + pci_iounmap(pdev, pcie215dev->lcr_iomem); + pcie215dev->lcr_iomem = NULL; + } + + pci_release_regions(pdev); + pci_disable_device(pdev); + + misc_deregister(&pcie215_misc_dev_t); +} + +/** + * pcie215_suspend - Suspend PCIe215 board. + * + * This function handles suspend callback of the power management system. + * Apart from standard steps required to do it driver does these + * additional steps: + * + * - Disable pins by writing 0 to IER + * - Disable PCIe interrupts generation + * + * Return : 0 on success, negative error code otherwise. + */ +static int pcie215_suspend(struct device *dev) +{ + unsigned long flags; + + struct pci_dev *pdev = to_pci_dev(dev); + struct pcie215 *pcie215dev = pci_get_drvdata(pdev); + + spin_lock_irqsave(&pcie215dev->lock, flags); + + /* + * Disable interrupt triggering before we go to power save state + */ + pcie215_irq_triggers_enable(pcie215dev, 0); + pcie215_irq_disable(pcie215dev); + + spin_unlock_irqrestore(&pcie215dev->lock, flags); + + /* + * Let the PCI subsystem do the rest (save the PCI standard + * configuration registers, prepare it for system wakeup + * (if necessary), and to put it into a low-power state). + */ + + return 0; +} + +/** + * pcie215_resume - Resume the PCIe215 board from the suspension. + * + * This function handles resume callback of the power management system. + * Apart from standard steps required to do it driver does these + * additional steps: + * + * - Enable generation of interrupts + * - Configure pins according to stored mask of enabled triggers + * + * Return : 0 on success, negative error code otherwise. + */ +static int pcie215_resume(struct device *dev) +{ + unsigned long flags; + struct pci_dev *pdev = to_pci_dev(dev); + struct pcie215 *pcie215dev = pci_get_drvdata(pdev); + + spin_lock_irqsave(&pcie215dev->lock, flags); + + /* Enable triggering of interrupts */ + pcie215_irq_triggers_enable(pcie215dev, pcie215dev->triggers); + pcie215_irq_enable(pcie215dev); + + spin_unlock_irqrestore(&pcie215dev->lock, flags); + + /* + * Let the PCI subsystem do the rest (restoring PCI standard + * registers, etc). + */ + + return 0; +} + +static const struct pci_device_id pcie215_supported_devices[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE215) }, + { } +}; + +static const struct dev_pm_ops pcie215_pm_ops = { + .suspend = pcie215_suspend, + .resume = pcie215_resume, +}; + +static struct pci_driver pcie215_driver = { + .name = PCIE215, + .id_table = pcie215_supported_devices, + .probe = pcie215_probe, + .remove = pcie215_remove, + .driver.pm = &pcie215_pm_ops, +}; + +module_pci_driver(pcie215_driver); +MODULE_VERSION(PCIE215_DRV_VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR(PCIE215_DRV_AUTHOR); +MODULE_DESCRIPTION(PCIE215_DRV_DESC); diff --git a/drivers/staging/pcie215/pcie215_ioctl.h b/drivers/staging/pcie215/pcie215_ioctl.h new file mode 100644 index 000000000000..1e66dc56dcd5 --- /dev/null +++ b/drivers/staging/pcie215/pcie215_ioctl.h @@ -0,0 +1,22 @@ +#ifndef PCIE215_IOCTL_H +#define PCIE215_IOCTL_H + +#ifndef __KERNEL__ + #include <asm/ioctl.h> +#endif + +/* + * Ioctl interface. + */ +#define PCIE215_IOCTL_MAGIC '4' + +/* Enable/Disable interrupts triggering by Altera fpga on PCIe215 */ +#define PCIE215_IOCTL_IRQ_ENABLE _IOW(PCIE215_IOCTL_MAGIC, 0, \ + unsigned long) +/* Enable/Disable pins as interrupt triggers on PCIe215 */ +#define PCIE215_IOCTL_IRQ_TRIGGERS_ENABLE _IOW(PCIE215_IOCTL_MAGIC, 1, \ + unsigned long) +#define PCIE215_IOCTL_IRQ_TRIGGERS_DISABLE _IOW(PCIE215_IOCTL_MAGIC, 2, \ + unsigned long) + +#endif // PCIE215_IOCTL_H -- 2.11.0 _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel