diff options
author | Andrea Bastoni <bastoni@cs.unc.edu> | 2010-05-30 19:16:45 -0400 |
---|---|---|
committer | Andrea Bastoni <bastoni@cs.unc.edu> | 2010-05-30 19:16:45 -0400 |
commit | ada47b5fe13d89735805b566185f4885f5a3f750 (patch) | |
tree | 644b88f8a71896307d71438e9b3af49126ffb22b /drivers/gpio/cs5535-gpio.c | |
parent | 43e98717ad40a4ae64545b5ba047c7b86aa44f4f (diff) | |
parent | 3280f21d43ee541f97f8cda5792150d2dbec20d5 (diff) |
Merge branch 'wip-2.6.34' into old-private-masterarchived-private-master
Diffstat (limited to 'drivers/gpio/cs5535-gpio.c')
-rw-r--r-- | drivers/gpio/cs5535-gpio.c | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/drivers/gpio/cs5535-gpio.c b/drivers/gpio/cs5535-gpio.c new file mode 100644 index 000000000000..0c3c498f2260 --- /dev/null +++ b/drivers/gpio/cs5535-gpio.c | |||
@@ -0,0 +1,357 @@ | |||
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 | /* | ||
23 | * Some GPIO pins | ||
24 | * 31-29,23 : reserved (always mask out) | ||
25 | * 28 : Power Button | ||
26 | * 26 : PME# | ||
27 | * 22-16 : LPC | ||
28 | * 14,15 : SMBus | ||
29 | * 9,8 : UART1 | ||
30 | * 7 : PCI INTB | ||
31 | * 3,4 : UART2/DDC | ||
32 | * 2 : IDE_IRQ0 | ||
33 | * 1 : AC_BEEP | ||
34 | * 0 : PCI INTA | ||
35 | * | ||
36 | * If a mask was not specified, allow all except | ||
37 | * reserved and Power Button | ||
38 | */ | ||
39 | #define GPIO_DEFAULT_MASK 0x0F7FFFFF | ||
40 | |||
41 | static ulong mask = GPIO_DEFAULT_MASK; | ||
42 | module_param_named(mask, mask, ulong, 0444); | ||
43 | MODULE_PARM_DESC(mask, "GPIO channel mask."); | ||
44 | |||
45 | static struct cs5535_gpio_chip { | ||
46 | struct gpio_chip chip; | ||
47 | resource_size_t base; | ||
48 | |||
49 | struct pci_dev *pdev; | ||
50 | spinlock_t lock; | ||
51 | } cs5535_gpio_chip; | ||
52 | |||
53 | /* | ||
54 | * The CS5535/CS5536 GPIOs support a number of extra features not defined | ||
55 | * by the gpio_chip API, so these are exported. For a full list of the | ||
56 | * registers, see include/linux/cs5535.h. | ||
57 | */ | ||
58 | |||
59 | static void __cs5535_gpio_set(struct cs5535_gpio_chip *chip, unsigned offset, | ||
60 | unsigned int reg) | ||
61 | { | ||
62 | if (offset < 16) | ||
63 | /* low bank register */ | ||
64 | outl(1 << offset, chip->base + reg); | ||
65 | else | ||
66 | /* high bank register */ | ||
67 | outl(1 << (offset - 16), chip->base + 0x80 + reg); | ||
68 | } | ||
69 | |||
70 | void cs5535_gpio_set(unsigned offset, unsigned int reg) | ||
71 | { | ||
72 | struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; | ||
73 | unsigned long flags; | ||
74 | |||
75 | spin_lock_irqsave(&chip->lock, flags); | ||
76 | __cs5535_gpio_set(chip, offset, reg); | ||
77 | spin_unlock_irqrestore(&chip->lock, flags); | ||
78 | } | ||
79 | EXPORT_SYMBOL_GPL(cs5535_gpio_set); | ||
80 | |||
81 | static void __cs5535_gpio_clear(struct cs5535_gpio_chip *chip, unsigned offset, | ||
82 | unsigned int reg) | ||
83 | { | ||
84 | if (offset < 16) | ||
85 | /* low bank register */ | ||
86 | outl(1 << (offset + 16), chip->base + reg); | ||
87 | else | ||
88 | /* high bank register */ | ||
89 | outl(1 << offset, chip->base + 0x80 + reg); | ||
90 | } | ||
91 | |||
92 | void cs5535_gpio_clear(unsigned offset, unsigned int reg) | ||
93 | { | ||
94 | struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; | ||
95 | unsigned long flags; | ||
96 | |||
97 | spin_lock_irqsave(&chip->lock, flags); | ||
98 | __cs5535_gpio_clear(chip, offset, reg); | ||
99 | spin_unlock_irqrestore(&chip->lock, flags); | ||
100 | } | ||
101 | EXPORT_SYMBOL_GPL(cs5535_gpio_clear); | ||
102 | |||
103 | int cs5535_gpio_isset(unsigned offset, unsigned int reg) | ||
104 | { | ||
105 | struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; | ||
106 | unsigned long flags; | ||
107 | long val; | ||
108 | |||
109 | spin_lock_irqsave(&chip->lock, flags); | ||
110 | if (offset < 16) | ||
111 | /* low bank register */ | ||
112 | val = inl(chip->base + reg); | ||
113 | else { | ||
114 | /* high bank register */ | ||
115 | val = inl(chip->base + 0x80 + reg); | ||
116 | offset -= 16; | ||
117 | } | ||
118 | spin_unlock_irqrestore(&chip->lock, flags); | ||
119 | |||
120 | return (val & (1 << offset)) ? 1 : 0; | ||
121 | } | ||
122 | EXPORT_SYMBOL_GPL(cs5535_gpio_isset); | ||
123 | |||
124 | /* | ||
125 | * Generic gpio_chip API support. | ||
126 | */ | ||
127 | |||
128 | static int chip_gpio_request(struct gpio_chip *c, unsigned offset) | ||
129 | { | ||
130 | struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; | ||
131 | unsigned long flags; | ||
132 | |||
133 | spin_lock_irqsave(&chip->lock, flags); | ||
134 | |||
135 | /* check if this pin is available */ | ||
136 | if ((mask & (1 << offset)) == 0) { | ||
137 | dev_info(&chip->pdev->dev, | ||
138 | "pin %u is not available (check mask)\n", offset); | ||
139 | spin_unlock_irqrestore(&chip->lock, flags); | ||
140 | return -EINVAL; | ||
141 | } | ||
142 | |||
143 | /* disable output aux 1 & 2 on this pin */ | ||
144 | __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX1); | ||
145 | __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX2); | ||
146 | |||
147 | /* disable input aux 1 on this pin */ | ||
148 | __cs5535_gpio_clear(chip, offset, GPIO_INPUT_AUX1); | ||
149 | |||
150 | spin_unlock_irqrestore(&chip->lock, flags); | ||
151 | |||
152 | return 0; | ||
153 | } | ||
154 | |||
155 | static int chip_gpio_get(struct gpio_chip *chip, unsigned offset) | ||
156 | { | ||
157 | return cs5535_gpio_isset(offset, GPIO_READ_BACK); | ||
158 | } | ||
159 | |||
160 | static void chip_gpio_set(struct gpio_chip *chip, unsigned offset, int val) | ||
161 | { | ||
162 | if (val) | ||
163 | cs5535_gpio_set(offset, GPIO_OUTPUT_VAL); | ||
164 | else | ||
165 | cs5535_gpio_clear(offset, GPIO_OUTPUT_VAL); | ||
166 | } | ||
167 | |||
168 | static int chip_direction_input(struct gpio_chip *c, unsigned offset) | ||
169 | { | ||
170 | struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; | ||
171 | unsigned long flags; | ||
172 | |||
173 | spin_lock_irqsave(&chip->lock, flags); | ||
174 | __cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE); | ||
175 | __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_ENABLE); | ||
176 | spin_unlock_irqrestore(&chip->lock, flags); | ||
177 | |||
178 | return 0; | ||
179 | } | ||
180 | |||
181 | static int chip_direction_output(struct gpio_chip *c, unsigned offset, int val) | ||
182 | { | ||
183 | struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; | ||
184 | unsigned long flags; | ||
185 | |||
186 | spin_lock_irqsave(&chip->lock, flags); | ||
187 | |||
188 | __cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE); | ||
189 | __cs5535_gpio_set(chip, offset, GPIO_OUTPUT_ENABLE); | ||
190 | if (val) | ||
191 | __cs5535_gpio_set(chip, offset, GPIO_OUTPUT_VAL); | ||
192 | else | ||
193 | __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_VAL); | ||
194 | |||
195 | spin_unlock_irqrestore(&chip->lock, flags); | ||
196 | |||
197 | return 0; | ||
198 | } | ||
199 | |||
200 | static char *cs5535_gpio_names[] = { | ||
201 | "GPIO0", "GPIO1", "GPIO2", "GPIO3", | ||
202 | "GPIO4", "GPIO5", "GPIO6", "GPIO7", | ||
203 | "GPIO8", "GPIO9", "GPIO10", "GPIO11", | ||
204 | "GPIO12", "GPIO13", "GPIO14", "GPIO15", | ||
205 | "GPIO16", "GPIO17", "GPIO18", "GPIO19", | ||
206 | "GPIO20", "GPIO21", "GPIO22", NULL, | ||
207 | "GPIO24", "GPIO25", "GPIO26", "GPIO27", | ||
208 | "GPIO28", NULL, NULL, NULL, | ||
209 | }; | ||
210 | |||
211 | static struct cs5535_gpio_chip cs5535_gpio_chip = { | ||
212 | .chip = { | ||
213 | .owner = THIS_MODULE, | ||
214 | .label = DRV_NAME, | ||
215 | |||
216 | .base = 0, | ||
217 | .ngpio = 32, | ||
218 | .names = cs5535_gpio_names, | ||
219 | .request = chip_gpio_request, | ||
220 | |||
221 | .get = chip_gpio_get, | ||
222 | .set = chip_gpio_set, | ||
223 | |||
224 | .direction_input = chip_direction_input, | ||
225 | .direction_output = chip_direction_output, | ||
226 | }, | ||
227 | }; | ||
228 | |||
229 | static int __init cs5535_gpio_probe(struct pci_dev *pdev, | ||
230 | const struct pci_device_id *pci_id) | ||
231 | { | ||
232 | int err; | ||
233 | ulong mask_orig = mask; | ||
234 | |||
235 | /* There are two ways to get the GPIO base address; one is by | ||
236 | * fetching it from MSR_LBAR_GPIO, the other is by reading the | ||
237 | * PCI BAR info. The latter method is easier (especially across | ||
238 | * different architectures), so we'll stick with that for now. If | ||
239 | * it turns out to be unreliable in the face of crappy BIOSes, we | ||
240 | * can always go back to using MSRs.. */ | ||
241 | |||
242 | err = pci_enable_device_io(pdev); | ||
243 | if (err) { | ||
244 | dev_err(&pdev->dev, "can't enable device IO\n"); | ||
245 | goto done; | ||
246 | } | ||
247 | |||
248 | err = pci_request_region(pdev, GPIO_BAR, DRV_NAME); | ||
249 | if (err) { | ||
250 | dev_err(&pdev->dev, "can't alloc PCI BAR #%d\n", GPIO_BAR); | ||
251 | goto done; | ||
252 | } | ||
253 | |||
254 | /* set up the driver-specific struct */ | ||
255 | cs5535_gpio_chip.base = pci_resource_start(pdev, GPIO_BAR); | ||
256 | cs5535_gpio_chip.pdev = pdev; | ||
257 | spin_lock_init(&cs5535_gpio_chip.lock); | ||
258 | |||
259 | dev_info(&pdev->dev, "allocated PCI BAR #%d: base 0x%llx\n", GPIO_BAR, | ||
260 | (unsigned long long) cs5535_gpio_chip.base); | ||
261 | |||
262 | /* mask out reserved pins */ | ||
263 | mask &= 0x1F7FFFFF; | ||
264 | |||
265 | /* do not allow pin 28, Power Button, as there's special handling | ||
266 | * in the PMC needed. (note 12, p. 48) */ | ||
267 | mask &= ~(1 << 28); | ||
268 | |||
269 | if (mask_orig != mask) | ||
270 | dev_info(&pdev->dev, "mask changed from 0x%08lX to 0x%08lX\n", | ||
271 | mask_orig, mask); | ||
272 | |||
273 | /* finally, register with the generic GPIO API */ | ||
274 | err = gpiochip_add(&cs5535_gpio_chip.chip); | ||
275 | if (err) | ||
276 | goto release_region; | ||
277 | |||
278 | dev_info(&pdev->dev, DRV_NAME ": GPIO support successfully loaded.\n"); | ||
279 | return 0; | ||
280 | |||
281 | release_region: | ||
282 | pci_release_region(pdev, GPIO_BAR); | ||
283 | done: | ||
284 | return err; | ||
285 | } | ||
286 | |||
287 | static void __exit cs5535_gpio_remove(struct pci_dev *pdev) | ||
288 | { | ||
289 | int err; | ||
290 | |||
291 | err = gpiochip_remove(&cs5535_gpio_chip.chip); | ||
292 | if (err) { | ||
293 | /* uhh? */ | ||
294 | dev_err(&pdev->dev, "unable to remove gpio_chip?\n"); | ||
295 | } | ||
296 | pci_release_region(pdev, GPIO_BAR); | ||
297 | } | ||
298 | |||
299 | static struct pci_device_id cs5535_gpio_pci_tbl[] = { | ||
300 | { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA) }, | ||
301 | { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) }, | ||
302 | { 0, }, | ||
303 | }; | ||
304 | MODULE_DEVICE_TABLE(pci, cs5535_gpio_pci_tbl); | ||
305 | |||
306 | /* | ||
307 | * We can't use the standard PCI driver registration stuff here, since | ||
308 | * that allows only one driver to bind to each PCI device (and we want | ||
309 | * multiple drivers to be able to bind to the device). Instead, manually | ||
310 | * scan for the PCI device, request a single region, and keep track of the | ||
311 | * devices that we're using. | ||
312 | */ | ||
313 | |||
314 | static int __init cs5535_gpio_scan_pci(void) | ||
315 | { | ||
316 | struct pci_dev *pdev; | ||
317 | int err = -ENODEV; | ||
318 | int i; | ||
319 | |||
320 | for (i = 0; i < ARRAY_SIZE(cs5535_gpio_pci_tbl); i++) { | ||
321 | pdev = pci_get_device(cs5535_gpio_pci_tbl[i].vendor, | ||
322 | cs5535_gpio_pci_tbl[i].device, NULL); | ||
323 | if (pdev) { | ||
324 | err = cs5535_gpio_probe(pdev, &cs5535_gpio_pci_tbl[i]); | ||
325 | if (err) | ||
326 | pci_dev_put(pdev); | ||
327 | |||
328 | /* we only support a single CS5535/6 southbridge */ | ||
329 | break; | ||
330 | } | ||
331 | } | ||
332 | |||
333 | return err; | ||
334 | } | ||
335 | |||
336 | static void __exit cs5535_gpio_free_pci(void) | ||
337 | { | ||
338 | cs5535_gpio_remove(cs5535_gpio_chip.pdev); | ||
339 | pci_dev_put(cs5535_gpio_chip.pdev); | ||
340 | } | ||
341 | |||
342 | static int __init cs5535_gpio_init(void) | ||
343 | { | ||
344 | return cs5535_gpio_scan_pci(); | ||
345 | } | ||
346 | |||
347 | static void __exit cs5535_gpio_exit(void) | ||
348 | { | ||
349 | cs5535_gpio_free_pci(); | ||
350 | } | ||
351 | |||
352 | module_init(cs5535_gpio_init); | ||
353 | module_exit(cs5535_gpio_exit); | ||
354 | |||
355 | MODULE_AUTHOR("Andres Salomon <dilinger@collabora.co.uk>"); | ||
356 | MODULE_DESCRIPTION("AMD CS5535/CS5536 GPIO driver"); | ||
357 | MODULE_LICENSE("GPL"); | ||