162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for the CS5535/CS5536 Multi-Function General Purpose Timers (MFGPT)
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2006, Advanced Micro Devices, Inc.
662306a36Sopenharmony_ci * Copyright (C) 2007  Andres Salomon <dilinger@debian.org>
762306a36Sopenharmony_ci * Copyright (C) 2009  Andres Salomon <dilinger@collabora.co.uk>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/spinlock.h>
1462306a36Sopenharmony_ci#include <linux/interrupt.h>
1562306a36Sopenharmony_ci#include <linux/module.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <linux/cs5535.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define DRV_NAME "cs5535-mfgpt"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic int mfgpt_reset_timers;
2362306a36Sopenharmony_cimodule_param_named(mfgptfix, mfgpt_reset_timers, int, 0644);
2462306a36Sopenharmony_ciMODULE_PARM_DESC(mfgptfix, "Try to reset the MFGPT timers during init; "
2562306a36Sopenharmony_ci		"required by some broken BIOSes (ie, TinyBIOS < 0.99) or kexec "
2662306a36Sopenharmony_ci		"(1 = reset the MFGPT using an undocumented bit, "
2762306a36Sopenharmony_ci		"2 = perform a soft reset by unconfiguring all timers); "
2862306a36Sopenharmony_ci		"use what works best for you.");
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct cs5535_mfgpt_timer {
3162306a36Sopenharmony_ci	struct cs5535_mfgpt_chip *chip;
3262306a36Sopenharmony_ci	int nr;
3362306a36Sopenharmony_ci};
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic struct cs5535_mfgpt_chip {
3662306a36Sopenharmony_ci	DECLARE_BITMAP(avail, MFGPT_MAX_TIMERS);
3762306a36Sopenharmony_ci	resource_size_t base;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	struct platform_device *pdev;
4062306a36Sopenharmony_ci	spinlock_t lock;
4162306a36Sopenharmony_ci	int initialized;
4262306a36Sopenharmony_ci} cs5535_mfgpt_chip;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ciint cs5535_mfgpt_toggle_event(struct cs5535_mfgpt_timer *timer, int cmp,
4562306a36Sopenharmony_ci		int event, int enable)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	uint32_t msr, mask, value, dummy;
4862306a36Sopenharmony_ci	int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	if (!timer) {
5162306a36Sopenharmony_ci		WARN_ON(1);
5262306a36Sopenharmony_ci		return -EIO;
5362306a36Sopenharmony_ci	}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	/*
5662306a36Sopenharmony_ci	 * The register maps for these are described in sections 6.17.1.x of
5762306a36Sopenharmony_ci	 * the AMD Geode CS5536 Companion Device Data Book.
5862306a36Sopenharmony_ci	 */
5962306a36Sopenharmony_ci	switch (event) {
6062306a36Sopenharmony_ci	case MFGPT_EVENT_RESET:
6162306a36Sopenharmony_ci		/*
6262306a36Sopenharmony_ci		 * XXX: According to the docs, we cannot reset timers above
6362306a36Sopenharmony_ci		 * 6; that is, resets for 7 and 8 will be ignored.  Is this
6462306a36Sopenharmony_ci		 * a problem?   -dilinger
6562306a36Sopenharmony_ci		 */
6662306a36Sopenharmony_ci		msr = MSR_MFGPT_NR;
6762306a36Sopenharmony_ci		mask = 1 << (timer->nr + 24);
6862306a36Sopenharmony_ci		break;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	case MFGPT_EVENT_NMI:
7162306a36Sopenharmony_ci		msr = MSR_MFGPT_NR;
7262306a36Sopenharmony_ci		mask = 1 << (timer->nr + shift);
7362306a36Sopenharmony_ci		break;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	case MFGPT_EVENT_IRQ:
7662306a36Sopenharmony_ci		msr = MSR_MFGPT_IRQ;
7762306a36Sopenharmony_ci		mask = 1 << (timer->nr + shift);
7862306a36Sopenharmony_ci		break;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	default:
8162306a36Sopenharmony_ci		return -EIO;
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	rdmsr(msr, value, dummy);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (enable)
8762306a36Sopenharmony_ci		value |= mask;
8862306a36Sopenharmony_ci	else
8962306a36Sopenharmony_ci		value &= ~mask;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	wrmsr(msr, value, dummy);
9262306a36Sopenharmony_ci	return 0;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_toggle_event);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ciint cs5535_mfgpt_set_irq(struct cs5535_mfgpt_timer *timer, int cmp, int *irq,
9762306a36Sopenharmony_ci		int enable)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	uint32_t zsel, lpc, dummy;
10062306a36Sopenharmony_ci	int shift;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	if (!timer) {
10362306a36Sopenharmony_ci		WARN_ON(1);
10462306a36Sopenharmony_ci		return -EIO;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	/*
10862306a36Sopenharmony_ci	 * Unfortunately, MFGPTs come in pairs sharing their IRQ lines. If VSA
10962306a36Sopenharmony_ci	 * is using the same CMP of the timer's Siamese twin, the IRQ is set to
11062306a36Sopenharmony_ci	 * 2, and we mustn't use nor change it.
11162306a36Sopenharmony_ci	 * XXX: Likewise, 2 Linux drivers might clash if the 2nd overwrites the
11262306a36Sopenharmony_ci	 * IRQ of the 1st. This can only happen if forcing an IRQ, calling this
11362306a36Sopenharmony_ci	 * with *irq==0 is safe. Currently there _are_ no 2 drivers.
11462306a36Sopenharmony_ci	 */
11562306a36Sopenharmony_ci	rdmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
11662306a36Sopenharmony_ci	shift = ((cmp == MFGPT_CMP1 ? 0 : 4) + timer->nr % 4) * 4;
11762306a36Sopenharmony_ci	if (((zsel >> shift) & 0xF) == 2)
11862306a36Sopenharmony_ci		return -EIO;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/* Choose IRQ: if none supplied, keep IRQ already set or use default */
12162306a36Sopenharmony_ci	if (!*irq)
12262306a36Sopenharmony_ci		*irq = (zsel >> shift) & 0xF;
12362306a36Sopenharmony_ci	if (!*irq)
12462306a36Sopenharmony_ci		*irq = CONFIG_CS5535_MFGPT_DEFAULT_IRQ;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	/* Can't use IRQ if it's 0 (=disabled), 2, or routed to LPC */
12762306a36Sopenharmony_ci	if (*irq < 1 || *irq == 2 || *irq > 15)
12862306a36Sopenharmony_ci		return -EIO;
12962306a36Sopenharmony_ci	rdmsr(MSR_PIC_IRQM_LPC, lpc, dummy);
13062306a36Sopenharmony_ci	if (lpc & (1 << *irq))
13162306a36Sopenharmony_ci		return -EIO;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	/* All chosen and checked - go for it */
13462306a36Sopenharmony_ci	if (cs5535_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable))
13562306a36Sopenharmony_ci		return -EIO;
13662306a36Sopenharmony_ci	if (enable) {
13762306a36Sopenharmony_ci		zsel = (zsel & ~(0xF << shift)) | (*irq << shift);
13862306a36Sopenharmony_ci		wrmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
13962306a36Sopenharmony_ci	}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return 0;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_set_irq);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistruct cs5535_mfgpt_timer *cs5535_mfgpt_alloc_timer(int timer_nr, int domain)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	struct cs5535_mfgpt_chip *mfgpt = &cs5535_mfgpt_chip;
14862306a36Sopenharmony_ci	struct cs5535_mfgpt_timer *timer = NULL;
14962306a36Sopenharmony_ci	unsigned long flags;
15062306a36Sopenharmony_ci	int max;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (!mfgpt->initialized)
15362306a36Sopenharmony_ci		goto done;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	/* only allocate timers from the working domain if requested */
15662306a36Sopenharmony_ci	if (domain == MFGPT_DOMAIN_WORKING)
15762306a36Sopenharmony_ci		max = 6;
15862306a36Sopenharmony_ci	else
15962306a36Sopenharmony_ci		max = MFGPT_MAX_TIMERS;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (timer_nr >= max) {
16262306a36Sopenharmony_ci		/* programmer error.  silly programmers! */
16362306a36Sopenharmony_ci		WARN_ON(1);
16462306a36Sopenharmony_ci		goto done;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	spin_lock_irqsave(&mfgpt->lock, flags);
16862306a36Sopenharmony_ci	if (timer_nr < 0) {
16962306a36Sopenharmony_ci		unsigned long t;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci		/* try to find any available timer */
17262306a36Sopenharmony_ci		t = find_first_bit(mfgpt->avail, max);
17362306a36Sopenharmony_ci		/* set timer_nr to -1 if no timers available */
17462306a36Sopenharmony_ci		timer_nr = t < max ? (int) t : -1;
17562306a36Sopenharmony_ci	} else {
17662306a36Sopenharmony_ci		/* check if the requested timer's available */
17762306a36Sopenharmony_ci		if (!test_bit(timer_nr, mfgpt->avail))
17862306a36Sopenharmony_ci			timer_nr = -1;
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (timer_nr >= 0)
18262306a36Sopenharmony_ci		/* if timer_nr is not -1, it's an available timer */
18362306a36Sopenharmony_ci		__clear_bit(timer_nr, mfgpt->avail);
18462306a36Sopenharmony_ci	spin_unlock_irqrestore(&mfgpt->lock, flags);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (timer_nr < 0)
18762306a36Sopenharmony_ci		goto done;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	timer = kmalloc(sizeof(*timer), GFP_KERNEL);
19062306a36Sopenharmony_ci	if (!timer) {
19162306a36Sopenharmony_ci		/* aw hell */
19262306a36Sopenharmony_ci		spin_lock_irqsave(&mfgpt->lock, flags);
19362306a36Sopenharmony_ci		__set_bit(timer_nr, mfgpt->avail);
19462306a36Sopenharmony_ci		spin_unlock_irqrestore(&mfgpt->lock, flags);
19562306a36Sopenharmony_ci		goto done;
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci	timer->chip = mfgpt;
19862306a36Sopenharmony_ci	timer->nr = timer_nr;
19962306a36Sopenharmony_ci	dev_info(&mfgpt->pdev->dev, "registered timer %d\n", timer_nr);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cidone:
20262306a36Sopenharmony_ci	return timer;
20362306a36Sopenharmony_ci}
20462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_alloc_timer);
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci/*
20762306a36Sopenharmony_ci * XXX: This frees the timer memory, but never resets the actual hardware
20862306a36Sopenharmony_ci * timer.  The old geode_mfgpt code did this; it would be good to figure
20962306a36Sopenharmony_ci * out a way to actually release the hardware timer.  See comments below.
21062306a36Sopenharmony_ci */
21162306a36Sopenharmony_civoid cs5535_mfgpt_free_timer(struct cs5535_mfgpt_timer *timer)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	unsigned long flags;
21462306a36Sopenharmony_ci	uint16_t val;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	/* timer can be made available again only if never set up */
21762306a36Sopenharmony_ci	val = cs5535_mfgpt_read(timer, MFGPT_REG_SETUP);
21862306a36Sopenharmony_ci	if (!(val & MFGPT_SETUP_SETUP)) {
21962306a36Sopenharmony_ci		spin_lock_irqsave(&timer->chip->lock, flags);
22062306a36Sopenharmony_ci		__set_bit(timer->nr, timer->chip->avail);
22162306a36Sopenharmony_ci		spin_unlock_irqrestore(&timer->chip->lock, flags);
22262306a36Sopenharmony_ci	}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	kfree(timer);
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_free_timer);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ciuint16_t cs5535_mfgpt_read(struct cs5535_mfgpt_timer *timer, uint16_t reg)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	return inw(timer->chip->base + reg + (timer->nr * 8));
23162306a36Sopenharmony_ci}
23262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_read);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_civoid cs5535_mfgpt_write(struct cs5535_mfgpt_timer *timer, uint16_t reg,
23562306a36Sopenharmony_ci		uint16_t value)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	outw(value, timer->chip->base + reg + (timer->nr * 8));
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cs5535_mfgpt_write);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci/*
24262306a36Sopenharmony_ci * This is a sledgehammer that resets all MFGPT timers. This is required by
24362306a36Sopenharmony_ci * some broken BIOSes which leave the system in an unstable state
24462306a36Sopenharmony_ci * (TinyBIOS 0.98, for example; fixed in 0.99).  It's uncertain as to
24562306a36Sopenharmony_ci * whether or not this secret MSR can be used to release individual timers.
24662306a36Sopenharmony_ci * Jordan tells me that he and Mitch once played w/ it, but it's unclear
24762306a36Sopenharmony_ci * what the results of that were (and they experienced some instability).
24862306a36Sopenharmony_ci */
24962306a36Sopenharmony_cistatic void reset_all_timers(void)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	uint32_t val, dummy;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	/* The following undocumented bit resets the MFGPT timers */
25462306a36Sopenharmony_ci	val = 0xFF; dummy = 0;
25562306a36Sopenharmony_ci	wrmsr(MSR_MFGPT_SETUP, val, dummy);
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci/*
25962306a36Sopenharmony_ci * This is another sledgehammer to reset all MFGPT timers.
26062306a36Sopenharmony_ci * Instead of using the undocumented bit method it clears
26162306a36Sopenharmony_ci * IRQ, NMI and RESET settings.
26262306a36Sopenharmony_ci */
26362306a36Sopenharmony_cistatic void soft_reset(void)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	int i;
26662306a36Sopenharmony_ci	struct cs5535_mfgpt_timer t;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
26962306a36Sopenharmony_ci		t.nr = i;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_RESET, 0);
27262306a36Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_RESET, 0);
27362306a36Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_NMI, 0);
27462306a36Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_NMI, 0);
27562306a36Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_IRQ, 0);
27662306a36Sopenharmony_ci		cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_IRQ, 0);
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci/*
28162306a36Sopenharmony_ci * Check whether any MFGPTs are available for the kernel to use.  In most
28262306a36Sopenharmony_ci * cases, firmware that uses AMD's VSA code will claim all timers during
28362306a36Sopenharmony_ci * bootup; we certainly don't want to take them if they're already in use.
28462306a36Sopenharmony_ci * In other cases (such as with VSAless OpenFirmware), the system firmware
28562306a36Sopenharmony_ci * leaves timers available for us to use.
28662306a36Sopenharmony_ci */
28762306a36Sopenharmony_cistatic int scan_timers(struct cs5535_mfgpt_chip *mfgpt)
28862306a36Sopenharmony_ci{
28962306a36Sopenharmony_ci	struct cs5535_mfgpt_timer timer = { .chip = mfgpt };
29062306a36Sopenharmony_ci	unsigned long flags;
29162306a36Sopenharmony_ci	int timers = 0;
29262306a36Sopenharmony_ci	uint16_t val;
29362306a36Sopenharmony_ci	int i;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	/* bios workaround */
29662306a36Sopenharmony_ci	if (mfgpt_reset_timers == 1)
29762306a36Sopenharmony_ci		reset_all_timers();
29862306a36Sopenharmony_ci	else if (mfgpt_reset_timers == 2)
29962306a36Sopenharmony_ci		soft_reset();
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	/* just to be safe, protect this section w/ lock */
30262306a36Sopenharmony_ci	spin_lock_irqsave(&mfgpt->lock, flags);
30362306a36Sopenharmony_ci	for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
30462306a36Sopenharmony_ci		timer.nr = i;
30562306a36Sopenharmony_ci		val = cs5535_mfgpt_read(&timer, MFGPT_REG_SETUP);
30662306a36Sopenharmony_ci		if (!(val & MFGPT_SETUP_SETUP) || mfgpt_reset_timers == 2) {
30762306a36Sopenharmony_ci			__set_bit(i, mfgpt->avail);
30862306a36Sopenharmony_ci			timers++;
30962306a36Sopenharmony_ci		}
31062306a36Sopenharmony_ci	}
31162306a36Sopenharmony_ci	spin_unlock_irqrestore(&mfgpt->lock, flags);
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	return timers;
31462306a36Sopenharmony_ci}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_cistatic int cs5535_mfgpt_probe(struct platform_device *pdev)
31762306a36Sopenharmony_ci{
31862306a36Sopenharmony_ci	struct resource *res;
31962306a36Sopenharmony_ci	int err = -EIO, t;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	if (mfgpt_reset_timers < 0 || mfgpt_reset_timers > 2) {
32262306a36Sopenharmony_ci		dev_err(&pdev->dev, "Bad mfgpt_reset_timers value: %i\n",
32362306a36Sopenharmony_ci			mfgpt_reset_timers);
32462306a36Sopenharmony_ci		goto done;
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	/* There are two ways to get the MFGPT base address; one is by
32862306a36Sopenharmony_ci	 * fetching it from MSR_LBAR_MFGPT, the other is by reading the
32962306a36Sopenharmony_ci	 * PCI BAR info.  The latter method is easier (especially across
33062306a36Sopenharmony_ci	 * different architectures), so we'll stick with that for now.  If
33162306a36Sopenharmony_ci	 * it turns out to be unreliable in the face of crappy BIOSes, we
33262306a36Sopenharmony_ci	 * can always go back to using MSRs.. */
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
33562306a36Sopenharmony_ci	if (!res) {
33662306a36Sopenharmony_ci		dev_err(&pdev->dev, "can't fetch device resource info\n");
33762306a36Sopenharmony_ci		goto done;
33862306a36Sopenharmony_ci	}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	if (!request_region(res->start, resource_size(res), pdev->name)) {
34162306a36Sopenharmony_ci		dev_err(&pdev->dev, "can't request region\n");
34262306a36Sopenharmony_ci		goto done;
34362306a36Sopenharmony_ci	}
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/* set up the driver-specific struct */
34662306a36Sopenharmony_ci	cs5535_mfgpt_chip.base = res->start;
34762306a36Sopenharmony_ci	cs5535_mfgpt_chip.pdev = pdev;
34862306a36Sopenharmony_ci	spin_lock_init(&cs5535_mfgpt_chip.lock);
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	dev_info(&pdev->dev, "reserved resource region %pR\n", res);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	/* detect the available timers */
35362306a36Sopenharmony_ci	t = scan_timers(&cs5535_mfgpt_chip);
35462306a36Sopenharmony_ci	dev_info(&pdev->dev, "%d MFGPT timers available\n", t);
35562306a36Sopenharmony_ci	cs5535_mfgpt_chip.initialized = 1;
35662306a36Sopenharmony_ci	return 0;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_cidone:
35962306a36Sopenharmony_ci	return err;
36062306a36Sopenharmony_ci}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_cistatic struct platform_driver cs5535_mfgpt_driver = {
36362306a36Sopenharmony_ci	.driver = {
36462306a36Sopenharmony_ci		.name = DRV_NAME,
36562306a36Sopenharmony_ci	},
36662306a36Sopenharmony_ci	.probe = cs5535_mfgpt_probe,
36762306a36Sopenharmony_ci};
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_cistatic int __init cs5535_mfgpt_init(void)
37162306a36Sopenharmony_ci{
37262306a36Sopenharmony_ci	return platform_driver_register(&cs5535_mfgpt_driver);
37362306a36Sopenharmony_ci}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_cimodule_init(cs5535_mfgpt_init);
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ciMODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
37862306a36Sopenharmony_ciMODULE_DESCRIPTION("CS5535/CS5536 MFGPT timer driver");
37962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
38062306a36Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME);
381