405 lines
13 KiB
C
405 lines
13 KiB
C
// 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 0x68
|
|
#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 0x0
|
|
#define IDIO_24_TTLCMOS_OUT_REG 0x3
|
|
#define IDIO_24_IN_BASE 0x4
|
|
#define IDIO_24_TTLCMOS_IN_REG 0x7
|
|
#define IDIO_24_COS_STATUS_BASE 0x8
|
|
#define IDIO_24_CONTROL_REG 0xC
|
|
#define IDIO_24_COS_ENABLE 0xE
|
|
#define IDIO_24_SOFT_RESET 0xF
|
|
|
|
#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(0x0, 0x3), regmap_reg_range(0x8, 0xC),
|
|
regmap_reg_range(0xE, 0xF),
|
|
};
|
|
static const struct regmap_range idio_24_rd_ranges[] = {
|
|
regmap_reg_range(0x0, 0xC), regmap_reg_range(0xE, 0xF),
|
|
};
|
|
static const struct regmap_range idio_24_volatile_ranges[] = {
|
|
regmap_reg_range(0x4, 0xB), regmap_reg_range(0xF, 0xF),
|
|
};
|
|
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 -EOPNOTSUPP;
|
|
|
|
*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) {
|
|
dev_err(dev, "Failed to enable PCI device (%d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
err = pcim_iomap_regions(pdev, BIT(pci_plx_bar_index) | BIT(pci_bar_index), name);
|
|
if (err) {
|
|
dev_err(dev, "Unable to map PCI I/O addresses (%d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
pex8311_regs = pcim_iomap_table(pdev)[pci_plx_bar_index];
|
|
idio_24_regs = pcim_iomap_table(pdev)[pci_bar_index];
|
|
|
|
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, 0x0, 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(0x494F, 0x0FD0) }, { PCI_DEVICE(0x494F, 0x0BD0) },
|
|
{ PCI_DEVICE(0x494F, 0x07D0) }, { PCI_DEVICE(0x494F, 0x0FC0) },
|
|
{ 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");
|