]> bbs.cooldavid.org Git - net-next-2.6.git/blame - drivers/gpio/cs5535-gpio.c
Andres has moved
[net-next-2.6.git] / drivers / gpio / cs5535-gpio.c
CommitLineData
5f0a96b0
AS
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
1ea3fa7b
TM
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
41static ulong mask = GPIO_DEFAULT_MASK;
42module_param_named(mask, mask, ulong, 0444);
43MODULE_PARM_DESC(mask, "GPIO channel mask.");
44
5f0a96b0
AS
45static 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
59static 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
70void 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}
79EXPORT_SYMBOL_GPL(cs5535_gpio_set);
80
81static 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
92void 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}
101EXPORT_SYMBOL_GPL(cs5535_gpio_clear);
102
103int 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}
122EXPORT_SYMBOL_GPL(cs5535_gpio_isset);
123
124/*
125 * Generic gpio_chip API support.
126 */
127
1ea3fa7b
TM
128static 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
5f0a96b0
AS
155static int chip_gpio_get(struct gpio_chip *chip, unsigned offset)
156{
a8a5164c 157 return cs5535_gpio_isset(offset, GPIO_READ_BACK);
5f0a96b0
AS
158}
159
160static 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
168static 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);
a8a5164c 175 __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_ENABLE);
5f0a96b0
AS
176 spin_unlock_irqrestore(&chip->lock, flags);
177
178 return 0;
179}
180
181static 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
a8a5164c 188 __cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE);
5f0a96b0
AS
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
62154991 200static const char * const cs5535_gpio_names[] = {
1ea3fa7b
TM
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
5f0a96b0
AS
211static struct cs5535_gpio_chip cs5535_gpio_chip = {
212 .chip = {
213 .owner = THIS_MODULE,
214 .label = DRV_NAME,
215
216 .base = 0,
1ea3fa7b
TM
217 .ngpio = 32,
218 .names = cs5535_gpio_names,
219 .request = chip_gpio_request,
5f0a96b0
AS
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
229static int __init cs5535_gpio_probe(struct pci_dev *pdev,
230 const struct pci_device_id *pci_id)
231{
232 int err;
1ea3fa7b 233 ulong mask_orig = mask;
5f0a96b0
AS
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
1ea3fa7b
TM
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
5f0a96b0
AS
273 /* finally, register with the generic GPIO API */
274 err = gpiochip_add(&cs5535_gpio_chip.chip);
1ea3fa7b 275 if (err)
5f0a96b0 276 goto release_region;
5f0a96b0 277
1ea3fa7b 278 dev_info(&pdev->dev, DRV_NAME ": GPIO support successfully loaded.\n");
5f0a96b0
AS
279 return 0;
280
281release_region:
282 pci_release_region(pdev, GPIO_BAR);
283done:
284 return err;
285}
286
287static 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
299static 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};
304MODULE_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
314static 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
336static 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
342static int __init cs5535_gpio_init(void)
343{
344 return cs5535_gpio_scan_pci();
345}
346
347static void __exit cs5535_gpio_exit(void)
348{
349 cs5535_gpio_free_pci();
350}
351
352module_init(cs5535_gpio_init);
353module_exit(cs5535_gpio_exit);
354
d45840d9 355MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
5f0a96b0
AS
356MODULE_DESCRIPTION("AMD CS5535/CS5536 GPIO driver");
357MODULE_LICENSE("GPL");