Skip to content

Commit e292ccd

Browse files
chenhuacairalfbaechle
authored andcommitted
MIPS: Loongson-3: Add RS780/SBX00 HPET support
CPUFreq driver need external timer, so add hpet at first. In Loongson 3, only Core-0 can receive external interrupt. As a result, timekeeping cannot absolutely use HPET timer. We use a hybrid solution: Core-0 use HPET as its clock event device, but other cores still use MIPS; clock source is global and doesn't need interrupt, so use HPET. Signed-off-by: Huacai Chen <[email protected]> Signed-off-by: Hongliang Tao <[email protected]> Cc: John Crispin <[email protected]> Cc: Steven J. Hill <[email protected]> Cc: [email protected] Cc: Fuxin Zhang <[email protected]> Cc: Zhangjin Wu <[email protected]> Patchwork: https://patchwork.linux-mips.org/patch/8329/ Signed-off-by: Ralf Baechle <[email protected]>
1 parent 89467e7 commit e292ccd

File tree

6 files changed

+350
-1
lines changed

6 files changed

+350
-1
lines changed

arch/mips/include/asm/hpet.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#ifndef _ASM_HPET_H
2+
#define _ASM_HPET_H
3+
4+
#ifdef CONFIG_RS780_HPET
5+
6+
#define HPET_MMAP_SIZE 1024
7+
8+
#define HPET_ID 0x000
9+
#define HPET_PERIOD 0x004
10+
#define HPET_CFG 0x010
11+
#define HPET_STATUS 0x020
12+
#define HPET_COUNTER 0x0f0
13+
14+
#define HPET_Tn_CFG(n) (0x100 + 0x20 * n)
15+
#define HPET_Tn_CMP(n) (0x108 + 0x20 * n)
16+
#define HPET_Tn_ROUTE(n) (0x110 + 0x20 * n)
17+
18+
#define HPET_T0_IRS 0x001
19+
#define HPET_T1_IRS 0x002
20+
#define HPET_T3_IRS 0x004
21+
22+
#define HPET_T0_CFG 0x100
23+
#define HPET_T0_CMP 0x108
24+
#define HPET_T0_ROUTE 0x110
25+
#define HPET_T1_CFG 0x120
26+
#define HPET_T1_CMP 0x128
27+
#define HPET_T1_ROUTE 0x130
28+
#define HPET_T2_CFG 0x140
29+
#define HPET_T2_CMP 0x148
30+
#define HPET_T2_ROUTE 0x150
31+
32+
#define HPET_ID_REV 0x000000ff
33+
#define HPET_ID_NUMBER 0x00001f00
34+
#define HPET_ID_64BIT 0x00002000
35+
#define HPET_ID_LEGSUP 0x00008000
36+
#define HPET_ID_VENDOR 0xffff0000
37+
#define HPET_ID_NUMBER_SHIFT 8
38+
#define HPET_ID_VENDOR_SHIFT 16
39+
40+
#define HPET_CFG_ENABLE 0x001
41+
#define HPET_CFG_LEGACY 0x002
42+
#define HPET_LEGACY_8254 2
43+
#define HPET_LEGACY_RTC 8
44+
45+
#define HPET_TN_LEVEL 0x0002
46+
#define HPET_TN_ENABLE 0x0004
47+
#define HPET_TN_PERIODIC 0x0008
48+
#define HPET_TN_PERIODIC_CAP 0x0010
49+
#define HPET_TN_64BIT_CAP 0x0020
50+
#define HPET_TN_SETVAL 0x0040
51+
#define HPET_TN_32BIT 0x0100
52+
#define HPET_TN_ROUTE 0x3e00
53+
#define HPET_TN_FSB 0x4000
54+
#define HPET_TN_FSB_CAP 0x8000
55+
#define HPET_TN_ROUTE_SHIFT 9
56+
57+
/* Max HPET Period is 10^8 femto sec as in HPET spec */
58+
#define HPET_MAX_PERIOD 100000000UL
59+
/*
60+
* Min HPET period is 10^5 femto sec just for safety. If it is less than this,
61+
* then 32 bit HPET counter wrapsaround in less than 0.5 sec.
62+
*/
63+
#define HPET_MIN_PERIOD 100000UL
64+
65+
#define HPET_ADDR 0x20000
66+
#define HPET_MMIO_ADDR 0x90000e0000020000
67+
#define HPET_FREQ 14318780
68+
#define HPET_COMPARE_VAL ((HPET_FREQ + HZ / 2) / HZ)
69+
#define HPET_T0_IRQ 0
70+
71+
extern void __init setup_hpet_timer(void);
72+
#endif /* CONFIG_RS780_HPET */
73+
#endif /* _ASM_HPET_H */

