Blame view

drivers/gpio/gpio-cs5535.c 9.56 KB
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
1
2
3
4
5
6
7
8
9
10
11
12
13
  /*
   * AMD CS5535/CS5536 GPIO driver
   * Copyright (C) 2006  Advanced Micro Devices, Inc.
   * Copyright (C) 2007-2009  Andres Salomon <dilinger@collabora.co.uk>
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of version 2 of the GNU General Public License
   * as published by the Free Software Foundation.
   */
  
  #include <linux/kernel.h>
  #include <linux/spinlock.h>
  #include <linux/module.h>
df9666940   Andres Salomon   gpio: Convert cs5...
14
  #include <linux/platform_device.h>
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
15
16
17
  #include <linux/gpio.h>
  #include <linux/io.h>
  #include <linux/cs5535.h>
1b912c1bc   Andres Salomon   drivers/gpio/cs55...
18
  #include <asm/msr.h>
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
19
20
  
  #define DRV_NAME "cs5535-gpio"
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
21

1ea3fa7bb   Tobias Mueller   cs5535-gpio: requ...
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  /*
   * Some GPIO pins
   *  31-29,23 : reserved (always mask out)
   *  28       : Power Button
   *  26       : PME#
   *  22-16    : LPC
   *  14,15    : SMBus
   *  9,8      : UART1
   *  7        : PCI INTB
   *  3,4      : UART2/DDC
   *  2        : IDE_IRQ0
   *  1        : AC_BEEP
   *  0        : PCI INTA
   *
   * If a mask was not specified, allow all except
   * reserved and Power Button
   */
  #define GPIO_DEFAULT_MASK 0x0F7FFFFF
  
  static ulong mask = GPIO_DEFAULT_MASK;
  module_param_named(mask, mask, ulong, 0444);
  MODULE_PARM_DESC(mask, "GPIO channel mask.");
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
44
45
46
  static struct cs5535_gpio_chip {
  	struct gpio_chip chip;
  	resource_size_t base;
df9666940   Andres Salomon   gpio: Convert cs5...
47
  	struct platform_device *pdev;
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
48
49
50
51
52
53
54
55
  	spinlock_t lock;
  } cs5535_gpio_chip;
  
  /*
   * The CS5535/CS5536 GPIOs support a number of extra features not defined
   * by the gpio_chip API, so these are exported.  For a full list of the
   * registers, see include/linux/cs5535.h.
   */
001851659   Andres Salomon   cs5535-gpio: don'...
56
57
  static void errata_outl(struct cs5535_gpio_chip *chip, u32 val,
  		unsigned int reg)
