diff options
Diffstat (limited to 'arch/x86/kernel/hpet.c')
| -rw-r--r-- | arch/x86/kernel/hpet.c | 224 | 
1 files changed, 119 insertions, 105 deletions
diff --git a/arch/x86/kernel/hpet.c b/arch/x86/kernel/hpet.c index ae03cab4352..319bcb9372f 100644 --- a/arch/x86/kernel/hpet.c +++ b/arch/x86/kernel/hpet.c @@ -1,9 +1,10 @@  #include <linux/clocksource.h>  #include <linux/clockchips.h>  #include <linux/interrupt.h> -#include <linux/sysdev.h> +#include <linux/export.h>  #include <linux/delay.h>  #include <linux/errno.h> +#include <linux/i8253.h>  #include <linux/slab.h>  #include <linux/hpet.h>  #include <linux/init.h> @@ -12,8 +13,8 @@  #include <linux/io.h>  #include <asm/fixmap.h> -#include <asm/i8253.h>  #include <asm/hpet.h> +#include <asm/time.h>  #define HPET_MASK			CLOCKSOURCE_MASK(32) @@ -27,7 +28,8 @@  #define HPET_DEV_FSB_CAP		0x1000  #define HPET_DEV_PERI_CAP		0x2000 -#define EVT_TO_HPET_DEV(evt) container_of(evt, struct hpet_dev, evt) +#define HPET_MIN_CYCLES			128 +#define HPET_MIN_PROG_DELTA		(HPET_MIN_CYCLES + (HPET_MIN_CYCLES >> 1))  /*   * HPET address is set in acpi/boot.c, when an ACPI entry exists @@ -50,6 +52,11 @@ struct hpet_dev {  	char				name[10];  }; +inline struct hpet_dev *EVT_TO_HPET_DEV(struct clock_event_device *evtdev) +{ +	return container_of(evtdev, struct hpet_dev, evt); +} +  inline unsigned int hpet_readl(unsigned int a)  {  	return readl(hpet_virt_address + a); @@ -67,9 +74,6 @@ static inline void hpet_writel(unsigned int d, unsigned int a)  static inline void hpet_set_mapping(void)  {  	hpet_virt_address = ioremap_nocache(hpet_address, HPET_MMAP_SIZE); -#ifdef CONFIG_X86_64 -	__set_fixmap(VSYSCALL_HPET, hpet_address, PAGE_KERNEL_VSYSCALL_NOCACHE); -#endif  }  static inline void hpet_clear_mapping(void) @@ -81,19 +85,24 @@ static inline void hpet_clear_mapping(void)  /*   * HPET command line enable / disable   */ -static int boot_hpet_disable; +int boot_hpet_disable;  int hpet_force_user;  static int hpet_verbose;  static int __init hpet_setup(char *str)  { -	if (str) { +	while (str) { +		char *next = strchr(str, ','); + +		if (next) +			*next++ = 0;  		if (!strncmp("disable", str, 7))  			boot_hpet_disable = 1;  		if (!strncmp("force", str, 5))  			hpet_force_user = 1;  		if (!strncmp("verbose", str, 7))  			hpet_verbose = 1; +		str = next;  	}  	return 1;  } @@ -214,7 +223,7 @@ static void hpet_reserve_platform_timers(unsigned int id) { }  /*   * Common hpet info   */ -static unsigned long hpet_period; +static unsigned long hpet_freq;  static void hpet_legacy_set_mode(enum clock_event_mode mode,  			  struct clock_event_device *evt); @@ -229,7 +238,6 @@ static struct clock_event_device hpet_clockevent = {  	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,  	.set_mode	= hpet_legacy_set_mode,  	.set_next_event = hpet_legacy_next_event, -	.shift		= 32,  	.irq		= 0,  	.rating		= 50,  }; @@ -287,27 +295,12 @@ static void hpet_legacy_clockevent_register(void)  	hpet_enable_legacy_int();  	/* -	 * The mult factor is defined as (include/linux/clockchips.h) -	 *  mult/2^shift = cyc/ns (in contrast to ns/cyc in clocksource.h) -	 * hpet_period is in units of femtoseconds (per cycle), so -	 *  mult/2^shift = cyc/ns = 10^6/hpet_period -	 *  mult = (10^6 * 2^shift)/hpet_period -	 *  mult = (FSEC_PER_NSEC << hpet_clockevent.shift)/hpet_period -	 */ -	hpet_clockevent.mult = div_sc((unsigned long) FSEC_PER_NSEC, -				      hpet_period, hpet_clockevent.shift); -	/* Calculate the min / max delta */ -	hpet_clockevent.max_delta_ns = clockevent_delta2ns(0x7FFFFFFF, -							   &hpet_clockevent); -	/* 5 usec minimum reprogramming delta. */ -	hpet_clockevent.min_delta_ns = 5000; - -	/*  	 * Start hpet with the boot cpu mask and make it  	 * global after the IO_APIC has been initialized.  	 */  	hpet_clockevent.cpumask = cpumask_of(smp_processor_id()); -	clockevents_register_device(&hpet_clockevent); +	clockevents_config_and_register(&hpet_clockevent, hpet_freq, +					HPET_MIN_PROG_DELTA, 0x7FFFFFFF);  	global_clock_event = &hpet_clockevent;  	printk(KERN_DEBUG "hpet clockevent registered\n");  } @@ -328,8 +321,6 @@ static void hpet_set_mode(enum clock_event_mode mode,  		now = hpet_readl(HPET_COUNTER);  		cmp = now + (unsigned int) delta;  		cfg = hpet_readl(HPET_Tn_CFG(timer)); -		/* Make sure we use edge triggered interrupts */ -		cfg &= ~HPET_TN_LEVEL;  		cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC |  		       HPET_TN_SETVAL | HPET_TN_32BIT;  		hpet_writel(cfg, HPET_Tn_CFG(timer)); @@ -393,22 +384,24 @@ static int hpet_next_event(unsigned long delta,  	 * the wraparound into account) nor a simple count down event  	 * mode. Further the write to the comparator register is  	 * delayed internally up to two HPET clock cycles in certain -	 * chipsets (ATI, ICH9,10). We worked around that by reading -	 * back the compare register, but that required another -	 * workaround for ICH9,10 chips where the first readout after -	 * write can return the old stale value. We already have a -	 * minimum delta of 5us enforced, but a NMI or SMI hitting +	 * chipsets (ATI, ICH9,10). Some newer AMD chipsets have even +	 * longer delays. We worked around that by reading back the +	 * compare register, but that required another workaround for +	 * ICH9,10 chips where the first readout after write can +	 * return the old stale value. We already had a minimum +	 * programming delta of 5us enforced, but a NMI or SMI hitting  	 * between the counter readout and the comparator write can  	 * move us behind that point easily. Now instead of reading  	 * the compare register back several times, we make the ETIME  	 * decision based on the following: Return ETIME if the -	 * counter value after the write is less than 8 HPET cycles +	 * counter value after the write is less than HPET_MIN_CYCLES  	 * away from the event or if the counter is already ahead of -	 * the event. +	 * the event. The minimum programming delta for the generic +	 * clockevents code is set to 1.5 * HPET_MIN_CYCLES.  	 */  	res = (s32)(cnt - hpet_readl(HPET_COUNTER)); -	return res < 8 ? -ETIME : 0; +	return res < HPET_MIN_CYCLES ? -ETIME : 0;  }  static void hpet_legacy_set_mode(enum clock_event_mode mode, @@ -438,7 +431,7 @@ void hpet_msi_unmask(struct irq_data *data)  	/* unmask it */  	cfg = hpet_readl(HPET_Tn_CFG(hdev->num)); -	cfg |= HPET_TN_FSB; +	cfg |= HPET_TN_ENABLE | HPET_TN_FSB;  	hpet_writel(cfg, HPET_Tn_CFG(hdev->num));  } @@ -449,7 +442,7 @@ void hpet_msi_mask(struct irq_data *data)  	/* mask it */  	cfg = hpet_readl(HPET_Tn_CFG(hdev->num)); -	cfg &= ~HPET_TN_FSB; +	cfg &= ~(HPET_TN_ENABLE | HPET_TN_FSB);  	hpet_writel(cfg, HPET_Tn_CFG(hdev->num));  } @@ -482,8 +475,8 @@ static int hpet_msi_next_event(unsigned long delta,  static int hpet_setup_msi_irq(unsigned int irq)  { -	if (arch_setup_hpet_msi(irq, hpet_blockid)) { -		destroy_irq(irq); +	if (x86_msi.setup_hpet_msi(irq, hpet_blockid)) { +		irq_free_hwirq(irq);  		return -EINVAL;  	}  	return 0; @@ -491,13 +484,12 @@ static int hpet_setup_msi_irq(unsigned int irq)  static int hpet_assign_irq(struct hpet_dev *dev)  { -	unsigned int irq; +	unsigned int irq = irq_alloc_hwirq(-1); -	irq = create_irq_nr(0, -1);  	if (!irq)  		return -EINVAL; -	set_irq_data(irq, dev); +	irq_set_handler_data(irq, dev);  	if (hpet_setup_msi_irq(irq))  		return -EINVAL; @@ -525,7 +517,7 @@ static int hpet_setup_irq(struct hpet_dev *dev)  {  	if (request_irq(dev->irq, hpet_interrupt_handler, -			IRQF_TIMER | IRQF_DISABLED | IRQF_NOBALANCING, +			IRQF_TIMER | IRQF_NOBALANCING,  			dev->name, dev))  		return -1; @@ -543,7 +535,6 @@ static int hpet_setup_irq(struct hpet_dev *dev)  static void init_one_hpet_msi_clockevent(struct hpet_dev *hdev, int cpu)  {  	struct clock_event_device *evt = &hdev->evt; -	uint64_t hpet_freq;  	WARN_ON(cpu != smp_processor_id());  	if (!(hdev->flags & HPET_DEV_VALID)) @@ -565,24 +556,10 @@ static void init_one_hpet_msi_clockevent(struct hpet_dev *hdev, int cpu)  	evt->set_mode = hpet_msi_set_mode;  	evt->set_next_event = hpet_msi_next_event; -	evt->shift = 32; - -	/* -	 * The period is a femto seconds value. We need to calculate the -	 * scaled math multiplication factor for nanosecond to hpet tick -	 * conversion. -	 */ -	hpet_freq = FSEC_PER_SEC; -	do_div(hpet_freq, hpet_period); -	evt->mult = div_sc((unsigned long) hpet_freq, -				      NSEC_PER_SEC, evt->shift); -	/* Calculate the max delta */ -	evt->max_delta_ns = clockevent_delta2ns(0x7FFFFFFF, evt); -	/* 5 usec minimum reprogramming delta. */ -	evt->min_delta_ns = 5000; -  	evt->cpumask = cpumask_of(hdev->cpu); -	clockevents_register_device(evt); + +	clockevents_config_and_register(evt, hpet_freq, HPET_MIN_PROG_DELTA, +					0x7FFFFFFF);  }  #ifdef CONFIG_HPET @@ -718,7 +695,7 @@ static int hpet_cpuhp_notify(struct notifier_block *n,  		/* FIXME: add schedule_work_on() */  		schedule_delayed_work_on(cpu, &work.work, 0);  		wait_for_completion(&work.complete); -		destroy_timer_on_stack(&work.work.timer); +		destroy_delayed_work_on_stack(&work.work);  		break;  	case CPU_DEAD:  		if (hdev) { @@ -764,13 +741,6 @@ static cycle_t read_hpet(struct clocksource *cs)  	return (cycle_t)hpet_readl(HPET_COUNTER);  } -#ifdef CONFIG_X86_64 -static cycle_t __vsyscall_fn vread_hpet(void) -{ -	return readl((const void __iomem *)fix_to_virt(VSYSCALL_HPET) + 0xf0); -} -#endif -  static struct clocksource clocksource_hpet = {  	.name		= "hpet",  	.rating		= 250, @@ -778,15 +748,12 @@ static struct clocksource clocksource_hpet = {  	.mask		= HPET_MASK,  	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,  	.resume		= hpet_resume_counter, -#ifdef CONFIG_X86_64 -	.vread		= vread_hpet, -#endif +	.archdata	= { .vclock_mode = VCLOCK_HPET },  };  static int hpet_clocksource_register(void)  {  	u64 start, now; -	u64 hpet_freq;  	cycle_t t1;  	/* Start the counter */ @@ -813,34 +780,20 @@ static int hpet_clocksource_register(void)  		return -ENODEV;  	} -	/* -	 * The definition of mult is (include/linux/clocksource.h) -	 * mult/2^shift = ns/cyc and hpet_period is in units of fsec/cyc -	 * so we first need to convert hpet_period to ns/cyc units: -	 *  mult/2^shift = ns/cyc = hpet_period/10^6 -	 *  mult = (hpet_period * 2^shift)/10^6 -	 *  mult = (hpet_period << shift)/FSEC_PER_NSEC -	 */ - -	/* Need to convert hpet_period (fsec/cyc) to cyc/sec: -	 * -	 * cyc/sec = FSEC_PER_SEC/hpet_period(fsec/cyc) -	 * cyc/sec = (FSEC_PER_NSEC * NSEC_PER_SEC)/hpet_period -	 */ -	hpet_freq = FSEC_PER_SEC; -	do_div(hpet_freq, hpet_period);  	clocksource_register_hz(&clocksource_hpet, (u32)hpet_freq); -  	return 0;  } +static u32 *hpet_boot_cfg; +  /**   * hpet_enable - Try to setup the HPET timer. Returns 1 on success.   */  int __init hpet_enable(void)  { -	unsigned int id; -	int i; +	u32 hpet_period, cfg, id; +	u64 freq; +	unsigned int i, last;  	if (!is_hpet_capable())  		return 0; @@ -878,21 +831,59 @@ int __init hpet_enable(void)  		goto out_nohpet;  	/* +	 * The period is a femto seconds value. Convert it to a +	 * frequency. +	 */ +	freq = FSEC_PER_SEC; +	do_div(freq, hpet_period); +	hpet_freq = freq; + +	/*  	 * Read the HPET ID register to retrieve the IRQ routing  	 * information and the number of channels  	 */  	id = hpet_readl(HPET_ID);  	hpet_print_config(); +	last = (id & HPET_ID_NUMBER) >> HPET_ID_NUMBER_SHIFT; +  #ifdef CONFIG_HPET_EMULATE_RTC  	/*  	 * The legacy routing mode needs at least two channels, tick timer  	 * and the rtc emulation channel.  	 */ -	if (!(id & HPET_ID_NUMBER)) +	if (!last)  		goto out_nohpet;  #endif +	cfg = hpet_readl(HPET_CFG); +	hpet_boot_cfg = kmalloc((last + 2) * sizeof(*hpet_boot_cfg), +				GFP_KERNEL); +	if (hpet_boot_cfg) +		*hpet_boot_cfg = cfg; +	else +		pr_warn("HPET initial state will not be saved\n"); +	cfg &= ~(HPET_CFG_ENABLE | HPET_CFG_LEGACY); +	hpet_writel(cfg, HPET_CFG); +	if (cfg) +		pr_warn("HPET: Unrecognized bits %#x set in global cfg\n", +			cfg); + +	for (i = 0; i <= last; ++i) { +		cfg = hpet_readl(HPET_Tn_CFG(i)); +		if (hpet_boot_cfg) +			hpet_boot_cfg[i + 1] = cfg; +		cfg &= ~(HPET_TN_ENABLE | HPET_TN_LEVEL | HPET_TN_FSB); +		hpet_writel(cfg, HPET_Tn_CFG(i)); +		cfg &= ~(HPET_TN_PERIODIC | HPET_TN_PERIODIC_CAP +			 | HPET_TN_64BIT_CAP | HPET_TN_32BIT | HPET_TN_ROUTE +			 | HPET_TN_FSB | HPET_TN_FSB_CAP); +		if (cfg) +			pr_warn("HPET: Unrecognized bits %#x set in cfg#%u\n", +				cfg, i); +	} +	hpet_print_config(); +  	if (hpet_clocksource_register())  		goto out_nohpet; @@ -946,12 +937,14 @@ static __init int hpet_late_init(void)  	if (boot_cpu_has(X86_FEATURE_ARAT))  		return 0; +	cpu_notifier_register_begin();  	for_each_online_cpu(cpu) {  		hpet_cpuhp_notify(NULL, CPU_ONLINE, (void *)(long)cpu);  	}  	/* This notifier should be called after workqueue is ready */ -	hotcpu_notifier(hpet_cpuhp_notify, -20); +	__hotcpu_notifier(hpet_cpuhp_notify, -20); +	cpu_notifier_register_done();  	return 0;  } @@ -960,14 +953,28 @@ fs_initcall(hpet_late_init);  void hpet_disable(void)  {  	if (is_hpet_capable() && hpet_virt_address) { -		unsigned int cfg = hpet_readl(HPET_CFG); +		unsigned int cfg = hpet_readl(HPET_CFG), id, last; -		if (hpet_legacy_int_enabled) { +		if (hpet_boot_cfg) +			cfg = *hpet_boot_cfg; +		else if (hpet_legacy_int_enabled) {  			cfg &= ~HPET_CFG_LEGACY;  			hpet_legacy_int_enabled = 0;  		}  		cfg &= ~HPET_CFG_ENABLE;  		hpet_writel(cfg, HPET_CFG); + +		if (!hpet_boot_cfg) +			return; + +		id = hpet_readl(HPET_ID); +		last = ((id & HPET_ID_NUMBER) >> HPET_ID_NUMBER_SHIFT); + +		for (id = 0; id <= last; ++id) +			hpet_writel(hpet_boot_cfg[id + 1], HPET_Tn_CFG(id)); + +		if (*hpet_boot_cfg & HPET_CFG_ENABLE) +			hpet_writel(*hpet_boot_cfg, HPET_CFG);  	}  } @@ -1088,6 +1095,14 @@ int hpet_rtc_timer_init(void)  }  EXPORT_SYMBOL_GPL(hpet_rtc_timer_init); +static void hpet_disable_rtc_channel(void) +{ +	unsigned long cfg; +	cfg = hpet_readl(HPET_T1_CFG); +	cfg &= ~HPET_TN_ENABLE; +	hpet_writel(cfg, HPET_T1_CFG); +} +  /*   * The functions below are called from rtc driver.   * Return 0 if HPET is not being used. @@ -1099,6 +1114,9 @@ int hpet_mask_rtc_irq_bit(unsigned long bit_mask)  		return 0;  	hpet_rtc_flags &= ~bit_mask; +	if (unlikely(!hpet_rtc_flags)) +		hpet_disable_rtc_channel(); +  	return 1;  }  EXPORT_SYMBOL_GPL(hpet_mask_rtc_irq_bit); @@ -1164,15 +1182,11 @@ EXPORT_SYMBOL_GPL(hpet_rtc_dropped_irq);  static void hpet_rtc_timer_reinit(void)  { -	unsigned int cfg, delta; +	unsigned int delta;  	int lost_ints = -1; -	if (unlikely(!hpet_rtc_flags)) { -		cfg = hpet_readl(HPET_T1_CFG); -		cfg &= ~HPET_TN_ENABLE; -		hpet_writel(cfg, HPET_T1_CFG); -		return; -	} +	if (unlikely(!hpet_rtc_flags)) +		hpet_disable_rtc_channel();  	if (!(hpet_rtc_flags & RTC_PIE) || hpet_pie_limit)  		delta = hpet_default_delta;  | 
