| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 | // SPDX-License-Identifier: GPL-2.0#include <linux/init.h>#include <linux/pci.h>#include <linux/percpu.h>#include <linux/delay.h>#include <linux/spinlock.h>#include <linux/interrupt.h>#include <asm/hpet.h>#include <asm/time.h>#define SMBUS_CFG_BASE		(loongson_sysconf.ht_control_base + 0x0300a000)#define SMBUS_PCI_REG40		0x40#define SMBUS_PCI_REG64		0x64#define SMBUS_PCI_REGB4		0xb4#define HPET_MIN_CYCLES		16#define HPET_MIN_PROG_DELTA	(HPET_MIN_CYCLES * 12)static DEFINE_SPINLOCK(hpet_lock);DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device);static unsigned int smbus_read(int offset){	return *(volatile unsigned int *)(SMBUS_CFG_BASE + offset);}static void smbus_write(int offset, int data){	*(volatile unsigned int *)(SMBUS_CFG_BASE + offset) = data;}static void smbus_enable(int offset, int bit){	unsigned int cfg = smbus_read(offset);	cfg |= bit;	smbus_write(offset, cfg);}static int hpet_read(int offset){	return *(volatile unsigned int *)(HPET_MMIO_ADDR + offset);}static void hpet_write(int offset, int data){	*(volatile unsigned int *)(HPET_MMIO_ADDR + offset) = data;}static void hpet_start_counter(void){	unsigned int cfg = hpet_read(HPET_CFG);	cfg |= HPET_CFG_ENABLE;	hpet_write(HPET_CFG, cfg);}static void hpet_stop_counter(void){	unsigned int cfg = hpet_read(HPET_CFG);	cfg &= ~HPET_CFG_ENABLE;	hpet_write(HPET_CFG, cfg);}static void hpet_reset_counter(void){	hpet_write(HPET_COUNTER, 0);	hpet_write(HPET_COUNTER + 4, 0);}static void hpet_restart_counter(void){	hpet_stop_counter();	hpet_reset_counter();	hpet_start_counter();}static void hpet_enable_legacy_int(void){	/* Do nothing on Loongson-3 */}static int hpet_set_state_periodic(struct clock_event_device *evt){	int cfg;	spin_lock(&hpet_lock);	pr_info("set clock event to periodic mode!\n");	/* stop counter */	hpet_stop_counter();	/* enables the timer0 to generate a periodic interrupt */	cfg = hpet_read(HPET_T0_CFG);	cfg &= ~HPET_TN_LEVEL;	cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_SETVAL |		HPET_TN_32BIT;	hpet_write(HPET_T0_CFG, cfg);	/* set the comparator */	hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);	udelay(1);	hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL);	/* start counter */	hpet_start_counter();	spin_unlock(&hpet_lock);	return 0;}static int hpet_set_state_shutdown(struct clock_event_device *evt){	int cfg;	spin_lock(&hpet_lock);	cfg = hpet_read(HPET_T0_CFG);	cfg &= ~HPET_TN_ENABLE;	hpet_write(HPET_T0_CFG, cfg);	spin_unlock(&hpet_lock);	return 0;}static int hpet_set_state_oneshot(struct clock_event_device *evt){	int cfg;	spin_lock(&hpet_lock);	pr_info("set clock event to one shot mode!\n");	cfg = hpet_read(HPET_T0_CFG);	/*	 * set timer0 type	 * 1 : periodic interrupt	 * 0 : non-periodic(oneshot) interrupt	 */	cfg &= ~HPET_TN_PERIODIC;	cfg |= HPET_TN_ENABLE | HPET_TN_32BIT;	hpet_write(HPET_T0_CFG, cfg);	spin_unlock(&hpet_lock);	return 0;}static int hpet_tick_resume(struct clock_event_device *evt){	spin_lock(&hpet_lock);	hpet_enable_legacy_int();	spin_unlock(&hpet_lock);	return 0;}static int hpet_next_event(unsigned long delta,		struct clock_event_device *evt){	u32 cnt;	s32 res;	cnt = hpet_read(HPET_COUNTER);	cnt += (u32) delta;	hpet_write(HPET_T0_CMP, cnt);	res = (s32)(cnt - hpet_read(HPET_COUNTER));	return res < HPET_MIN_CYCLES ? -ETIME : 0;}static irqreturn_t hpet_irq_handler(int irq, void *data){	int is_irq;	struct clock_event_device *cd;	unsigned int cpu = smp_processor_id();	is_irq = hpet_read(HPET_STATUS);	if (is_irq & HPET_T0_IRS) {		/* clear the TIMER0 irq status register */		hpet_write(HPET_STATUS, HPET_T0_IRS);		cd = &per_cpu(hpet_clockevent_device, cpu);		cd->event_handler(cd);		return IRQ_HANDLED;	}	return IRQ_NONE;}static struct irqaction hpet_irq = {	.handler = hpet_irq_handler,	.flags = IRQF_NOBALANCING | IRQF_TIMER,	.name = "hpet",};/* * hpet address assignation and irq setting should be done in bios. * but pmon don't do this, we just setup here directly. * The operation under is normal. unfortunately, hpet_setup process * is before pci initialize. * * { *	struct pci_dev *pdev; * *	pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL); *	pci_write_config_word(pdev, SMBUS_PCI_REGB4, HPET_ADDR); * *	... * } */static void hpet_setup(void){	/* set hpet base address */	smbus_write(SMBUS_PCI_REGB4, HPET_ADDR);	/* enable decoding of access to HPET MMIO*/	smbus_enable(SMBUS_PCI_REG40, (1 << 28));	/* HPET irq enable */	smbus_enable(SMBUS_PCI_REG64, (1 << 10));	hpet_enable_legacy_int();}void __init setup_hpet_timer(void){	unsigned int cpu = smp_processor_id();	struct clock_event_device *cd;	hpet_setup();	cd = &per_cpu(hpet_clockevent_device, cpu);	cd->name = "hpet";	cd->rating = 100;	cd->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;	cd->set_state_shutdown = hpet_set_state_shutdown;	cd->set_state_periodic = hpet_set_state_periodic;	cd->set_state_oneshot = hpet_set_state_oneshot;	cd->tick_resume = hpet_tick_resume;	cd->set_next_event = hpet_next_event;	cd->irq = HPET_T0_IRQ;	cd->cpumask = cpumask_of(cpu);	clockevent_set_clock(cd, HPET_FREQ);	cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd);	cd->max_delta_ticks = 0x7fffffff;	cd->min_delta_ns = clockevent_delta2ns(HPET_MIN_PROG_DELTA, cd);	cd->min_delta_ticks = HPET_MIN_PROG_DELTA;	clockevents_register_device(cd);	setup_irq(HPET_T0_IRQ, &hpet_irq);	pr_info("hpet clock event device register\n");}static u64 hpet_read_counter(struct clocksource *cs){	return (u64)hpet_read(HPET_COUNTER);}static void hpet_suspend(struct clocksource *cs){}static void hpet_resume(struct clocksource *cs){	hpet_setup();	hpet_restart_counter();}static struct clocksource csrc_hpet = {	.name = "hpet",	/* mips clocksource rating is less than 300, so hpet is better. */	.rating = 300,	.read = hpet_read_counter,	.mask = CLOCKSOURCE_MASK(32),	/* oneshot mode work normal with this flag */	.flags = CLOCK_SOURCE_IS_CONTINUOUS,	.suspend = hpet_suspend,	.resume = hpet_resume,	.mult = 0,	.shift = 10,};int __init init_hpet_clocksource(void){	csrc_hpet.mult = clocksource_hz2mult(HPET_FREQ, csrc_hpet.shift);	return clocksource_register_hz(&csrc_hpet, HPET_FREQ);}arch_initcall(init_hpet_clocksource);
 |