853ff8832   Andres Salomon   cs5535-gpio: appl...
58
  {
001851659   Andres Salomon   cs5535-gpio: don'...
59
  	unsigned long addr = chip->base + 0x80 + reg;
853ff8832   Andres Salomon   cs5535-gpio: appl...
60
61
62
63
64
  	/*
  	 * According to the CS5536 errata (#36), after suspend
  	 * a write to the high bank GPIO register will clear all
  	 * non-selected bits; the recommended workaround is a
  	 * read-modify-write operation.
001851659   Andres Salomon   cs5535-gpio: don'...
65
66
67
  	 *
  	 * Don't apply this errata to the edge status GPIOs, as writing
  	 * to their lower bits will clear them.
853ff8832   Andres Salomon   cs5535-gpio: appl...
68
  	 */
44658a11f   Andres Salomon   cs5535-gpio: hand...
69
70
71
72
73
74
  	if (reg != GPIO_POSITIVE_EDGE_STS && reg != GPIO_NEGATIVE_EDGE_STS) {
  		if (val & 0xffff)
  			val |= (inl(addr) & 0xffff); /* ignore the high bits */
  		else
  			val |= (inl(addr) ^ (val >> 16));
  	}
853ff8832   Andres Salomon   cs5535-gpio: appl...
75
76
  	outl(val, addr);
  }
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
77
78
79
80
81
82
83
84
  static void __cs5535_gpio_set(struct cs5535_gpio_chip *chip, unsigned offset,
  		unsigned int reg)
  {
  	if (offset < 16)
  		/* low bank register */
  		outl(1 << offset, chip->base + reg);
  	else
  		/* high bank register */
001851659   Andres Salomon   cs5535-gpio: don'...
85
  		errata_outl(chip, 1 << (offset - 16), reg);
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
  }
  
  void cs5535_gpio_set(unsigned offset, unsigned int reg)
  {
  	struct cs5535_gpio_chip *chip = &cs5535_gpio_chip;
  	unsigned long flags;
  
  	spin_lock_irqsave(&chip->lock, flags);
  	__cs5535_gpio_set(chip, offset, reg);
  	spin_unlock_irqrestore(&chip->lock, flags);
  }
  EXPORT_SYMBOL_GPL(cs5535_gpio_set);
  
  static void __cs5535_gpio_clear(struct cs5535_gpio_chip *chip, unsigned offset,
  		unsigned int reg)
  {
  	if (offset < 16)
  		/* low bank register */
  		outl(1 << (offset + 16), chip->base + reg);
  	else
  		/* high bank register */
001851659   Andres Salomon   cs5535-gpio: don'...
107
  		errata_outl(chip, 1 << offset, reg);
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
  }
  
  void cs5535_gpio_clear(unsigned offset, unsigned int reg)
  {
  	struct cs5535_gpio_chip *chip = &cs5535_gpio_chip;
  	unsigned long flags;
  
  	spin_lock_irqsave(&chip->lock, flags);
  	__cs5535_gpio_clear(chip, offset, reg);
  	spin_unlock_irqrestore(&chip->lock, flags);
  }
  EXPORT_SYMBOL_GPL(cs5535_gpio_clear);
  
  int cs5535_gpio_isset(unsigned offset, unsigned int reg)
  {
  	struct cs5535_gpio_chip *chip = &cs5535_gpio_chip;
  	unsigned long flags;
  	long val;
  
  	spin_lock_irqsave(&chip->lock, flags);
  	if (offset < 16)
  		/* low bank register */
  		val = inl(chip->base + reg);
  	else {
  		/* high bank register */
  		val = inl(chip->base + 0x80 + reg);
  		offset -= 16;
  	}
  	spin_unlock_irqrestore(&chip->lock, flags);
  
  	return (val & (1 << offset)) ? 1 : 0;
  }
  EXPORT_SYMBOL_GPL(cs5535_gpio_isset);
1b912c1bc   Andres Salomon   drivers/gpio/cs55...
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
  int cs5535_gpio_set_irq(unsigned group, unsigned irq)
  {
  	uint32_t lo, hi;
  
  	if (group > 7 || irq > 15)
  		return -EINVAL;
  
  	rdmsr(MSR_PIC_ZSEL_HIGH, lo, hi);
  
  	lo &= ~(0xF << (group * 4));
  	lo |= (irq & 0xF) << (group * 4);
  
  	wrmsr(MSR_PIC_ZSEL_HIGH, lo, hi);
  	return 0;
  }
  EXPORT_SYMBOL_GPL(cs5535_gpio_set_irq);
  
  void cs5535_gpio_setup_event(unsigned offset, int pair, int pme)
  {
  	struct cs5535_gpio_chip *chip = &cs5535_gpio_chip;
  	uint32_t shift = (offset % 8) * 4;
  	unsigned long flags;
  	uint32_t val;
  
  	if (offset >= 24)
  		offset = GPIO_MAP_W;
  	else if (offset >= 16)
  		offset = GPIO_MAP_Z;
  	else if (offset >= 8)
  		offset = GPIO_MAP_Y;
  	else
  		offset = GPIO_MAP_X;
  
  	spin_lock_irqsave(&chip->lock, flags);
  	val = inl(chip->base + offset);
  
  	/* Clear whatever was there before */
  	val &= ~(0xF << shift);
  
  	/* Set the new value */
  	val |= ((pair & 7) << shift);
  
  	/* Set the PME bit if this is a PME event */
  	if (pme)
  		val |= (1 << (shift + 3));
  
  	outl(val, chip->base + offset);
  	spin_unlock_irqrestore(&chip->lock, flags);
  }
  EXPORT_SYMBOL_GPL(cs5535_gpio_setup_event);
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
191
192
193
  /*
   * Generic gpio_chip API support.
   */
