lkml.org 
[lkml]   [2003]   [Aug]   [19]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Subject[PATCH][2.6][5/5]Support for HPET based timer
Date
From
5/5 - hpet5.patch - This can be a standalone patch. Without this
patch we loose interrupt generation capability
of RTC (/dev/rtc), due to HPET. With this patch
we basically try to emulate RTC interrupt
functions in software using HPET counter 1.






diff -purN linux-2.6.0-test1-hpet/arch/i386/kernel/time_hpet.c linux-2.6.0-test1-hpet-rtc/arch/i386/kernel/time_hpet.c
--- linux-2.6.0-test1-hpet/arch/i386/kernel/time_hpet.c 2003-08-18 20:22:06.000000000 -0700
+++ linux-2.6.0-test1-hpet-rtc/arch/i386/kernel/time_hpet.c 2003-08-18 20:25:14.000000000 -0700
@@ -151,3 +151,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 <linux/mc146818rtc.h>
+#include <linux/rtc.h>
+
+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;
+ /*
+ * Stop the timers and reset the main counter.
+ */
+ 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 -purN linux-2.6.0-test1-hpet/include/asm-i386/hpet.h linux-2.6.0-test1-hpet-rtc/include/asm-i386/hpet.h
--- linux-2.6.0-test1-hpet/include/asm-i386/hpet.h 2003-08-18 20:22:40.000000000 -0700
+++ linux-2.6.0-test1-hpet-rtc/include/asm-i386/hpet.h 2003-08-18 20:25:32.000000000 -0700
@@ -100,5 +100,15 @@ extern unsigned long hpet_virt_address;
extern int hpet_enable(void);
extern int is_hpet_enabled(void);

+#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 -purN linux-2.6.0-test1-hpet/include/asm-i386/mc146818rtc.h linux-2.6.0-test1-hpet-rtc/include/asm-i386/mc146818rtc.h
--- linux-2.6.0-test1-hpet/include/asm-i386/mc146818rtc.h 2003-08-18 20:23:43.000000000 -0700
+++ linux-2.6.0-test1-hpet-rtc/include/asm-i386/mc146818rtc.h 2003-08-18 20:25:44.000000000 -0700
@@ -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 */
diff -purN linux-2.6.0-test1/drivers/char/rtc.c linux-2.6.0-test1-hpet/drivers/char/rtc.c
--- linux-2.6.0-test1/drivers/char/rtc.c 2003-07-13 20:33:12.000000000 -0700
+++ linux-2.6.0-test1-hpet/drivers/char/rtc.c 2003-08-18 20:08:12.000000000 -0700
@@ -43,6 +43,8 @@
* 1.10e Maciej W. Rozycki: Handle DECstation's year weirdness.
* 1.11 Takashi Iwai: Kernel access functions
* rtc_register/rtc_unregister/rtc_control
+ * Venkatesh Pallipadi: Hooks for emulating rtc on HPET base-timer
+ * CONFIG_HPET_EMULATE_RTC
*/

#define RTC_VERSION "1.11"
@@ -78,6 +80,10 @@
#include <asm/uaccess.h>
#include <asm/system.h>

