Commit 90233e4e authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] HPET 6/6: rtc emulation

From: "Pallipadi, Venkatesh" <venkatesh.pallipadi@intel.com>

This can be a standalone patch.  With this patch we basically try to emulate
RTC interrupt functions in software using HPET counter 1.
parent 7d236b47
......@@ -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 <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;
/*
* 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
......@@ -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 <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>
......@@ -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, struct file *file,
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, void *dev_id, struct pt_regs *regs)
spin_lock (&rtc_lock);
rtc_irq_data += 0x100;
rtc_irq_data &= ~0xff;
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, unsigned long arg, int kernel)
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, unsigned long arg, int kernel)
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 *inode, struct file *file)
*/
spin_lock_irq(&rtc_lock);
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 */
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 @@ static int __init rtc_init(void)
}
#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 @@ static int __init rtc_init(void)
init_timer(&rtc_irq_timer);
rtc_irq_timer.function = rtc_dropped_irq;
spin_lock_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);
rtc_freq = 1024;
no_irq2:
#endif
......@@ -1019,6 +1072,11 @@ static void rtc_dropped_irq(unsigned long data)
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_updating(void)
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 char bit)
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 char bit)
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);
......
......@@ -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 */
......@@ -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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment