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