+#if defined(__i386__)
+#include <asm/hpet.h>
+#endif
+
#ifdef __sparc__
#include <linux/pci.h>
#include <asm/ebus.h>
@@ -118,7 +124,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);
@@ -180,7 +186,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,
@@ -192,7 +198,19 @@ 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);
+#ifdef CONFIG_HPET_EMULATE_RTC
+ 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
+#endif
+ {
+ 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);
@@ -427,6 +445,14 @@ static int rtc_do_ioctl(unsigned int cmd
sec = alm_tm.tm_sec;

spin_lock_irq(&rtc_lock);
+#ifdef CONFIG_HPET_EMULATE_RTC
+ 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
+ */
+ }
+#endif
if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) ||
RTC_ALWAYS_BCD)
{
@@ -580,6 +606,12 @@ static int rtc_do_ioctl(unsigned int cmd
return -EINVAL;

spin_lock_irq(&rtc_lock);
+#ifdef CONFIG_HPET_EMULATE_RTC
+ if (hpet_set_periodic_freq(arg)) {
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+ }
+#endif
rtc_freq = arg;

val = CMOS_READ(RTC_FREQ_SELECT) & 0xf0;
@@ -665,6 +697,9 @@ static int rtc_release(struct inode *ino
*/

spin_lock_irq(&rtc_lock);
+#ifdef CONFIG_HPET_EMULATE_RTC
+ hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE);
+#endif
tmp = CMOS_READ(RTC_CONTROL);
tmp &= ~RTC_PIE;
tmp &= ~RTC_AIE;
@@ -754,6 +789,9 @@ int rtc_unregister(rtc_task_t *task)
unsigned char tmp;

spin_lock_irq(&rtc_lock);
+#ifdef CONFIG_HPET_EMULATE_RTC
+ hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE);
+#endif
spin_lock(&rtc_task_lock);
if (rtc_callback != task) {
spin_unlock(&rtc_task_lock);
@@ -820,6 +858,8 @@ static struct miscdevice rtc_dev=
&rtc_fops
};

+static irqreturn_t (*rtc_int_handler_ptr)(int irq, void *dev_id, struct pt_regs *regs);
+
static int __init rtc_init(void)
{
#if defined(__alpha__) || defined(__mips__)
@@ -888,17 +928,30 @@ no_irq:
}

#if RTC_IRQ
- if(request_irq(RTC_IRQ, rtc_interrupt, SA_INTERRUPT, "rtc", NULL))
+#ifdef CONFIG_HPET_EMULATE_RTC
+ if (is_hpet_enabled()) {
+ rtc_int_handler_ptr = hpet_rtc_interrupt;
+ } else
+#endif
+ {
+ 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;
}
+#ifdef CONFIG_HPET_EMULATE_RTC
+ hpet_rtc_timer_init();
+#endif
#endif

#endif /* __sparc__ vs. others */

+ printk("rtc_timier_init() init\n");
if (misc_register(&rtc_dev))
{
#if RTC_IRQ
@@ -961,8 +1014,11 @@ no_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;
+#ifdef CONFIG_HPET_EMULATE_RTC
+ hpet_set_periodic_freq(rtc_freq);
+#endif
+ spin_unlock_irq(&rtc_lock);
no_irq2:
#endif

@@ -1013,6 +1069,13 @@ static void rtc_dropped_irq(unsigned lon

spin_lock_irq (&rtc_lock);

+#ifdef CONFIG_HPET_EMULATE_RTC
+ if (hpet_rtc_dropped_irq()) {
+ spin_unlock_irq(&rtc_lock);
+ return;
+ }
+#endif
+
/* 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);
@@ -1142,7 +1205,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;
@@ -1248,6 +1311,12 @@ static void mask_rtc_irq_bit(unsigned ch
unsigned char val;

spin_lock_irq(&rtc_lock);
+#ifdef CONFIG_HPET_EMULATE_RTC
+ if (hpet_mask_rtc_irq_bit(bit)) {
+ spin_unlock_irq(&rtc_lock);
+ return;
+ }
+#endif
val = CMOS_READ(RTC_CONTROL);
val &= ~bit;
CMOS_WRITE(val, RTC_CONTROL);
@@ -1262,6 +1331,12 @@ static void set_rtc_irq_bit(unsigned cha
unsigned char val;

spin_lock_irq(&rtc_lock);
+#ifdef CONFIG_HPET_EMULATE_RTC
+ if (hpet_set_rtc_irq_bit(bit)) {
+ spin_unlock_irq(&rtc_lock);
+ return;
+ }
+#endif
val = CMOS_READ(RTC_CONTROL);
val |= bit;
CMOS_WRITE(val, RTC_CONTROL);

[unhandled content-type:application/x-zip-compressed]
\
 
 \ /
  Last update: 2005-03-22 13:47    [W:0.058 / U:1.284 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site