16057d40fSYD Tseng /* 26057d40fSYD Tseng * AMD Promontory GPIO driver 36057d40fSYD Tseng * 46057d40fSYD Tseng * Copyright (C) 2015 ASMedia Technology Inc. 56057d40fSYD Tseng * Author: YD Tseng <yd_tseng@asmedia.com.tw> 66057d40fSYD Tseng * 76057d40fSYD Tseng * This program is free software; you can redistribute it and/or modify 86057d40fSYD Tseng * it under the terms of the GNU General Public License version 2 as 96057d40fSYD Tseng * published by the Free Software Foundation. 106057d40fSYD Tseng */ 116057d40fSYD Tseng 126057d40fSYD Tseng #include <linux/kernel.h> 136057d40fSYD Tseng #include <linux/module.h> 146057d40fSYD Tseng #include <linux/gpio/driver.h> 156057d40fSYD Tseng #include <linux/spinlock.h> 166057d40fSYD Tseng #include <linux/acpi.h> 176057d40fSYD Tseng #include <linux/platform_device.h> 186057d40fSYD Tseng 196057d40fSYD Tseng #define PT_TOTAL_GPIO 8 206057d40fSYD Tseng 216057d40fSYD Tseng /* PCI-E MMIO register offsets */ 226057d40fSYD Tseng #define PT_DIRECTION_REG 0x00 236057d40fSYD Tseng #define PT_INPUTDATA_REG 0x04 246057d40fSYD Tseng #define PT_OUTPUTDATA_REG 0x08 256057d40fSYD Tseng #define PT_CLOCKRATE_REG 0x0C 266057d40fSYD Tseng #define PT_SYNC_REG 0x28 276057d40fSYD Tseng 286057d40fSYD Tseng struct pt_gpio_chip { 296057d40fSYD Tseng struct gpio_chip gc; 306057d40fSYD Tseng void __iomem *reg_base; 316057d40fSYD Tseng spinlock_t lock; 326057d40fSYD Tseng }; 336057d40fSYD Tseng 346057d40fSYD Tseng #define to_pt_gpio(c) container_of(c, struct pt_gpio_chip, gc) 356057d40fSYD Tseng 366057d40fSYD Tseng static int pt_gpio_request(struct gpio_chip *gc, unsigned offset) 376057d40fSYD Tseng { 386057d40fSYD Tseng struct pt_gpio_chip *pt_gpio = to_pt_gpio(gc); 396057d40fSYD Tseng unsigned long flags; 406057d40fSYD Tseng u32 using_pins; 416057d40fSYD Tseng 4258383c78SLinus Walleij dev_dbg(gc->parent, "pt_gpio_request offset=%x\n", offset); 436057d40fSYD Tseng 446057d40fSYD Tseng spin_lock_irqsave(&pt_gpio->lock, flags); 456057d40fSYD Tseng 466057d40fSYD Tseng using_pins = readl(pt_gpio->reg_base + PT_SYNC_REG); 476057d40fSYD Tseng if (using_pins & BIT(offset)) { 4858383c78SLinus Walleij dev_warn(gc->parent, "PT GPIO pin %x reconfigured\n", 496057d40fSYD Tseng offset); 506057d40fSYD Tseng spin_unlock_irqrestore(&pt_gpio->lock, flags); 516057d40fSYD Tseng return -EINVAL; 526057d40fSYD Tseng } 536057d40fSYD Tseng 546057d40fSYD Tseng writel(using_pins | BIT(offset), pt_gpio->reg_base + PT_SYNC_REG); 556057d40fSYD Tseng 566057d40fSYD Tseng spin_unlock_irqrestore(&pt_gpio->lock, flags); 576057d40fSYD Tseng 586057d40fSYD Tseng return 0; 596057d40fSYD Tseng } 606057d40fSYD Tseng 616057d40fSYD Tseng static void pt_gpio_free(struct gpio_chip *gc, unsigned offset) 626057d40fSYD Tseng { 636057d40fSYD Tseng struct pt_gpio_chip *pt_gpio = to_pt_gpio(gc); 646057d40fSYD Tseng unsigned long flags; 656057d40fSYD Tseng u32 using_pins; 666057d40fSYD Tseng 676057d40fSYD Tseng spin_lock_irqsave(&pt_gpio->lock, flags); 686057d40fSYD Tseng 696057d40fSYD Tseng using_pins = readl(pt_gpio->reg_base + PT_SYNC_REG); 706057d40fSYD Tseng using_pins &= ~BIT(offset); 716057d40fSYD Tseng writel(using_pins, pt_gpio->reg_base + PT_SYNC_REG); 726057d40fSYD Tseng 736057d40fSYD Tseng spin_unlock_irqrestore(&pt_gpio->lock, flags); 746057d40fSYD Tseng 7558383c78SLinus Walleij dev_dbg(gc->parent, "pt_gpio_free offset=%x\n", offset); 766057d40fSYD Tseng } 776057d40fSYD Tseng 786057d40fSYD Tseng static void pt_gpio_set_value(struct gpio_chip *gc, unsigned offset, int value) 796057d40fSYD Tseng { 806057d40fSYD Tseng struct pt_gpio_chip *pt_gpio = to_pt_gpio(gc); 816057d40fSYD Tseng unsigned long flags; 826057d40fSYD Tseng u32 data; 836057d40fSYD Tseng 8458383c78SLinus Walleij dev_dbg(gc->parent, "pt_gpio_set_value offset=%x, value=%x\n", 856057d40fSYD Tseng offset, value); 866057d40fSYD Tseng 876057d40fSYD Tseng spin_lock_irqsave(&pt_gpio->lock, flags); 886057d40fSYD Tseng 896057d40fSYD Tseng data = readl(pt_gpio->reg_base + PT_OUTPUTDATA_REG); 906057d40fSYD Tseng data &= ~BIT(offset); 916057d40fSYD Tseng if (value) 926057d40fSYD Tseng data |= BIT(offset); 936057d40fSYD Tseng writel(data, pt_gpio->reg_base + PT_OUTPUTDATA_REG); 946057d40fSYD Tseng 956057d40fSYD Tseng spin_unlock_irqrestore(&pt_gpio->lock, flags); 966057d40fSYD Tseng } 976057d40fSYD Tseng 986057d40fSYD Tseng static int pt_gpio_get_value(struct gpio_chip *gc, unsigned offset) 996057d40fSYD Tseng { 1006057d40fSYD Tseng struct pt_gpio_chip *pt_gpio = to_pt_gpio(gc); 1016057d40fSYD Tseng unsigned long flags; 1026057d40fSYD Tseng u32 data; 1036057d40fSYD Tseng 1046057d40fSYD Tseng spin_lock_irqsave(&pt_gpio->lock, flags); 1056057d40fSYD Tseng 1066057d40fSYD Tseng data = readl(pt_gpio->reg_base + PT_DIRECTION_REG); 1076057d40fSYD Tseng 1086057d40fSYD Tseng /* configure as output */ 1096057d40fSYD Tseng if (data & BIT(offset)) 1106057d40fSYD Tseng data = readl(pt_gpio->reg_base + PT_OUTPUTDATA_REG); 1116057d40fSYD Tseng else /* configure as input */ 1126057d40fSYD Tseng data = readl(pt_gpio->reg_base + PT_INPUTDATA_REG); 1136057d40fSYD Tseng 1146057d40fSYD Tseng spin_unlock_irqrestore(&pt_gpio->lock, flags); 1156057d40fSYD Tseng 1166057d40fSYD Tseng data >>= offset; 1176057d40fSYD Tseng data &= 1; 1186057d40fSYD Tseng 11958383c78SLinus Walleij dev_dbg(gc->parent, "pt_gpio_get_value offset=%x, value=%x\n", 1206057d40fSYD Tseng offset, data); 1216057d40fSYD Tseng 1226057d40fSYD Tseng return data; 1236057d40fSYD Tseng } 1246057d40fSYD Tseng 1256057d40fSYD Tseng static int pt_gpio_direction_input(struct gpio_chip *gc, unsigned offset) 1266057d40fSYD Tseng { 1276057d40fSYD Tseng struct pt_gpio_chip *pt_gpio = to_pt_gpio(gc); 1286057d40fSYD Tseng unsigned long flags; 1296057d40fSYD Tseng u32 data; 1306057d40fSYD Tseng 13158383c78SLinus Walleij dev_dbg(gc->parent, "pt_gpio_dirction_input offset=%x\n", offset); 1326057d40fSYD Tseng 1336057d40fSYD Tseng spin_lock_irqsave(&pt_gpio->lock, flags); 1346057d40fSYD Tseng 1356057d40fSYD Tseng data = readl(pt_gpio->reg_base + PT_DIRECTION_REG); 1366057d40fSYD Tseng data &= ~BIT(offset); 1376057d40fSYD Tseng writel(data, pt_gpio->reg_base + PT_DIRECTION_REG); 1386057d40fSYD Tseng 1396057d40fSYD Tseng spin_unlock_irqrestore(&pt_gpio->lock, flags); 1406057d40fSYD Tseng 1416057d40fSYD Tseng return 0; 1426057d40fSYD Tseng } 1436057d40fSYD Tseng 1446057d40fSYD Tseng static int pt_gpio_direction_output(struct gpio_chip *gc, 1456057d40fSYD Tseng unsigned offset, int value) 1466057d40fSYD Tseng { 1476057d40fSYD Tseng struct pt_gpio_chip *pt_gpio = to_pt_gpio(gc); 1486057d40fSYD Tseng unsigned long flags; 1496057d40fSYD Tseng u32 data; 1506057d40fSYD Tseng 15158383c78SLinus Walleij dev_dbg(gc->parent, "pt_gpio_direction_output offset=%x, value=%x\n", 1526057d40fSYD Tseng offset, value); 1536057d40fSYD Tseng 1546057d40fSYD Tseng spin_lock_irqsave(&pt_gpio->lock, flags); 1556057d40fSYD Tseng 1566057d40fSYD Tseng data = readl(pt_gpio->reg_base + PT_OUTPUTDATA_REG); 1576057d40fSYD Tseng if (value) 1586057d40fSYD Tseng data |= BIT(offset); 1596057d40fSYD Tseng else 1606057d40fSYD Tseng data &= ~BIT(offset); 1616057d40fSYD Tseng writel(data, pt_gpio->reg_base + PT_OUTPUTDATA_REG); 1626057d40fSYD Tseng 1636057d40fSYD Tseng data = readl(pt_gpio->reg_base + PT_DIRECTION_REG); 1646057d40fSYD Tseng data |= BIT(offset); 1656057d40fSYD Tseng writel(data, pt_gpio->reg_base + PT_DIRECTION_REG); 1666057d40fSYD Tseng 1676057d40fSYD Tseng spin_unlock_irqrestore(&pt_gpio->lock, flags); 1686057d40fSYD Tseng 1696057d40fSYD Tseng return 0; 1706057d40fSYD Tseng } 1716057d40fSYD Tseng 1726057d40fSYD Tseng static int pt_gpio_probe(struct platform_device *pdev) 1736057d40fSYD Tseng { 1746057d40fSYD Tseng struct device *dev = &pdev->dev; 1756057d40fSYD Tseng struct acpi_device *acpi_dev; 1766057d40fSYD Tseng acpi_handle handle = ACPI_HANDLE(dev); 1776057d40fSYD Tseng struct pt_gpio_chip *pt_gpio; 1786057d40fSYD Tseng struct resource *res_mem; 1796057d40fSYD Tseng int ret = 0; 1806057d40fSYD Tseng 1816057d40fSYD Tseng if (acpi_bus_get_device(handle, &acpi_dev)) { 1826057d40fSYD Tseng dev_err(dev, "PT GPIO device node not found\n"); 1836057d40fSYD Tseng return -ENODEV; 1846057d40fSYD Tseng } 1856057d40fSYD Tseng 1866057d40fSYD Tseng pt_gpio = devm_kzalloc(dev, sizeof(struct pt_gpio_chip), GFP_KERNEL); 1876057d40fSYD Tseng if (!pt_gpio) 1886057d40fSYD Tseng return -ENOMEM; 1896057d40fSYD Tseng 1906057d40fSYD Tseng res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1916057d40fSYD Tseng if (!res_mem) { 1926057d40fSYD Tseng dev_err(&pdev->dev, "Failed to get MMIO resource for PT GPIO.\n"); 1936057d40fSYD Tseng return -EINVAL; 1946057d40fSYD Tseng } 1956057d40fSYD Tseng pt_gpio->reg_base = devm_ioremap_resource(dev, res_mem); 1966057d40fSYD Tseng if (IS_ERR(pt_gpio->reg_base)) { 1976057d40fSYD Tseng dev_err(&pdev->dev, "Failed to map MMIO resource for PT GPIO.\n"); 1986057d40fSYD Tseng return PTR_ERR(pt_gpio->reg_base); 1996057d40fSYD Tseng } 2006057d40fSYD Tseng 2016057d40fSYD Tseng spin_lock_init(&pt_gpio->lock); 2026057d40fSYD Tseng 2036057d40fSYD Tseng pt_gpio->gc.label = pdev->name; 2046057d40fSYD Tseng pt_gpio->gc.owner = THIS_MODULE; 20558383c78SLinus Walleij pt_gpio->gc.parent = dev; 2066057d40fSYD Tseng pt_gpio->gc.request = pt_gpio_request; 2076057d40fSYD Tseng pt_gpio->gc.free = pt_gpio_free; 2086057d40fSYD Tseng pt_gpio->gc.direction_input = pt_gpio_direction_input; 2096057d40fSYD Tseng pt_gpio->gc.direction_output = pt_gpio_direction_output; 2106057d40fSYD Tseng pt_gpio->gc.get = pt_gpio_get_value; 2116057d40fSYD Tseng pt_gpio->gc.set = pt_gpio_set_value; 2126057d40fSYD Tseng pt_gpio->gc.base = -1; 2136057d40fSYD Tseng pt_gpio->gc.ngpio = PT_TOTAL_GPIO; 2146057d40fSYD Tseng #if defined(CONFIG_OF_GPIO) 2156057d40fSYD Tseng pt_gpio->gc.of_node = pdev->dev.of_node; 2166057d40fSYD Tseng #endif 2176057d40fSYD Tseng ret = gpiochip_add(&pt_gpio->gc); 2186057d40fSYD Tseng if (ret) { 2196057d40fSYD Tseng dev_err(&pdev->dev, "Failed to register GPIO lib\n"); 2206057d40fSYD Tseng return ret; 2216057d40fSYD Tseng } 2226057d40fSYD Tseng 2236057d40fSYD Tseng platform_set_drvdata(pdev, pt_gpio); 2246057d40fSYD Tseng 2256057d40fSYD Tseng /* initialize register setting */ 2266057d40fSYD Tseng writel(0, pt_gpio->reg_base + PT_SYNC_REG); 2276057d40fSYD Tseng writel(0, pt_gpio->reg_base + PT_CLOCKRATE_REG); 2286057d40fSYD Tseng 2296057d40fSYD Tseng dev_dbg(&pdev->dev, "PT GPIO driver loaded\n"); 2306057d40fSYD Tseng return ret; 2316057d40fSYD Tseng } 2326057d40fSYD Tseng 2336057d40fSYD Tseng static int pt_gpio_remove(struct platform_device *pdev) 2346057d40fSYD Tseng { 2356057d40fSYD Tseng struct pt_gpio_chip *pt_gpio = platform_get_drvdata(pdev); 2366057d40fSYD Tseng 2376057d40fSYD Tseng gpiochip_remove(&pt_gpio->gc); 2386057d40fSYD Tseng 2396057d40fSYD Tseng return 0; 2406057d40fSYD Tseng } 2416057d40fSYD Tseng 2426057d40fSYD Tseng static const struct acpi_device_id pt_gpio_acpi_match[] = { 2436057d40fSYD Tseng { "AMDF030", 0 }, 2446057d40fSYD Tseng { }, 2456057d40fSYD Tseng }; 2466057d40fSYD Tseng MODULE_DEVICE_TABLE(acpi, pt_gpio_acpi_match); 2476057d40fSYD Tseng 2486057d40fSYD Tseng static struct platform_driver pt_gpio_driver = { 2496057d40fSYD Tseng .driver = { 2506057d40fSYD Tseng .name = "pt-gpio", 2516057d40fSYD Tseng .acpi_match_table = ACPI_PTR(pt_gpio_acpi_match), 2526057d40fSYD Tseng }, 2536057d40fSYD Tseng .probe = pt_gpio_probe, 2546057d40fSYD Tseng .remove = pt_gpio_remove, 2556057d40fSYD Tseng }; 2566057d40fSYD Tseng 2576057d40fSYD Tseng module_platform_driver(pt_gpio_driver); 2586057d40fSYD Tseng 2596057d40fSYD Tseng MODULE_LICENSE("GPL"); 2606057d40fSYD Tseng MODULE_AUTHOR("YD Tseng <yd_tseng@asmedia.com.tw>"); 2616057d40fSYD Tseng MODULE_DESCRIPTION("AMD Promontory GPIO Driver"); 262