1 // SPDX-License-Identifier: GPL-2.0
2 #include <linux/init.h>
4 #include <linux/percpu.h>
5 #include <linux/delay.h>
6 #include <linux/spinlock.h>
7 #include <linux/interrupt.h>
12 #define SMBUS_CFG_BASE (loongson_sysconf.ht_control_base + 0x0300a000)
13 #define SMBUS_PCI_REG40 0x40
14 #define SMBUS_PCI_REG64 0x64
15 #define SMBUS_PCI_REGB4 0xb4
17 #define HPET_MIN_CYCLES 16
18 #define HPET_MIN_PROG_DELTA (HPET_MIN_CYCLES * 12)
20 static DEFINE_SPINLOCK(hpet_lock
);
21 DEFINE_PER_CPU(struct clock_event_device
, hpet_clockevent_device
);
23 static unsigned int smbus_read(int offset
)
25 return *(volatile unsigned int *)(SMBUS_CFG_BASE
+ offset
);
28 static void smbus_write(int offset
, int data
)
30 *(volatile unsigned int *)(SMBUS_CFG_BASE
+ offset
) = data
;
33 static void smbus_enable(int offset
, int bit
)
35 unsigned int cfg
= smbus_read(offset
);
38 smbus_write(offset
, cfg
);
41 static int hpet_read(int offset
)
43 return *(volatile unsigned int *)(HPET_MMIO_ADDR
+ offset
);
46 static void hpet_write(int offset
, int data
)
48 *(volatile unsigned int *)(HPET_MMIO_ADDR
+ offset
) = data
;
51 static void hpet_start_counter(void)
53 unsigned int cfg
= hpet_read(HPET_CFG
);
55 cfg
|= HPET_CFG_ENABLE
;
56 hpet_write(HPET_CFG
, cfg
);
59 static void hpet_stop_counter(void)
61 unsigned int cfg
= hpet_read(HPET_CFG
);
63 cfg
&= ~HPET_CFG_ENABLE
;
64 hpet_write(HPET_CFG
, cfg
);
67 static void hpet_reset_counter(void)
69 hpet_write(HPET_COUNTER
, 0);
70 hpet_write(HPET_COUNTER
+ 4, 0);
73 static void hpet_restart_counter(void)
80 static void hpet_enable_legacy_int(void)
82 /* Do nothing on Loongson-3 */
85 static int hpet_set_state_periodic(struct clock_event_device
*evt
)
89 spin_lock(&hpet_lock
);
91 pr_info("set clock event to periodic mode!\n");
95 /* enables the timer0 to generate a periodic interrupt */
96 cfg
= hpet_read(HPET_T0_CFG
);
97 cfg
&= ~HPET_TN_LEVEL
;
98 cfg
|= HPET_TN_ENABLE
| HPET_TN_PERIODIC
| HPET_TN_SETVAL
|
100 hpet_write(HPET_T0_CFG
, cfg
);
102 /* set the comparator */
103 hpet_write(HPET_T0_CMP
, HPET_COMPARE_VAL
);
105 hpet_write(HPET_T0_CMP
, HPET_COMPARE_VAL
);
108 hpet_start_counter();
110 spin_unlock(&hpet_lock
);
114 static int hpet_set_state_shutdown(struct clock_event_device
*evt
)
118 spin_lock(&hpet_lock
);
120 cfg
= hpet_read(HPET_T0_CFG
);
121 cfg
&= ~HPET_TN_ENABLE
;
122 hpet_write(HPET_T0_CFG
, cfg
);
124 spin_unlock(&hpet_lock
);
128 static int hpet_set_state_oneshot(struct clock_event_device
*evt
)
132 spin_lock(&hpet_lock
);
134 pr_info("set clock event to one shot mode!\n");
135 cfg
= hpet_read(HPET_T0_CFG
);
138 * 1 : periodic interrupt
139 * 0 : non-periodic(oneshot) interrupt
141 cfg
&= ~HPET_TN_PERIODIC
;
142 cfg
|= HPET_TN_ENABLE
| HPET_TN_32BIT
;
143 hpet_write(HPET_T0_CFG
, cfg
);
145 spin_unlock(&hpet_lock
);
149 static int hpet_tick_resume(struct clock_event_device
*evt
)
151 spin_lock(&hpet_lock
);
152 hpet_enable_legacy_int();
153 spin_unlock(&hpet_lock
);
158 static int hpet_next_event(unsigned long delta
,
159 struct clock_event_device
*evt
)
164 cnt
= hpet_read(HPET_COUNTER
);
166 hpet_write(HPET_T0_CMP
, cnt
);
168 res
= (s32
)(cnt
- hpet_read(HPET_COUNTER
));
170 return res
< HPET_MIN_CYCLES
? -ETIME
: 0;
173 static irqreturn_t
hpet_irq_handler(int irq
, void *data
)
176 struct clock_event_device
*cd
;
177 unsigned int cpu
= smp_processor_id();
179 is_irq
= hpet_read(HPET_STATUS
);
180 if (is_irq
& HPET_T0_IRS
) {
181 /* clear the TIMER0 irq status register */
182 hpet_write(HPET_STATUS
, HPET_T0_IRS
);
183 cd
= &per_cpu(hpet_clockevent_device
, cpu
);
184 cd
->event_handler(cd
);
190 static struct irqaction hpet_irq
= {
191 .handler
= hpet_irq_handler
,
192 .flags
= IRQF_NOBALANCING
| IRQF_TIMER
,
197 * hpet address assignation and irq setting should be done in bios.
198 * but pmon don't do this, we just setup here directly.
199 * The operation under is normal. unfortunately, hpet_setup process
200 * is before pci initialize.
203 * struct pci_dev *pdev;
205 * pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
206 * pci_write_config_word(pdev, SMBUS_PCI_REGB4, HPET_ADDR);
211 static void hpet_setup(void)
213 /* set hpet base address */
214 smbus_write(SMBUS_PCI_REGB4
, HPET_ADDR
);
216 /* enable decoding of access to HPET MMIO*/
217 smbus_enable(SMBUS_PCI_REG40
, (1 << 28));
219 /* HPET irq enable */
220 smbus_enable(SMBUS_PCI_REG64
, (1 << 10));
222 hpet_enable_legacy_int();
225 void __init
setup_hpet_timer(void)
227 unsigned int cpu
= smp_processor_id();
228 struct clock_event_device
*cd
;
232 cd
= &per_cpu(hpet_clockevent_device
, cpu
);
235 cd
->features
= CLOCK_EVT_FEAT_PERIODIC
| CLOCK_EVT_FEAT_ONESHOT
;
236 cd
->set_state_shutdown
= hpet_set_state_shutdown
;
237 cd
->set_state_periodic
= hpet_set_state_periodic
;
238 cd
->set_state_oneshot
= hpet_set_state_oneshot
;
239 cd
->tick_resume
= hpet_tick_resume
;
240 cd
->set_next_event
= hpet_next_event
;
241 cd
->irq
= HPET_T0_IRQ
;
242 cd
->cpumask
= cpumask_of(cpu
);
243 clockevent_set_clock(cd
, HPET_FREQ
);
244 cd
->max_delta_ns
= clockevent_delta2ns(0x7fffffff, cd
);
245 cd
->max_delta_ticks
= 0x7fffffff;
246 cd
->min_delta_ns
= clockevent_delta2ns(HPET_MIN_PROG_DELTA
, cd
);
247 cd
->min_delta_ticks
= HPET_MIN_PROG_DELTA
;
249 clockevents_register_device(cd
);
250 setup_irq(HPET_T0_IRQ
, &hpet_irq
);
251 pr_info("hpet clock event device register\n");
254 static u64
hpet_read_counter(struct clocksource
*cs
)
256 return (u64
)hpet_read(HPET_COUNTER
);
259 static void hpet_suspend(struct clocksource
*cs
)
263 static void hpet_resume(struct clocksource
*cs
)
266 hpet_restart_counter();
269 static struct clocksource csrc_hpet
= {
271 /* mips clocksource rating is less than 300, so hpet is better. */
273 .read
= hpet_read_counter
,
274 .mask
= CLOCKSOURCE_MASK(32),
275 /* oneshot mode work normal with this flag */
276 .flags
= CLOCK_SOURCE_IS_CONTINUOUS
,
277 .suspend
= hpet_suspend
,
278 .resume
= hpet_resume
,
283 int __init
init_hpet_clocksource(void)
285 csrc_hpet
.mult
= clocksource_hz2mult(HPET_FREQ
, csrc_hpet
.shift
);
286 return clocksource_register_hz(&csrc_hpet
, HPET_FREQ
);
289 arch_initcall(init_hpet_clocksource
);