1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * AMD Promontory GPIO driver |
4 | * |
5 | * Copyright (C) 2015 ASMedia Technology Inc. |
6 | * Author: YD Tseng <yd_tseng@asmedia.com.tw> |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> |
11 | #include <linux/gpio/driver.h> |
12 | #include <linux/spinlock.h> |
13 | #include <linux/acpi.h> |
14 | #include <linux/platform_device.h> |
15 | |
16 | #define PT_TOTAL_GPIO 8 |
17 | #define PT_TOTAL_GPIO_EX 24 |
18 | |
19 | /* PCI-E MMIO register offsets */ |
20 | #define PT_DIRECTION_REG 0x00 |
21 | #define PT_INPUTDATA_REG 0x04 |
22 | #define PT_OUTPUTDATA_REG 0x08 |
23 | #define PT_CLOCKRATE_REG 0x0C |
24 | #define PT_SYNC_REG 0x28 |
25 | |
26 | struct pt_gpio_chip { |
27 | struct gpio_chip gc; |
28 | void __iomem *reg_base; |
29 | }; |
30 | |
31 | static int pt_gpio_request(struct gpio_chip *gc, unsigned offset) |
32 | { |
33 | struct pt_gpio_chip *pt_gpio = gpiochip_get_data(gc); |
34 | unsigned long flags; |
35 | u32 using_pins; |
36 | |
37 | dev_dbg(gc->parent, "pt_gpio_request offset=%x\n" , offset); |
38 | |
39 | raw_spin_lock_irqsave(&gc->bgpio_lock, flags); |
40 | |
41 | using_pins = readl(addr: pt_gpio->reg_base + PT_SYNC_REG); |
42 | if (using_pins & BIT(offset)) { |
43 | dev_warn(gc->parent, "PT GPIO pin %x reconfigured\n" , |
44 | offset); |
45 | raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags); |
46 | return -EINVAL; |
47 | } |
48 | |
49 | writel(val: using_pins | BIT(offset), addr: pt_gpio->reg_base + PT_SYNC_REG); |
50 | |
51 | raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags); |
52 | |
53 | return 0; |
54 | } |
55 | |
56 | static void pt_gpio_free(struct gpio_chip *gc, unsigned offset) |
57 | { |
58 | struct pt_gpio_chip *pt_gpio = gpiochip_get_data(gc); |
59 | unsigned long flags; |
60 | u32 using_pins; |
61 | |
62 | raw_spin_lock_irqsave(&gc->bgpio_lock, flags); |
63 | |
64 | using_pins = readl(addr: pt_gpio->reg_base + PT_SYNC_REG); |
65 | using_pins &= ~BIT(offset); |
66 | writel(val: using_pins, addr: pt_gpio->reg_base + PT_SYNC_REG); |
67 | |
68 | raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags); |
69 | |
70 | dev_dbg(gc->parent, "pt_gpio_free offset=%x\n" , offset); |
71 | } |
72 | |
73 | static int pt_gpio_probe(struct platform_device *pdev) |
74 | { |
75 | struct device *dev = &pdev->dev; |
76 | struct pt_gpio_chip *pt_gpio; |
77 | int ret = 0; |
78 | |
79 | if (!ACPI_COMPANION(dev)) { |
80 | dev_err(dev, "PT GPIO device node not found\n" ); |
81 | return -ENODEV; |
82 | } |
83 | |
84 | pt_gpio = devm_kzalloc(dev, size: sizeof(struct pt_gpio_chip), GFP_KERNEL); |
85 | if (!pt_gpio) |
86 | return -ENOMEM; |
87 | |
88 | pt_gpio->reg_base = devm_platform_ioremap_resource(pdev, index: 0); |
89 | if (IS_ERR(ptr: pt_gpio->reg_base)) { |
90 | dev_err(dev, "Failed to map MMIO resource for PT GPIO.\n" ); |
91 | return PTR_ERR(ptr: pt_gpio->reg_base); |
92 | } |
93 | |
94 | ret = bgpio_init(gc: &pt_gpio->gc, dev, sz: 4, |
95 | dat: pt_gpio->reg_base + PT_INPUTDATA_REG, |
96 | set: pt_gpio->reg_base + PT_OUTPUTDATA_REG, NULL, |
97 | dirout: pt_gpio->reg_base + PT_DIRECTION_REG, NULL, |
98 | BGPIOF_READ_OUTPUT_REG_SET); |
99 | if (ret) { |
100 | dev_err(dev, "bgpio_init failed\n" ); |
101 | return ret; |
102 | } |
103 | |
104 | pt_gpio->gc.owner = THIS_MODULE; |
105 | pt_gpio->gc.request = pt_gpio_request; |
106 | pt_gpio->gc.free = pt_gpio_free; |
107 | pt_gpio->gc.ngpio = (uintptr_t)device_get_match_data(dev); |
108 | |
109 | ret = gpiochip_add_data(&pt_gpio->gc, pt_gpio); |
110 | if (ret) { |
111 | dev_err(dev, "Failed to register GPIO lib\n" ); |
112 | return ret; |
113 | } |
114 | |
115 | platform_set_drvdata(pdev, data: pt_gpio); |
116 | |
117 | /* initialize register setting */ |
118 | writel(val: 0, addr: pt_gpio->reg_base + PT_SYNC_REG); |
119 | writel(val: 0, addr: pt_gpio->reg_base + PT_CLOCKRATE_REG); |
120 | |
121 | dev_dbg(dev, "PT GPIO driver loaded\n" ); |
122 | return ret; |
123 | } |
124 | |
125 | static void pt_gpio_remove(struct platform_device *pdev) |
126 | { |
127 | struct pt_gpio_chip *pt_gpio = platform_get_drvdata(pdev); |
128 | |
129 | gpiochip_remove(gc: &pt_gpio->gc); |
130 | } |
131 | |
132 | static const struct acpi_device_id pt_gpio_acpi_match[] = { |
133 | { "AMDF030" , PT_TOTAL_GPIO }, |
134 | { "AMDIF030" , PT_TOTAL_GPIO }, |
135 | { "AMDIF031" , PT_TOTAL_GPIO_EX }, |
136 | { }, |
137 | }; |
138 | MODULE_DEVICE_TABLE(acpi, pt_gpio_acpi_match); |
139 | |
140 | static struct platform_driver pt_gpio_driver = { |
141 | .driver = { |
142 | .name = "pt-gpio" , |
143 | .acpi_match_table = ACPI_PTR(pt_gpio_acpi_match), |
144 | }, |
145 | .probe = pt_gpio_probe, |
146 | .remove_new = pt_gpio_remove, |
147 | }; |
148 | |
149 | module_platform_driver(pt_gpio_driver); |
150 | |
151 | MODULE_LICENSE("GPL" ); |
152 | MODULE_AUTHOR("YD Tseng <yd_tseng@asmedia.com.tw>" ); |
153 | MODULE_DESCRIPTION("AMD Promontory GPIO Driver" ); |
154 | |