18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Clock event driver for the CS5535/CS5536
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/irq.h>
148c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/cs5535.h>
178c2ecf20Sopenharmony_ci#include <linux/clockchips.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define DRV_NAME "cs5535-clockevt"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic int timer_irq;
228c2ecf20Sopenharmony_cimodule_param_hw_named(irq, timer_irq, int, irq, 0644);
238c2ecf20Sopenharmony_ciMODULE_PARM_DESC(irq, "Which IRQ to use for the clock source MFGPT ticks.");
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/*
268c2ecf20Sopenharmony_ci * We are using the 32.768kHz input clock - it's the only one that has the
278c2ecf20Sopenharmony_ci * ranges we find desirable.  The following table lists the suitable
288c2ecf20Sopenharmony_ci * divisors and the associated Hz, minimum interval and the maximum interval:
298c2ecf20Sopenharmony_ci *
308c2ecf20Sopenharmony_ci *  Divisor   Hz      Min Delta (s)  Max Delta (s)
318c2ecf20Sopenharmony_ci *   1        32768   .00048828125      2.000
328c2ecf20Sopenharmony_ci *   2        16384   .0009765625       4.000
338c2ecf20Sopenharmony_ci *   4         8192   .001953125        8.000
348c2ecf20Sopenharmony_ci *   8         4096   .00390625        16.000
358c2ecf20Sopenharmony_ci *   16        2048   .0078125         32.000
368c2ecf20Sopenharmony_ci *   32        1024   .015625          64.000
378c2ecf20Sopenharmony_ci *   64         512   .03125          128.000
388c2ecf20Sopenharmony_ci *  128         256   .0625           256.000
398c2ecf20Sopenharmony_ci *  256         128   .125            512.000
408c2ecf20Sopenharmony_ci */
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic struct cs5535_mfgpt_timer *cs5535_event_clock;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci/* Selected from the table above */
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci#define MFGPT_DIVISOR 16
478c2ecf20Sopenharmony_ci#define MFGPT_SCALE  4     /* divisor = 2^(scale) */
488c2ecf20Sopenharmony_ci#define MFGPT_HZ  (32768 / MFGPT_DIVISOR)
498c2ecf20Sopenharmony_ci#define MFGPT_PERIODIC (MFGPT_HZ / HZ)
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci/*
528c2ecf20Sopenharmony_ci * The MFGPT timers on the CS5536 provide us with suitable timers to use
538c2ecf20Sopenharmony_ci * as clock event sources - not as good as a HPET or APIC, but certainly
548c2ecf20Sopenharmony_ci * better than the PIT.  This isn't a general purpose MFGPT driver, but
558c2ecf20Sopenharmony_ci * a simplified one designed specifically to act as a clock event source.
568c2ecf20Sopenharmony_ci * For full details about the MFGPT, please consult the CS5536 data sheet.
578c2ecf20Sopenharmony_ci */
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic void disable_timer(struct cs5535_mfgpt_timer *timer)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	/* avoid races by clearing CMP1 and CMP2 unconditionally */
628c2ecf20Sopenharmony_ci	cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,
638c2ecf20Sopenharmony_ci			(uint16_t) ~MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP1 |
648c2ecf20Sopenharmony_ci				MFGPT_SETUP_CMP2);
658c2ecf20Sopenharmony_ci}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic void start_timer(struct cs5535_mfgpt_timer *timer, uint16_t delta)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	cs5535_mfgpt_write(timer, MFGPT_REG_CMP2, delta);
708c2ecf20Sopenharmony_ci	cs5535_mfgpt_write(timer, MFGPT_REG_COUNTER, 0);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,
738c2ecf20Sopenharmony_ci			MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
748c2ecf20Sopenharmony_ci}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic int mfgpt_shutdown(struct clock_event_device *evt)
778c2ecf20Sopenharmony_ci{
788c2ecf20Sopenharmony_ci	disable_timer(cs5535_event_clock);
798c2ecf20Sopenharmony_ci	return 0;
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic int mfgpt_set_periodic(struct clock_event_device *evt)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	disable_timer(cs5535_event_clock);
858c2ecf20Sopenharmony_ci	start_timer(cs5535_event_clock, MFGPT_PERIODIC);
868c2ecf20Sopenharmony_ci	return 0;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	start_timer(cs5535_event_clock, delta);
928c2ecf20Sopenharmony_ci	return 0;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic struct clock_event_device cs5535_clockevent = {
968c2ecf20Sopenharmony_ci	.name = DRV_NAME,
978c2ecf20Sopenharmony_ci	.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
988c2ecf20Sopenharmony_ci	.set_state_shutdown = mfgpt_shutdown,
998c2ecf20Sopenharmony_ci	.set_state_periodic = mfgpt_set_periodic,
1008c2ecf20Sopenharmony_ci	.set_state_oneshot = mfgpt_shutdown,
1018c2ecf20Sopenharmony_ci	.tick_resume = mfgpt_shutdown,
1028c2ecf20Sopenharmony_ci	.set_next_event = mfgpt_next_event,
1038c2ecf20Sopenharmony_ci	.rating = 250,
1048c2ecf20Sopenharmony_ci};
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistatic irqreturn_t mfgpt_tick(int irq, void *dev_id)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	uint16_t val = cs5535_mfgpt_read(cs5535_event_clock, MFGPT_REG_SETUP);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	/* See if the interrupt was for us */
1118c2ecf20Sopenharmony_ci	if (!(val & (MFGPT_SETUP_SETUP | MFGPT_SETUP_CMP2 | MFGPT_SETUP_CMP1)))
1128c2ecf20Sopenharmony_ci		return IRQ_NONE;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	/* Turn off the clock (and clear the event) */
1158c2ecf20Sopenharmony_ci	disable_timer(cs5535_event_clock);
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	if (clockevent_state_detached(&cs5535_clockevent) ||
1188c2ecf20Sopenharmony_ci	    clockevent_state_shutdown(&cs5535_clockevent))
1198c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	/* Clear the counter */
1228c2ecf20Sopenharmony_ci	cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_COUNTER, 0);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	/* Restart the clock in periodic mode */
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	if (clockevent_state_periodic(&cs5535_clockevent))
1278c2ecf20Sopenharmony_ci		cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP,
1288c2ecf20Sopenharmony_ci				MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	cs5535_clockevent.event_handler(&cs5535_clockevent);
1318c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic int __init cs5535_mfgpt_init(void)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	unsigned long flags = IRQF_NOBALANCING | IRQF_TIMER | IRQF_SHARED;
1378c2ecf20Sopenharmony_ci	struct cs5535_mfgpt_timer *timer;
1388c2ecf20Sopenharmony_ci	int ret;
1398c2ecf20Sopenharmony_ci	uint16_t val;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	timer = cs5535_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING);
1428c2ecf20Sopenharmony_ci	if (!timer) {
1438c2ecf20Sopenharmony_ci		printk(KERN_ERR DRV_NAME ": Could not allocate MFGPT timer\n");
1448c2ecf20Sopenharmony_ci		return -ENODEV;
1458c2ecf20Sopenharmony_ci	}
1468c2ecf20Sopenharmony_ci	cs5535_event_clock = timer;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	/* Set up the IRQ on the MFGPT side */
1498c2ecf20Sopenharmony_ci	if (cs5535_mfgpt_setup_irq(timer, MFGPT_CMP2, &timer_irq)) {
1508c2ecf20Sopenharmony_ci		printk(KERN_ERR DRV_NAME ": Could not set up IRQ %d\n",
1518c2ecf20Sopenharmony_ci				timer_irq);
1528c2ecf20Sopenharmony_ci		goto err_timer;
1538c2ecf20Sopenharmony_ci	}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	/* And register it with the kernel */
1568c2ecf20Sopenharmony_ci	ret = request_irq(timer_irq, mfgpt_tick, flags, DRV_NAME, timer);
1578c2ecf20Sopenharmony_ci	if (ret) {
1588c2ecf20Sopenharmony_ci		printk(KERN_ERR DRV_NAME ": Unable to set up the interrupt.\n");
1598c2ecf20Sopenharmony_ci		goto err_irq;
1608c2ecf20Sopenharmony_ci	}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	/* Set the clock scale and enable the event mode for CMP2 */
1638c2ecf20Sopenharmony_ci	val = MFGPT_SCALE | (3 << 8);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP, val);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	/* Set up the clock event */
1688c2ecf20Sopenharmony_ci	printk(KERN_INFO DRV_NAME
1698c2ecf20Sopenharmony_ci		": Registering MFGPT timer as a clock event, using IRQ %d\n",
1708c2ecf20Sopenharmony_ci		timer_irq);
1718c2ecf20Sopenharmony_ci	clockevents_config_and_register(&cs5535_clockevent, MFGPT_HZ,
1728c2ecf20Sopenharmony_ci					0xF, 0xFFFE);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	return 0;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_cierr_irq:
1778c2ecf20Sopenharmony_ci	cs5535_mfgpt_release_irq(cs5535_event_clock, MFGPT_CMP2, &timer_irq);
1788c2ecf20Sopenharmony_cierr_timer:
1798c2ecf20Sopenharmony_ci	cs5535_mfgpt_free_timer(cs5535_event_clock);
1808c2ecf20Sopenharmony_ci	printk(KERN_ERR DRV_NAME ": Unable to set up the MFGPT clock source\n");
1818c2ecf20Sopenharmony_ci	return -EIO;
1828c2ecf20Sopenharmony_ci}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_cimodule_init(cs5535_mfgpt_init);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
1878c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("CS5535/CS5536 MFGPT clock event driver");
1888c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
189