18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Driver for the CS5535/CS5536 Multi-Function General Purpose Timers (MFGPT)
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2006, Advanced Micro Devices, Inc.
68c2ecf20Sopenharmony_ci * Copyright (C) 2007  Andres Salomon <dilinger@debian.org>
78c2ecf20Sopenharmony_ci * Copyright (C) 2009  Andres Salomon <dilinger@collabora.co.uk>
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book.
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/kernel.h>
138c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
148c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
178c2ecf20Sopenharmony_ci#include <linux/cs5535.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define DRV_NAME "cs5535-mfgpt"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistatic int mfgpt_reset_timers;
238c2ecf20Sopenharmony_cimodule_param_named(mfgptfix, mfgpt_reset_timers, int, 0644);
248c2ecf20Sopenharmony_ciMODULE_PARM_DESC(mfgptfix, "Try to reset the MFGPT timers during init; "
258c2ecf20Sopenharmony_ci		"required by some broken BIOSes (ie, TinyBIOS < 0.99) or kexec "
268c2ecf20Sopenharmony_ci		"(1 = reset the MFGPT using an undocumented bit, "
278c2ecf20Sopenharmony_ci		"2 = perform a soft reset by unconfiguring all timers); "
288c2ecf20Sopenharmony_ci		"use what works best for you.");
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistruct cs5535_mfgpt_timer {
318c2ecf20Sopenharmony_ci	struct cs5535_mfgpt_chip *chip;
328c2ecf20Sopenharmony_ci	int nr;
338c2ecf20Sopenharmony_ci};
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic struct cs5535_mfgpt_chip {
368c2ecf20Sopenharmony_ci	DECLARE_BITMAP(avail, MFGPT_MAX_TIMERS);
378c2ecf20Sopenharmony_ci	resource_size_t base;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	struct platform_device *pdev;
408c2ecf20Sopenharmony_ci	spinlock_t lock;
418c2ecf20Sopenharmony_ci	int initialized;
428c2ecf20Sopenharmony_ci} cs5535_mfgpt_chip;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ciint cs5535_mfgpt_toggle_event(struct cs5535_mfgpt_timer *timer, int cmp,
458c2ecf20Sopenharmony_ci		int event, int enable)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	uint32_t msr, mask, value, dummy;
488c2ecf20Sopenharmony_ci	int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	if (!timer) {
518c2ecf20Sopenharmony_ci		WARN_ON(1);
528c2ecf20Sopenharmony_ci		return -EIO;
538c2ecf20Sopenharmony_ci	}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	/*
568c2ecf20Sopenharmony_ci	 * The register maps for these are described in sections 6.17.1.x of
578c2ecf20Sopenharmony_ci	 * the AMD Geode CS5536 Companion Device Data Book.
588c2ecf20Sopenharmony_ci	 */
598c2ecf20Sopenharmony_ci	switch (event) {
608c2ecf20Sopenharmony_ci	case MFGPT_EVENT_RESET:
618c2ecf20Sopenharmony_ci		/*
628c2ecf20Sopenharmony_ci		 * XXX: According to the docs, we cannot reset timers above
638c2ecf20Sopenharmony_ci		 * 6; that is, resets for 7 and 8 will be ignored.  Is this
648c2ecf20Sopenharmony_ci		 * a problem?   -dilinger
658c2ecf20Sopenharmony_ci		 */
668c2ecf20Sopenharmony_ci		msr = MSR_MFGPT_NR;
678c2ecf20Sopenharmony_ci		mask = 1 << (timer->nr + 24);
688c2ecf20Sopenharmony_ci		break;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	case MFGPT_EVENT_NMI:
718c2ecf20Sopenharmony_ci		msr = MSR_MFGPT_NR;
728c2ecf20Sopenharmony_ci		mask = 1 << (timer->nr + shift);
738c2ecf20Sopenharmony_ci		break;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	case MFGPT_EVENT_IRQ:
768c2ecf20Sopenharmony_ci		msr = MSR_MFGPT_IRQ;
778c2ecf20Sopenharmony_ci		mask = 1 << (timer->nr + shift);
788c2ecf20Sopenharmony_ci		break;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	default:
818c2ecf20Sopenharmony_ci		return -EIO;
828c2ecf20Sopenharmony_ci	}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	rdmsr(msr, value, dummy);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	if (enable)
878c2ecf20Sopenharmony_ci		value |= mask;
888c2ecf20Sopenharmony_ci	else
898c2ecf20Sopenharmony_ci		value &= ~mask;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	wrmsr(msr, value, dummy);
928c2ecf20Sopenharmony_ci	return 0;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_toggle_event);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ciint cs5535_mfgpt_set_irq(struct cs5535_mfgpt_timer *timer, int cmp, int *irq,
978c2ecf20Sopenharmony_ci		int enable)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	uint32_t zsel, lpc, dummy;
1008c2ecf20Sopenharmony_ci	int shift;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	if (!timer) {
1038c2ecf20Sopenharmony_ci		WARN_ON(1);
1048c2ecf20Sopenharmony_ci		return -EIO;
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	/*
1088c2ecf20Sopenharmony_ci	 * Unfortunately, MFGPTs come in pairs sharing their IRQ lines. If VSA
1098c2ecf20Sopenharmony_ci	 * is using the same CMP of the timer's Siamese twin, the IRQ is set to
1108c2ecf20Sopenharmony_ci	 * 2, and we mustn't use nor change it.
1118c2ecf20Sopenharmony_ci	 * XXX: Likewise, 2 Linux drivers might clash if the 2nd overwrites the
1128c2ecf20Sopenharmony_ci	 * IRQ of the 1st. This can only happen if forcing an IRQ, calling this
1138c2ecf20Sopenharmony_ci	 * with *irq==0 is safe. Currently there _are_ no 2 drivers.
1148c2ecf20Sopenharmony_ci	 */
1158c2ecf20Sopenharmony_ci	rdmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
1168c2ecf20Sopenharmony_ci	shift = ((cmp == MFGPT_CMP1 ? 0 : 4) + timer->nr % 4) * 4;
1178c2ecf20Sopenharmony_ci	if (((zsel >> shift) & 0xF) == 2)
1188c2ecf20Sopenharmony_ci		return -EIO;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	/* Choose IRQ: if none supplied, keep IRQ already set or use default */
1218c2ecf20Sopenharmony_ci	if (!*irq)
1228c2ecf20Sopenharmony_ci		*irq = (zsel >> shift) & 0xF;
1238c2ecf20Sopenharmony_ci	if (!*irq)
1248c2ecf20Sopenharmony_ci		*irq = CONFIG_CS5535_MFGPT_DEFAULT_IRQ;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	/* Can't use IRQ if it's 0 (=disabled), 2, or routed to LPC */
1278c2ecf20Sopenharmony_ci	if (*irq < 1 || *irq == 2 || *irq > 15)
1288c2ecf20Sopenharmony_ci		return -EIO;
1298c2ecf20Sopenharmony_ci	rdmsr(MSR_PIC_IRQM_LPC, lpc, dummy);
1308c2ecf20Sopenharmony_ci	if (lpc & (1 << *irq))
1318c2ecf20Sopenharmony_ci		return -EIO;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	/* All chosen and checked - go for it */
1348c2ecf20Sopenharmony_ci	if (cs5535_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable))
1358c2ecf20Sopenharmony_ci		return -EIO;
1368c2ecf20Sopenharmony_ci	if (enable) {
1378c2ecf20Sopenharmony_ci		zsel = (zsel & ~(0xF << shift)) | (*irq << shift);
1388c2ecf20Sopenharmony_ci		wrmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	return 0;
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_set_irq);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistruct cs5535_mfgpt_timer *cs5535_mfgpt_alloc_timer(int timer_nr, int domain)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	struct cs5535_mfgpt_chip *mfgpt = &cs5535_mfgpt_chip;
1488c2ecf20Sopenharmony_ci	struct cs5535_mfgpt_timer *timer = NULL;
1498c2ecf20Sopenharmony_ci	unsigned long flags;
1508c2ecf20Sopenharmony_ci	int max;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	if (!mfgpt->initialized)
1538c2ecf20Sopenharmony_ci		goto done;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	/* only allocate timers from the working domain if requested */
1568c2ecf20Sopenharmony_ci	if (domain == MFGPT_DOMAIN_WORKING)
1578c2ecf20Sopenharmony_ci		max = 6;
1588c2ecf20Sopenharmony_ci	else
1598c2ecf20Sopenharmony_ci		max = MFGPT_MAX_TIMERS;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	if (timer_nr >= max) {
1628c2ecf20Sopenharmony_ci		/* programmer error.  silly programmers! */
1638c2ecf20Sopenharmony_ci		WARN_ON(1);
1648c2ecf20Sopenharmony_ci		goto done;
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mfgpt->lock, flags);
1688c2ecf20Sopenharmony_ci	if (timer_nr < 0) {
1698c2ecf20Sopenharmony_ci		unsigned long t;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci		/* try to find any available timer */
1728c2ecf20Sopenharmony_ci		t = find_first_bit(mfgpt->avail, max);
1738c2ecf20Sopenharmony_ci		/* set timer_nr to -1 if no timers available */
1748c2ecf20Sopenharmony_ci		timer_nr = t < max ? (int) t : -1;
1758c2ecf20Sopenharmony_ci	} else {
1768c2ecf20Sopenharmony_ci		/* check if the requested timer's available */
1778c2ecf20Sopenharmony_ci		if (!test_bit(timer_nr, mfgpt->avail))
1788c2ecf20Sopenharmony_ci			timer_nr = -1;
1798c2ecf20Sopenharmony_ci	}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	if (timer_nr >= 0)
1828c2ecf20Sopenharmony_ci		/* if timer_nr is not -1, it's an available timer */
1838c2ecf20Sopenharmony_ci		__clear_bit(timer_nr, mfgpt->avail);
1848c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mfgpt->lock, flags);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	if (timer_nr < 0)
1878c2ecf20Sopenharmony_ci		goto done;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	timer = kmalloc(sizeof(*timer), GFP_KERNEL);
1908c2ecf20Sopenharmony_ci	if (!timer) {
1918c2ecf20Sopenharmony_ci		/* aw hell */
1928c2ecf20Sopenharmony_ci		spin_lock_irqsave(&mfgpt->lock, flags);
1938c2ecf20Sopenharmony_ci		__set_bit(timer_nr, mfgpt->avail);
1948c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&mfgpt->lock, flags);
1958c2ecf20Sopenharmony_ci		goto done;
1968c2ecf20Sopenharmony_ci	}
1978c2ecf20Sopenharmony_ci	timer->chip = mfgpt;
1988c2ecf20Sopenharmony_ci	timer->nr = timer_nr;
1998c2ecf20Sopenharmony_ci	dev_info(&mfgpt->pdev->dev, "registered timer %d\n", timer_nr);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_cidone:
2028c2ecf20Sopenharmony_ci	return timer;
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_alloc_timer);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci/*
2078c2ecf20Sopenharmony_ci * XXX: This frees the timer memory, but never resets the actual hardware
2088c2ecf20Sopenharmony_ci * timer.  The old geode_mfgpt code did this; it would be good to figure
2098c2ecf20Sopenharmony_ci * out a way to actually release the hardware timer.  See comments below.
2108c2ecf20Sopenharmony_ci */
2118c2ecf20Sopenharmony_civoid cs5535_mfgpt_free_timer(struct cs5535_mfgpt_timer *timer)
2128c2ecf20Sopenharmony_ci{
2138c2ecf20Sopenharmony_ci	unsigned long flags;
2148c2ecf20Sopenharmony_ci	uint16_t val;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	/* timer can be made available again only if never set up */
2178c2ecf20Sopenharmony_ci	val = cs5535_mfgpt_read(timer, MFGPT_REG_SETUP);
2188c2ecf20Sopenharmony_ci	if (!(val & MFGPT_SETUP_SETUP)) {
2198c2ecf20Sopenharmony_ci		spin_lock_irqsave(&timer->chip->lock, flags);
2208c2ecf20Sopenharmony_ci		__set_bit(timer->nr, timer->chip->avail);
2218c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&timer->chip->lock, flags);
2228c2ecf20Sopenharmony_ci	}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	kfree(timer);
2258c2ecf20Sopenharmony_ci}
2268c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_free_timer);
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ciuint16_t cs5535_mfgpt_read(struct cs5535_mfgpt_timer *timer, uint16_t reg)
2298c2ecf20Sopenharmony_ci{
2308c2ecf20Sopenharmony_ci	return inw(timer->chip->base + reg + (timer->nr * 8));
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_read);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_civoid cs5535_mfgpt_write(struct cs5535_mfgpt_timer *timer, uint16_t reg,
2358c2ecf20Sopenharmony_ci		uint16_t value)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	outw(value, timer->chip->base + reg + (timer->nr * 8));
2388c2ecf20Sopenharmony_ci}
2398c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_write);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci/*
2428c2ecf20Sopenharmony_ci * This is a sledgehammer that resets all MFGPT timers. This is required by
2438c2ecf20Sopenharmony_ci * some broken BIOSes which leave the system in an unstable state
2448c2ecf20Sopenharmony_ci * (TinyBIOS 0.98, for example; fixed in 0.99).  It's uncertain as to
2458c2ecf20Sopenharmony_ci * whether or not this secret MSR can be used to release individual timers.
2468c2ecf20Sopenharmony_ci * Jordan tells me that he and Mitch once played w/ it, but it's unclear
2478c2ecf20Sopenharmony_ci * what the results of that were (and they experienced some instability).
2488c2ecf20Sopenharmony_ci */
2498c2ecf20Sopenharmony_cistatic void reset_all_timers(void)
2508c2ecf20Sopenharmony_ci{
2518c2ecf20Sopenharmony_ci	uint32_t val, dummy;
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	/* The following undocumented bit resets the MFGPT timers */
2548c2ecf20Sopenharmony_ci	val = 0xFF; dummy = 0;
2558c2ecf20Sopenharmony_ci	wrmsr(MSR_MFGPT_SETUP, val, dummy);
2568c2ecf20Sopenharmony_ci}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci/*
2598c2ecf20Sopenharmony_ci * This is another sledgehammer to reset all MFGPT timers.
2608c2ecf20Sopenharmony_ci * Instead of using the undocumented bit method it clears
2618c2ecf20Sopenharmony_ci * IRQ, NMI and RESET settings.
2628c2ecf20Sopenharmony_ci */
2638c2ecf20Sopenharmony_cistatic void soft_reset(void)
2648c2ecf20Sopenharmony_ci{
2658c2ecf20Sopenharmony_ci	int i;
2668c2ecf20Sopenharmony_ci	struct cs5535_mfgpt_timer t;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
2698c2ecf20Sopenharmony_ci		t.nr = i;
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_RESET, 0);
2728c2ecf20Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_RESET, 0);
2738c2ecf20Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_NMI, 0);
2748c2ecf20Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_NMI, 0);
2758c2ecf20Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_IRQ, 0);
2768c2ecf20Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_IRQ, 0);
2778c2ecf20Sopenharmony_ci	}
2788c2ecf20Sopenharmony_ci}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci/*
2818c2ecf20Sopenharmony_ci * Check whether any MFGPTs are available for the kernel to use.  In most
2828c2ecf20Sopenharmony_ci * cases, firmware that uses AMD's VSA code will claim all timers during
2838c2ecf20Sopenharmony_ci * bootup; we certainly don't want to take them if they're already in use.
2848c2ecf20Sopenharmony_ci * In other cases (such as with VSAless OpenFirmware), the system firmware
2858c2ecf20Sopenharmony_ci * leaves timers available for us to use.
2868c2ecf20Sopenharmony_ci */
2878c2ecf20Sopenharmony_cistatic int scan_timers(struct cs5535_mfgpt_chip *mfgpt)
2888c2ecf20Sopenharmony_ci{
2898c2ecf20Sopenharmony_ci	struct cs5535_mfgpt_timer timer = { .chip = mfgpt };
2908c2ecf20Sopenharmony_ci	unsigned long flags;
2918c2ecf20Sopenharmony_ci	int timers = 0;
2928c2ecf20Sopenharmony_ci	uint16_t val;
2938c2ecf20Sopenharmony_ci	int i;
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	/* bios workaround */
2968c2ecf20Sopenharmony_ci	if (mfgpt_reset_timers == 1)
2978c2ecf20Sopenharmony_ci		reset_all_timers();
2988c2ecf20Sopenharmony_ci	else if (mfgpt_reset_timers == 2)
2998c2ecf20Sopenharmony_ci		soft_reset();
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	/* just to be safe, protect this section w/ lock */
3028c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mfgpt->lock, flags);
3038c2ecf20Sopenharmony_ci	for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
3048c2ecf20Sopenharmony_ci		timer.nr = i;
3058c2ecf20Sopenharmony_ci		val = cs5535_mfgpt_read(&timer, MFGPT_REG_SETUP);
3068c2ecf20Sopenharmony_ci		if (!(val & MFGPT_SETUP_SETUP) || mfgpt_reset_timers == 2) {
3078c2ecf20Sopenharmony_ci			__set_bit(i, mfgpt->avail);
3088c2ecf20Sopenharmony_ci			timers++;
3098c2ecf20Sopenharmony_ci		}
3108c2ecf20Sopenharmony_ci	}
3118c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mfgpt->lock, flags);
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	return timers;
3148c2ecf20Sopenharmony_ci}
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_cistatic int cs5535_mfgpt_probe(struct platform_device *pdev)
3178c2ecf20Sopenharmony_ci{
3188c2ecf20Sopenharmony_ci	struct resource *res;
3198c2ecf20Sopenharmony_ci	int err = -EIO, t;
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	if (mfgpt_reset_timers < 0 || mfgpt_reset_timers > 2) {
3228c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Bad mfgpt_reset_timers value: %i\n",
3238c2ecf20Sopenharmony_ci			mfgpt_reset_timers);
3248c2ecf20Sopenharmony_ci		goto done;
3258c2ecf20Sopenharmony_ci	}
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	/* There are two ways to get the MFGPT base address; one is by
3288c2ecf20Sopenharmony_ci	 * fetching it from MSR_LBAR_MFGPT, the other is by reading the
3298c2ecf20Sopenharmony_ci	 * PCI BAR info.  The latter method is easier (especially across
3308c2ecf20Sopenharmony_ci	 * different architectures), so we'll stick with that for now.  If
3318c2ecf20Sopenharmony_ci	 * it turns out to be unreliable in the face of crappy BIOSes, we
3328c2ecf20Sopenharmony_ci	 * can always go back to using MSRs.. */
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
3358c2ecf20Sopenharmony_ci	if (!res) {
3368c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "can't fetch device resource info\n");
3378c2ecf20Sopenharmony_ci		goto done;
3388c2ecf20Sopenharmony_ci	}
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	if (!request_region(res->start, resource_size(res), pdev->name)) {
3418c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "can't request region\n");
3428c2ecf20Sopenharmony_ci		goto done;
3438c2ecf20Sopenharmony_ci	}
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	/* set up the driver-specific struct */
3468c2ecf20Sopenharmony_ci	cs5535_mfgpt_chip.base = res->start;
3478c2ecf20Sopenharmony_ci	cs5535_mfgpt_chip.pdev = pdev;
3488c2ecf20Sopenharmony_ci	spin_lock_init(&cs5535_mfgpt_chip.lock);
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	dev_info(&pdev->dev, "reserved resource region %pR\n", res);
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	/* detect the available timers */
3538c2ecf20Sopenharmony_ci	t = scan_timers(&cs5535_mfgpt_chip);
3548c2ecf20Sopenharmony_ci	dev_info(&pdev->dev, "%d MFGPT timers available\n", t);
3558c2ecf20Sopenharmony_ci	cs5535_mfgpt_chip.initialized = 1;
3568c2ecf20Sopenharmony_ci	return 0;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_cidone:
3598c2ecf20Sopenharmony_ci	return err;
3608c2ecf20Sopenharmony_ci}
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_cistatic struct platform_driver cs5535_mfgpt_driver = {
3638c2ecf20Sopenharmony_ci	.driver = {
3648c2ecf20Sopenharmony_ci		.name = DRV_NAME,
3658c2ecf20Sopenharmony_ci	},
3668c2ecf20Sopenharmony_ci	.probe = cs5535_mfgpt_probe,
3678c2ecf20Sopenharmony_ci};
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_cistatic int __init cs5535_mfgpt_init(void)
3718c2ecf20Sopenharmony_ci{
3728c2ecf20Sopenharmony_ci	return platform_driver_register(&cs5535_mfgpt_driver);
3738c2ecf20Sopenharmony_ci}
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_cimodule_init(cs5535_mfgpt_init);
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
3788c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("CS5535/CS5536 MFGPT timer driver");
3798c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
3808c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME);
381