1ea3fa7bb   Tobias Mueller   cs5535-gpio: requ...
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
  static int chip_gpio_request(struct gpio_chip *c, unsigned offset)
  {
  	struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c;
  	unsigned long flags;
  
  	spin_lock_irqsave(&chip->lock, flags);
  
  	/* check if this pin is available */
  	if ((mask & (1 << offset)) == 0) {
  		dev_info(&chip->pdev->dev,
  			"pin %u is not available (check mask)
  ", offset);
  		spin_unlock_irqrestore(&chip->lock, flags);
  		return -EINVAL;
  	}
  
  	/* disable output aux 1 & 2 on this pin */
  	__cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX1);
  	__cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX2);
  
  	/* disable input aux 1 on this pin */
  	__cs5535_gpio_clear(chip, offset, GPIO_INPUT_AUX1);
  
  	spin_unlock_irqrestore(&chip->lock, flags);
  
  	return 0;
  }
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
221
222
  static int chip_gpio_get(struct gpio_chip *chip, unsigned offset)
  {
a8a5164c2   Ben Gardner   gpio: cs5535-gpio...
223
  	return cs5535_gpio_isset(offset, GPIO_READ_BACK);
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
  }
  
  static void chip_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
  {
  	if (val)
  		cs5535_gpio_set(offset, GPIO_OUTPUT_VAL);
  	else
  		cs5535_gpio_clear(offset, GPIO_OUTPUT_VAL);
  }
  
  static int chip_direction_input(struct gpio_chip *c, unsigned offset)
  {
  	struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c;
  	unsigned long flags;
  
  	spin_lock_irqsave(&chip->lock, flags);
  	__cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE);
a8a5164c2   Ben Gardner   gpio: cs5535-gpio...
241
  	__cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_ENABLE);
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
242
243
244
245
246
247
248
249
250
251
252
  	spin_unlock_irqrestore(&chip->lock, flags);
  
  	return 0;
  }
  
  static int chip_direction_output(struct gpio_chip *c, unsigned offset, int val)
  {
  	struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c;
  	unsigned long flags;
  
  	spin_lock_irqsave(&chip->lock, flags);
a8a5164c2   Ben Gardner   gpio: cs5535-gpio...
253
  	__cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE);
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
254
255
256
257
258
259
260
261
262
263
  	__cs5535_gpio_set(chip, offset, GPIO_OUTPUT_ENABLE);
  	if (val)
  		__cs5535_gpio_set(chip, offset, GPIO_OUTPUT_VAL);
  	else
  		__cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_VAL);
  
  	spin_unlock_irqrestore(&chip->lock, flags);
  
  	return 0;
  }
62154991a   Uwe Kleine-König   gpiolib: make nam...
264
  static const char * const cs5535_gpio_names[] = {
1ea3fa7bb   Tobias Mueller   cs5535-gpio: requ...
265
266
267
268
269
270
271
272
273
  	"GPIO0", "GPIO1", "GPIO2", "GPIO3",
  	"GPIO4", "GPIO5", "GPIO6", "GPIO7",
  	"GPIO8", "GPIO9", "GPIO10", "GPIO11",
  	"GPIO12", "GPIO13", "GPIO14", "GPIO15",
  	"GPIO16", "GPIO17", "GPIO18", "GPIO19",
  	"GPIO20", "GPIO21", "GPIO22", NULL,
  	"GPIO24", "GPIO25", "GPIO26", "GPIO27",
  	"GPIO28", NULL, NULL, NULL,
  };
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
274
275
276
277
278
279
  static struct cs5535_gpio_chip cs5535_gpio_chip = {
  	.chip = {
  		.owner = THIS_MODULE,
  		.label = DRV_NAME,
  
  		.base = 0,
1ea3fa7bb   Tobias Mueller   cs5535-gpio: requ...
280
281
282
  		.ngpio = 32,
  		.names = cs5535_gpio_names,
  		.request = chip_gpio_request,
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
283
284
285
286
287
288
289
290
  
  		.get = chip_gpio_get,
  		.set = chip_gpio_set,
  
  		.direction_input = chip_direction_input,
  		.direction_output = chip_direction_output,
  	},
  };
