From: "Pallipadi, Venkatesh" This can be a standalone patch. With this patch we basically try to emulate RTC interrupt functions in software using HPET counter 1. 25-akpm/arch/i386/kernel/time_hpet.c | 221 +++++++++++++++++++++++++++++++++ 25-akpm/drivers/char/rtc.c | 110 +++++++++++++--- 25-akpm/include/asm-i386/hpet.h | 10 + 25-akpm/include/asm-i386/mc146818rtc.h | 4 4 files changed, 319 insertions(+), 26 deletions(-) diff -puN arch/i386/kernel/time_hpet.c~hpet-06-rtc-emulation arch/i386/kernel/time_hpet.c --- 25/arch/i386/kernel/time_hpet.c~hpet-06-rtc-emulation Fri Aug 29 17:13:40 2003 +++ 25-akpm/arch/i386/kernel/time_hpet.c Fri Aug 29 17:13:40 2003 @@ -168,4 +168,225 @@ static int __init hpet_setup(char* str) __setup("hpet=", hpet_setup); +#ifdef CONFIG_HPET_EMULATE_RTC +/* HPET in LegacyReplacement Mode eats up RTC interrupt line. When, HPET + * is enabled, we support RTC interrupt functionality in software. + * RTC has 3 kinds of interrupts: + * 1) Update Interrupt - generate an interrupt, every sec, when RTC clock + * is updated + * 2) Alarm Interrupt - generate an interrupt at a specific time of day + * 3) Periodic Interrupt - generate periodic interrupt, with frequencies + * 2Hz-8192Hz (2Hz-64Hz for non-root user) (all freqs in powers of 2) + * (1) and (2) above are implemented using polling at a frequency of + * 64 Hz. The exact frequency is a tradeoff between accuracy and interrupt + * overhead. (DEFAULT_RTC_INT_FREQ) + * For (3), we use interrupts at 64Hz or user specified periodic + * frequency, whichever is higher. + */ +#include +#include + +extern irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs); +extern void get_rtc_time(struct rtc_time *rtc_tm); + +#define DEFAULT_RTC_INT_FREQ 64 +#define RTC_NUM_INTS 1 + +static unsigned long UIE_on; +static unsigned long prev_update_sec; + +static unsigned long AIE_on; +static struct rtc_time alarm_time; + +static unsigned long PIE_on; +static unsigned long PIE_freq = DEFAULT_RTC_INT_FREQ; +static unsigned long PIE_count; + +static unsigned long hpet_rtc_int_freq; /* RTC interrupt frequency */ + +/* + * Timer 1 for RTC, we do not use periodic interrupt feature, + * even if HPET supports periodic interrupts on Timer 1. + * The reason being, to set up a periodic interrupt in HPET, we need to + * stop the main counter. And if we do that everytime someone diables/enables + * RTC, we will have adverse effect on main kernel timer running on Timer 0. + * So, for the time being, simulate the periodic interrupt in software. + * + * hpet_rtc_timer_init() is called for the first time and during subsequent + * interuppts reinit happens through hpet_rtc_timer_reinit(). + */ +int hpet_rtc_timer_init(void) +{ + unsigned int cfg, cnt; + unsigned long flags; + + if (!is_hpet_enabled()) + return 0; + /* + * Set the counter 1 and enable the interrupts. + */ + if (PIE_on && (PIE_freq > DEFAULT_RTC_INT_FREQ)) + hpet_rtc_int_freq = PIE_freq; + else + hpet_rtc_int_freq = DEFAULT_RTC_INT_FREQ; + + local_irq_save(flags); + cnt = hpet_readl(HPET_COUNTER); + cnt += ((hpet_tick*HZ)/hpet_rtc_int_freq); + hpet_writel(cnt, HPET_T1_CMP); + local_irq_restore(flags); + + cfg = hpet_readl(HPET_T1_CFG); + cfg |= HPET_TN_ENABLE | HPET_TN_SETVAL | HPET_TN_32BIT; + hpet_writel(cfg, HPET_T1_CFG); + + return 1; +} + +static void hpet_rtc_timer_reinit(void) +{ + unsigned int cfg, cnt; + + if (!(PIE_on | AIE_on | UIE_on)) + return; + + if (PIE_on && (PIE_freq > DEFAULT_RTC_INT_FREQ)) + hpet_rtc_int_freq = PIE_freq; + else + hpet_rtc_int_freq = DEFAULT_RTC_INT_FREQ; + + /* It is more accurate to use the comparator value than current count.*/ + cnt = hpet_readl(HPET_T1_CMP); + cnt += hpet_tick*HZ/hpet_rtc_int_freq; + hpet_writel(cnt, HPET_T1_CMP); + + cfg = hpet_readl(HPET_T1_CFG); + cfg |= HPET_TN_ENABLE | HPET_TN_SETVAL | HPET_TN_32BIT; + hpet_writel(cfg, HPET_T1_CFG); + + return; +} + +/* + * The functions below are called from rtc driver. + * Return 0 if HPET is not being used. + * Otherwise do the necessary changes and return 1. + */ +int hpet_mask_rtc_irq_bit(unsigned long bit_mask) +{ + if (!is_hpet_enabled()) + return 0; + + if (bit_mask & RTC_UIE) + UIE_on = 0; + if (bit_mask & RTC_PIE) + PIE_on = 0; + if (bit_mask & RTC_AIE) + AIE_on = 0; + + return 1; +} + +int hpet_set_rtc_irq_bit(unsigned long bit_mask) +{ + int timer_init_reqd = 0; + + if (!is_hpet_enabled()) + return 0; + + if (!(PIE_on | AIE_on | UIE_on)) + timer_init_reqd = 1; + + if (bit_mask & RTC_UIE) { + UIE_on = 1; + } + if (bit_mask & RTC_PIE) { + PIE_on = 1; + PIE_count = 0; + } + if (bit_mask & RTC_AIE) { + AIE_on = 1; + } + + if (timer_init_reqd) + hpet_rtc_timer_init(); + + return 1; +} + +int hpet_set_alarm_time(unsigned char hrs, unsigned char min, unsigned char sec) +{ + if (!is_hpet_enabled()) + return 0; + + alarm_time.tm_hour = hrs; + alarm_time.tm_min = min; + alarm_time.tm_sec = sec; + + return 1; +} + +int hpet_set_periodic_freq(unsigned long freq) +{ + if (!is_hpet_enabled()) + return 0; + + PIE_freq = freq; + PIE_count = 0; + + return 1; +} + +int hpet_rtc_dropped_irq(void) +{ + if (!is_hpet_enabled()) + return 0; + + return 1; +} + +irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct rtc_time curr_time; + unsigned long rtc_int_flag = 0; + int call_rtc_interrupt = 0; + + hpet_rtc_timer_reinit(); + + if (UIE_on | AIE_on) { + get_rtc_time(&curr_time); + } + if (UIE_on) { + if (curr_time.tm_sec != prev_update_sec) { + /* Set update int info, call real rtc int routine */ + call_rtc_interrupt = 1; + rtc_int_flag = RTC_UF; + prev_update_sec = curr_time.tm_sec; + } + } + if (PIE_on) { + PIE_count++; + if (PIE_count >= hpet_rtc_int_freq/PIE_freq) { + /* Set periodic int info, call real rtc int routine */ + call_rtc_interrupt = 1; + rtc_int_flag |= RTC_PF; + PIE_count = 0; + } + } + if (AIE_on) { + if ((curr_time.tm_sec == alarm_time.tm_sec) && + (curr_time.tm_min == alarm_time.tm_min) && + (curr_time.tm_hour == alarm_time.tm_hour)) { + /* Set alarm int info, call real rtc int routine */ + call_rtc_interrupt = 1; + rtc_int_flag |= RTC_AF; + } + } + if (call_rtc_interrupt) { + rtc_int_flag |= (RTC_IRQF | (RTC_NUM_INTS << 8)); + rtc_interrupt(rtc_int_flag, dev_id, regs); + } + return IRQ_HANDLED; +} +#endif diff -puN drivers/char/rtc.c~hpet-06-rtc-emulation drivers/char/rtc.c --- 25/drivers/char/rtc.c~hpet-06-rtc-emulation Fri Aug 29 17:13:40 2003 +++ 25-akpm/drivers/char/rtc.c Fri Aug 29 17:13:40 2003 @@ -44,10 +44,12 @@ * 1.11 Takashi Iwai: Kernel access functions * rtc_register/rtc_unregister/rtc_control * 1.11a Daniele Bellucci: Audit create_proc_read_entry in rtc_init + * 1.12 Venkatesh Pallipadi: Hooks for emulating rtc on HPET base-timer + * CONFIG_HPET_EMULATE_RTC * */ -#define RTC_VERSION "1.11a" +#define RTC_VERSION "1.12" #define RTC_IO_EXTENT 0x8 @@ -80,6 +82,10 @@ #include #include +#if defined(__i386__) +#include +#endif + #ifdef __sparc__ #include #include @@ -95,6 +101,17 @@ static int rtc_irq = PCI_IRQ_NONE; static int rtc_has_irq = 1; #endif +#ifndef CONFIG_HPET_EMULATE_RTC +#define is_hpet_enabled() 0 +#define hpet_set_alarm_time(hrs, min, sec) 0 +#define hpet_set_periodic_freq(arg) 0 +#define hpet_mask_rtc_irq_bit(arg) 0 +#define hpet_set_rtc_irq_bit(arg) 0 +#define hpet_rtc_timer_init() do { } while (0) +#define hpet_rtc_dropped_irq() 0 +static inline irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs) {return 0;} +#endif + /* * We sponge a minor off of the misc major. No need slurping * up another valuable major dev number for this. If you add @@ -120,7 +137,7 @@ static int rtc_ioctl(struct inode *inode static unsigned int rtc_poll(struct file *file, poll_table *wait); #endif -static void get_rtc_time (struct rtc_time *rtc_tm); +void get_rtc_time (struct rtc_time *rtc_tm); static void get_rtc_alm_time (struct rtc_time *alm_tm); #if RTC_IRQ static void rtc_dropped_irq(unsigned long data); @@ -182,7 +199,7 @@ static const unsigned char days_in_mo[] * (See ./arch/XXXX/kernel/time.c for the set_rtc_mmss() function.) */ -static irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs) +irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs) { /* * Can be an alarm interrupt, update complete interrupt, @@ -194,7 +211,16 @@ static irqreturn_t rtc_interrupt(int irq spin_lock (&rtc_lock); rtc_irq_data += 0x100; rtc_irq_data &= ~0xff; - rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); + if (is_hpet_enabled()) { + /* + * In this case it is HPET RTC interrupt handler + * calling us, with the interrupt information + * passed as arg1, instead of irq. + */ + rtc_irq_data |= (unsigned long)irq & 0xF0; + } else { + rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); + } if (rtc_status & RTC_TIMER_ON) mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100); @@ -429,6 +455,12 @@ static int rtc_do_ioctl(unsigned int cmd sec = alm_tm.tm_sec; spin_lock_irq(&rtc_lock); + if (hpet_set_alarm_time(hrs, min, sec)) { + /* + * Fallthru and set alarm time in CMOS too, + * so that we will get proper value in RTC_ALM_READ + */ + } if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { @@ -582,6 +614,10 @@ static int rtc_do_ioctl(unsigned int cmd return -EINVAL; spin_lock_irq(&rtc_lock); + if (hpet_set_periodic_freq(arg)) { + spin_unlock_irq(&rtc_lock); + return 0; + } rtc_freq = arg; val = CMOS_READ(RTC_FREQ_SELECT) & 0xf0; @@ -667,13 +703,14 @@ static int rtc_release(struct inode *ino */ spin_lock_irq(&rtc_lock); - tmp = CMOS_READ(RTC_CONTROL); - tmp &= ~RTC_PIE; - tmp &= ~RTC_AIE; - tmp &= ~RTC_UIE; - CMOS_WRITE(tmp, RTC_CONTROL); - CMOS_READ(RTC_INTR_FLAGS); - + if (!hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE)) { + tmp = CMOS_READ(RTC_CONTROL); + tmp &= ~RTC_PIE; + tmp &= ~RTC_AIE; + tmp &= ~RTC_UIE; + CMOS_WRITE(tmp, RTC_CONTROL); + CMOS_READ(RTC_INTR_FLAGS); + } if (rtc_status & RTC_TIMER_ON) { rtc_status &= ~RTC_TIMER_ON; del_timer(&rtc_irq_timer); @@ -765,12 +802,14 @@ int rtc_unregister(rtc_task_t *task) rtc_callback = NULL; /* disable controls */ - tmp = CMOS_READ(RTC_CONTROL); - tmp &= ~RTC_PIE; - tmp &= ~RTC_AIE; - tmp &= ~RTC_UIE; - CMOS_WRITE(tmp, RTC_CONTROL); - CMOS_READ(RTC_INTR_FLAGS); + if (!hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE)) { + tmp = CMOS_READ(RTC_CONTROL); + tmp &= ~RTC_PIE; + tmp &= ~RTC_AIE; + tmp &= ~RTC_UIE; + CMOS_WRITE(tmp, RTC_CONTROL); + CMOS_READ(RTC_INTR_FLAGS); + } if (rtc_status & RTC_TIMER_ON) { rtc_status &= ~RTC_TIMER_ON; del_timer(&rtc_irq_timer); @@ -822,6 +861,10 @@ static struct miscdevice rtc_dev= &rtc_fops }; +#if RTC_IRQ +static irqreturn_t (*rtc_int_handler_ptr)(int irq, void *dev_id, struct pt_regs *regs); +#endif + static int __init rtc_init(void) { #if defined(__alpha__) || defined(__mips__) @@ -889,12 +932,20 @@ no_irq: } #if RTC_IRQ - if (request_irq(RTC_IRQ, rtc_interrupt, SA_INTERRUPT, "rtc", NULL)) { + if (is_hpet_enabled()) { + rtc_int_handler_ptr = hpet_rtc_interrupt; + } else { + rtc_int_handler_ptr = rtc_interrupt; + } + + if(request_irq(RTC_IRQ, rtc_int_handler_ptr, SA_INTERRUPT, "rtc", NULL)) { /* Yeah right, seeing as irq 8 doesn't even hit the bus. */ printk(KERN_ERR "rtc: IRQ %d is not free.\n", RTC_IRQ); release_region(RTC_PORT(0), RTC_IO_EXTENT); return -EIO; } + hpet_rtc_timer_init(); + #endif #endif /* __sparc__ vs. others */ @@ -965,10 +1016,12 @@ no_irq: init_timer(&rtc_irq_timer); rtc_irq_timer.function = rtc_dropped_irq; spin_lock_irq(&rtc_lock); - /* Initialize periodic freq. to CMOS reset default, which is 1024Hz */ - CMOS_WRITE(((CMOS_READ(RTC_FREQ_SELECT) & 0xF0) | 0x06), RTC_FREQ_SELECT); - spin_unlock_irq(&rtc_lock); rtc_freq = 1024; + if (!hpet_set_periodic_freq(rtc_freq)) { + /* Initialize periodic freq. to CMOS reset default, which is 1024Hz */ + CMOS_WRITE(((CMOS_READ(RTC_FREQ_SELECT) & 0xF0) | 0x06), RTC_FREQ_SELECT); + } + spin_unlock_irq(&rtc_lock); no_irq2: #endif @@ -1019,6 +1072,11 @@ static void rtc_dropped_irq(unsigned lon spin_lock_irq (&rtc_lock); + if (hpet_rtc_dropped_irq()) { + spin_unlock_irq(&rtc_lock); + return; + } + /* Just in case someone disabled the timer from behind our back... */ if (rtc_status & RTC_TIMER_ON) mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100); @@ -1148,7 +1206,7 @@ static inline unsigned char rtc_is_updat return uip; } -static void get_rtc_time(struct rtc_time *rtc_tm) +void get_rtc_time(struct rtc_time *rtc_tm) { unsigned long uip_watchdog = jiffies; unsigned char ctrl; @@ -1254,6 +1312,10 @@ static void mask_rtc_irq_bit(unsigned ch unsigned char val; spin_lock_irq(&rtc_lock); + if (hpet_mask_rtc_irq_bit(bit)) { + spin_unlock_irq(&rtc_lock); + return; + } val = CMOS_READ(RTC_CONTROL); val &= ~bit; CMOS_WRITE(val, RTC_CONTROL); @@ -1268,6 +1330,10 @@ static void set_rtc_irq_bit(unsigned cha unsigned char val; spin_lock_irq(&rtc_lock); + if (hpet_set_rtc_irq_bit(bit)) { + spin_unlock_irq(&rtc_lock); + return; + } val = CMOS_READ(RTC_CONTROL); val |= bit; CMOS_WRITE(val, RTC_CONTROL); diff -puN include/asm-i386/hpet.h~hpet-06-rtc-emulation include/asm-i386/hpet.h --- 25/include/asm-i386/hpet.h~hpet-06-rtc-emulation Fri Aug 29 17:13:40 2003 +++ 25-akpm/include/asm-i386/hpet.h Fri Aug 29 17:13:40 2003 @@ -102,5 +102,15 @@ extern int is_hpet_capable(void); extern int hpet_readl(unsigned long a); extern void hpet_writel(unsigned long d, unsigned long a); +#ifdef CONFIG_RTC +#define CONFIG_HPET_EMULATE_RTC 1 +extern int hpet_mask_rtc_irq_bit(unsigned long bit_mask); +extern int hpet_set_rtc_irq_bit(unsigned long bit_mask); +extern int hpet_set_alarm_time(unsigned char hrs, unsigned char min, unsigned char sec); +extern int hpet_set_periodic_freq(unsigned long freq); +extern int hpet_rtc_dropped_irq(void); +extern int hpet_rtc_timer_init(void); +extern irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs); +#endif /* CONFIG_RTC */ #endif /* CONFIG_HPET_TIMER */ #endif /* _I386_HPET_H */ diff -puN include/asm-i386/mc146818rtc.h~hpet-06-rtc-emulation include/asm-i386/mc146818rtc.h --- 25/include/asm-i386/mc146818rtc.h~hpet-06-rtc-emulation Fri Aug 29 17:13:40 2003 +++ 25-akpm/include/asm-i386/mc146818rtc.h Fri Aug 29 17:13:40 2003 @@ -24,10 +24,6 @@ outb_p((addr),RTC_PORT(0)); \ outb_p((val),RTC_PORT(1)); \ }) -#ifdef CONFIG_HPET_TIMER -#define RTC_IRQ 0 -#else #define RTC_IRQ 8 -#endif #endif /* _ASM_MC146818RTC_H */ _