Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,14 @@
config GP_PCI1XXXX
tristate "Microchip PCI1XXXX PCIe to GPIO Expander + OTP/EEPROM manager"
depends on PCI
depends on GPIOLIB
depends on NVMEM_SYSFS
select GPIOLIB_IRQCHIP
select AUXILIARY_BUS
help
PCI1XXXX is a PCIe GEN 3 switch with one of the endpoints having
multiple functions and one of the functions is a GPIO controller
which also has registers to interface with the OTP and EEPROM.
Select yes, no or module here to include or exclude the driver
for the GPIO function.
@@ -0,0 +1 @@
obj-$(CONFIG_GP_PCI1XXXX) := mchp_pci1xxxx_gp.o mchp_pci1xxxx_gpio.o mchp_pci1xxxx_otpe2p.o
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2022 Microchip Technology Inc.
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/spinlock.h>
#include <linux/gpio/driver.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/idr.h>
#include "mchp_pci1xxxx_gp.h"
struct aux_bus_device {
struct auxiliary_device_wrapper *aux_device_wrapper[2];
};
static DEFINE_IDA(gp_client_ida);
static const char aux_dev_otp_e2p_name[15] = "gp_otp_e2p";
static const char aux_dev_gpio_name[15] = "gp_gpio";
static void gp_auxiliary_device_release(struct device *dev)
{
struct auxiliary_device_wrapper *aux_device_wrapper =
(struct auxiliary_device_wrapper *)container_of(dev,
struct auxiliary_device_wrapper, aux_dev.dev);
ida_free(&gp_client_ida, aux_device_wrapper->aux_dev.id);
kfree(aux_device_wrapper);
}
static int gp_aux_bus_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct aux_bus_device *aux_bus;
int retval;
retval = pcim_enable_device(pdev);
if (retval)
return retval;
aux_bus = devm_kzalloc(&pdev->dev, sizeof(*aux_bus), GFP_KERNEL);
if (!aux_bus)
return -ENOMEM;
aux_bus->aux_device_wrapper[0] = kzalloc_obj(*aux_bus->aux_device_wrapper[0]);
if (!aux_bus->aux_device_wrapper[0])
return -ENOMEM;
retval = ida_alloc(&gp_client_ida, GFP_KERNEL);
if (retval < 0)
goto err_ida_alloc_0;
aux_bus->aux_device_wrapper[0]->aux_dev.name = aux_dev_otp_e2p_name;
aux_bus->aux_device_wrapper[0]->aux_dev.dev.parent = &pdev->dev;
aux_bus->aux_device_wrapper[0]->aux_dev.dev.release = gp_auxiliary_device_release;
aux_bus->aux_device_wrapper[0]->aux_dev.id = retval;
aux_bus->aux_device_wrapper[0]->gp_aux_data.region_start = pci_resource_start(pdev, 0);
aux_bus->aux_device_wrapper[0]->gp_aux_data.region_length = pci_resource_end(pdev, 0);
retval = auxiliary_device_init(&aux_bus->aux_device_wrapper[0]->aux_dev);
if (retval < 0)
goto err_aux_dev_init_0;
retval = auxiliary_device_add(&aux_bus->aux_device_wrapper[0]->aux_dev);
if (retval)
goto err_aux_dev_add_0;
aux_bus->aux_device_wrapper[1] = kzalloc_obj(*aux_bus->aux_device_wrapper[1]);
if (!aux_bus->aux_device_wrapper[1]) {
retval = -ENOMEM;
goto err_aux_dev_add_0;
}
retval = ida_alloc(&gp_client_ida, GFP_KERNEL);
if (retval < 0)
goto err_ida_alloc_1;
aux_bus->aux_device_wrapper[1]->aux_dev.name = aux_dev_gpio_name;
aux_bus->aux_device_wrapper[1]->aux_dev.dev.parent = &pdev->dev;
aux_bus->aux_device_wrapper[1]->aux_dev.dev.release = gp_auxiliary_device_release;
aux_bus->aux_device_wrapper[1]->aux_dev.id = retval;
aux_bus->aux_device_wrapper[1]->gp_aux_data.region_start = pci_resource_start(pdev, 0);
aux_bus->aux_device_wrapper[1]->gp_aux_data.region_length = pci_resource_end(pdev, 0);
retval = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
if (retval < 0)
goto err_aux_dev_init_1;
retval = pci_irq_vector(pdev, 0);
if (retval < 0)
goto err_aux_dev_init_1;
pdev->irq = retval;
aux_bus->aux_device_wrapper[1]->gp_aux_data.irq_num = pdev->irq;
retval = auxiliary_device_init(&aux_bus->aux_device_wrapper[1]->aux_dev);
if (retval < 0)
goto err_aux_dev_init_1;
retval = auxiliary_device_add(&aux_bus->aux_device_wrapper[1]->aux_dev);
if (retval)
goto err_aux_dev_add_1;
pci_set_drvdata(pdev, aux_bus);
pci_set_master(pdev);
return 0;
err_aux_dev_add_1:
auxiliary_device_uninit(&aux_bus->aux_device_wrapper[1]->aux_dev);
goto err_aux_dev_add_0;
err_aux_dev_init_1:
ida_free(&gp_client_ida, aux_bus->aux_device_wrapper[1]->aux_dev.id);
err_ida_alloc_1:
kfree(aux_bus->aux_device_wrapper[1]);
err_aux_dev_add_0:
auxiliary_device_uninit(&aux_bus->aux_device_wrapper[0]->aux_dev);
goto err_ret;
err_aux_dev_init_0:
ida_free(&gp_client_ida, aux_bus->aux_device_wrapper[0]->aux_dev.id);
err_ida_alloc_0:
kfree(aux_bus->aux_device_wrapper[0]);
err_ret:
return retval;
}
static void gp_aux_bus_remove(struct pci_dev *pdev)
{
struct aux_bus_device *aux_bus = pci_get_drvdata(pdev);
auxiliary_device_delete(&aux_bus->aux_device_wrapper[0]->aux_dev);
auxiliary_device_uninit(&aux_bus->aux_device_wrapper[0]->aux_dev);
auxiliary_device_delete(&aux_bus->aux_device_wrapper[1]->aux_dev);
auxiliary_device_uninit(&aux_bus->aux_device_wrapper[1]->aux_dev);
}
static const struct pci_device_id pci1xxxx_tbl[] = {
{ PCI_DEVICE(0x1055, 0xA005) },
{ PCI_DEVICE(0x1055, 0xA015) },
{ PCI_DEVICE(0x1055, 0xA025) },
{ PCI_DEVICE(0x1055, 0xA035) },
{ PCI_DEVICE(0x1055, 0xA045) },
{ PCI_DEVICE(0x1055, 0xA055) },
{0,}
};
MODULE_DEVICE_TABLE(pci, pci1xxxx_tbl);
static struct pci_driver pci1xxxx_gp_driver = {
.name = "PCI1xxxxGP",
.id_table = pci1xxxx_tbl,
.probe = gp_aux_bus_probe,
.remove = gp_aux_bus_remove,
};
module_pci_driver(pci1xxxx_gp_driver);
MODULE_DESCRIPTION("Microchip Technology Inc. PCI1xxxx GP expander");
MODULE_AUTHOR("Kumaravel Thiagarajan <kumaravel.thiagarajan@microchip.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2022 Microchip Technology Inc. */
#ifndef _GPIO_PCI1XXXX_H
#define _GPIO_PCI1XXXX_H
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/types.h>
#include <linux/auxiliary_bus.h>
/* Perform operations like variable length write, read and write with read back for OTP / EEPROM
* Perform bit mode write in OTP
*/
struct gp_aux_data_type {
int irq_num;
resource_size_t region_start;
resource_size_t region_length;
};
struct auxiliary_device_wrapper {
struct auxiliary_device aux_dev;
struct gp_aux_data_type gp_aux_data;
};
#endif
@@ -0,0 +1,533 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2022 Microchip Technology Inc.
// pci1xxxx gpio driver
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/gpio/driver.h>
#include <linux/bio.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#include "mchp_pci1xxxx_gp.h"
#define PCI1XXXX_NR_PINS 93
#define PCI_DEV_REV_OFFSET 0x08
#define PERI_GEN_RESET 0
#define OUT_EN_OFFSET(x) ((((x) / 32) * 4) + 0x400)
#define INP_EN_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0x10)
#define OUT_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0x20)
#define INP_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0x30)
#define PULLUP_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0x40)
#define PULLDOWN_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0x50)
#define OPENDRAIN_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0x60)
#define WAKEMASK_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0x70)
#define MODE_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0x80)
#define INTR_LO_TO_HI_EDGE_CONFIG(x) ((((x) / 32) * 4) + 0x400 + 0x90)
#define INTR_HI_TO_LO_EDGE_CONFIG(x) ((((x) / 32) * 4) + 0x400 + 0xA0)
#define INTR_LEVEL_CONFIG_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0xB0)
#define INTR_LEVEL_MASK_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0xC0)
#define INTR_STAT_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0xD0)
#define DEBOUNCE_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0xE0)
#define PIO_GLOBAL_CONFIG_OFFSET (0x400 + 0xF0)
#define PIO_PCI_CTRL_REG_OFFSET (0x400 + 0xF4)
#define INTR_MASK_OFFSET(x) ((((x) / 32) * 4) + 0x400 + 0x100)
#define INTR_STATUS_OFFSET(x) (((x) * 4) + 0x400 + 0xD0)
struct pci1xxxx_gpio {
struct auxiliary_device *aux_dev;
void __iomem *reg_base;
raw_spinlock_t wa_lock;
struct gpio_chip gpio;
spinlock_t lock;
u32 gpio_wake_mask[3];
int irq_base;
u8 dev_rev;
};
static int pci1xxxx_gpio_get_device_revision(struct pci1xxxx_gpio *priv)
{
struct device *parent = priv->aux_dev->dev.parent;
struct pci_dev *pcidev = to_pci_dev(parent);
int ret;
u32 val;
ret = pci_read_config_dword(pcidev, PCI_DEV_REV_OFFSET, &val);
if (ret)
return ret;
priv->dev_rev = val;
return 0;
}
static int pci1xxxx_gpio_get_direction(struct gpio_chip *gpio, unsigned int nr)
{
struct pci1xxxx_gpio *priv = gpiochip_get_data(gpio);
u32 data;
int ret = -EINVAL;
data = readl(priv->reg_base + INP_EN_OFFSET(nr));
if (data & BIT(nr % 32)) {
ret = 1;
} else {
data = readl(priv->reg_base + OUT_EN_OFFSET(nr));
if (data & BIT(nr % 32))
ret = 0;
}
return ret;
}
static inline void pci1xxx_assign_bit(void __iomem *base_addr, unsigned int reg_offset,
unsigned int bitpos, bool set)
{
u32 data;
data = readl(base_addr + reg_offset);
if (set)
data |= BIT(bitpos);
else
data &= ~BIT(bitpos);
writel(data, base_addr + reg_offset);
}
static int pci1xxxx_gpio_direction_input(struct gpio_chip *gpio, unsigned int nr)
{
struct pci1xxxx_gpio *priv = gpiochip_get_data(gpio);
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
pci1xxx_assign_bit(priv->reg_base, INP_EN_OFFSET(nr), (nr % 32), true);
pci1xxx_assign_bit(priv->reg_base, OUT_EN_OFFSET(nr), (nr % 32), false);
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static int pci1xxxx_gpio_get(struct gpio_chip *gpio, unsigned int nr)
{
struct pci1xxxx_gpio *priv = gpiochip_get_data(gpio);
return (readl(priv->reg_base + INP_OFFSET(nr)) >> (nr % 32)) & 1;
}
static int pci1xxxx_gpio_direction_output(struct gpio_chip *gpio,
unsigned int nr, int val)
{
struct pci1xxxx_gpio *priv = gpiochip_get_data(gpio);
unsigned long flags;
u32 data;
spin_lock_irqsave(&priv->lock, flags);
pci1xxx_assign_bit(priv->reg_base, INP_EN_OFFSET(nr), (nr % 32), false);
pci1xxx_assign_bit(priv->reg_base, OUT_EN_OFFSET(nr), (nr % 32), true);
data = readl(priv->reg_base + OUT_OFFSET(nr));
if (val)
data |= (1 << (nr % 32));
else
data &= ~(1 << (nr % 32));
writel(data, priv->reg_base + OUT_OFFSET(nr));
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static int pci1xxxx_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val)
{
struct pci1xxxx_gpio *priv = gpiochip_get_data(gpio);
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
pci1xxx_assign_bit(priv->reg_base, OUT_OFFSET(nr), (nr % 32), val);
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static int pci1xxxx_gpio_set_config(struct gpio_chip *gpio, unsigned int offset,
unsigned long config)
{
struct pci1xxxx_gpio *priv = gpiochip_get_data(gpio);
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&priv->lock, flags);
switch (pinconf_to_config_param(config)) {
case PIN_CONFIG_BIAS_PULL_UP:
pci1xxx_assign_bit(priv->reg_base, PULLUP_OFFSET(offset), (offset % 32), true);
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
pci1xxx_assign_bit(priv->reg_base, PULLDOWN_OFFSET(offset), (offset % 32), true);
break;
case PIN_CONFIG_BIAS_DISABLE:
pci1xxx_assign_bit(priv->reg_base, PULLUP_OFFSET(offset), (offset % 32), false);
pci1xxx_assign_bit(priv->reg_base, PULLDOWN_OFFSET(offset), (offset % 32), false);
break;
case PIN_CONFIG_DRIVE_OPEN_DRAIN:
pci1xxx_assign_bit(priv->reg_base, OPENDRAIN_OFFSET(offset), (offset % 32), true);
break;
case PIN_CONFIG_DRIVE_PUSH_PULL:
pci1xxx_assign_bit(priv->reg_base, OPENDRAIN_OFFSET(offset), (offset % 32), false);
break;
default:
ret = -ENOTSUPP;
break;
}
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
static void pci1xxxx_gpio_irq_ack(struct irq_data *data)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
struct pci1xxxx_gpio *priv = gpiochip_get_data(chip);
unsigned int gpio = irqd_to_hwirq(data);
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
writel(BIT(gpio % 32), priv->reg_base + INTR_STAT_OFFSET(gpio));
spin_unlock_irqrestore(&priv->lock, flags);
}
static void pci1xxxx_gpio_irq_set_mask(struct irq_data *data, bool set)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
struct pci1xxxx_gpio *priv = gpiochip_get_data(chip);
unsigned int gpio = irqd_to_hwirq(data);
unsigned long flags;
if (!set)
gpiochip_enable_irq(chip, gpio);
spin_lock_irqsave(&priv->lock, flags);
pci1xxx_assign_bit(priv->reg_base, INTR_MASK_OFFSET(gpio), (gpio % 32), set);
spin_unlock_irqrestore(&priv->lock, flags);
if (set)
gpiochip_disable_irq(chip, gpio);
}
static void pci1xxxx_gpio_irq_mask(struct irq_data *data)
{
pci1xxxx_gpio_irq_set_mask(data, true);
}
static void pci1xxxx_gpio_irq_unmask(struct irq_data *data)
{
pci1xxxx_gpio_irq_set_mask(data, false);
}
static int pci1xxxx_gpio_set_type(struct irq_data *data, unsigned int trigger_type)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
struct pci1xxxx_gpio *priv = gpiochip_get_data(chip);
unsigned int gpio = irqd_to_hwirq(data);
unsigned int bitpos = gpio % 32;
if (trigger_type & IRQ_TYPE_EDGE_FALLING) {
pci1xxx_assign_bit(priv->reg_base, INTR_HI_TO_LO_EDGE_CONFIG(gpio),
bitpos, false);
pci1xxx_assign_bit(priv->reg_base, MODE_OFFSET(gpio),
bitpos, false);
irq_set_handler_locked(data, handle_edge_irq);
} else {
pci1xxx_assign_bit(priv->reg_base, INTR_HI_TO_LO_EDGE_CONFIG(gpio),
bitpos, true);
}
if (trigger_type & IRQ_TYPE_EDGE_RISING) {
pci1xxx_assign_bit(priv->reg_base, INTR_LO_TO_HI_EDGE_CONFIG(gpio),
bitpos, false);
pci1xxx_assign_bit(priv->reg_base, MODE_OFFSET(gpio), bitpos,
false);
irq_set_handler_locked(data, handle_edge_irq);
} else {
pci1xxx_assign_bit(priv->reg_base, INTR_LO_TO_HI_EDGE_CONFIG(gpio),
bitpos, true);
}
if (trigger_type & IRQ_TYPE_LEVEL_LOW) {
pci1xxx_assign_bit(priv->reg_base, INTR_LEVEL_CONFIG_OFFSET(gpio),
bitpos, true);
pci1xxx_assign_bit(priv->reg_base, INTR_LEVEL_MASK_OFFSET(gpio),
bitpos, false);
pci1xxx_assign_bit(priv->reg_base, MODE_OFFSET(gpio), bitpos,
true);
irq_set_handler_locked(data, handle_edge_irq);
}
if (trigger_type & IRQ_TYPE_LEVEL_HIGH) {
pci1xxx_assign_bit(priv->reg_base, INTR_LEVEL_CONFIG_OFFSET(gpio),
bitpos, false);
pci1xxx_assign_bit(priv->reg_base, INTR_LEVEL_MASK_OFFSET(gpio),
bitpos, false);
pci1xxx_assign_bit(priv->reg_base, MODE_OFFSET(gpio), bitpos,
true);
irq_set_handler_locked(data, handle_edge_irq);
}
if ((!(trigger_type & IRQ_TYPE_LEVEL_LOW)) && (!(trigger_type & IRQ_TYPE_LEVEL_HIGH)))
pci1xxx_assign_bit(priv->reg_base, INTR_LEVEL_MASK_OFFSET(gpio), bitpos, true);
return true;
}
static int pci1xxxx_gpio_set_wake(struct irq_data *data, unsigned int enable)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
struct pci1xxxx_gpio *priv = gpiochip_get_data(chip);
unsigned int gpio = irqd_to_hwirq(data);
unsigned int bitpos = gpio % 32;
unsigned int bank = gpio / 32;
if (enable)
priv->gpio_wake_mask[bank] |= (1 << bitpos);
else
priv->gpio_wake_mask[bank] &= ~(1 << bitpos);
return 0;
}
static irqreturn_t pci1xxxx_gpio_irq_handler(int irq, void *dev_id)
{
struct pci1xxxx_gpio *priv = dev_id;
struct gpio_chip *gc = &priv->gpio;
unsigned long int_status = 0;
unsigned long wa_flags;
unsigned long flags;
u8 pincount;
int bit;
u8 gpiobank;
spin_lock_irqsave(&priv->lock, flags);
pci1xxx_assign_bit(priv->reg_base, PIO_GLOBAL_CONFIG_OFFSET, 16, true);
spin_unlock_irqrestore(&priv->lock, flags);
for (gpiobank = 0; gpiobank < 3; gpiobank++) {
spin_lock_irqsave(&priv->lock, flags);
int_status = readl(priv->reg_base + INTR_STATUS_OFFSET(gpiobank));
spin_unlock_irqrestore(&priv->lock, flags);
if (gpiobank == 2)
pincount = 29;
else
pincount = 32;
for_each_set_bit(bit, &int_status, pincount) {
unsigned int irq;
spin_lock_irqsave(&priv->lock, flags);
writel(BIT(bit), priv->reg_base + INTR_STATUS_OFFSET(gpiobank));
spin_unlock_irqrestore(&priv->lock, flags);
irq = irq_find_mapping(gc->irq.domain, (bit + (gpiobank * 32)));
raw_spin_lock_irqsave(&priv->wa_lock, wa_flags);
generic_handle_irq(irq);
raw_spin_unlock_irqrestore(&priv->wa_lock, wa_flags);
}
}
spin_lock_irqsave(&priv->lock, flags);
pci1xxx_assign_bit(priv->reg_base, PIO_GLOBAL_CONFIG_OFFSET, 16, false);
spin_unlock_irqrestore(&priv->lock, flags);
return IRQ_HANDLED;
}
static const struct irq_chip pci1xxxx_gpio_irqchip = {
.name = "pci1xxxx_gpio",
.irq_ack = pci1xxxx_gpio_irq_ack,
.irq_mask = pci1xxxx_gpio_irq_mask,
.irq_unmask = pci1xxxx_gpio_irq_unmask,
.irq_set_type = pci1xxxx_gpio_set_type,
.irq_set_wake = pci1xxxx_gpio_set_wake,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static int pci1xxxx_gpio_suspend(struct device *dev)
{
struct pci1xxxx_gpio *priv = dev_get_drvdata(dev);
struct device *parent = priv->aux_dev->dev.parent;
struct pci_dev *pcidev = to_pci_dev(parent);
unsigned int gpio_bank_base;
unsigned int wake_mask;
unsigned int gpiobank;
unsigned long flags;
for (gpiobank = 0; gpiobank < 3; gpiobank++) {
wake_mask = priv->gpio_wake_mask[gpiobank];
if (wake_mask) {
gpio_bank_base = gpiobank * 32;
pci1xxx_assign_bit(priv->reg_base,
PIO_PCI_CTRL_REG_OFFSET, 0, true);
writel(~wake_mask, priv->reg_base +
WAKEMASK_OFFSET(gpio_bank_base));
}
}
spin_lock_irqsave(&priv->lock, flags);
pci1xxx_assign_bit(priv->reg_base, PIO_GLOBAL_CONFIG_OFFSET,
16, true);
pci1xxx_assign_bit(priv->reg_base, PIO_GLOBAL_CONFIG_OFFSET,
17, false);
pci1xxx_assign_bit(priv->reg_base, PERI_GEN_RESET, 16, true);
if (priv->dev_rev >= 0xC0)
pci1xxx_assign_bit(priv->reg_base, PERI_GEN_RESET, 17, true);
spin_unlock_irqrestore(&priv->lock, flags);
device_set_wakeup_enable(&pcidev->dev, true);
pci_wake_from_d3(pcidev, true);
return 0;
}
static int pci1xxxx_gpio_resume(struct device *dev)
{
struct pci1xxxx_gpio *priv = dev_get_drvdata(dev);
struct device *parent = priv->aux_dev->dev.parent;
struct pci_dev *pcidev = to_pci_dev(parent);
unsigned int gpio_bank_base;
unsigned int wake_mask;
unsigned int gpiobank;
unsigned long flags;
for (gpiobank = 0; gpiobank < 3; gpiobank++) {
wake_mask = priv->gpio_wake_mask[gpiobank];
if (wake_mask) {
gpio_bank_base = gpiobank * 32;
writel(wake_mask, priv->reg_base +
INTR_STAT_OFFSET(gpio_bank_base));
pci1xxx_assign_bit(priv->reg_base,
PIO_PCI_CTRL_REG_OFFSET, 0, false);
writel(0xffffffff, priv->reg_base +
WAKEMASK_OFFSET(gpio_bank_base));
}
}
spin_lock_irqsave(&priv->lock, flags);
pci1xxx_assign_bit(priv->reg_base, PIO_GLOBAL_CONFIG_OFFSET,
17, true);
pci1xxx_assign_bit(priv->reg_base, PIO_GLOBAL_CONFIG_OFFSET,
16, false);
pci1xxx_assign_bit(priv->reg_base, PERI_GEN_RESET, 16, false);
if (priv->dev_rev >= 0xC0)
pci1xxx_assign_bit(priv->reg_base, PERI_GEN_RESET, 17, false);
spin_unlock_irqrestore(&priv->lock, flags);
pci_wake_from_d3(pcidev, false);
return 0;
}
static int pci1xxxx_gpio_setup(struct pci1xxxx_gpio *priv, int irq)
{
struct gpio_chip *gchip = &priv->gpio;
struct gpio_irq_chip *girq;
int retval;
gchip->label = dev_name(&priv->aux_dev->dev);
gchip->parent = &priv->aux_dev->dev;
gchip->owner = THIS_MODULE;
gchip->direction_input = pci1xxxx_gpio_direction_input;
gchip->direction_output = pci1xxxx_gpio_direction_output;
gchip->get_direction = pci1xxxx_gpio_get_direction;
gchip->get = pci1xxxx_gpio_get;
gchip->set = pci1xxxx_gpio_set;
gchip->set_config = pci1xxxx_gpio_set_config;
gchip->dbg_show = NULL;
gchip->base = -1;
gchip->ngpio = PCI1XXXX_NR_PINS;
gchip->can_sleep = false;
retval = devm_request_threaded_irq(&priv->aux_dev->dev, irq,
NULL, pci1xxxx_gpio_irq_handler,
IRQF_ONESHOT, "PCI1xxxxGPIO", priv);
if (retval)
return retval;
girq = &priv->gpio.irq;
gpio_irq_chip_set_chip(girq, &pci1xxxx_gpio_irqchip);
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_bad_irq;
return 0;
}
static int pci1xxxx_gpio_probe(struct auxiliary_device *aux_dev,
const struct auxiliary_device_id *id)
{
struct auxiliary_device_wrapper *aux_dev_wrapper;
struct gp_aux_data_type *pdata;
struct pci1xxxx_gpio *priv;
int retval;
aux_dev_wrapper = (struct auxiliary_device_wrapper *)
container_of(aux_dev, struct auxiliary_device_wrapper, aux_dev);
pdata = &aux_dev_wrapper->gp_aux_data;
if (!pdata)
return -EINVAL;
priv = devm_kzalloc(&aux_dev->dev, sizeof(struct pci1xxxx_gpio), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
priv->aux_dev = aux_dev;
if (!devm_request_mem_region(&aux_dev->dev, pdata->region_start, 0x800, aux_dev->name))
return -EBUSY;
priv->reg_base = devm_ioremap(&aux_dev->dev, pdata->region_start, 0x800);
if (!priv->reg_base)
return -ENOMEM;
writel(0x0264, (priv->reg_base + 0x400 + 0xF0));
retval = pci1xxxx_gpio_setup(priv, pdata->irq_num);
if (retval < 0)
return retval;
retval = pci1xxxx_gpio_get_device_revision(priv);
if (retval)
return retval;
dev_set_drvdata(&aux_dev->dev, priv);
return devm_gpiochip_add_data(&aux_dev->dev, &priv->gpio, priv);
}
static DEFINE_SIMPLE_DEV_PM_OPS(pci1xxxx_gpio_pm_ops, pci1xxxx_gpio_suspend, pci1xxxx_gpio_resume);
static const struct auxiliary_device_id pci1xxxx_gpio_auxiliary_id_table[] = {
{.name = "mchp_pci1xxxx_gp.gp_gpio"},
{}
};
MODULE_DEVICE_TABLE(auxiliary, pci1xxxx_gpio_auxiliary_id_table);
static struct auxiliary_driver pci1xxxx_gpio_driver = {
.driver = {
.name = "PCI1xxxxGPIO",
.pm = &pci1xxxx_gpio_pm_ops,
},
.probe = pci1xxxx_gpio_probe,
.id_table = pci1xxxx_gpio_auxiliary_id_table
};
module_auxiliary_driver(pci1xxxx_gpio_driver);
MODULE_DESCRIPTION("Microchip Technology Inc. PCI1xxxx GPIO controller");
MODULE_AUTHOR("Kumaravel Thiagarajan <kumaravel.thiagarajan@microchip.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,441 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2022-2023 Microchip Technology Inc.
// PCI1xxxx OTP/EEPROM driver
#include <linux/auxiliary_bus.h>
#include <linux/device.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/nvmem-provider.h>
#include "mchp_pci1xxxx_gp.h"
#define AUX_DRIVER_NAME "PCI1xxxxOTPE2P"
#define EEPROM_NAME "pci1xxxx_eeprom"
#define OTP_NAME "pci1xxxx_otp"
#define PERI_PF3_SYSTEM_REG_ADDR_BASE 0x2000
#define PERI_PF3_SYSTEM_REG_LENGTH 0x4000
#define EEPROM_SIZE_BYTES 8192
#define OTP_SIZE_BYTES 8192
#define CONFIG_REG_ADDR_BASE 0
#define EEPROM_REG_ADDR_BASE 0x0E00
#define OTP_REG_ADDR_BASE 0x1000
#define MMAP_OTP_OFFSET(x) (OTP_REG_ADDR_BASE + (x))
#define MMAP_EEPROM_OFFSET(x) (EEPROM_REG_ADDR_BASE + (x))
#define MMAP_CFG_OFFSET(x) (CONFIG_REG_ADDR_BASE + (x))
#define EEPROM_CMD_REG 0x00
#define EEPROM_DATA_REG 0x04
#define EEPROM_CMD_EPC_WRITE (BIT(29) | BIT(28))
#define EEPROM_CMD_EPC_TIMEOUT_BIT BIT(17)
#define EEPROM_CMD_EPC_BUSY_BIT BIT(31)
#define STATUS_READ_DELAY_US 1
#define STATUS_READ_TIMEOUT_US 20000
#define OTP_ADDR_HIGH_OFFSET 0x04
#define OTP_ADDR_LOW_OFFSET 0x08
#define OTP_PRGM_DATA_OFFSET 0x10
#define OTP_PRGM_MODE_OFFSET 0x14
#define OTP_RD_DATA_OFFSET 0x18
#define OTP_FUNC_CMD_OFFSET 0x20
#define OTP_CMD_GO_OFFSET 0x28
#define OTP_PASS_FAIL_OFFSET 0x2C
#define OTP_STATUS_OFFSET 0x30
#define OTP_FUNC_RD_BIT BIT(0)
#define OTP_FUNC_PGM_BIT BIT(1)
#define OTP_CMD_GO_BIT BIT(0)
#define OTP_STATUS_BUSY_BIT BIT(0)
#define OTP_PGM_MODE_BYTE_BIT BIT(0)
#define OTP_FAIL_BIT BIT(0)
#define OTP_PWR_DN_BIT BIT(0)
#define OTP_PWR_DN_OFFSET 0x00
#define CFG_SYS_LOCK_OFFSET 0xA0
#define CFG_SYS_LOCK_PF3 BIT(5)
#define BYTE_LOW (GENMASK(7, 0))
#define BYTE_HIGH (GENMASK(12, 8))
struct pci1xxxx_otp_eeprom_device {
struct auxiliary_device *pdev;
void __iomem *reg_base;
struct nvmem_config nvmem_config_eeprom;
struct nvmem_device *nvmem_eeprom;
struct nvmem_config nvmem_config_otp;
struct nvmem_device *nvmem_otp;
};
static int set_sys_lock(struct pci1xxxx_otp_eeprom_device *priv)
{
void __iomem *sys_lock = priv->reg_base +
MMAP_CFG_OFFSET(CFG_SYS_LOCK_OFFSET);
u8 data;
writel(CFG_SYS_LOCK_PF3, sys_lock);
data = readl(sys_lock);
if (data != CFG_SYS_LOCK_PF3)
return -EPERM;
return 0;
}
static void release_sys_lock(struct pci1xxxx_otp_eeprom_device *priv)
{
void __iomem *sys_lock = priv->reg_base +
MMAP_CFG_OFFSET(CFG_SYS_LOCK_OFFSET);
writel(0, sys_lock);
}
static bool is_eeprom_responsive(struct pci1xxxx_otp_eeprom_device *priv)
{
void __iomem *rb = priv->reg_base;
u32 regval;
int ret;
writel(EEPROM_CMD_EPC_TIMEOUT_BIT,
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
writel(EEPROM_CMD_EPC_BUSY_BIT,
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
/* Wait for the EPC_BUSY bit to get cleared or timeout bit to get set*/
ret = read_poll_timeout(readl, regval, !(regval & EEPROM_CMD_EPC_BUSY_BIT),
STATUS_READ_DELAY_US, STATUS_READ_TIMEOUT_US,
true, rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
/* Return failure if either of software or hardware timeouts happen */
if (ret < 0 || (!ret && (regval & EEPROM_CMD_EPC_TIMEOUT_BIT)))
return false;
return true;
}
static int pci1xxxx_eeprom_read(void *priv_t, unsigned int off,
void *buf_t, size_t count)
{
struct pci1xxxx_otp_eeprom_device *priv = priv_t;
void __iomem *rb = priv->reg_base;
char *buf = buf_t;
u32 regval;
u32 byte;
int ret;
if (off >= priv->nvmem_config_eeprom.size)
return -EFAULT;
if ((off + count) > priv->nvmem_config_eeprom.size)
count = priv->nvmem_config_eeprom.size - off;
ret = set_sys_lock(priv);
if (ret)
return ret;
for (byte = 0; byte < count; byte++) {
writel(EEPROM_CMD_EPC_BUSY_BIT | (off + byte), rb +
MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
ret = read_poll_timeout(readl, regval,
!(regval & EEPROM_CMD_EPC_BUSY_BIT),
STATUS_READ_DELAY_US,
STATUS_READ_TIMEOUT_US, true,
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
if (ret < 0 || (!ret && (regval & EEPROM_CMD_EPC_TIMEOUT_BIT))) {
ret = -EIO;
goto error;
}
buf[byte] = readl(rb + MMAP_EEPROM_OFFSET(EEPROM_DATA_REG));
}
error:
release_sys_lock(priv);
return ret;
}
static int pci1xxxx_eeprom_write(void *priv_t, unsigned int off,
void *value_t, size_t count)
{
struct pci1xxxx_otp_eeprom_device *priv = priv_t;
void __iomem *rb = priv->reg_base;
char *value = value_t;
u32 regval;
u32 byte;
int ret;
if (off >= priv->nvmem_config_eeprom.size)
return -EFAULT;
if ((off + count) > priv->nvmem_config_eeprom.size)
count = priv->nvmem_config_eeprom.size - off;
ret = set_sys_lock(priv);
if (ret)
return ret;
for (byte = 0; byte < count; byte++) {
writel(*(value + byte), rb + MMAP_EEPROM_OFFSET(EEPROM_DATA_REG));
regval = EEPROM_CMD_EPC_TIMEOUT_BIT | EEPROM_CMD_EPC_WRITE |
(off + byte);
writel(regval, rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
writel(EEPROM_CMD_EPC_BUSY_BIT | regval,
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
ret = read_poll_timeout(readl, regval,
!(regval & EEPROM_CMD_EPC_BUSY_BIT),
STATUS_READ_DELAY_US,
STATUS_READ_TIMEOUT_US, true,
rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG));
if (ret < 0 || (!ret && (regval & EEPROM_CMD_EPC_TIMEOUT_BIT))) {
ret = -EIO;
goto error;
}
}
error:
release_sys_lock(priv);
return ret;
}
static void otp_device_set_address(struct pci1xxxx_otp_eeprom_device *priv,
u16 address)
{
u16 lo, hi;
lo = address & BYTE_LOW;
hi = (address & BYTE_HIGH) >> 8;
writew(lo, priv->reg_base + MMAP_OTP_OFFSET(OTP_ADDR_LOW_OFFSET));
writew(hi, priv->reg_base + MMAP_OTP_OFFSET(OTP_ADDR_HIGH_OFFSET));
}
static int pci1xxxx_otp_read(void *priv_t, unsigned int off,
void *buf_t, size_t count)
{
struct pci1xxxx_otp_eeprom_device *priv = priv_t;
void __iomem *rb = priv->reg_base;
char *buf = buf_t;
u32 regval;
u32 byte;
int ret;
u8 data;
if (off >= priv->nvmem_config_otp.size)
return -EFAULT;
if ((off + count) > priv->nvmem_config_otp.size)
count = priv->nvmem_config_otp.size - off;
ret = set_sys_lock(priv);
if (ret)
return ret;
for (byte = 0; byte < count; byte++) {
otp_device_set_address(priv, (u16)(off + byte));
data = readl(rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET));
writel(data | OTP_FUNC_RD_BIT,
rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET));
data = readl(rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET));
writel(data | OTP_CMD_GO_BIT,
rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET));
ret = read_poll_timeout(readl, regval,
!(regval & OTP_STATUS_BUSY_BIT),
STATUS_READ_DELAY_US,
STATUS_READ_TIMEOUT_US, true,
rb + MMAP_OTP_OFFSET(OTP_STATUS_OFFSET));
data = readl(rb + MMAP_OTP_OFFSET(OTP_PASS_FAIL_OFFSET));
if (ret < 0 || data & OTP_FAIL_BIT) {
ret = -EIO;
goto error;
}
buf[byte] = readl(rb + MMAP_OTP_OFFSET(OTP_RD_DATA_OFFSET));
}
error:
release_sys_lock(priv);
return ret;
}
static int pci1xxxx_otp_write(void *priv_t, unsigned int off,
void *value_t, size_t count)
{
struct pci1xxxx_otp_eeprom_device *priv = priv_t;
void __iomem *rb = priv->reg_base;
char *value = value_t;
u32 regval;
u32 byte;
int ret;
u8 data;
if (off >= priv->nvmem_config_otp.size)
return -EFAULT;
if ((off + count) > priv->nvmem_config_otp.size)
count = priv->nvmem_config_otp.size - off;
ret = set_sys_lock(priv);
if (ret)
return ret;
for (byte = 0; byte < count; byte++) {
otp_device_set_address(priv, (u16)(off + byte));
/*
* Set OTP_PGM_MODE_BYTE command bit in OTP_PRGM_MODE register
* to enable Byte programming
*/
data = readl(rb + MMAP_OTP_OFFSET(OTP_PRGM_MODE_OFFSET));
writel(data | OTP_PGM_MODE_BYTE_BIT,
rb + MMAP_OTP_OFFSET(OTP_PRGM_MODE_OFFSET));
writel(*(value + byte), rb + MMAP_OTP_OFFSET(OTP_PRGM_DATA_OFFSET));
data = readl(rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET));
writel(data | OTP_FUNC_PGM_BIT,
rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET));
data = readl(rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET));
writel(data | OTP_CMD_GO_BIT,
rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET));
ret = read_poll_timeout(readl, regval,
!(regval & OTP_STATUS_BUSY_BIT),
STATUS_READ_DELAY_US,
STATUS_READ_TIMEOUT_US, true,
rb + MMAP_OTP_OFFSET(OTP_STATUS_OFFSET));
data = readl(rb + MMAP_OTP_OFFSET(OTP_PASS_FAIL_OFFSET));
if (ret < 0 || data & OTP_FAIL_BIT) {
ret = -EIO;
goto error;
}
}
error:
release_sys_lock(priv);
return ret;
}
static int pci1xxxx_otp_eeprom_probe(struct auxiliary_device *aux_dev,
const struct auxiliary_device_id *id)
{
struct auxiliary_device_wrapper *aux_dev_wrapper;
struct pci1xxxx_otp_eeprom_device *priv;
struct gp_aux_data_type *pdata;
int ret;
u8 data;
aux_dev_wrapper = container_of(aux_dev, struct auxiliary_device_wrapper,
aux_dev);
pdata = &aux_dev_wrapper->gp_aux_data;
if (!pdata)
return -EINVAL;
priv = devm_kzalloc(&aux_dev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->pdev = aux_dev;
if (!devm_request_mem_region(&aux_dev->dev, pdata->region_start +
PERI_PF3_SYSTEM_REG_ADDR_BASE,
PERI_PF3_SYSTEM_REG_LENGTH,
aux_dev->name))
return -ENOMEM;
priv->reg_base = devm_ioremap(&aux_dev->dev, pdata->region_start +
PERI_PF3_SYSTEM_REG_ADDR_BASE,
PERI_PF3_SYSTEM_REG_LENGTH);
if (!priv->reg_base)
return -ENOMEM;
ret = set_sys_lock(priv);
if (ret)
return ret;
/* Set OTP_PWR_DN to 0 to make OTP Operational */
data = readl(priv->reg_base + MMAP_OTP_OFFSET(OTP_PWR_DN_OFFSET));
writel(data & ~OTP_PWR_DN_BIT,
priv->reg_base + MMAP_OTP_OFFSET(OTP_PWR_DN_OFFSET));
dev_set_drvdata(&aux_dev->dev, priv);
if (is_eeprom_responsive(priv)) {
priv->nvmem_config_eeprom.type = NVMEM_TYPE_EEPROM;
priv->nvmem_config_eeprom.name = EEPROM_NAME;
priv->nvmem_config_eeprom.id = NVMEM_DEVID_AUTO;
priv->nvmem_config_eeprom.dev = &aux_dev->dev;
priv->nvmem_config_eeprom.owner = THIS_MODULE;
priv->nvmem_config_eeprom.reg_read = pci1xxxx_eeprom_read;
priv->nvmem_config_eeprom.reg_write = pci1xxxx_eeprom_write;
priv->nvmem_config_eeprom.priv = priv;
priv->nvmem_config_eeprom.stride = 1;
priv->nvmem_config_eeprom.word_size = 1;
priv->nvmem_config_eeprom.size = EEPROM_SIZE_BYTES;
priv->nvmem_eeprom = devm_nvmem_register(&aux_dev->dev,
&priv->nvmem_config_eeprom);
if (IS_ERR(priv->nvmem_eeprom))
return PTR_ERR(priv->nvmem_eeprom);
}
release_sys_lock(priv);
priv->nvmem_config_otp.type = NVMEM_TYPE_OTP;
priv->nvmem_config_otp.name = OTP_NAME;
priv->nvmem_config_otp.id = NVMEM_DEVID_AUTO;
priv->nvmem_config_otp.dev = &aux_dev->dev;
priv->nvmem_config_otp.owner = THIS_MODULE;
priv->nvmem_config_otp.reg_read = pci1xxxx_otp_read;
priv->nvmem_config_otp.reg_write = pci1xxxx_otp_write;
priv->nvmem_config_otp.priv = priv;
priv->nvmem_config_otp.stride = 1;
priv->nvmem_config_otp.word_size = 1;
priv->nvmem_config_otp.size = OTP_SIZE_BYTES;
priv->nvmem_otp = devm_nvmem_register(&aux_dev->dev,
&priv->nvmem_config_otp);
if (IS_ERR(priv->nvmem_otp))
return PTR_ERR(priv->nvmem_otp);
return ret;
}
static void pci1xxxx_otp_eeprom_remove(struct auxiliary_device *aux_dev)
{
struct pci1xxxx_otp_eeprom_device *priv;
void __iomem *sys_lock;
priv = dev_get_drvdata(&aux_dev->dev);
sys_lock = priv->reg_base + MMAP_CFG_OFFSET(CFG_SYS_LOCK_OFFSET);
writel(CFG_SYS_LOCK_PF3, sys_lock);
/* Shut down OTP */
writel(OTP_PWR_DN_BIT,
priv->reg_base + MMAP_OTP_OFFSET(OTP_PWR_DN_OFFSET));
writel(0, sys_lock);
}
static const struct auxiliary_device_id pci1xxxx_otp_eeprom_auxiliary_id_table[] = {
{.name = "mchp_pci1xxxx_gp.gp_otp_e2p"},
{},
};
MODULE_DEVICE_TABLE(auxiliary, pci1xxxx_otp_eeprom_auxiliary_id_table);
static struct auxiliary_driver pci1xxxx_otp_eeprom_driver = {
.driver = {
.name = AUX_DRIVER_NAME,
},
.probe = pci1xxxx_otp_eeprom_probe,
.remove = pci1xxxx_otp_eeprom_remove,
.id_table = pci1xxxx_otp_eeprom_auxiliary_id_table
};
module_auxiliary_driver(pci1xxxx_otp_eeprom_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kumaravel Thiagarajan <kumaravel.thiagarajan@microchip.com>");
MODULE_AUTHOR("Tharun Kumar P <tharunkumar.pasumarthi@microchip.com>");
MODULE_AUTHOR("Vaibhaav Ram T.L <vaibhaavram.tl@microchip.com>");
MODULE_DESCRIPTION("Microchip Technology Inc. PCI1xxxx OTP EEPROM Programmer");