// SPDX-License-Identifier: GPL-2.0-only
/*
* GPIO driver for the ACCES PCIe - IDIO - 24 family
* Copyright ( C ) 2018 William Breathitt Gray
*
* This driver supports the following ACCES devices : PCIe - IDIO - 24 ,
* PCIe - IDI - 24 , PCIe - IDO - 24 , and PCIe - IDIO - 12 .
*/
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/regmap.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/regmap.h>
#include <linux/spinlock.h>
#include <linux/types.h>
/*
* PLX PEX8311 PCI LCS_INTCSR Interrupt Control / Status
*
* Bit : Description
* 0 : Enable Interrupt Sources ( Bit 0 )
* 1 : Enable Interrupt Sources ( Bit 1 )
* 2 : Generate Internal PCI Bus Internal SERR # Interrupt
* 3 : Mailbox Interrupt Enable
* 4 : Power Management Interrupt Enable
* 5 : Power Management Interrupt
* 6 : Slave Read Local Data Parity Check Error Enable
* 7 : Slave Read Local Data Parity Check Error Status
* 8 : Internal PCI Wire Interrupt Enable
* 9 : PCI Express Doorbell Interrupt Enable
* 10 : PCI Abort Interrupt Enable
* 11 : Local Interrupt Input Enable
* 12 : Retry Abort Enable
* 13 : PCI Express Doorbell Interrupt Active
* 14 : PCI Abort Interrupt Active
* 15 : Local Interrupt Input Active
* 16 : Local Interrupt Output Enable
* 17 : Local Doorbell Interrupt Enable
* 18 : DMA Channel 0 Interrupt Enable
* 19 : DMA Channel 1 Interrupt Enable
* 20 : Local Doorbell Interrupt Active
* 21 : DMA Channel 0 Interrupt Active
* 22 : DMA Channel 1 Interrupt Active
* 23 : Built - In Self - Test ( BIST ) Interrupt Active
* 24 : Direct Master was the Bus Master during a Master or Target Abort
* 25 : DMA Channel 0 was the Bus Master during a Master or Target Abort
* 26 : DMA Channel 1 was the Bus Master during a Master or Target Abort
* 27 : Target Abort after internal 256 consecutive Master Retrys
* 28 : PCI Bus wrote data to LCS_MBOX0
* 29 : PCI Bus wrote data to LCS_MBOX1
* 30 : PCI Bus wrote data to LCS_MBOX2
* 31 : PCI Bus wrote data to LCS_MBOX3
*/
#define PLX_PEX8311_PCI_LCS_INTCSR 0 x68
#define INTCSR_INTERNAL_PCI_WIRE BIT(8 )
#define INTCSR_LOCAL_INPUT BIT(11 )
#define IDIO_24_ENABLE_IRQ (INTCSR_INTERNAL_PCI_WIRE | INTCSR_LOCAL_INPUT)
#define IDIO_24_OUT_BASE 0 x0
#define IDIO_24_TTLCMOS_OUT_REG 0 x3
#define IDIO_24_IN_BASE 0 x4
#define IDIO_24_TTLCMOS_IN_REG 0 x7
#define IDIO_24_COS_STATUS_BASE 0 x8
#define IDIO_24_CONTROL_REG 0 xC
#define IDIO_24_COS_ENABLE 0 xE
#define IDIO_24_SOFT_RESET 0 xF
#define CONTROL_REG_OUT_MODE BIT(1 )
#define COS_ENABLE_RISING BIT(1 )
#define COS_ENABLE_FALLING BIT(4 )
#define COS_ENABLE_BOTH (COS_ENABLE_RISING | COS_ENABLE_FALLING)
static const struct regmap_config pex8311_intcsr_regmap_config = {
.name = "pex8311_intcsr" ,
.reg_bits = 32 ,
.reg_stride = 1 ,
.reg_base = PLX_PEX8311_PCI_LCS_INTCSR,
.val_bits = 32 ,
.io_port = true ,
};
static const struct regmap_range idio_24_wr_ranges[] = {
regmap_reg_range(0 x0, 0 x3), regmap_reg_range(0 x8, 0 xC),
regmap_reg_range(0 xE, 0 xF),
};
static const struct regmap_range idio_24_rd_ranges[] = {
regmap_reg_range(0 x0, 0 xC), regmap_reg_range(0 xE, 0 xF),
};
static const struct regmap_range idio_24_volatile_ranges[] = {
regmap_reg_range(0 x4, 0 xB), regmap_reg_range(0 xF, 0 xF),
};
static const struct regmap_access_table idio_24_wr_table = {
.yes_ranges = idio_24_wr_ranges,
.n_yes_ranges = ARRAY_SIZE(idio_24_wr_ranges),
};
static const struct regmap_access_table idio_24_rd_table = {
.yes_ranges = idio_24_rd_ranges,
.n_yes_ranges = ARRAY_SIZE(idio_24_rd_ranges),
};
static const struct regmap_access_table idio_24_volatile_table = {
.yes_ranges = idio_24_volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(idio_24_volatile_ranges),
};
static const struct regmap_config idio_24_regmap_config = {
.reg_bits = 8 ,
.reg_stride = 1 ,
.val_bits = 8 ,
.io_port = true ,
.wr_table = &idio_24_wr_table,
.rd_table = &idio_24_rd_table,
.volatile_table = &idio_24_volatile_table,
.cache_type = REGCACHE_FLAT,
.use_raw_spinlock = true ,
};
#define IDIO_24_NGPIO_PER_REG 8
#define IDIO_24_REGMAP_IRQ(_id) \
[24 + _id] = { \
.reg_offset = (_id) / IDIO_24_NGPIO_PER_REG, \
.mask = BIT((_id) % IDIO_24_NGPIO_PER_REG), \
.type = { .types_supported = IRQ_TYPE_EDGE_BOTH }, \
}
#define IDIO_24_IIN_IRQ(_id) IDIO_24_REGMAP_IRQ(_id)
#define IDIO_24_TTL_IRQ(_id) IDIO_24_REGMAP_IRQ(24 + _id)
static const struct regmap_irq idio_24_regmap_irqs[] = {
IDIO_24_IIN_IRQ(0 ), IDIO_24_IIN_IRQ(1 ), IDIO_24_IIN_IRQ(2 ), /* IIN 0-2 */
IDIO_24_IIN_IRQ(3 ), IDIO_24_IIN_IRQ(4 ), IDIO_24_IIN_IRQ(5 ), /* IIN 3-5 */
IDIO_24_IIN_IRQ(6 ), IDIO_24_IIN_IRQ(7 ), IDIO_24_IIN_IRQ(8 ), /* IIN 6-8 */
IDIO_24_IIN_IRQ(9 ), IDIO_24_IIN_IRQ(10 ), IDIO_24_IIN_IRQ(11 ), /* IIN 9-11 */
IDIO_24_IIN_IRQ(12 ), IDIO_24_IIN_IRQ(13 ), IDIO_24_IIN_IRQ(14 ), /* IIN 12-14 */
IDIO_24_IIN_IRQ(15 ), IDIO_24_IIN_IRQ(16 ), IDIO_24_IIN_IRQ(17 ), /* IIN 15-17 */
IDIO_24_IIN_IRQ(18 ), IDIO_24_IIN_IRQ(19 ), IDIO_24_IIN_IRQ(20 ), /* IIN 18-20 */
IDIO_24_IIN_IRQ(21 ), IDIO_24_IIN_IRQ(22 ), IDIO_24_IIN_IRQ(23 ), /* IIN 21-23 */
IDIO_24_TTL_IRQ(0 ), IDIO_24_TTL_IRQ(1 ), IDIO_24_TTL_IRQ(2 ), /* TTL 0-2 */
IDIO_24_TTL_IRQ(3 ), IDIO_24_TTL_IRQ(4 ), IDIO_24_TTL_IRQ(5 ), /* TTL 3-5 */
IDIO_24_TTL_IRQ(6 ), IDIO_24_TTL_IRQ(7 ), /* TTL 6-7 */
};
/**
* struct idio_24_gpio - GPIO device private data structure
* @ map : regmap for the device
* @ lock : synchronization lock to prevent I / O race conditions
* @ irq_type : type configuration for IRQs
*/
struct idio_24_gpio {
struct regmap *map;
raw_spinlock_t lock;
u8 irq_type;
};
static int idio_24_handle_mask_sync(const int index, const unsigned int mask_buf_def,
const unsigned int mask_buf, void *const irq_drv_data)
{
const unsigned int type_mask = COS_ENABLE_BOTH << index;
struct idio_24_gpio *const idio24gpio = irq_drv_data;
u8 type;
int ret;
raw_spin_lock(&idio24gpio->lock);
/* if all are masked, then disable interrupts, else set to type */
type = (mask_buf == mask_buf_def) ? ~type_mask : idio24gpio->irq_type;
ret = regmap_update_bits(idio24gpio->map, IDIO_24_COS_ENABLE, type_mask, type);
raw_spin_unlock(&idio24gpio->lock);
return ret;
}
static int idio_24_set_type_config(unsigned int **const buf, const unsigned int type,
const struct regmap_irq *const irq_data, const int idx,
void *const irq_drv_data)
{
const unsigned int offset = irq_data->reg_offset;
const unsigned int rising = COS_ENABLE_RISING << offset;
const unsigned int falling = COS_ENABLE_FALLING << offset;
const unsigned int mask = COS_ENABLE_BOTH << offset;
struct idio_24_gpio *const idio24gpio = irq_drv_data;
unsigned int new ;
unsigned int cos_enable;
int ret;
switch (type) {
case IRQ_TYPE_EDGE_RISING:
new = rising;
break ;
case IRQ_TYPE_EDGE_FALLING:
new = falling;
break ;
case IRQ_TYPE_EDGE_BOTH:
new = mask;
break ;
default :
return -EINVAL;
}
raw_spin_lock(&idio24gpio->lock);
/* replace old bitmap with new bitmap */
idio24gpio->irq_type = (idio24gpio->irq_type & ~mask) | (new & mask);
ret = regmap_read(idio24gpio->map, IDIO_24_COS_ENABLE, &cos_enable);
if (ret)
goto exit_unlock;
/* if COS is currently enabled then update the edge type */
if (cos_enable & mask) {
ret = regmap_update_bits(idio24gpio->map, IDIO_24_COS_ENABLE, mask,
idio24gpio->irq_type);
if (ret)
goto exit_unlock;
}
exit_unlock:
raw_spin_unlock(&idio24gpio->lock);
return ret;
}
static int idio_24_reg_mask_xlate(struct gpio_regmap *const gpio, const unsigned int base,
const unsigned int offset, unsigned int *const reg,
unsigned int *const mask)
{
const unsigned int out_stride = offset / IDIO_24_NGPIO_PER_REG;
const unsigned int in_stride = (offset - 24 ) / IDIO_24_NGPIO_PER_REG;
struct regmap *const map = gpio_regmap_get_drvdata(gpio);
int err;
unsigned int ctrl_reg;
switch (base) {
case IDIO_24_OUT_BASE:
*mask = BIT(offset % IDIO_24_NGPIO_PER_REG);
/* FET Outputs */
if (offset < 24 ) {
*reg = IDIO_24_OUT_BASE + out_stride;
return 0 ;
}
/* Isolated Inputs */
if (offset < 48 ) {
*reg = IDIO_24_IN_BASE + in_stride;
return 0 ;
}
err = regmap_read(map, IDIO_24_CONTROL_REG, &ctrl_reg);
if (err)
return err;
/* TTL/CMOS Outputs */
if (ctrl_reg & CONTROL_REG_OUT_MODE) {
*reg = IDIO_24_TTLCMOS_OUT_REG;
return 0 ;
}
/* TTL/CMOS Inputs */
*reg = IDIO_24_TTLCMOS_IN_REG;
return 0 ;
case IDIO_24_CONTROL_REG:
/* We can only set direction for TTL/CMOS lines */
if (offset < 48 )
return -ENOTSUPP;
*reg = IDIO_24_CONTROL_REG;
*mask = CONTROL_REG_OUT_MODE;
return 0 ;
default :
/* Should never reach this path */
return -EINVAL;
}
}
#define IDIO_24_NGPIO 56
static const char *idio_24_names[IDIO_24_NGPIO] = {
"OUT0" , "OUT1" , "OUT2" , "OUT3" , "OUT4" , "OUT5" , "OUT6" , "OUT7" ,
"OUT8" , "OUT9" , "OUT10" , "OUT11" , "OUT12" , "OUT13" , "OUT14" , "OUT15" ,
"OUT16" , "OUT17" , "OUT18" , "OUT19" , "OUT20" , "OUT21" , "OUT22" , "OUT23" ,
"IIN0" , "IIN1" , "IIN2" , "IIN3" , "IIN4" , "IIN5" , "IIN6" , "IIN7" ,
"IIN8" , "IIN9" , "IIN10" , "IIN11" , "IIN12" , "IIN13" , "IIN14" , "IIN15" ,
"IIN16" , "IIN17" , "IIN18" , "IIN19" , "IIN20" , "IIN21" , "IIN22" , "IIN23" ,
"TTL0" , "TTL1" , "TTL2" , "TTL3" , "TTL4" , "TTL5" , "TTL6" , "TTL7"
};
static int idio_24_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct device *const dev = &pdev->dev;
struct idio_24_gpio *idio24gpio;
int err;
const size_t pci_plx_bar_index = 1 ;
const size_t pci_bar_index = 2 ;
const char *const name = pci_name(pdev);
struct gpio_regmap_config gpio_config = {};
void __iomem *pex8311_regs;
void __iomem *idio_24_regs;
struct regmap *intcsr_map;
struct regmap_irq_chip *chip;
struct regmap_irq_chip_data *chip_data;
err = pcim_enable_device(pdev);
if (err)
return dev_err_probe(dev, err, "Failed to enable PCI device\n" );
pex8311_regs = pcim_iomap_region(pdev, pci_plx_bar_index, "pex8311" );
if (IS_ERR(pex8311_regs))
return dev_err_probe(dev, PTR_ERR(pex8311_regs), "Unable to map PEX 8311 I/O addresses\n" );
idio_24_regs = pcim_iomap_region(pdev, pci_bar_index, name);
if (IS_ERR(idio_24_regs))
return dev_err_probe(dev, PTR_ERR(idio_24_regs), "Unable to map PCIe-IDIO-24 I/O addresses\n" );
intcsr_map = devm_regmap_init_mmio(dev, pex8311_regs, &pex8311_intcsr_regmap_config);
if (IS_ERR(intcsr_map))
return dev_err_probe(dev, PTR_ERR(intcsr_map),
"Unable to initialize PEX8311 register map\n" );
idio24gpio = devm_kzalloc(dev, sizeof (*idio24gpio), GFP_KERNEL);
if (!idio24gpio)
return -ENOMEM;
idio24gpio->map = devm_regmap_init_mmio(dev, idio_24_regs, &idio_24_regmap_config);
if (IS_ERR(idio24gpio->map))
return dev_err_probe(dev, PTR_ERR(idio24gpio->map),
"Unable to initialize register map\n" );
raw_spin_lock_init(&idio24gpio->lock);
/* Initialize all IRQ type configuration to IRQ_TYPE_EDGE_BOTH */
idio24gpio->irq_type = GENMASK(7 , 0 );
chip = devm_kzalloc(dev, sizeof (*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->name = name;
chip->status_base = IDIO_24_COS_STATUS_BASE;
chip->mask_base = IDIO_24_COS_ENABLE;
chip->ack_base = IDIO_24_COS_STATUS_BASE;
chip->num_regs = 4 ;
chip->irqs = idio_24_regmap_irqs;
chip->num_irqs = ARRAY_SIZE(idio_24_regmap_irqs);
chip->handle_mask_sync = idio_24_handle_mask_sync;
chip->set_type_config = idio_24_set_type_config;
chip->irq_drv_data = idio24gpio;
/* Software board reset */
err = regmap_write(idio24gpio->map, IDIO_24_SOFT_RESET, 0 );
if (err)
return err;
/*
* enable PLX PEX8311 internal PCI wire interrupt and local interrupt
* input
*/
err = regmap_update_bits(intcsr_map, 0 x0, IDIO_24_ENABLE_IRQ, IDIO_24_ENABLE_IRQ);
if (err)
return err;
err = devm_regmap_add_irq_chip(dev, idio24gpio->map, pdev->irq, 0 , 0 , chip, &chip_data);
if (err)
return dev_err_probe(dev, err, "IRQ registration failed\n" );
gpio_config.parent = dev;
gpio_config.regmap = idio24gpio->map;
gpio_config.ngpio = IDIO_24_NGPIO;
gpio_config.names = idio_24_names;
gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(IDIO_24_OUT_BASE);
gpio_config.reg_set_base = GPIO_REGMAP_ADDR(IDIO_24_OUT_BASE);
gpio_config.reg_dir_out_base = GPIO_REGMAP_ADDR(IDIO_24_CONTROL_REG);
gpio_config.ngpio_per_reg = IDIO_24_NGPIO_PER_REG;
gpio_config.irq_domain = regmap_irq_get_domain(chip_data);
gpio_config.reg_mask_xlate = idio_24_reg_mask_xlate;
gpio_config.drvdata = idio24gpio->map;
return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
}
static const struct pci_device_id idio_24_pci_dev_id[] = {
{ PCI_DEVICE(0 x494F, 0 x0FD0) }, { PCI_DEVICE(0 x494F, 0 x0BD0) },
{ PCI_DEVICE(0 x494F, 0 x07D0) }, { PCI_DEVICE(0 x494F, 0 x0FC0) },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, idio_24_pci_dev_id);
static struct pci_driver idio_24_driver = {
.name = "pcie-idio-24" ,
.id_table = idio_24_pci_dev_id,
.probe = idio_24_probe
};
module_pci_driver(idio_24_driver);
MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>" );
MODULE_DESCRIPTION("ACCES PCIe-IDIO-24 GPIO driver" );
MODULE_LICENSE("GPL v2" );
Messung V0.5 in Prozent C=92 H=97 G=94
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland