Blame view

drivers/misc/cs5535-mfgpt.c 10 KB
25763b3c8   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-only
82dca611b   Andres Salomon   cs5535: add a gen...
2
3
4
5
6
7
8
  /*
   * Driver for the CS5535/CS5536 Multi-Function General Purpose Timers (MFGPT)
   *
   * Copyright (C) 2006, Advanced Micro Devices, Inc.
   * Copyright (C) 2007  Andres Salomon <dilinger@debian.org>
   * Copyright (C) 2009  Andres Salomon <dilinger@collabora.co.uk>
   *
82dca611b   Andres Salomon   cs5535: add a gen...
9
10
11
12
13
14
15
   * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book.
   */
  
  #include <linux/kernel.h>
  #include <linux/spinlock.h>
  #include <linux/interrupt.h>
  #include <linux/module.h>
69bc6def3   Andres Salomon   misc: Convert cs5...
16
  #include <linux/platform_device.h>
82dca611b   Andres Salomon   cs5535: add a gen...
17
  #include <linux/cs5535.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
18
  #include <linux/slab.h>
82dca611b   Andres Salomon   cs5535: add a gen...
19
20
  
  #define DRV_NAME "cs5535-mfgpt"
82dca611b   Andres Salomon   cs5535: add a gen...
21
22
23
  
  static int mfgpt_reset_timers;
  module_param_named(mfgptfix, mfgpt_reset_timers, int, 0644);
945480b15   Richard Weinberger   cs5535-mfgpt: Add...
24
25
26
  MODULE_PARM_DESC(mfgptfix, "Try to reset the MFGPT timers during init; "
  		"required by some broken BIOSes (ie, TinyBIOS < 0.99) or kexec "
  		"(1 = reset the MFGPT using an undocumented bit, "
33facb4d6   Richard Weinberger   cs5535-mfgpt: Fix...
27
28
  		"2 = perform a soft reset by unconfiguring all timers); "
  		"use what works best for you.");
82dca611b   Andres Salomon   cs5535: add a gen...
29
30
31
32
33
34
35
36
37
  
  struct cs5535_mfgpt_timer {
  	struct cs5535_mfgpt_chip *chip;
  	int nr;
  };
  
  static struct cs5535_mfgpt_chip {
  	DECLARE_BITMAP(avail, MFGPT_MAX_TIMERS);
  	resource_size_t base;
69bc6def3   Andres Salomon   misc: Convert cs5...
38
  	struct platform_device *pdev;
82dca611b   Andres Salomon   cs5535: add a gen...
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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
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
  	spinlock_t lock;
  	int initialized;
  } cs5535_mfgpt_chip;
  
  int cs5535_mfgpt_toggle_event(struct cs5535_mfgpt_timer *timer, int cmp,
  		int event, int enable)
  {
  	uint32_t msr, mask, value, dummy;
  	int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
  
  	if (!timer) {
  		WARN_ON(1);
  		return -EIO;
  	}
  
  	/*
  	 * The register maps for these are described in sections 6.17.1.x of
  	 * the AMD Geode CS5536 Companion Device Data Book.
  	 */
  	switch (event) {
  	case MFGPT_EVENT_RESET:
  		/*
  		 * XXX: According to the docs, we cannot reset timers above
  		 * 6; that is, resets for 7 and 8 will be ignored.  Is this
  		 * a problem?   -dilinger
  		 */
  		msr = MSR_MFGPT_NR;
  		mask = 1 << (timer->nr + 24);
  		break;
  
  	case MFGPT_EVENT_NMI:
  		msr = MSR_MFGPT_NR;
  		mask = 1 << (timer->nr + shift);
  		break;
  
  	case MFGPT_EVENT_IRQ:
  		msr = MSR_MFGPT_IRQ;
  		mask = 1 << (timer->nr + shift);
  		break;
  
  	default:
  		return -EIO;
  	}
  
  	rdmsr(msr, value, dummy);
  
  	if (enable)
  		value |= mask;
  	else
  		value &= ~mask;
  
  	wrmsr(msr, value, dummy);
  	return 0;
  }
  EXPORT_SYMBOL_GPL(cs5535_mfgpt_toggle_event);
  
  int cs5535_mfgpt_set_irq(struct cs5535_mfgpt_timer *timer, int cmp, int *irq,
  		int enable)
  {
  	uint32_t zsel, lpc, dummy;
  	int shift;
  
  	if (!timer) {
  		WARN_ON(1);
  		return -EIO;
  	}
  
  	/*
  	 * Unfortunately, MFGPTs come in pairs sharing their IRQ lines. If VSA
  	 * is using the same CMP of the timer's Siamese twin, the IRQ is set to
  	 * 2, and we mustn't use nor change it.
  	 * XXX: Likewise, 2 Linux drivers might clash if the 2nd overwrites the
  	 * IRQ of the 1st. This can only happen if forcing an IRQ, calling this
  	 * with *irq==0 is safe. Currently there _are_ no 2 drivers.
  	 */
  	rdmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
  	shift = ((cmp == MFGPT_CMP1 ? 0 : 4) + timer->nr % 4) * 4;
  	if (((zsel >> shift) & 0xF) == 2)
  		return -EIO;
  
  	/* Choose IRQ: if none supplied, keep IRQ already set or use default */
  	if (!*irq)
  		*irq = (zsel >> shift) & 0xF;
  	if (!*irq)
  		*irq = CONFIG_CS5535_MFGPT_DEFAULT_IRQ;
  
  	/* Can't use IRQ if it's 0 (=disabled), 2, or routed to LPC */
  	if (*irq < 1 || *irq == 2 || *irq > 15)
  		return -EIO;
  	rdmsr(MSR_PIC_IRQM_LPC, lpc, dummy);
  	if (lpc & (1 << *irq))
  		return -EIO;
  
  	/* All chosen and checked - go for it */
  	if (cs5535_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable))
  		return -EIO;
  	if (enable) {
  		zsel = (zsel & ~(0xF << shift)) | (*irq << shift);
  		wrmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
  	}
  
  	return 0;
  }
  EXPORT_SYMBOL_GPL(cs5535_mfgpt_set_irq);
  
  struct cs5535_mfgpt_timer *cs5535_mfgpt_alloc_timer(int timer_nr, int domain)
  {
  	struct cs5535_mfgpt_chip *mfgpt = &cs5535_mfgpt_chip;
  	struct cs5535_mfgpt_timer *timer = NULL;
  	unsigned long flags;
  	int max;
  
  	if (!mfgpt->initialized)
  		goto done;
  
  	/* only allocate timers from the working domain if requested */
  	if (domain == MFGPT_DOMAIN_WORKING)
  		max = 6;
  	else
  		max = MFGPT_MAX_TIMERS;
  
  	if (timer_nr >= max) {
  		/* programmer error.  silly programmers! */
  		WARN_ON(1);
  		goto done;
  	}
  
  	spin_lock_irqsave(&mfgpt->lock, flags);
  	if (timer_nr < 0) {
  		unsigned long t;
  
  		/* try to find any available timer */
  		t = find_first_bit(mfgpt->avail, max);
  		/* set timer_nr to -1 if no timers available */
  		timer_nr = t < max ? (int) t : -1;
  	} else {
  		/* check if the requested timer's available */
4bbd61fb9   Christian Gmeiner   drivers/misc/cs55...
176
  		if (!test_bit(timer_nr, mfgpt->avail))
82dca611b   Andres Salomon   cs5535: add a gen...
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
  			timer_nr = -1;
  	}
  
  	if (timer_nr >= 0)
  		/* if timer_nr is not -1, it's an available timer */
  		__clear_bit(timer_nr, mfgpt->avail);
  	spin_unlock_irqrestore(&mfgpt->lock, flags);
  
  	if (timer_nr < 0)
  		goto done;
  
  	timer = kmalloc(sizeof(*timer), GFP_KERNEL);
  	if (!timer) {
  		/* aw hell */
  		spin_lock_irqsave(&mfgpt->lock, flags);
  		__set_bit(timer_nr, mfgpt->avail);
  		spin_unlock_irqrestore(&mfgpt->lock, flags);
  		goto done;
  	}
  	timer->chip = mfgpt;
  	timer->nr = timer_nr;
  	dev_info(&mfgpt->pdev->dev, "registered timer %d
  ", timer_nr);
  
  done:
  	return timer;
  }
  EXPORT_SYMBOL_GPL(cs5535_mfgpt_alloc_timer);
  
  /*
   * XXX: This frees the timer memory, but never resets the actual hardware
   * timer.  The old geode_mfgpt code did this; it would be good to figure
   * out a way to actually release the hardware timer.  See comments below.
   */
  void cs5535_mfgpt_free_timer(struct cs5535_mfgpt_timer *timer)
  {
ecd626917   Jens Rottmann   cs5535-mfgpt: reu...
213
214
215
216
217
218
219
220
221
222
  	unsigned long flags;
  	uint16_t val;
  
  	/* timer can be made available again only if never set up */
  	val = cs5535_mfgpt_read(timer, MFGPT_REG_SETUP);
  	if (!(val & MFGPT_SETUP_SETUP)) {
  		spin_lock_irqsave(&timer->chip->lock, flags);
  		__set_bit(timer->nr, timer->chip->avail);
  		spin_unlock_irqrestore(&timer->chip->lock, flags);
  	}
82dca611b   Andres Salomon   cs5535: add a gen...
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
  	kfree(timer);
  }
  EXPORT_SYMBOL_GPL(cs5535_mfgpt_free_timer);
  
  uint16_t cs5535_mfgpt_read(struct cs5535_mfgpt_timer *timer, uint16_t reg)
  {
  	return inw(timer->chip->base + reg + (timer->nr * 8));
  }
  EXPORT_SYMBOL_GPL(cs5535_mfgpt_read);
  
  void cs5535_mfgpt_write(struct cs5535_mfgpt_timer *timer, uint16_t reg,
  		uint16_t value)
  {
  	outw(value, timer->chip->base + reg + (timer->nr * 8));
  }
  EXPORT_SYMBOL_GPL(cs5535_mfgpt_write);
  
  /*
   * This is a sledgehammer that resets all MFGPT timers. This is required by
   * some broken BIOSes which leave the system in an unstable state
   * (TinyBIOS 0.98, for example; fixed in 0.99).  It's uncertain as to
   * whether or not this secret MSR can be used to release individual timers.
   * Jordan tells me that he and Mitch once played w/ it, but it's unclear
   * what the results of that were (and they experienced some instability).
   */
80c8ae289   Bill Pemberton   misc: remove use ...
248
  static void reset_all_timers(void)
82dca611b   Andres Salomon   cs5535: add a gen...
249
250
251
252
253
254
255
256
257
  {
  	uint32_t val, dummy;
  
  	/* The following undocumented bit resets the MFGPT timers */
  	val = 0xFF; dummy = 0;
  	wrmsr(MSR_MFGPT_SETUP, val, dummy);
  }
  
  /*
945480b15   Richard Weinberger   cs5535-mfgpt: Add...
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
   * This is another sledgehammer to reset all MFGPT timers.
   * Instead of using the undocumented bit method it clears
   * IRQ, NMI and RESET settings.
   */
  static void soft_reset(void)
  {
  	int i;
  	struct cs5535_mfgpt_timer t;
  
  	for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
  		t.nr = i;
  
  		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_RESET, 0);
  		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_RESET, 0);
  		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_NMI, 0);
  		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_NMI, 0);
  		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_IRQ, 0);
  		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_IRQ, 0);
  	}
  }
  
  /*
82dca611b   Andres Salomon   cs5535: add a gen...
280
281
282
283
284
285
   * Check whether any MFGPTs are available for the kernel to use.  In most
   * cases, firmware that uses AMD's VSA code will claim all timers during
   * bootup; we certainly don't want to take them if they're already in use.
   * In other cases (such as with VSAless OpenFirmware), the system firmware
   * leaves timers available for us to use.
   */
80c8ae289   Bill Pemberton   misc: remove use ...
286
  static int scan_timers(struct cs5535_mfgpt_chip *mfgpt)
82dca611b   Andres Salomon   cs5535: add a gen...
287
288
289
290
291
292
293
294
  {
  	struct cs5535_mfgpt_timer timer = { .chip = mfgpt };
  	unsigned long flags;
  	int timers = 0;
  	uint16_t val;
  	int i;
  
  	/* bios workaround */
945480b15   Richard Weinberger   cs5535-mfgpt: Add...
295
  	if (mfgpt_reset_timers == 1)
82dca611b   Andres Salomon   cs5535: add a gen...
296
  		reset_all_timers();
945480b15   Richard Weinberger   cs5535-mfgpt: Add...
297
298
  	else if (mfgpt_reset_timers == 2)
  		soft_reset();
82dca611b   Andres Salomon   cs5535: add a gen...
299
300
301
302
303
304
  
  	/* just to be safe, protect this section w/ lock */
  	spin_lock_irqsave(&mfgpt->lock, flags);
  	for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
  		timer.nr = i;
  		val = cs5535_mfgpt_read(&timer, MFGPT_REG_SETUP);
945480b15   Richard Weinberger   cs5535-mfgpt: Add...
305
  		if (!(val & MFGPT_SETUP_SETUP) || mfgpt_reset_timers == 2) {
82dca611b   Andres Salomon   cs5535: add a gen...
306
307
308
309
310
311
312
313
  			__set_bit(i, mfgpt->avail);
  			timers++;
  		}
  	}
  	spin_unlock_irqrestore(&mfgpt->lock, flags);
  
  	return timers;
  }
80c8ae289   Bill Pemberton   misc: remove use ...
314
  static int cs5535_mfgpt_probe(struct platform_device *pdev)
82dca611b   Andres Salomon   cs5535: add a gen...
315
  {
69bc6def3   Andres Salomon   misc: Convert cs5...
316
317
  	struct resource *res;
  	int err = -EIO, t;
82dca611b   Andres Salomon   cs5535: add a gen...
318

945480b15   Richard Weinberger   cs5535-mfgpt: Add...
319
320
321
322
323
324
  	if (mfgpt_reset_timers < 0 || mfgpt_reset_timers > 2) {
  		dev_err(&pdev->dev, "Bad mfgpt_reset_timers value: %i
  ",
  			mfgpt_reset_timers);
  		goto done;
  	}
82dca611b   Andres Salomon   cs5535: add a gen...
325
326
327
328
329
330
  	/* There are two ways to get the MFGPT base address; one is by
  	 * fetching it from MSR_LBAR_MFGPT, 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.. */
69bc6def3   Andres Salomon   misc: Convert cs5...
331
332
333
334
  	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
  	if (!res) {
  		dev_err(&pdev->dev, "can't fetch device resource info
  ");
82dca611b   Andres Salomon   cs5535: add a gen...
335
336
  		goto done;
  	}
69bc6def3   Andres Salomon   misc: Convert cs5...
337
338
339
  	if (!request_region(res->start, resource_size(res), pdev->name)) {
  		dev_err(&pdev->dev, "can't request region
  ");
82dca611b   Andres Salomon   cs5535: add a gen...
340
341
342
343
  		goto done;
  	}
  
  	/* set up the driver-specific struct */
69bc6def3   Andres Salomon   misc: Convert cs5...
344
  	cs5535_mfgpt_chip.base = res->start;
82dca611b   Andres Salomon   cs5535: add a gen...
345
346
  	cs5535_mfgpt_chip.pdev = pdev;
  	spin_lock_init(&cs5535_mfgpt_chip.lock);
7eb19812e   Joe Perches   misc: Fix cs5535 ...
347
348
  	dev_info(&pdev->dev, "reserved resource region %pR
  ", res);
82dca611b   Andres Salomon   cs5535: add a gen...
349
350
351
  
  	/* detect the available timers */
  	t = scan_timers(&cs5535_mfgpt_chip);
69bc6def3   Andres Salomon   misc: Convert cs5...
352
353
  	dev_info(&pdev->dev, "%d MFGPT timers available
  ", t);
82dca611b   Andres Salomon   cs5535: add a gen...
354
355
356
357
358
359
  	cs5535_mfgpt_chip.initialized = 1;
  	return 0;
  
  done:
  	return err;
  }
853a1378e   Nikanth Karthikesan   cs5535: Fix secti...
360
  static struct platform_driver cs5535_mfgpt_driver = {
69bc6def3   Andres Salomon   misc: Convert cs5...
361
362
  	.driver = {
  		.name = DRV_NAME,
69bc6def3   Andres Salomon   misc: Convert cs5...
363
364
  	},
  	.probe = cs5535_mfgpt_probe,
82dca611b   Andres Salomon   cs5535: add a gen...
365
  };
82dca611b   Andres Salomon   cs5535: add a gen...
366

82dca611b   Andres Salomon   cs5535: add a gen...
367
368
369
  
  static int __init cs5535_mfgpt_init(void)
  {
853a1378e   Nikanth Karthikesan   cs5535: Fix secti...
370
  	return platform_driver_register(&cs5535_mfgpt_driver);
82dca611b   Andres Salomon   cs5535: add a gen...
371
372
373
  }
  
  module_init(cs5535_mfgpt_init);
d45840d9f   Andres Salomon   Andres has moved
374
  MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
82dca611b   Andres Salomon   cs5535: add a gen...
375
376
  MODULE_DESCRIPTION("CS5535/CS5536 MFGPT timer driver");
  MODULE_LICENSE("GPL");
ec9d0cf57   Andres Salomon   gpio/misc: Add MO...
377
  MODULE_ALIAS("platform:" DRV_NAME);