162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Clock event driver for the CS5535/CS5536
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/irq.h>
1462306a36Sopenharmony_ci#include <linux/interrupt.h>
1562306a36Sopenharmony_ci#include <linux/module.h>
1662306a36Sopenharmony_ci#include <linux/cs5535.h>
1762306a36Sopenharmony_ci#include <linux/clockchips.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define DRV_NAME "cs5535-clockevt"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic int timer_irq;
2262306a36Sopenharmony_cimodule_param_hw_named(irq, timer_irq, int, irq, 0644);
2362306a36Sopenharmony_ciMODULE_PARM_DESC(irq, "Which IRQ to use for the clock source MFGPT ticks.");
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/*
2662306a36Sopenharmony_ci * We are using the 32.768kHz input clock - it's the only one that has the
2762306a36Sopenharmony_ci * ranges we find desirable.  The following table lists the suitable
2862306a36Sopenharmony_ci * divisors and the associated Hz, minimum interval and the maximum interval:
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci *  Divisor   Hz      Min Delta (s)  Max Delta (s)
3162306a36Sopenharmony_ci *   1        32768   .00048828125      2.000
3262306a36Sopenharmony_ci *   2        16384   .0009765625       4.000
3362306a36Sopenharmony_ci *   4         8192   .001953125        8.000
3462306a36Sopenharmony_ci *   8         4096   .00390625        16.000
3562306a36Sopenharmony_ci *   16        2048   .0078125         32.000
3662306a36Sopenharmony_ci *   32        1024   .015625          64.000
3762306a36Sopenharmony_ci *   64         512   .03125          128.000
3862306a36Sopenharmony_ci *  128         256   .0625           256.000
3962306a36Sopenharmony_ci *  256         128   .125            512.000
4062306a36Sopenharmony_ci */
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic struct cs5535_mfgpt_timer *cs5535_event_clock;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/* Selected from the table above */
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci#define MFGPT_DIVISOR 16
4762306a36Sopenharmony_ci#define MFGPT_SCALE  4     /* divisor = 2^(scale) */
4862306a36Sopenharmony_ci#define MFGPT_HZ  (32768 / MFGPT_DIVISOR)
4962306a36Sopenharmony_ci#define MFGPT_PERIODIC (MFGPT_HZ / HZ)
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/*
5262306a36Sopenharmony_ci * The MFGPT timers on the CS5536 provide us with suitable timers to use
5362306a36Sopenharmony_ci * as clock event sources - not as good as a HPET or APIC, but certainly
5462306a36Sopenharmony_ci * better than the PIT.  This isn't a general purpose MFGPT driver, but
5562306a36Sopenharmony_ci * a simplified one designed specifically to act as a clock event source.
5662306a36Sopenharmony_ci * For full details about the MFGPT, please consult the CS5536 data sheet.
5762306a36Sopenharmony_ci */
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic void disable_timer(struct cs5535_mfgpt_timer *timer)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	/* avoid races by clearing CMP1 and CMP2 unconditionally */
6262306a36Sopenharmony_ci	cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,
6362306a36Sopenharmony_ci			(uint16_t) ~MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP1 |
6462306a36Sopenharmony_ci				MFGPT_SETUP_CMP2);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic void start_timer(struct cs5535_mfgpt_timer *timer, uint16_t delta)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	cs5535_mfgpt_write(timer, MFGPT_REG_CMP2, delta);
7062306a36Sopenharmony_ci	cs5535_mfgpt_write(timer, MFGPT_REG_COUNTER, 0);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,
7362306a36Sopenharmony_ci			MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic int mfgpt_shutdown(struct clock_event_device *evt)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	disable_timer(cs5535_event_clock);
7962306a36Sopenharmony_ci	return 0;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic int mfgpt_set_periodic(struct clock_event_device *evt)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	disable_timer(cs5535_event_clock);
8562306a36Sopenharmony_ci	start_timer(cs5535_event_clock, MFGPT_PERIODIC);
8662306a36Sopenharmony_ci	return 0;
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	start_timer(cs5535_event_clock, delta);
9262306a36Sopenharmony_ci	return 0;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic struct clock_event_device cs5535_clockevent = {
9662306a36Sopenharmony_ci	.name = DRV_NAME,
9762306a36Sopenharmony_ci	.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
9862306a36Sopenharmony_ci	.set_state_shutdown = mfgpt_shutdown,
9962306a36Sopenharmony_ci	.set_state_periodic = mfgpt_set_periodic,
10062306a36Sopenharmony_ci	.set_state_oneshot = mfgpt_shutdown,
10162306a36Sopenharmony_ci	.tick_resume = mfgpt_shutdown,
10262306a36Sopenharmony_ci	.set_next_event = mfgpt_next_event,
10362306a36Sopenharmony_ci	.rating = 250,
10462306a36Sopenharmony_ci};
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic irqreturn_t mfgpt_tick(int irq, void *dev_id)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	uint16_t val = cs5535_mfgpt_read(cs5535_event_clock, MFGPT_REG_SETUP);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	/* See if the interrupt was for us */
11162306a36Sopenharmony_ci	if (!(val & (MFGPT_SETUP_SETUP | MFGPT_SETUP_CMP2 | MFGPT_SETUP_CMP1)))
11262306a36Sopenharmony_ci		return IRQ_NONE;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	/* Turn off the clock (and clear the event) */
11562306a36Sopenharmony_ci	disable_timer(cs5535_event_clock);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (clockevent_state_detached(&cs5535_clockevent) ||
11862306a36Sopenharmony_ci	    clockevent_state_shutdown(&cs5535_clockevent))
11962306a36Sopenharmony_ci		return IRQ_HANDLED;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	/* Clear the counter */
12262306a36Sopenharmony_ci	cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_COUNTER, 0);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* Restart the clock in periodic mode */
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (clockevent_state_periodic(&cs5535_clockevent))
12762306a36Sopenharmony_ci		cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP,
12862306a36Sopenharmony_ci				MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	cs5535_clockevent.event_handler(&cs5535_clockevent);
13162306a36Sopenharmony_ci	return IRQ_HANDLED;
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic int __init cs5535_mfgpt_init(void)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	unsigned long flags = IRQF_NOBALANCING | IRQF_TIMER | IRQF_SHARED;
13762306a36Sopenharmony_ci	struct cs5535_mfgpt_timer *timer;
13862306a36Sopenharmony_ci	int ret;
13962306a36Sopenharmony_ci	uint16_t val;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	timer = cs5535_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING);
14262306a36Sopenharmony_ci	if (!timer) {
14362306a36Sopenharmony_ci		printk(KERN_ERR DRV_NAME ": Could not allocate MFGPT timer\n");
14462306a36Sopenharmony_ci		return -ENODEV;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci	cs5535_event_clock = timer;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	/* Set up the IRQ on the MFGPT side */
14962306a36Sopenharmony_ci	if (cs5535_mfgpt_setup_irq(timer, MFGPT_CMP2, &timer_irq)) {
15062306a36Sopenharmony_ci		printk(KERN_ERR DRV_NAME ": Could not set up IRQ %d\n",
15162306a36Sopenharmony_ci				timer_irq);
15262306a36Sopenharmony_ci		goto err_timer;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	/* And register it with the kernel */
15662306a36Sopenharmony_ci	ret = request_irq(timer_irq, mfgpt_tick, flags, DRV_NAME, timer);
15762306a36Sopenharmony_ci	if (ret) {
15862306a36Sopenharmony_ci		printk(KERN_ERR DRV_NAME ": Unable to set up the interrupt.\n");
15962306a36Sopenharmony_ci		goto err_irq;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	/* Set the clock scale and enable the event mode for CMP2 */
16362306a36Sopenharmony_ci	val = MFGPT_SCALE | (3 << 8);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP, val);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/* Set up the clock event */
16862306a36Sopenharmony_ci	printk(KERN_INFO DRV_NAME
16962306a36Sopenharmony_ci		": Registering MFGPT timer as a clock event, using IRQ %d\n",
17062306a36Sopenharmony_ci		timer_irq);
17162306a36Sopenharmony_ci	clockevents_config_and_register(&cs5535_clockevent, MFGPT_HZ,
17262306a36Sopenharmony_ci					0xF, 0xFFFE);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	return 0;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cierr_irq:
17762306a36Sopenharmony_ci	cs5535_mfgpt_release_irq(cs5535_event_clock, MFGPT_CMP2, &timer_irq);
17862306a36Sopenharmony_cierr_timer:
17962306a36Sopenharmony_ci	cs5535_mfgpt_free_timer(cs5535_event_clock);
18062306a36Sopenharmony_ci	printk(KERN_ERR DRV_NAME ": Unable to set up the MFGPT clock source\n");
18162306a36Sopenharmony_ci	return -EIO;
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cimodule_init(cs5535_mfgpt_init);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ciMODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
18762306a36Sopenharmony_ciMODULE_DESCRIPTION("CS5535/CS5536 MFGPT clock event driver");
18862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
189