diff options
author | Andres Salomon <dilinger@collabora.co.uk> | 2009-12-14 21:00:32 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-12-15 11:53:27 -0500 |
commit | 5f0a96b044d8edaee20f4a32ef6c393599ca55f8 (patch) | |
tree | a46994e95854d1771ea6829826793f8bceb39751 /drivers/gpio | |
parent | 1f2f38d89d1eced2079189cd880eeacee378370a (diff) |
cs5535-gpio: add AMD CS5535/CS5536 GPIO driver support
This creates a CS5535/CS5536 GPIO driver which uses a gpio_chip backend
(allowing GPIO users to use the generic GPIO API if desired) while also
allowing architecture-specific users directly (via the cs5535_gpio_*
functions).
Tested on an OLPC machine. Some Leemotes also use CS5536 (with a mips
cpu), which is why this is in drivers/gpio rather than arch/x86.
Currently, it conflicts with older geode GPIO support; once MFGPT support
is reworked to also be more generic, the older geode code will be removed.
Signed-off-by: Andres Salomon <dilinger@collabora.co.uk>
Cc: Takashi Iwai <tiwai@suse.de>
Cc: Jordan Crouse <jordan@cosmicpenguin.net>
Cc: David Brownell <david-b@pacbell.net>
Reviewed-by: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/gpio')
-rw-r--r-- | drivers/gpio/Kconfig | 10 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/cs5535-gpio.c | 282 |
3 files changed, 293 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 2ad0128c63c6..df4b82d1348e 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig | |||
@@ -174,6 +174,16 @@ config GPIO_ADP5520 | |||
174 | 174 | ||
175 | comment "PCI GPIO expanders:" | 175 | comment "PCI GPIO expanders:" |
176 | 176 | ||
177 | config GPIO_CS5535 | ||
178 | tristate "AMD CS5535/CS5536 GPIO support" | ||
179 | depends on PCI && !CS5535_GPIO && !MGEODE_LX | ||
180 | help | ||
181 | The AMD CS5535 and CS5536 southbridges support 28 GPIO pins that | ||
182 | can be used for quite a number of things. The CS5535/6 is found on | ||
183 | AMD Geode and Lemote Yeeloong devices. | ||
184 | |||
185 | If unsure, say N. | ||
186 | |||
177 | config GPIO_BT8XX | 187 | config GPIO_BT8XX |
178 | tristate "BT8XX GPIO abuser" | 188 | tristate "BT8XX GPIO abuser" |
179 | depends on PCI && VIDEO_BT848=n | 189 | depends on PCI && VIDEO_BT848=n |
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 00a532c9a1e2..270b6d7839f5 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile | |||
@@ -16,6 +16,7 @@ obj-$(CONFIG_GPIO_PL061) += pl061.o | |||
16 | obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o | 16 | obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o |
17 | obj-$(CONFIG_GPIO_UCB1400) += ucb1400_gpio.o | 17 | obj-$(CONFIG_GPIO_UCB1400) += ucb1400_gpio.o |
18 | obj-$(CONFIG_GPIO_XILINX) += xilinx_gpio.o | 18 | obj-$(CONFIG_GPIO_XILINX) += xilinx_gpio.o |
19 | obj-$(CONFIG_GPIO_CS5535) += cs5535-gpio.o | ||
19 | obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o | 20 | obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o |
20 | obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o | 21 | obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o |
21 | obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o | 22 | obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o |
diff --git a/drivers/gpio/cs5535-gpio.c b/drivers/gpio/cs5535-gpio.c new file mode 100644 index 000000000000..56138893819a --- /dev/null +++ b/drivers/gpio/cs5535-gpio.c | |||
@@ -0,0 +1,282 @@ | |||
1 | /* | ||
2 | * AMD CS5535/CS5536 GPIO driver | ||
3 | * Copyright (C) 2006 Advanced Micro Devices, Inc. | ||
4 | * Copyright (C) 2007-2009 Andres Salomon <dilinger@collabora.co.uk> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of version 2 of the GNU General Public License | ||
8 | * as published by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/kernel.h> | ||
12 | #include <linux/spinlock.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/pci.h> | ||
15 | #include <linux/gpio.h> | ||
16 | #include <linux/io.h> | ||
17 | #include <linux/cs5535.h> | ||
18 | |||
19 | #define DRV_NAME "cs5535-gpio" | ||
20 | #define GPIO_BAR 1 | ||
21 | |||
22 | static struct cs5535_gpio_chip { | ||
23 | struct gpio_chip chip; | ||
24 | resource_size_t base; | ||
25 | |||
26 | struct pci_dev *pdev; | ||
27 | spinlock_t lock; | ||
28 | } cs5535_gpio_chip; | ||
29 | |||
30 | /* | ||
31 | * The CS5535/CS5536 GPIOs support a number of extra features not defined | ||
32 | * by the gpio_chip API, so these are exported. For a full list of the | ||
33 | * registers, see include/linux/cs5535.h. | ||
34 | */ | ||
35 | |||
36 | static void __cs5535_gpio_set(struct cs5535_gpio_chip *chip, unsigned offset, | ||
37 | unsigned int reg) | ||
38 | { | ||
39 | if (offset < 16) | ||
40 | /* low bank register */ | ||
41 | outl(1 << offset, chip->base + reg); | ||
42 | else | ||
43 | /* high bank register */ | ||
44 | outl(1 << (offset - 16), chip->base + 0x80 + reg); | ||
45 | } | ||
46 | |||
47 | void cs5535_gpio_set(unsigned offset, unsigned int reg) | ||
48 | { | ||
49 | struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; | ||
50 | unsigned long flags; | ||
51 | |||
52 | spin_lock_irqsave(&chip->lock, flags); | ||
53 | __cs5535_gpio_set(chip, offset, reg); | ||
54 | spin_unlock_irqrestore(&chip->lock, flags); | ||
55 | } | ||
56 | EXPORT_SYMBOL_GPL(cs5535_gpio_set); | ||
57 | |||
58 | static void __cs5535_gpio_clear(struct cs5535_gpio_chip *chip, unsigned offset, | ||
59 | unsigned int reg) | ||
60 | { | ||
61 | if (offset < 16) | ||
62 | /* low bank register */ | ||
63 | outl(1 << (offset + 16), chip->base + reg); | ||
64 | else | ||
65 | /* high bank register */ | ||
66 | outl(1 << offset, chip->base + 0x80 + reg); | ||
67 | } | ||
68 | |||
69 | void cs5535_gpio_clear(unsigned offset, unsigned int reg) | ||
70 | { | ||
71 | struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; | ||
72 | unsigned long flags; | ||
73 | |||
74 | spin_lock_irqsave(&chip->lock, flags); | ||
75 | __cs5535_gpio_clear(chip, offset, reg); | ||
76 | spin_unlock_irqrestore(&chip->lock, flags); | ||
77 | } | ||
78 | EXPORT_SYMBOL_GPL(cs5535_gpio_clear); | ||
79 | |||
80 | int cs5535_gpio_isset(unsigned offset, unsigned int reg) | ||
81 | { | ||
82 | struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; | ||
83 | unsigned long flags; | ||
84 | long val; | ||
85 | |||
86 | spin_lock_irqsave(&chip->lock, flags); | ||
87 | if (offset < 16) | ||
88 | /* low bank register */ | ||
89 | val = inl(chip->base + reg); | ||
90 | else { | ||
91 | /* high bank register */ | ||
92 | val = inl(chip->base + 0x80 + reg); | ||
93 | offset -= 16; | ||
94 | } | ||
95 | spin_unlock_irqrestore(&chip->lock, flags); | ||
96 | |||
97 | return (val & (1 << offset)) ? 1 : 0; | ||
98 | } | ||
99 | EXPORT_SYMBOL_GPL(cs5535_gpio_isset); | ||
100 | |||
101 | /* | ||
102 | * Generic gpio_chip API support. | ||
103 | */ | ||
104 | |||
105 | static int chip_gpio_get(struct gpio_chip *chip, unsigned offset) | ||
106 | { | ||
107 | return cs5535_gpio_isset(offset, GPIO_OUTPUT_VAL); | ||
108 | } | ||
109 | |||
110 | static void chip_gpio_set(struct gpio_chip *chip, unsigned offset, int val) | ||
111 | { | ||
112 | if (val) | ||
113 | cs5535_gpio_set(offset, GPIO_OUTPUT_VAL); | ||
114 | else | ||
115 | cs5535_gpio_clear(offset, GPIO_OUTPUT_VAL); | ||
116 | } | ||
117 | |||
118 | static int chip_direction_input(struct gpio_chip *c, unsigned offset) | ||
119 | { | ||
120 | struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; | ||
121 | unsigned long flags; | ||
122 | |||
123 | spin_lock_irqsave(&chip->lock, flags); | ||
124 | __cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE); | ||
125 | spin_unlock_irqrestore(&chip->lock, flags); | ||
126 | |||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | static int chip_direction_output(struct gpio_chip *c, unsigned offset, int val) | ||
131 | { | ||
132 | struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; | ||
133 | unsigned long flags; | ||
134 | |||
135 | spin_lock_irqsave(&chip->lock, flags); | ||
136 | |||
137 | __cs5535_gpio_set(chip, offset, GPIO_OUTPUT_ENABLE); | ||
138 | if (val) | ||
139 | __cs5535_gpio_set(chip, offset, GPIO_OUTPUT_VAL); | ||
140 | else | ||
141 | __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_VAL); | ||
142 | |||
143 | spin_unlock_irqrestore(&chip->lock, flags); | ||
144 | |||
145 | return 0; | ||
146 | } | ||
147 | |||
148 | static struct cs5535_gpio_chip cs5535_gpio_chip = { | ||
149 | .chip = { | ||
150 | .owner = THIS_MODULE, | ||
151 | .label = DRV_NAME, | ||
152 | |||
153 | .base = 0, | ||
154 | .ngpio = 28, | ||
155 | |||
156 | .get = chip_gpio_get, | ||
157 | .set = chip_gpio_set, | ||
158 | |||
159 | .direction_input = chip_direction_input, | ||
160 | .direction_output = chip_direction_output, | ||
161 | }, | ||
162 | }; | ||
163 | |||
164 | static int __init cs5535_gpio_probe(struct pci_dev *pdev, | ||
165 | const struct pci_device_id *pci_id) | ||
166 | { | ||
167 | int err; | ||
168 | |||
169 | /* There are two ways to get the GPIO base address; one is by | ||
170 | * fetching it from MSR_LBAR_GPIO, the other is by reading the | ||
171 | * PCI BAR info. The latter method is easier (especially across | ||
172 | * different architectures), so we'll stick with that for now. If | ||
173 | * it turns out to be unreliable in the face of crappy BIOSes, we | ||
174 | * can always go back to using MSRs.. */ | ||
175 | |||
176 | err = pci_enable_device_io(pdev); | ||
177 | if (err) { | ||
178 | dev_err(&pdev->dev, "can't enable device IO\n"); | ||
179 | goto done; | ||
180 | } | ||
181 | |||
182 | err = pci_request_region(pdev, GPIO_BAR, DRV_NAME); | ||
183 | if (err) { | ||
184 | dev_err(&pdev->dev, "can't alloc PCI BAR #%d\n", GPIO_BAR); | ||
185 | goto done; | ||
186 | } | ||
187 | |||
188 | /* set up the driver-specific struct */ | ||
189 | cs5535_gpio_chip.base = pci_resource_start(pdev, GPIO_BAR); | ||
190 | cs5535_gpio_chip.pdev = pdev; | ||
191 | spin_lock_init(&cs5535_gpio_chip.lock); | ||
192 | |||
193 | dev_info(&pdev->dev, "allocated PCI BAR #%d: base 0x%llx\n", GPIO_BAR, | ||
194 | (unsigned long long) cs5535_gpio_chip.base); | ||
195 | |||
196 | /* finally, register with the generic GPIO API */ | ||
197 | err = gpiochip_add(&cs5535_gpio_chip.chip); | ||
198 | if (err) { | ||
199 | dev_err(&pdev->dev, "failed to register gpio chip\n"); | ||
200 | goto release_region; | ||
201 | } | ||
202 | |||
203 | printk(KERN_INFO DRV_NAME ": GPIO support successfully loaded.\n"); | ||
204 | return 0; | ||
205 | |||
206 | release_region: | ||
207 | pci_release_region(pdev, GPIO_BAR); | ||
208 | done: | ||
209 | return err; | ||
210 | } | ||
211 | |||
212 | static void __exit cs5535_gpio_remove(struct pci_dev *pdev) | ||
213 | { | ||
214 | int err; | ||
215 | |||
216 | err = gpiochip_remove(&cs5535_gpio_chip.chip); | ||
217 | if (err) { | ||
218 | /* uhh? */ | ||
219 | dev_err(&pdev->dev, "unable to remove gpio_chip?\n"); | ||
220 | } | ||
221 | pci_release_region(pdev, GPIO_BAR); | ||
222 | } | ||
223 | |||
224 | static struct pci_device_id cs5535_gpio_pci_tbl[] = { | ||
225 | { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA) }, | ||
226 | { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) }, | ||
227 | { 0, }, | ||
228 | }; | ||
229 | MODULE_DEVICE_TABLE(pci, cs5535_gpio_pci_tbl); | ||
230 | |||
231 | /* | ||
232 | * We can't use the standard PCI driver registration stuff here, since | ||
233 | * that allows only one driver to bind to each PCI device (and we want | ||
234 | * multiple drivers to be able to bind to the device). Instead, manually | ||
235 | * scan for the PCI device, request a single region, and keep track of the | ||
236 | * devices that we're using. | ||
237 | */ | ||
238 | |||
239 | static int __init cs5535_gpio_scan_pci(void) | ||
240 | { | ||
241 | struct pci_dev *pdev; | ||
242 | int err = -ENODEV; | ||
243 | int i; | ||
244 | |||
245 | for (i = 0; i < ARRAY_SIZE(cs5535_gpio_pci_tbl); i++) { | ||
246 | pdev = pci_get_device(cs5535_gpio_pci_tbl[i].vendor, | ||
247 | cs5535_gpio_pci_tbl[i].device, NULL); | ||
248 | if (pdev) { | ||
249 | err = cs5535_gpio_probe(pdev, &cs5535_gpio_pci_tbl[i]); | ||
250 | if (err) | ||
251 | pci_dev_put(pdev); | ||
252 | |||
253 | /* we only support a single CS5535/6 southbridge */ | ||
254 | break; | ||
255 | } | ||
256 | } | ||
257 | |||
258 | return err; | ||
259 | } | ||
260 | |||
261 | static void __exit cs5535_gpio_free_pci(void) | ||
262 | { | ||
263 | cs5535_gpio_remove(cs5535_gpio_chip.pdev); | ||
264 | pci_dev_put(cs5535_gpio_chip.pdev); | ||
265 | } | ||
266 | |||
267 | static int __init cs5535_gpio_init(void) | ||
268 | { | ||
269 | return cs5535_gpio_scan_pci(); | ||
270 | } | ||
271 | |||
272 | static void __exit cs5535_gpio_exit(void) | ||
273 | { | ||
274 | cs5535_gpio_free_pci(); | ||
275 | } | ||
276 | |||
277 | module_init(cs5535_gpio_init); | ||
278 | module_exit(cs5535_gpio_exit); | ||
279 | |||
280 | MODULE_AUTHOR("Andres Salomon <dilinger@collabora.co.uk>"); | ||
281 | MODULE_DESCRIPTION("AMD CS5535/CS5536 GPIO driver"); | ||
282 | MODULE_LICENSE("GPL"); | ||