arch/mips/loongson/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@ config CS5536_MFGPT
108108

109109
If unsure, say Yes.
110110

111+
config RS780_HPET
112+
bool "RS780/SBX00 HPET Timer"
113+
depends on LOONGSON_MACH3X
114+
select MIPS_EXTERNAL_TIMER
115+
help
116+
This option enables the hpet timer of AMD RS780/SBX00.
117+
118+
If you want to enable the Loongson3 CPUFreq Driver, Please enable
119+
this option at first, otherwise, You will get wrong system time.
120+
121+
If unsure, say Yes.
122+
111123
config LOONGSON_SUSPEND
112124
bool
113125
default y

arch/mips/loongson/common/time.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
#include <asm/mc146818-time.h>
1414
#include <asm/time.h>
15+
#include <asm/hpet.h>
1516

1617
#include <loongson.h>
1718
#include <cs5536/cs5536_mfgpt.h>
@@ -21,7 +22,11 @@ void __init plat_time_init(void)
2122
/* setup mips r4k timer */
2223
mips_hpt_frequency = cpu_clock_freq / 2;
2324

25+
#ifdef CONFIG_RS780_HPET
26+
setup_hpet_timer();
27+
#else
2428
setup_mfgpt0_timer();
29+
#endif
2530
}
2631

2732
void read_persistent_clock(struct timespec *ts)

arch/mips/loongson/loongson-3/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ obj-y += irq.o cop2-ex.o platform.o
66
obj-$(CONFIG_SMP) += smp.o
77

88
obj-$(CONFIG_NUMA) += numa.o
9+
10+
obj-$(CONFIG_RS780_HPET) += hpet.o
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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+
16+
static DEFINE_SPINLOCK(hpet_lock);
17+
DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device);
18+
19+
static unsigned int smbus_read(int offset)
20+
{
21+
return *(volatile unsigned int *)(SMBUS_CFG_BASE + offset);
22+
}
23+
24+
static void smbus_write(int offset, int data)
25+
{
26+
*(volatile unsigned int *)(SMBUS_CFG_BASE + offset) = data;
27+
}
28+
29+
static 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+
37+
static int hpet_read(int offset)
38+
{
39+
return *(volatile unsigned int *)(HPET_MMIO_ADDR + offset);
40+
}
41+
42+
static void hpet_write(int offset, int data)
43+
{
44+
*(volatile unsigned int *)(HPET_MMIO_ADDR + offset) = data;
45+
}
46+
47+
static 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+
55+
static 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+
63+
static void hpet_reset_counter(void)
64+
{
65+
hpet_write(HPET_COUNTER, 0);
66+
hpet_write(HPET_COUNTER + 4, 0);
67+
}
68+
69+
static void hpet_restart_counter(void)
70+
{
71+
hpet_stop_counter();
72+
hpet_reset_counter();
73+
hpet_start_counter();
74+
}
75+
76+
static void hpet_enable_legacy_int(void)
77+
{
78+
/* Do nothing on Loongson-3 */
79+
}
80+
81+
static 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+
132+
static 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+
146+
static 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+
163+
static struct irqaction hpet_irq = {
164+
.handler = hpet_irq_handler,
165+
.flags = IRQF_DISABLED | 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+
*/
184+
static 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+
198+
void __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+
222+
static cycle_t hpet_read_counter(struct clocksource *cs)
223+
{
224+
return (cycle_t)hpet_read(HPET_COUNTER);
225+
}
226+
227+
static void hpet_suspend(struct clocksource *cs)
228+
{
229+
}
230+
231+
static void hpet_resume(struct clocksource *cs)
232+
{
233+
hpet_setup();
234+
hpet_restart_counter();
235+
}
236+
237+
static 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+
251+
int __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+
257+
arch_initcall(init_hpet_clocksource);

arch/mips/loongson/loongson-3/irq.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
#include "smp.h"
1111

12-
unsigned int ht_irq[] = {1, 3, 4, 5, 6, 7, 8, 12, 14, 15};
12+
unsigned int ht_irq[] = {0, 1, 3, 4, 5, 6, 7, 8, 12, 14, 15};
1313

1414
static void ht_irqdispatch(void)
1515
{

0 commit comments

Comments
 (0)