This driver provide GPIO functionality on Asmedia 28XX and 18XX PCI-E Bridge Signed-off-by: Richard Hsu <saraon640529@xxxxxxxxx> --- patch | 312 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 patch diff --git a/patch b/patch index 0000000..a713f91 --- /dev/null +++ b/patch @@ -0,0 +1,312 @@ +diff -uprN -X linux-5.7.0-rc6/Documentation/dontdiff linux-5.7.0-rc6/drivers/gpio/gpio-asm28xx-18xx.c linux-5.7.0-rc6-patch/drivers/gpio/gpio-asm28xx-18xx.c +--- linux-5.7.0-rc6/drivers/gpio/gpio-asm28xx-18xx.c 1970-01-01 08:00:00.000000000 +0800 ++++ linux-5.7.0-rc6-patch/drivers/gpio/gpio-asm28xx-18xx.c 2020-05-22 11:55:13.736272177 +0800 +@@ -0,0 +1,282 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Asmedia 28xx/18xx GPIO driver ++ * ++ * Copyright (C) 2020 ASMedia Technology Inc. ++ * Author: Richard Hsu <Richard_Hsu@xxxxxxxxxxxxxx> ++ */ ++ ++ ++#include <linux/module.h> ++#include <linux/kernel.h> ++#include <linux/gpio/driver.h> ++#include <linux/pci.h> ++#include <linux/spinlock.h> ++#include <linux/pm_runtime.h> ++ ++ ++/* GPIO registers offsets */ ++#define ASM_GPIO_CTRL 0x920 ++#define ASM_GPIO_OUTPUT 0x928 ++#define ASM_GPIO_INPUT 0x930 ++#define ASM_REG_SWITCH 0xFFF ++ ++#define ASM_REG_SWITCH_CTL 0x01 ++ ++#define ASM_GPIO_PIN5 5 ++#define ASM_GPIO_DEFAULT 0 ++ ++ ++#define PCI_DEVICE_ID_ASM_28XX_PID1 0x2824 ++#define PCI_DEVICE_ID_ASM_28XX_PID2 0x2812 ++#define PCI_DEVICE_ID_ASM_28XX_PID3 0x2806 ++#define PCI_DEVICE_ID_ASM_18XX_PID1 0x1824 ++#define PCI_DEVICE_ID_ASM_18XX_PID2 0x1812 ++#define PCI_DEVICE_ID_ASM_18XX_PID3 0x1806 ++#define PCI_DEVICE_ID_ASM_81XX_PID1 0x812a ++#define PCI_DEVICE_ID_ASM_81XX_PID2 0x812b ++#define PCI_DEVICE_ID_ASM_80XX_PID1 0x8061 ++ ++/* ++ * Data for PCI driver interface ++ * ++ * This data only exists for exporting the supported ++ * PCI ids via MODULE_DEVICE_TABLE. We do not actually ++ * register a pci_driver, because someone else might one day ++ * want to register another driver on the same PCI id. ++ */ ++static const struct pci_device_id pci_tbl[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, PCI_DEVICE_ID_ASM_28XX_PID1), 0 }, ++ { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, PCI_DEVICE_ID_ASM_28XX_PID2), 0 }, ++ { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, PCI_DEVICE_ID_ASM_28XX_PID3), 0 }, ++ { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, PCI_DEVICE_ID_ASM_18XX_PID1), 0 }, ++ { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, PCI_DEVICE_ID_ASM_18XX_PID2), 0 }, ++ { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, PCI_DEVICE_ID_ASM_18XX_PID3), 0 }, ++ { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, PCI_DEVICE_ID_ASM_81XX_PID1), 0 }, ++ { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, PCI_DEVICE_ID_ASM_81XX_PID2), 0 }, ++ { PCI_DEVICE(PCI_VENDOR_ID_ASMEDIA, PCI_DEVICE_ID_ASM_80XX_PID1), 0 }, ++ { 0, }, /* terminate list */ ++}; ++MODULE_DEVICE_TABLE(pci, pci_tbl); ++ ++ ++struct asm28xx_gpio { ++ struct gpio_chip chip; ++ struct pci_dev *pdev; ++ spinlock_t lock; ++}; ++ ++void pci_config_pm_runtime_get(struct pci_dev *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct device *parent = dev->parent; ++ ++ if (parent) ++ pm_runtime_get_sync(parent); ++ pm_runtime_get_noresume(dev); ++ /* ++ * pdev->current_state is set to PCI_D3cold during suspending, ++ * so wait until suspending completes ++ */ ++ pm_runtime_barrier(dev); ++ /* ++ * Only need to resume devices in D3cold, because config ++ * registers are still accessible for devices suspended but ++ * not in D3cold. ++ */ ++ if (pdev->current_state == PCI_D3cold) ++ pm_runtime_resume(dev); ++} ++ ++void pci_config_pm_runtime_put(struct pci_dev *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct device *parent = dev->parent; ++ ++ pm_runtime_put(dev); ++ if (parent) ++ pm_runtime_put_sync(parent); ++} ++ ++static int asm28xx_gpio_request(struct gpio_chip *chip, unsigned offset) ++{ ++ ++ dev_dbg(chip->parent, "ASMEDIA-28xx/18xx gpio %d request\n", offset); ++ ++ if (offset == ASM_GPIO_PIN5) ++ return -ENODEV; ++ ++ return 0; ++} ++ ++static void asm28xx_gpio_free(struct gpio_chip *chip, unsigned offset) ++{ ++ dev_dbg(chip->parent, "ASMEDIA-28xx/18xx gpio %d free\n", offset); ++} ++ ++static void asm28xx_gpio_set(struct gpio_chip *chip, unsigned offset, int value) ++{ ++ struct asm28xx_gpio *agp = gpiochip_get_data(chip); ++ unsigned char temp; ++ unsigned long flags; ++ ++ pci_config_pm_runtime_get(agp->pdev); ++ spin_lock_irqsave(&agp->lock, flags); ++ pci_read_config_byte(agp->pdev, ASM_GPIO_OUTPUT, &temp); ++ if (value) ++ temp |= BIT(offset); ++ else ++ temp &= ~BIT(offset); ++ ++ pci_write_config_byte(agp->pdev, ASM_GPIO_OUTPUT, temp); ++ spin_unlock_irqrestore(&agp->lock, flags); ++ pci_config_pm_runtime_put(agp->pdev); ++ dev_dbg(chip->parent, "ASMEDIA-28xx/18xx gpio %d set %d reg=%02x\n", offset, value, temp); ++} ++ ++static int asm28xx_gpio_get(struct gpio_chip *chip, unsigned offset) ++{ ++ struct asm28xx_gpio *agp = gpiochip_get_data(chip); ++ unsigned char temp; ++ unsigned long flags; ++ ++ pci_config_pm_runtime_get(agp->pdev); ++ spin_lock_irqsave(&agp->lock, flags); ++ pci_read_config_byte(agp->pdev, ASM_GPIO_INPUT, &temp); ++ spin_unlock_irqrestore(&agp->lock, flags); ++ pci_config_pm_runtime_put(agp->pdev); ++ ++ dev_dbg(chip->parent, "ASMEDIA-28xx/18xx GPIO Pin %d get reg=%02x\n", offset, temp); ++ return (temp & BIT(offset)) ? 1 : 0; ++} ++ ++static int asm28xx_gpio_dirout(struct gpio_chip *chip, unsigned offset, int value) ++{ ++ struct asm28xx_gpio *agp = gpiochip_get_data(chip); ++ unsigned char temp; ++ unsigned long flags; ++ ++ pci_config_pm_runtime_get(agp->pdev); ++ spin_lock_irqsave(&agp->lock, flags); ++ pci_read_config_byte(agp->pdev, ASM_GPIO_CTRL, &temp); ++ temp |= BIT(offset); ++ pci_write_config_byte(agp->pdev, ASM_GPIO_CTRL, temp); ++ spin_unlock_irqrestore(&agp->lock, flags); ++ pci_config_pm_runtime_put(agp->pdev); ++ dev_dbg(chip->parent, "ASMEDIA-28xx/18xx dirout gpio %d reg=%02x\n", offset, temp); ++ ++ return 0; ++} ++ ++static int asm28xx_gpio_dirin(struct gpio_chip *chip, unsigned offset) ++{ ++ struct asm28xx_gpio *agp = gpiochip_get_data(chip); ++ unsigned char temp; ++ unsigned long flags; ++ ++ pci_config_pm_runtime_get(agp->pdev); ++ spin_lock_irqsave(&agp->lock, flags); ++ pci_read_config_byte(agp->pdev, ASM_GPIO_CTRL, &temp); ++ temp &= ~BIT(offset); ++ pci_write_config_byte(agp->pdev, ASM_GPIO_CTRL, temp); ++ spin_unlock_irqrestore(&agp->lock, flags); ++ pci_config_pm_runtime_put(agp->pdev); ++ dev_dbg(chip->parent, "ASMEDIA-28xx/18xx dirin gpio %d reg=%02x\n", offset, temp); ++ ++ return 0; ++} ++ ++static struct asm28xx_gpio gp = { ++ .chip = { ++ .label = "ASM28XX-18XX GPIO", ++ .owner = THIS_MODULE, ++ .ngpio = 8, ++ .request = asm28xx_gpio_request, ++ .free = asm28xx_gpio_free, ++ .set = asm28xx_gpio_set, ++ .get = asm28xx_gpio_get, ++ .direction_output = asm28xx_gpio_dirout, ++ .direction_input = asm28xx_gpio_dirin, ++ }, ++}; ++ ++static int __init asm28xx_gpio_init(void) ++{ ++ int err = -ENODEV; ++ struct pci_dev *pdev = NULL; ++ const struct pci_device_id *ent; ++ u8 temp; ++ unsigned long flags; ++ int type; ++ ++ for_each_pci_dev(pdev) { ++ ent = pci_match_id(pci_tbl, pdev); ++ if (ent) { ++ /* Because GPIO Registers only work on Upstream port. */ ++ type = pci_pcie_type(pdev); ++ if (type == PCI_EXP_TYPE_UPSTREAM) { ++ dev_info(&pdev->dev, "ASMEDIA-28xx/18xx Init Upstream detected\n"); ++ goto found; ++ } ++ } ++ } ++ ++ /* ASMEDIA-28xx/18xx GPIO not found. */ ++ dev_info(&pdev->dev, "ASMEDIA-28xx/18xx Init GPIO not detected\n"); ++ goto out; ++ ++found: ++ gp.pdev = pdev; ++ gp.chip.parent = &pdev->dev; ++ ++ spin_lock_init(&gp.lock); ++ ++ err = gpiochip_add_data(&gp.chip, &gp); ++ if (err) { ++ dev_err(&pdev->dev, "GPIO registering failed (%d)\n", err); ++ goto out; ++ } ++ ++ pci_config_pm_runtime_get(pdev); ++ ++ /* Set PCI_CFG_Switch bit = 1,then we can access GPIO Registers. */ ++ spin_lock_irqsave(&gp.lock, flags); ++ pci_read_config_byte(pdev, ASM_REG_SWITCH, &temp); ++ temp |= ASM_REG_SWITCH_CTL; ++ pci_write_config_byte(pdev, ASM_REG_SWITCH, temp); ++ pci_read_config_byte(pdev, ASM_REG_SWITCH, &temp); ++ spin_unlock_irqrestore(&gp.lock, flags); ++ ++ pci_config_pm_runtime_put(pdev); ++ dev_err(&pdev->dev, "ASMEDIA-28xx/18xx Init SWITCH = 0x%x\n", temp); ++out: ++ return err; ++} ++ ++static void __exit asm28xx_gpio_exit(void) ++{ ++ unsigned long flags; ++ ++ pci_config_pm_runtime_get(gp.pdev); ++ ++ spin_lock_irqsave(&gp.lock, flags); ++ /* Set GPIO Registers to default value. */ ++ pci_write_config_byte(gp.pdev, ASM_GPIO_OUTPUT, ASM_GPIO_DEFAULT); ++ pci_write_config_byte(gp.pdev, ASM_GPIO_INPUT, ASM_GPIO_DEFAULT); ++ pci_write_config_byte(gp.pdev, ASM_GPIO_CTRL, ASM_GPIO_DEFAULT); ++ /* Clear PCI_CFG_Switch bit = 0,then we can't access GPIO Registers. */ ++ pci_write_config_byte(gp.pdev, ASM_REG_SWITCH, ASM_GPIO_DEFAULT); ++ spin_unlock_irqrestore(&gp.lock, flags); ++ pci_config_pm_runtime_put(gp.pdev); ++ ++ gpiochip_remove(&gp.chip); ++} ++ ++module_init(asm28xx_gpio_init); ++module_exit(asm28xx_gpio_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Richard Hsu <Richard_Hsu@xxxxxxxxxxxxxx>"); ++MODULE_DESCRIPTION("Asmedia 28xx 18xx GPIO Driver"); ++ ++ +diff -uprN -X linux-5.7.0-rc6/Documentation/dontdiff linux-5.7.0-rc6/drivers/gpio/Kconfig linux-5.7.0-rc6-patch/drivers/gpio/Kconfig +--- linux-5.7.0-rc6/drivers/gpio/Kconfig 2020-05-22 11:54:00.862644198 +0800 ++++ linux-5.7.0-rc6-patch/drivers/gpio/Kconfig 2020-05-22 11:55:13.680270929 +0800 +@@ -113,6 +113,14 @@ config GPIO_AMDPT + driver for GPIO functionality on Promontory IOHub + Require ACPI ASL code to enumerate as a platform device. + ++config GPIO_ASM28XX ++ tristate "Asmedia 28XX/18XX GPIO support" ++ depends on PCI ++ select GPIO_GENERIC ++ help ++ driver for GPIO functionality on Asmedia 28XX and 18XX PCI-E Bridge. ++ ++ + config GPIO_ASPEED + tristate "Aspeed GPIO support" + depends on (ARCH_ASPEED || COMPILE_TEST) && OF_GPIO +diff -uprN -X linux-5.7.0-rc6/Documentation/dontdiff linux-5.7.0-rc6/drivers/gpio/Makefile linux-5.7.0-rc6-patch/drivers/gpio/Makefile +--- linux-5.7.0-rc6/drivers/gpio/Makefile 2020-05-22 11:54:00.862644198 +0800 ++++ linux-5.7.0-rc6-patch/drivers/gpio/Makefile 2020-05-22 11:55:13.680270929 +0800 +@@ -176,3 +176,4 @@ obj-$(CONFIG_GPIO_XTENSA) += gpio-xtens + obj-$(CONFIG_GPIO_ZEVIO) += gpio-zevio.o + obj-$(CONFIG_GPIO_ZX) += gpio-zx.o + obj-$(CONFIG_GPIO_ZYNQ) += gpio-zynq.o ++obj-$(CONFIG_GPIO_ASM28XX) += gpio-asm28xx-18xx.o -- 2.17.1