125763b3cSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2371eec9bSDaniel Lezcano /*
3371eec9bSDaniel Lezcano  * Clock event driver for the CS5535/CS5536
4371eec9bSDaniel Lezcano  *
5371eec9bSDaniel Lezcano  * Copyright (C) 2006, Advanced Micro Devices, Inc.
6371eec9bSDaniel Lezcano  * Copyright (C) 2007  Andres Salomon <[email protected]>
7371eec9bSDaniel Lezcano  * Copyright (C) 2009  Andres Salomon <[email protected]>
8371eec9bSDaniel Lezcano  *
9371eec9bSDaniel Lezcano  * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book.
10371eec9bSDaniel Lezcano  */
11371eec9bSDaniel Lezcano 
12371eec9bSDaniel Lezcano #include <linux/kernel.h>
13371eec9bSDaniel Lezcano #include <linux/irq.h>
14371eec9bSDaniel Lezcano #include <linux/interrupt.h>
15371eec9bSDaniel Lezcano #include <linux/module.h>
16371eec9bSDaniel Lezcano #include <linux/cs5535.h>
17371eec9bSDaniel Lezcano #include <linux/clockchips.h>
18371eec9bSDaniel Lezcano 
19371eec9bSDaniel Lezcano #define DRV_NAME "cs5535-clockevt"
20371eec9bSDaniel Lezcano 
21371eec9bSDaniel Lezcano static int timer_irq;
22371eec9bSDaniel Lezcano module_param_hw_named(irq, timer_irq, int, irq, 0644);
23371eec9bSDaniel Lezcano MODULE_PARM_DESC(irq, "Which IRQ to use for the clock source MFGPT ticks.");
24371eec9bSDaniel Lezcano 
25371eec9bSDaniel Lezcano /*
26371eec9bSDaniel Lezcano  * We are using the 32.768kHz input clock - it's the only one that has the
27371eec9bSDaniel Lezcano  * ranges we find desirable.  The following table lists the suitable
28371eec9bSDaniel Lezcano  * divisors and the associated Hz, minimum interval and the maximum interval:
29371eec9bSDaniel Lezcano  *
30371eec9bSDaniel Lezcano  *  Divisor   Hz      Min Delta (s)  Max Delta (s)
31371eec9bSDaniel Lezcano  *   1        32768   .00048828125      2.000
32371eec9bSDaniel Lezcano  *   2        16384   .0009765625       4.000
33371eec9bSDaniel Lezcano  *   4         8192   .001953125        8.000
34371eec9bSDaniel Lezcano  *   8         4096   .00390625        16.000
35371eec9bSDaniel Lezcano  *   16        2048   .0078125         32.000
36371eec9bSDaniel Lezcano  *   32        1024   .015625          64.000
37371eec9bSDaniel Lezcano  *   64         512   .03125          128.000
38371eec9bSDaniel Lezcano  *  128         256   .0625           256.000
39371eec9bSDaniel Lezcano  *  256         128   .125            512.000
40371eec9bSDaniel Lezcano  */
41371eec9bSDaniel Lezcano 
42371eec9bSDaniel Lezcano static struct cs5535_mfgpt_timer *cs5535_event_clock;
43371eec9bSDaniel Lezcano 
44371eec9bSDaniel Lezcano /* Selected from the table above */
45371eec9bSDaniel Lezcano 
46371eec9bSDaniel Lezcano #define MFGPT_DIVISOR 16
47371eec9bSDaniel Lezcano #define MFGPT_SCALE  4     /* divisor = 2^(scale) */
48371eec9bSDaniel Lezcano #define MFGPT_HZ  (32768 / MFGPT_DIVISOR)
49371eec9bSDaniel Lezcano #define MFGPT_PERIODIC (MFGPT_HZ / HZ)
50371eec9bSDaniel Lezcano 
51371eec9bSDaniel Lezcano /*
52371eec9bSDaniel Lezcano  * The MFGPT timers on the CS5536 provide us with suitable timers to use
53371eec9bSDaniel Lezcano  * as clock event sources - not as good as a HPET or APIC, but certainly
54371eec9bSDaniel Lezcano  * better than the PIT.  This isn't a general purpose MFGPT driver, but
55371eec9bSDaniel Lezcano  * a simplified one designed specifically to act as a clock event source.
56371eec9bSDaniel Lezcano  * For full details about the MFGPT, please consult the CS5536 data sheet.
57371eec9bSDaniel Lezcano  */
58371eec9bSDaniel Lezcano 
disable_timer(struct cs5535_mfgpt_timer * timer)59371eec9bSDaniel Lezcano static void disable_timer(struct cs5535_mfgpt_timer *timer)
60371eec9bSDaniel Lezcano {
61371eec9bSDaniel Lezcano 	/* avoid races by clearing CMP1 and CMP2 unconditionally */
62371eec9bSDaniel Lezcano 	cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,
63371eec9bSDaniel Lezcano 			(uint16_t) ~MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP1 |
64371eec9bSDaniel Lezcano 				MFGPT_SETUP_CMP2);
65371eec9bSDaniel Lezcano }
66371eec9bSDaniel Lezcano 
start_timer(struct cs5535_mfgpt_timer * timer,uint16_t delta)67371eec9bSDaniel Lezcano static void start_timer(struct cs5535_mfgpt_timer *timer, uint16_t delta)
68371eec9bSDaniel Lezcano {
69371eec9bSDaniel Lezcano 	cs5535_mfgpt_write(timer, MFGPT_REG_CMP2, delta);
70371eec9bSDaniel Lezcano 	cs5535_mfgpt_write(timer, MFGPT_REG_COUNTER, 0);
71371eec9bSDaniel Lezcano 
72371eec9bSDaniel Lezcano 	cs5535_mfgpt_write(timer, MFGPT_REG_SETUP,
73371eec9bSDaniel Lezcano 			MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
74371eec9bSDaniel Lezcano }
75371eec9bSDaniel Lezcano 
mfgpt_shutdown(struct clock_event_device * evt)76371eec9bSDaniel Lezcano static int mfgpt_shutdown(struct clock_event_device *evt)
77371eec9bSDaniel Lezcano {
78371eec9bSDaniel Lezcano 	disable_timer(cs5535_event_clock);
79371eec9bSDaniel Lezcano 	return 0;
80371eec9bSDaniel Lezcano }
81371eec9bSDaniel Lezcano 
mfgpt_set_periodic(struct clock_event_device * evt)82371eec9bSDaniel Lezcano static int mfgpt_set_periodic(struct clock_event_device *evt)
83371eec9bSDaniel Lezcano {
84371eec9bSDaniel Lezcano 	disable_timer(cs5535_event_clock);
85371eec9bSDaniel Lezcano 	start_timer(cs5535_event_clock, MFGPT_PERIODIC);
86371eec9bSDaniel Lezcano 	return 0;
87371eec9bSDaniel Lezcano }
88371eec9bSDaniel Lezcano 
mfgpt_next_event(unsigned long delta,struct clock_event_device * evt)89371eec9bSDaniel Lezcano static int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt)
90371eec9bSDaniel Lezcano {
91371eec9bSDaniel Lezcano 	start_timer(cs5535_event_clock, delta);
92371eec9bSDaniel Lezcano 	return 0;
93371eec9bSDaniel Lezcano }
94371eec9bSDaniel Lezcano 
95371eec9bSDaniel Lezcano static struct clock_event_device cs5535_clockevent = {
96371eec9bSDaniel Lezcano 	.name = DRV_NAME,
97371eec9bSDaniel Lezcano 	.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
98371eec9bSDaniel Lezcano 	.set_state_shutdown = mfgpt_shutdown,
99371eec9bSDaniel Lezcano 	.set_state_periodic = mfgpt_set_periodic,
100371eec9bSDaniel Lezcano 	.set_state_oneshot = mfgpt_shutdown,
101371eec9bSDaniel Lezcano 	.tick_resume = mfgpt_shutdown,
102371eec9bSDaniel Lezcano 	.set_next_event = mfgpt_next_event,
103371eec9bSDaniel Lezcano 	.rating = 250,
104371eec9bSDaniel Lezcano };
105371eec9bSDaniel Lezcano 
mfgpt_tick(int irq,void * dev_id)106371eec9bSDaniel Lezcano static irqreturn_t mfgpt_tick(int irq, void *dev_id)
107371eec9bSDaniel Lezcano {
108371eec9bSDaniel Lezcano 	uint16_t val = cs5535_mfgpt_read(cs5535_event_clock, MFGPT_REG_SETUP);
109371eec9bSDaniel Lezcano 
110371eec9bSDaniel Lezcano 	/* See if the interrupt was for us */
111371eec9bSDaniel Lezcano 	if (!(val & (MFGPT_SETUP_SETUP | MFGPT_SETUP_CMP2 | MFGPT_SETUP_CMP1)))
112371eec9bSDaniel Lezcano 		return IRQ_NONE;
113371eec9bSDaniel Lezcano 
114371eec9bSDaniel Lezcano 	/* Turn off the clock (and clear the event) */
115371eec9bSDaniel Lezcano 	disable_timer(cs5535_event_clock);
116371eec9bSDaniel Lezcano 
117371eec9bSDaniel Lezcano 	if (clockevent_state_detached(&cs5535_clockevent) ||
118371eec9bSDaniel Lezcano 	    clockevent_state_shutdown(&cs5535_clockevent))
119371eec9bSDaniel Lezcano 		return IRQ_HANDLED;
120371eec9bSDaniel Lezcano 
121371eec9bSDaniel Lezcano 	/* Clear the counter */
122371eec9bSDaniel Lezcano 	cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_COUNTER, 0);
123371eec9bSDaniel Lezcano 
124371eec9bSDaniel Lezcano 	/* Restart the clock in periodic mode */
125371eec9bSDaniel Lezcano 
126371eec9bSDaniel Lezcano 	if (clockevent_state_periodic(&cs5535_clockevent))
127371eec9bSDaniel Lezcano 		cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP,
128371eec9bSDaniel Lezcano 				MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
129371eec9bSDaniel Lezcano 
130371eec9bSDaniel Lezcano 	cs5535_clockevent.event_handler(&cs5535_clockevent);
131371eec9bSDaniel Lezcano 	return IRQ_HANDLED;
132371eec9bSDaniel Lezcano }
133371eec9bSDaniel Lezcano 
cs5535_mfgpt_init(void)134371eec9bSDaniel Lezcano static int __init cs5535_mfgpt_init(void)
135371eec9bSDaniel Lezcano {
136*470cf1c2Safzal mohammed 	unsigned long flags = IRQF_NOBALANCING | IRQF_TIMER | IRQF_SHARED;
137371eec9bSDaniel Lezcano 	struct cs5535_mfgpt_timer *timer;
138371eec9bSDaniel Lezcano 	int ret;
139371eec9bSDaniel Lezcano 	uint16_t val;
140371eec9bSDaniel Lezcano 
141371eec9bSDaniel Lezcano 	timer = cs5535_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING);
142371eec9bSDaniel Lezcano 	if (!timer) {
143371eec9bSDaniel Lezcano 		printk(KERN_ERR DRV_NAME ": Could not allocate MFGPT timer\n");
144371eec9bSDaniel Lezcano 		return -ENODEV;
145371eec9bSDaniel Lezcano 	}
146371eec9bSDaniel Lezcano 	cs5535_event_clock = timer;
147371eec9bSDaniel Lezcano 
148371eec9bSDaniel Lezcano 	/* Set up the IRQ on the MFGPT side */
149371eec9bSDaniel Lezcano 	if (cs5535_mfgpt_setup_irq(timer, MFGPT_CMP2, &timer_irq)) {
150371eec9bSDaniel Lezcano 		printk(KERN_ERR DRV_NAME ": Could not set up IRQ %d\n",
151371eec9bSDaniel Lezcano 				timer_irq);
152371eec9bSDaniel Lezcano 		goto err_timer;
153371eec9bSDaniel Lezcano 	}
154371eec9bSDaniel Lezcano 
155371eec9bSDaniel Lezcano 	/* And register it with the kernel */
156*470cf1c2Safzal mohammed 	ret = request_irq(timer_irq, mfgpt_tick, flags, DRV_NAME, timer);
157371eec9bSDaniel Lezcano 	if (ret) {
158371eec9bSDaniel Lezcano 		printk(KERN_ERR DRV_NAME ": Unable to set up the interrupt.\n");
159371eec9bSDaniel Lezcano 		goto err_irq;
160371eec9bSDaniel Lezcano 	}
161371eec9bSDaniel Lezcano 
162371eec9bSDaniel Lezcano 	/* Set the clock scale and enable the event mode for CMP2 */
163371eec9bSDaniel Lezcano 	val = MFGPT_SCALE | (3 << 8);
164371eec9bSDaniel Lezcano 
165371eec9bSDaniel Lezcano 	cs5535_mfgpt_write(cs5535_event_clock, MFGPT_REG_SETUP, val);
166371eec9bSDaniel Lezcano 
167371eec9bSDaniel Lezcano 	/* Set up the clock event */
168371eec9bSDaniel Lezcano 	printk(KERN_INFO DRV_NAME
169371eec9bSDaniel Lezcano 		": Registering MFGPT timer as a clock event, using IRQ %d\n",
170371eec9bSDaniel Lezcano 		timer_irq);
171371eec9bSDaniel Lezcano 	clockevents_config_and_register(&cs5535_clockevent, MFGPT_HZ,
172371eec9bSDaniel Lezcano 					0xF, 0xFFFE);
173371eec9bSDaniel Lezcano 
174371eec9bSDaniel Lezcano 	return 0;
175371eec9bSDaniel Lezcano 
176371eec9bSDaniel Lezcano err_irq:
177371eec9bSDaniel Lezcano 	cs5535_mfgpt_release_irq(cs5535_event_clock, MFGPT_CMP2, &timer_irq);
178371eec9bSDaniel Lezcano err_timer:
179371eec9bSDaniel Lezcano 	cs5535_mfgpt_free_timer(cs5535_event_clock);
180371eec9bSDaniel Lezcano 	printk(KERN_ERR DRV_NAME ": Unable to set up the MFGPT clock source\n");
181371eec9bSDaniel Lezcano 	return -EIO;
182371eec9bSDaniel Lezcano }
183371eec9bSDaniel Lezcano 
184371eec9bSDaniel Lezcano module_init(cs5535_mfgpt_init);
185371eec9bSDaniel Lezcano 
186371eec9bSDaniel Lezcano MODULE_AUTHOR("Andres Salomon <[email protected]>");
187371eec9bSDaniel Lezcano MODULE_DESCRIPTION("CS5535/CS5536 MFGPT clock event driver");
188371eec9bSDaniel Lezcano MODULE_LICENSE("GPL");
189