1#include <linux/init.h>
2#include <linux/pci.h>
3#include <linux/percpu.h>
4#include <linux/delay.h>
5#include <linux/spinlock.h>
6#include <linux/interrupt.h>
7
8#include <asm/hpet.h>
9#include <asm/time.h>
10
11#define SMBUS_CFG_BASE		(loongson_sysconf.ht_control_base + 0x0300a000)
12#define SMBUS_PCI_REG40		0x40
13#define SMBUS_PCI_REG64		0x64
14#define SMBUS_PCI_REGB4		0xb4
15
16static DEFINE_SPINLOCK(hpet_lock);
17DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device);
18
19static unsigned int smbus_read(int offset)
20{
21	return *(volatile unsigned int *)(SMBUS_CFG_BASE + offset);
22}
23
24static void smbus_write(int offset, int data)
25{
26	*(volatile unsigned int *)(SMBUS_CFG_BASE + offset) = data;
27}
28
29static void smbus_enable(int offset, int bit)
30{
31	unsigned int cfg = smbus_read(offset);
32
33	cfg |= bit;
34	smbus_write(offset, cfg);
35}
36
37static int hpet_read(int offset)
38{
39	return *(volatile unsigned int *)(HPET_MMIO_ADDR + offset);
40}
41
42static void hpet_write(int offset, int data)
43{
44	*(volatile unsigned int *)(HPET_MMIO_ADDR + offset) = data;
45}
46
47static void hpet_start_counter(void)
48{
49	unsigned int cfg = hpet_read(HPET_CFG);
50
51	cfg |= HPET_CFG_ENABLE;
52	hpet_write(HPET_CFG, cfg);
53}
54
55static void hpet_stop_counter(void)
56{
57	unsigned int cfg = hpet_read(HPET_CFG);
58
59	cfg &= ~HPET_CFG_ENABLE;
60	hpet_write(HPET_CFG, cfg);
61}
62
63static void hpet_reset_counter(void)
64{
65	hpet_write(HPET_COUNTER, 0);
66	hpet_write(HPET_COUNTER + 4, 0);
67}
68
69static void hpet_restart_counter(void)
70{
71	hpet_stop_counter();
72	hpet_reset_counter();
73	hpet_start_counter();
74}
75
76static void hpet_enable_legacy_int(void)
77{
78	/* Do nothing on Loongson-3 */
79}
80
81static void hpet_set_mode(enum clock_event_mode mode,
82				struct clock_event_device *evt)
83{
84	int cfg = 0;
85
86	spin_lock(&hpet_lock);
87	switch (mode) {
88	case CLOCK_EVT_MODE_PERIODIC:
89		pr_info("set clock event to periodic mode!\n");
90		/* stop counter */
91		hpet_stop_counter();
92
93		/* enables the timer0 to generate a periodic interrupt */
94		cfg = hpet_read(HPET_T0_CFG);
95		cfg &= ~HPET_TN_LEVEL;
96		cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC |
97				HPET_TN_SETVAL | HPET_TN_32BIT;
98		hpet_write(HPET_T0_CFG, cfg);
99
100		/* set the comparator */
101		hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);
102		udelay(1);
103		hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);
104
105		/* start counter */
106		hpet_start_counter();
107		break;
108	case CLOCK_EVT_MODE_SHUTDOWN:
109	case CLOCK_EVT_MODE_UNUSED:
110		cfg = hpet_read(HPET_T0_CFG);
111		cfg &= ~HPET_TN_ENABLE;
112		hpet_write(HPET_T0_CFG, cfg);
113		break;
114	case CLOCK_EVT_MODE_ONESHOT:
115		pr_info("set clock event to one shot mode!\n");
116		cfg = hpet_read(HPET_T0_CFG);
117		/* set timer0 type
118		 * 1 : periodic interrupt
119		 * 0 : non-periodic(oneshot) interrupt
120		 */
121		cfg &= ~HPET_TN_PERIODIC;
122		cfg |= HPET_TN_ENABLE | HPET_TN_32BIT;
123		hpet_write(HPET_T0_CFG, cfg);
124		break;
125	case CLOCK_EVT_MODE_RESUME:
126		hpet_enable_legacy_int();
127		break;
128	}
129	spin_unlock(&hpet_lock);
130}
131
132static int hpet_next_event(unsigned long delta,
133		struct clock_event_device *evt)
134{
135	unsigned int cnt;
136	int res;
137
138	cnt = hpet_read(HPET_COUNTER);
139	cnt += delta;
140	hpet_write(HPET_T0_CMP, cnt);
141
142	res = ((int)(hpet_read(HPET_COUNTER) - cnt) > 0) ? -ETIME : 0;
143	return res;
144}
145
146static irqreturn_t hpet_irq_handler(int irq, void *data)
147{
148	int is_irq;
149	struct clock_event_device *cd;
150	unsigned int cpu = smp_processor_id();
151
152	is_irq = hpet_read(HPET_STATUS);
153	if (is_irq & HPET_T0_IRS) {
154		/* clear the TIMER0 irq status register */
155		hpet_write(HPET_STATUS, HPET_T0_IRS);
156		cd = &per_cpu(hpet_clockevent_device, cpu);
157		cd->event_handler(cd);
158		return IRQ_HANDLED;
159	}
160	return IRQ_NONE;
161}
162
163static struct irqaction hpet_irq = {
164	.handler = hpet_irq_handler,
165	.flags = IRQF_NOBALANCING | IRQF_TIMER,
166	.name = "hpet",
167};
168
169/*
170 * hpet address assignation and irq setting should be done in bios.
171 * but pmon don't do this, we just setup here directly.
172 * The operation under is normal. unfortunately, hpet_setup process
173 * is before pci initialize.
174 *
175 * {
176 *	struct pci_dev *pdev;
177 *
178 *	pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
179 *	pci_write_config_word(pdev, SMBUS_PCI_REGB4, HPET_ADDR);
180 *
181 *	...
182 * }
183 */
184static void hpet_setup(void)
185{
186	/* set hpet base address */
187	smbus_write(SMBUS_PCI_REGB4, HPET_ADDR);
188
189	/* enable decodeing of access to HPET MMIO*/
190	smbus_enable(SMBUS_PCI_REG40, (1 << 28));
191
192	/* HPET irq enable */
193	smbus_enable(SMBUS_PCI_REG64, (1 << 10));
194
195	hpet_enable_legacy_int();
196}
197
198void __init setup_hpet_timer(void)
199{
200	unsigned int cpu = smp_processor_id();
201	struct clock_event_device *cd;
202
203	hpet_setup();
204
205	cd = &per_cpu(hpet_clockevent_device, cpu);
206	cd->name = "hpet";
207	cd->rating = 320;
208	cd->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
209	cd->set_mode = hpet_set_mode;
210	cd->set_next_event = hpet_next_event;
211	cd->irq = HPET_T0_IRQ;
212	cd->cpumask = cpumask_of(cpu);
213	clockevent_set_clock(cd, HPET_FREQ);
214	cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd);
215	cd->min_delta_ns = 5000;
216
217	clockevents_register_device(cd);
218	setup_irq(HPET_T0_IRQ, &hpet_irq);
219	pr_info("hpet clock event device register\n");
220}
221
222static cycle_t hpet_read_counter(struct clocksource *cs)
223{
224	return (cycle_t)hpet_read(HPET_COUNTER);
225}
226
227static void hpet_suspend(struct clocksource *cs)
228{
229}
230
231static void hpet_resume(struct clocksource *cs)
232{
233	hpet_setup();
234	hpet_restart_counter();
235}
236
237static struct clocksource csrc_hpet = {
238	.name = "hpet",
239	/* mips clocksource rating is less than 300, so hpet is better. */
240	.rating = 300,
241	.read = hpet_read_counter,
242	.mask = CLOCKSOURCE_MASK(32),
243	/* oneshot mode work normal with this flag */
244	.flags = CLOCK_SOURCE_IS_CONTINUOUS,
245	.suspend = hpet_suspend,
246	.resume = hpet_resume,
247	.mult = 0,
248	.shift = 10,
249};
250
251int __init init_hpet_clocksource(void)
252{
253	csrc_hpet.mult = clocksource_hz2mult(HPET_FREQ, csrc_hpet.shift);
254	return clocksource_register_hz(&csrc_hpet, HPET_FREQ);
255}
256
257arch_initcall(init_hpet_clocksource);
258