162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * GPIO driver for AMD 8111 south bridges 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2012 Dmitry Eremin-Solenikov 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Based on the AMD RNG driver: 862306a36Sopenharmony_ci * Copyright 2005 (c) MontaVista Software, Inc. 962306a36Sopenharmony_ci * with the majority of the code coming from: 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) 1262306a36Sopenharmony_ci * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * derived from 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * Hardware driver for the AMD 768 Random Number Generator (RNG) 1762306a36Sopenharmony_ci * (c) Copyright 2001 Red Hat Inc 1862306a36Sopenharmony_ci * 1962306a36Sopenharmony_ci * derived from 2062306a36Sopenharmony_ci * 2162306a36Sopenharmony_ci * Hardware driver for Intel i810 Random Number Generator (RNG) 2262306a36Sopenharmony_ci * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com> 2362306a36Sopenharmony_ci * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com> 2462306a36Sopenharmony_ci */ 2562306a36Sopenharmony_ci#include <linux/ioport.h> 2662306a36Sopenharmony_ci#include <linux/module.h> 2762306a36Sopenharmony_ci#include <linux/kernel.h> 2862306a36Sopenharmony_ci#include <linux/gpio/driver.h> 2962306a36Sopenharmony_ci#include <linux/pci.h> 3062306a36Sopenharmony_ci#include <linux/spinlock.h> 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define PMBASE_OFFSET 0xb0 3362306a36Sopenharmony_ci#define PMBASE_SIZE 0x30 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define AMD_REG_GPIO(i) (0x10 + (i)) 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define AMD_GPIO_LTCH_STS 0x40 /* Latch status, w1 */ 3862306a36Sopenharmony_ci#define AMD_GPIO_RTIN 0x20 /* Real Time in, ro */ 3962306a36Sopenharmony_ci#define AMD_GPIO_DEBOUNCE 0x10 /* Debounce, rw */ 4062306a36Sopenharmony_ci#define AMD_GPIO_MODE_MASK 0x0c /* Pin Mode Select, rw */ 4162306a36Sopenharmony_ci#define AMD_GPIO_MODE_IN 0x00 4262306a36Sopenharmony_ci#define AMD_GPIO_MODE_OUT 0x04 4362306a36Sopenharmony_ci/* Enable alternative (e.g. clkout, IRQ, etc) function of the pin */ 4462306a36Sopenharmony_ci#define AMD_GPIO_MODE_ALTFN 0x08 /* Or 0x09 */ 4562306a36Sopenharmony_ci#define AMD_GPIO_X_MASK 0x03 /* In/Out specific, rw */ 4662306a36Sopenharmony_ci#define AMD_GPIO_X_IN_ACTIVEHI 0x01 /* Active High */ 4762306a36Sopenharmony_ci#define AMD_GPIO_X_IN_LATCH 0x02 /* Latched version is selected */ 4862306a36Sopenharmony_ci#define AMD_GPIO_X_OUT_LOW 0x00 4962306a36Sopenharmony_ci#define AMD_GPIO_X_OUT_HI 0x01 5062306a36Sopenharmony_ci#define AMD_GPIO_X_OUT_CLK0 0x02 5162306a36Sopenharmony_ci#define AMD_GPIO_X_OUT_CLK1 0x03 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci/* 5462306a36Sopenharmony_ci * Data for PCI driver interface 5562306a36Sopenharmony_ci * 5662306a36Sopenharmony_ci * This data only exists for exporting the supported 5762306a36Sopenharmony_ci * PCI ids via MODULE_DEVICE_TABLE. We do not actually 5862306a36Sopenharmony_ci * register a pci_driver, because someone else might one day 5962306a36Sopenharmony_ci * want to register another driver on the same PCI id. 6062306a36Sopenharmony_ci */ 6162306a36Sopenharmony_cistatic const struct pci_device_id pci_tbl[] = { 6262306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS), 0 }, 6362306a36Sopenharmony_ci { 0, }, /* terminate list */ 6462306a36Sopenharmony_ci}; 6562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, pci_tbl); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistruct amd_gpio { 6862306a36Sopenharmony_ci struct gpio_chip chip; 6962306a36Sopenharmony_ci u32 pmbase; 7062306a36Sopenharmony_ci void __iomem *pm; 7162306a36Sopenharmony_ci struct pci_dev *pdev; 7262306a36Sopenharmony_ci spinlock_t lock; /* guards hw registers and orig table */ 7362306a36Sopenharmony_ci u8 orig[32]; 7462306a36Sopenharmony_ci}; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic int amd_gpio_request(struct gpio_chip *chip, unsigned offset) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct amd_gpio *agp = gpiochip_get_data(chip); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci agp->orig[offset] = ioread8(agp->pm + AMD_REG_GPIO(offset)) & 8162306a36Sopenharmony_ci (AMD_GPIO_DEBOUNCE | AMD_GPIO_MODE_MASK | AMD_GPIO_X_MASK); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci dev_dbg(&agp->pdev->dev, "Requested gpio %d, data %x\n", offset, agp->orig[offset]); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic void amd_gpio_free(struct gpio_chip *chip, unsigned offset) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct amd_gpio *agp = gpiochip_get_data(chip); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci dev_dbg(&agp->pdev->dev, "Freed gpio %d, data %x\n", offset, agp->orig[offset]); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci iowrite8(agp->orig[offset], agp->pm + AMD_REG_GPIO(offset)); 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic void amd_gpio_set(struct gpio_chip *chip, unsigned offset, int value) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct amd_gpio *agp = gpiochip_get_data(chip); 10062306a36Sopenharmony_ci u8 temp; 10162306a36Sopenharmony_ci unsigned long flags; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci spin_lock_irqsave(&agp->lock, flags); 10462306a36Sopenharmony_ci temp = ioread8(agp->pm + AMD_REG_GPIO(offset)); 10562306a36Sopenharmony_ci temp = (temp & AMD_GPIO_DEBOUNCE) | AMD_GPIO_MODE_OUT | (value ? AMD_GPIO_X_OUT_HI : AMD_GPIO_X_OUT_LOW); 10662306a36Sopenharmony_ci iowrite8(temp, agp->pm + AMD_REG_GPIO(offset)); 10762306a36Sopenharmony_ci spin_unlock_irqrestore(&agp->lock, flags); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci dev_dbg(&agp->pdev->dev, "Setting gpio %d, value %d, reg=%02x\n", offset, !!value, temp); 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic int amd_gpio_get(struct gpio_chip *chip, unsigned offset) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci struct amd_gpio *agp = gpiochip_get_data(chip); 11562306a36Sopenharmony_ci u8 temp; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci temp = ioread8(agp->pm + AMD_REG_GPIO(offset)); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci dev_dbg(&agp->pdev->dev, "Getting gpio %d, reg=%02x\n", offset, temp); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci return (temp & AMD_GPIO_RTIN) ? 1 : 0; 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic int amd_gpio_dirout(struct gpio_chip *chip, unsigned offset, int value) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct amd_gpio *agp = gpiochip_get_data(chip); 12762306a36Sopenharmony_ci u8 temp; 12862306a36Sopenharmony_ci unsigned long flags; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci spin_lock_irqsave(&agp->lock, flags); 13162306a36Sopenharmony_ci temp = ioread8(agp->pm + AMD_REG_GPIO(offset)); 13262306a36Sopenharmony_ci temp = (temp & AMD_GPIO_DEBOUNCE) | AMD_GPIO_MODE_OUT | (value ? AMD_GPIO_X_OUT_HI : AMD_GPIO_X_OUT_LOW); 13362306a36Sopenharmony_ci iowrite8(temp, agp->pm + AMD_REG_GPIO(offset)); 13462306a36Sopenharmony_ci spin_unlock_irqrestore(&agp->lock, flags); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci dev_dbg(&agp->pdev->dev, "Dirout gpio %d, value %d, reg=%02x\n", offset, !!value, temp); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return 0; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic int amd_gpio_dirin(struct gpio_chip *chip, unsigned offset) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci struct amd_gpio *agp = gpiochip_get_data(chip); 14462306a36Sopenharmony_ci u8 temp; 14562306a36Sopenharmony_ci unsigned long flags; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci spin_lock_irqsave(&agp->lock, flags); 14862306a36Sopenharmony_ci temp = ioread8(agp->pm + AMD_REG_GPIO(offset)); 14962306a36Sopenharmony_ci temp = (temp & AMD_GPIO_DEBOUNCE) | AMD_GPIO_MODE_IN; 15062306a36Sopenharmony_ci iowrite8(temp, agp->pm + AMD_REG_GPIO(offset)); 15162306a36Sopenharmony_ci spin_unlock_irqrestore(&agp->lock, flags); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci dev_dbg(&agp->pdev->dev, "Dirin gpio %d, reg=%02x\n", offset, temp); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci return 0; 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic struct amd_gpio gp = { 15962306a36Sopenharmony_ci .chip = { 16062306a36Sopenharmony_ci .label = "AMD GPIO", 16162306a36Sopenharmony_ci .owner = THIS_MODULE, 16262306a36Sopenharmony_ci .base = -1, 16362306a36Sopenharmony_ci .ngpio = 32, 16462306a36Sopenharmony_ci .request = amd_gpio_request, 16562306a36Sopenharmony_ci .free = amd_gpio_free, 16662306a36Sopenharmony_ci .set = amd_gpio_set, 16762306a36Sopenharmony_ci .get = amd_gpio_get, 16862306a36Sopenharmony_ci .direction_output = amd_gpio_dirout, 16962306a36Sopenharmony_ci .direction_input = amd_gpio_dirin, 17062306a36Sopenharmony_ci }, 17162306a36Sopenharmony_ci}; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic int __init amd_gpio_init(void) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci int err = -ENODEV; 17662306a36Sopenharmony_ci struct pci_dev *pdev = NULL; 17762306a36Sopenharmony_ci const struct pci_device_id *ent; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* We look for our device - AMD South Bridge 18062306a36Sopenharmony_ci * I don't know about a system with two such bridges, 18162306a36Sopenharmony_ci * so we can assume that there is max. one device. 18262306a36Sopenharmony_ci * 18362306a36Sopenharmony_ci * We can't use plain pci_driver mechanism, 18462306a36Sopenharmony_ci * as the device is really a multiple function device, 18562306a36Sopenharmony_ci * main driver that binds to the pci_device is an smbus 18662306a36Sopenharmony_ci * driver and have to find & bind to the device this way. 18762306a36Sopenharmony_ci */ 18862306a36Sopenharmony_ci for_each_pci_dev(pdev) { 18962306a36Sopenharmony_ci ent = pci_match_id(pci_tbl, pdev); 19062306a36Sopenharmony_ci if (ent) 19162306a36Sopenharmony_ci goto found; 19262306a36Sopenharmony_ci } 19362306a36Sopenharmony_ci /* Device not found. */ 19462306a36Sopenharmony_ci goto out; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cifound: 19762306a36Sopenharmony_ci err = pci_read_config_dword(pdev, 0x58, &gp.pmbase); 19862306a36Sopenharmony_ci if (err) 19962306a36Sopenharmony_ci goto out; 20062306a36Sopenharmony_ci err = -EIO; 20162306a36Sopenharmony_ci gp.pmbase &= 0x0000FF00; 20262306a36Sopenharmony_ci if (gp.pmbase == 0) 20362306a36Sopenharmony_ci goto out; 20462306a36Sopenharmony_ci if (!devm_request_region(&pdev->dev, gp.pmbase + PMBASE_OFFSET, 20562306a36Sopenharmony_ci PMBASE_SIZE, "AMD GPIO")) { 20662306a36Sopenharmony_ci dev_err(&pdev->dev, "AMD GPIO region 0x%x already in use!\n", 20762306a36Sopenharmony_ci gp.pmbase + PMBASE_OFFSET); 20862306a36Sopenharmony_ci err = -EBUSY; 20962306a36Sopenharmony_ci goto out; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci gp.pm = ioport_map(gp.pmbase + PMBASE_OFFSET, PMBASE_SIZE); 21262306a36Sopenharmony_ci if (!gp.pm) { 21362306a36Sopenharmony_ci dev_err(&pdev->dev, "Couldn't map io port into io memory\n"); 21462306a36Sopenharmony_ci err = -ENOMEM; 21562306a36Sopenharmony_ci goto out; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci gp.pdev = pdev; 21862306a36Sopenharmony_ci gp.chip.parent = &pdev->dev; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci spin_lock_init(&gp.lock); 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci dev_info(&pdev->dev, "AMD-8111 GPIO detected\n"); 22362306a36Sopenharmony_ci err = gpiochip_add_data(&gp.chip, &gp); 22462306a36Sopenharmony_ci if (err) { 22562306a36Sopenharmony_ci dev_err(&pdev->dev, "GPIO registering failed (%d)\n", err); 22662306a36Sopenharmony_ci ioport_unmap(gp.pm); 22762306a36Sopenharmony_ci goto out; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ciout: 23262306a36Sopenharmony_ci pci_dev_put(pdev); 23362306a36Sopenharmony_ci return err; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic void __exit amd_gpio_exit(void) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci gpiochip_remove(&gp.chip); 23962306a36Sopenharmony_ci ioport_unmap(gp.pm); 24062306a36Sopenharmony_ci pci_dev_put(gp.pdev); 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cimodule_init(amd_gpio_init); 24462306a36Sopenharmony_cimodule_exit(amd_gpio_exit); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ciMODULE_AUTHOR("The Linux Kernel team"); 24762306a36Sopenharmony_ciMODULE_DESCRIPTION("GPIO driver for AMD chipsets"); 24862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 249