df9666940   Andres Salomon   gpio: Convert cs5...
291
  static int __devinit cs5535_gpio_probe(struct platform_device *pdev)
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
292
  {
df9666940   Andres Salomon   gpio: Convert cs5...
293
294
  	struct resource *res;
  	int err = -EIO;
1ea3fa7bb   Tobias Mueller   cs5535-gpio: requ...
295
  	ulong mask_orig = mask;
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
296
297
298
299
300
301
302
  
  	/* There are two ways to get the GPIO base address; one is by
  	 * fetching it from MSR_LBAR_GPIO, the other is by reading the
  	 * PCI BAR info.  The latter method is easier (especially across
  	 * different architectures), so we'll stick with that for now.  If
  	 * it turns out to be unreliable in the face of crappy BIOSes, we
  	 * can always go back to using MSRs.. */
df9666940   Andres Salomon   gpio: Convert cs5...
303
304
305
306
  	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
  	if (!res) {
  		dev_err(&pdev->dev, "can't fetch device resource info
  ");
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
307
308
  		goto done;
  	}
df9666940   Andres Salomon   gpio: Convert cs5...
309
310
311
  	if (!request_region(res->start, resource_size(res), pdev->name)) {
  		dev_err(&pdev->dev, "can't request region
  ");
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
312
313
314
315
  		goto done;
  	}
  
  	/* set up the driver-specific struct */
df9666940   Andres Salomon   gpio: Convert cs5...
316
  	cs5535_gpio_chip.base = res->start;
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
317
318
  	cs5535_gpio_chip.pdev = pdev;
  	spin_lock_init(&cs5535_gpio_chip.lock);
1db0b427e   Joe Perches   gpio: Fix cs5535 ...
319
320
  	dev_info(&pdev->dev, "reserved resource region %pR
  ", res);
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
321

1ea3fa7bb   Tobias Mueller   cs5535-gpio: requ...
322
323
324
325
326
327
328
329
330
331
332
  	/* mask out reserved pins */
  	mask &= 0x1F7FFFFF;
  
  	/* do not allow pin 28, Power Button, as there's special handling
  	 * in the PMC needed. (note 12, p. 48) */
  	mask &= ~(1 << 28);
  
  	if (mask_orig != mask)
  		dev_info(&pdev->dev, "mask changed from 0x%08lX to 0x%08lX
  ",
  				mask_orig, mask);
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
333
334
  	/* finally, register with the generic GPIO API */
  	err = gpiochip_add(&cs5535_gpio_chip.chip);
1ea3fa7bb   Tobias Mueller   cs5535-gpio: requ...
335
  	if (err)
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
336
  		goto release_region;
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
337

5f0a96b04   Andres Salomon   cs5535-gpio: add ...
338
339
340
  	return 0;
  
  release_region:
df9666940   Andres Salomon   gpio: Convert cs5...
341
  	release_region(res->start, resource_size(res));
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
342
343
344
  done:
  	return err;
  }
df9666940   Andres Salomon   gpio: Convert cs5...
345
  static int __devexit cs5535_gpio_remove(struct platform_device *pdev)
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
346
  {
df9666940   Andres Salomon   gpio: Convert cs5...
347
  	struct resource *r;
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
348
349
350
351
352
353
354
  	int err;
  
  	err = gpiochip_remove(&cs5535_gpio_chip.chip);
  	if (err) {
  		/* uhh? */
  		dev_err(&pdev->dev, "unable to remove gpio_chip?
  ");
df9666940   Andres Salomon   gpio: Convert cs5...
355
  		return err;
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
356
  	}
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
357

df9666940   Andres Salomon   gpio: Convert cs5...
358
359
360
  	r = platform_get_resource(pdev, IORESOURCE_IO, 0);
  	release_region(r->start, resource_size(r));
  	return 0;
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
361
  }
36885ff0e   Nikanth Karthikesan   gpio/cs5535-gpio:...
362
  static struct platform_driver cs5535_gpio_driver = {
df9666940   Andres Salomon   gpio: Convert cs5...
363
364
365
366
367
368
369
  	.driver = {
  		.name = DRV_NAME,
  		.owner = THIS_MODULE,
  	},
  	.probe = cs5535_gpio_probe,
  	.remove = __devexit_p(cs5535_gpio_remove),
  };
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
370

6f61415e9   Mark Brown   gpio: Convert GPI...
371
  module_platform_driver(cs5535_gpio_driver);
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
372

d45840d9f   Andres Salomon   Andres has moved
373
  MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
5f0a96b04   Andres Salomon   cs5535-gpio: add ...
374
375
  MODULE_DESCRIPTION("AMD CS5535/CS5536 GPIO driver");
  MODULE_LICENSE("GPL");
ec9d0cf57   Andres Salomon   gpio/misc: Add MO...
376
  MODULE_ALIAS("platform:" DRV_NAME);