blob: 3119c67b398ac990bca52baba11f0d59607793b4 [file] [log] [blame]
/* $Id: time.c,v 1.41 2001/11/20 18:24:55 kanoj Exp $
* time.c: UltraSparc timer and TOD clock support.
*
* Copyright (C) 1997 David S. Miller (davem@caip.rutgers.edu)
* Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be)
*
* Based largely on code which is:
*
* Copyright (C) 1996 Thomas K. Dyas (tdyas@eden.rutgers.edu)
*/
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/param.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/timex.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/mc146818rtc.h>
#include <linux/delay.h>
#include <asm/oplib.h>
#include <asm/mostek.h>
#include <asm/timer.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/sbus.h>
#include <asm/fhc.h>
#include <asm/pbm.h>
#include <asm/ebus.h>
#include <asm/isa.h>
#include <asm/starfire.h>
extern rwlock_t xtime_lock;
spinlock_t mostek_lock = SPIN_LOCK_UNLOCKED;
spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED;
unsigned long mstk48t02_regs = 0UL;
#ifdef CONFIG_PCI
unsigned long ds1287_regs = 0UL;
#endif
static unsigned long mstk48t08_regs = 0UL;
static unsigned long mstk48t59_regs = 0UL;
static int set_rtc_mmss(unsigned long);
/* timer_interrupt() needs to keep up the real-time clock,
* as well as call the "do_timer()" routine every clocktick
*
* NOTE: On SUN5 systems the ticker interrupt comes in using 2
* interrupts, one at level14 and one with softint bit 0.
*/
unsigned long timer_tick_offset;
unsigned long timer_tick_compare;
unsigned long timer_ticks_per_usec_quotient;
static __inline__ void timer_check_rtc(void)
{
/* last time the cmos clock got updated */
static long last_rtc_update=0;
/* Determine when to update the Mostek clock. */
if ((time_status & STA_UNSYNC) == 0 &&
xtime.tv_sec > last_rtc_update + 660 &&
xtime.tv_usec >= 500000 - ((unsigned) tick) / 2 &&
xtime.tv_usec <= 500000 + ((unsigned) tick) / 2) {
if (set_rtc_mmss(xtime.tv_sec) == 0)
last_rtc_update = xtime.tv_sec;
else
last_rtc_update = xtime.tv_sec - 600;
/* do it again in 60 s */
}
}
void sparc64_do_profile(unsigned long pc, unsigned long o7)
{
if (prof_buffer && current->pid) {
extern int _stext;
extern int rwlock_impl_begin, rwlock_impl_end;
extern int atomic_impl_begin, atomic_impl_end;
extern int __memcpy_begin, __memcpy_end;
extern int __bzero_begin, __bzero_end;
extern int __bitops_begin, __bitops_end;
if ((pc >= (unsigned long) &atomic_impl_begin &&
pc < (unsigned long) &atomic_impl_end) ||
(pc >= (unsigned long) &rwlock_impl_begin &&
pc < (unsigned long) &rwlock_impl_end) ||
(pc >= (unsigned long) &__memcpy_begin &&
pc < (unsigned long) &__memcpy_end) ||
(pc >= (unsigned long) &__bzero_begin &&
pc < (unsigned long) &__bzero_end) ||
(pc >= (unsigned long) &__bitops_begin &&
pc < (unsigned long) &__bitops_end))
pc = o7;
pc -= (unsigned long) &_stext;
pc >>= prof_shift;
if(pc >= prof_len)
pc = prof_len - 1;
atomic_inc((atomic_t *)&prof_buffer[pc]);
}
}
static void timer_interrupt(int irq, void *dev_id, struct pt_regs * regs)
{
unsigned long ticks, pstate;
write_lock(&xtime_lock);
do {
#ifndef CONFIG_SMP
if ((regs->tstate & TSTATE_PRIV) != 0)
sparc64_do_profile(regs->tpc, regs->u_regs[UREG_RETPC]);
#endif
do_timer(regs);
/* Guarentee that the following sequences execute
* uninterrupted.
*/
__asm__ __volatile__("rdpr %%pstate, %0\n\t"
"wrpr %0, %1, %%pstate"
: "=r" (pstate)
: "i" (PSTATE_IE));
/* Workaround for Spitfire Errata (#54 I think??), I discovered
* this via Sun BugID 4008234, mentioned in Solaris-2.5.1 patch
* number 103640.
*
* On Blackbird writes to %tick_cmpr can fail, the
* workaround seems to be to execute the wr instruction
* at the start of an I-cache line, and perform a dummy
* read back from %tick_cmpr right after writing to it. -DaveM
*
* Just to be anal we add a workaround for Spitfire
* Errata 50 by preventing pipeline bypasses on the
* final read of the %tick register into a compare
* instruction. The Errata 50 description states
* that %tick is not prone to this bug, but I am not
* taking any chances.
*/
if (!SPARC64_USE_STICK) {
__asm__ __volatile__(
" rd %%tick_cmpr, %0\n"
" ba,pt %%xcc, 1f\n"
" add %0, %2, %0\n"
" .align 64\n"
"1: wr %0, 0, %%tick_cmpr\n"
" rd %%tick_cmpr, %%g0\n"
" rd %%tick, %1\n"
" mov %1, %1"
: "=&r" (timer_tick_compare), "=r" (ticks)
: "r" (timer_tick_offset));
} else {
__asm__ __volatile__(
" rd %%asr25, %0\n"
" add %0, %2, %0\n"
" wr %0, 0, %%asr25\n"
" rd %%asr24, %1"
: "=&r" (timer_tick_compare), "=r" (ticks)
: "r" (timer_tick_offset));
}
/* Restore PSTATE_IE. */
__asm__ __volatile__("wrpr %0, 0x0, %%pstate"
: /* no outputs */
: "r" (pstate));
} while (ticks >= timer_tick_compare);
timer_check_rtc();
write_unlock(&xtime_lock);
}
#ifdef CONFIG_SMP
void timer_tick_interrupt(struct pt_regs *regs)
{
write_lock(&xtime_lock);
do_timer(regs);
/*
* Only keep timer_tick_offset uptodate, but don't set TICK_CMPR.
*/
if (!SPARC64_USE_STICK) {
__asm__ __volatile__(
" rd %%tick_cmpr, %0\n"
" add %0, %1, %0"
: "=&r" (timer_tick_compare)
: "r" (timer_tick_offset));
} else {
__asm__ __volatile__(
" rd %%asr25, %0\n"
" add %0, %1, %0"
: "=&r" (timer_tick_compare)
: "r" (timer_tick_offset));
}
timer_check_rtc();
write_unlock(&xtime_lock);
}
#endif
/* Kick start a stopped clock (procedure from the Sun NVRAM/hostid FAQ). */
static void __init kick_start_clock(void)
{
unsigned long regs = mstk48t02_regs;
u8 sec, tmp;
int i, count;
prom_printf("CLOCK: Clock was stopped. Kick start ");
spin_lock_irq(&mostek_lock);
/* Turn on the kick start bit to start the oscillator. */
tmp = mostek_read(regs + MOSTEK_CREG);
tmp |= MSTK_CREG_WRITE;
mostek_write(regs + MOSTEK_CREG, tmp);
tmp = mostek_read(regs + MOSTEK_SEC);
tmp &= ~MSTK_STOP;
mostek_write(regs + MOSTEK_SEC, tmp);
tmp = mostek_read(regs + MOSTEK_HOUR);
tmp |= MSTK_KICK_START;
mostek_write(regs + MOSTEK_HOUR, tmp);
tmp = mostek_read(regs + MOSTEK_CREG);
tmp &= ~MSTK_CREG_WRITE;
mostek_write(regs + MOSTEK_CREG, tmp);
spin_unlock_irq(&mostek_lock);
/* Delay to allow the clock oscillator to start. */
sec = MSTK_REG_SEC(regs);
for (i = 0; i < 3; i++) {
while (sec == MSTK_REG_SEC(regs))
for (count = 0; count < 100000; count++)
/* nothing */ ;
prom_printf(".");
sec = MSTK_REG_SEC(regs);
}
prom_printf("\n");
spin_lock_irq(&mostek_lock);
/* Turn off kick start and set a "valid" time and date. */
tmp = mostek_read(regs + MOSTEK_CREG);
tmp |= MSTK_CREG_WRITE;
mostek_write(regs + MOSTEK_CREG, tmp);
tmp = mostek_read(regs + MOSTEK_HOUR);
tmp &= ~MSTK_KICK_START;
mostek_write(regs + MOSTEK_HOUR, tmp);
MSTK_SET_REG_SEC(regs,0);
MSTK_SET_REG_MIN(regs,0);
MSTK_SET_REG_HOUR(regs,0);
MSTK_SET_REG_DOW(regs,5);
MSTK_SET_REG_DOM(regs,1);
MSTK_SET_REG_MONTH(regs,8);
MSTK_SET_REG_YEAR(regs,1996 - MSTK_YEAR_ZERO);
tmp = mostek_read(regs + MOSTEK_CREG);
tmp &= ~MSTK_CREG_WRITE;
mostek_write(regs + MOSTEK_CREG, tmp);
spin_unlock_irq(&mostek_lock);
/* Ensure the kick start bit is off. If it isn't, turn it off. */
while (mostek_read(regs + MOSTEK_HOUR) & MSTK_KICK_START) {
prom_printf("CLOCK: Kick start still on!\n");
spin_lock_irq(&mostek_lock);
tmp = mostek_read(regs + MOSTEK_CREG);
tmp |= MSTK_CREG_WRITE;
mostek_write(regs + MOSTEK_CREG, tmp);
tmp = mostek_read(regs + MOSTEK_HOUR);
tmp &= ~MSTK_KICK_START;
mostek_write(regs + MOSTEK_HOUR, tmp);
tmp = mostek_read(regs + MOSTEK_CREG);
tmp &= ~MSTK_CREG_WRITE;
mostek_write(regs + MOSTEK_CREG, tmp);
spin_unlock_irq(&mostek_lock);
}
prom_printf("CLOCK: Kick start procedure successful.\n");
}
/* Return nonzero if the clock chip battery is low. */
static int __init has_low_battery(void)
{
unsigned long regs = mstk48t02_regs;
u8 data1, data2;
spin_lock_irq(&mostek_lock);
data1 = mostek_read(regs + MOSTEK_EEPROM); /* Read some data. */
mostek_write(regs + MOSTEK_EEPROM, ~data1); /* Write back the complement. */
data2 = mostek_read(regs + MOSTEK_EEPROM); /* Read back the complement. */
mostek_write(regs + MOSTEK_EEPROM, data1); /* Restore original value. */
spin_unlock_irq(&mostek_lock);
return (data1 == data2); /* Was the write blocked? */
}
#ifndef BCD_TO_BIN
#define BCD_TO_BIN(val) (((val)&15) + ((val)>>4)*10)
#endif
#ifndef BIN_TO_BCD
#define BIN_TO_BCD(val) ((((val)/10)<<4) + (val)%10)
#endif
/* Probe for the real time clock chip. */
static void __init set_system_time(void)
{
unsigned int year, mon, day, hour, min, sec;
unsigned long mregs = mstk48t02_regs;
#ifdef CONFIG_PCI
unsigned long dregs = ds1287_regs;
#else
unsigned long dregs = 0UL;
#endif
u8 tmp;
do_get_fast_time = do_gettimeofday;
if (!mregs && !dregs) {
prom_printf("Something wrong, clock regs not mapped yet.\n");
prom_halt();
}
if (mregs) {
spin_lock_irq(&mostek_lock);
/* Traditional Mostek chip. */
tmp = mostek_read(mregs + MOSTEK_CREG);
tmp |= MSTK_CREG_READ;
mostek_write(mregs + MOSTEK_CREG, tmp);
sec = MSTK_REG_SEC(mregs);
min = MSTK_REG_MIN(mregs);
hour = MSTK_REG_HOUR(mregs);
day = MSTK_REG_DOM(mregs);
mon = MSTK_REG_MONTH(mregs);
year = MSTK_CVT_YEAR( MSTK_REG_YEAR(mregs) );
} else {
int i;
/* Dallas 12887 RTC chip. */
/* Stolen from arch/i386/kernel/time.c, see there for
* credits and descriptive comments.
*/
for (i = 0; i < 1000000; i++) {
if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP)
break;
udelay(10);
}
for (i = 0; i < 1000000; i++) {
if (!(CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP))
break;
udelay(10);
}
do {
sec = CMOS_READ(RTC_SECONDS);
min = CMOS_READ(RTC_MINUTES);
hour = CMOS_READ(RTC_HOURS);
day = CMOS_READ(RTC_DAY_OF_MONTH);
mon = CMOS_READ(RTC_MONTH);
year = CMOS_READ(RTC_YEAR);
} while (sec != CMOS_READ(RTC_SECONDS));
if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
BCD_TO_BIN(sec);
BCD_TO_BIN(min);
BCD_TO_BIN(hour);
BCD_TO_BIN(day);
BCD_TO_BIN(mon);
BCD_TO_BIN(year);
}
if ((year += 1900) < 1970)
year += 100;
}
xtime.tv_sec = mktime(year, mon, day, hour, min, sec);
xtime.tv_usec = 0;
if (mregs) {
tmp = mostek_read(mregs + MOSTEK_CREG);
tmp &= ~MSTK_CREG_READ;
mostek_write(mregs + MOSTEK_CREG, tmp);
spin_unlock_irq(&mostek_lock);
}
}
void __init clock_probe(void)
{
struct linux_prom_registers clk_reg[2];
char model[128];
int node, busnd = -1, err;
unsigned long flags;
#ifdef CONFIG_PCI
struct linux_ebus *ebus = NULL;
struct isa_bridge *isa_br = NULL;
#endif
static int invoked = 0;
if (invoked)
return;
invoked = 1;
if (this_is_starfire) {
/* davem suggests we keep this within the 4M locked kernel image */
static char obp_gettod[256];
static u32 unix_tod;
sprintf(obp_gettod, "h# %08x unix-gettod",
(unsigned int) (long) &unix_tod);
prom_feval(obp_gettod);
xtime.tv_sec = unix_tod;
xtime.tv_usec = 0;
return;
}
__save_and_cli(flags);
if(central_bus != NULL) {
busnd = central_bus->child->prom_node;
}
#ifdef CONFIG_PCI
else if (ebus_chain != NULL) {
ebus = ebus_chain;
busnd = ebus->prom_node;
} else if (isa_chain != NULL) {
isa_br = isa_chain;
busnd = isa_br->prom_node;
}
#endif
else if (sbus_root != NULL) {
busnd = sbus_root->prom_node;
}
if (busnd == -1) {
prom_printf("clock_probe: problem, cannot find bus to search.\n");
prom_halt();
}
node = prom_getchild(busnd);
while (1) {
if (!node)
model[0] = 0;
else
prom_getstring(node, "model", model, sizeof(model));
if (strcmp(model, "mk48t02") &&
strcmp(model, "mk48t08") &&
strcmp(model, "mk48t59") &&
strcmp(model, "m5819") &&
strcmp(model, "ds1287")) {
if (node)
node = prom_getsibling(node);
#ifdef CONFIG_PCI
while ((node == 0) && ebus != NULL) {
ebus = ebus->next;
if (ebus != NULL) {
busnd = ebus->prom_node;
node = prom_getchild(busnd);
}
}
while ((node == 0) && isa_br != NULL) {
isa_br = isa_br->next;
if (isa_br != NULL) {
busnd = isa_br->prom_node;
node = prom_getchild(busnd);
}
}
#endif
if (node == 0) {
prom_printf("clock_probe: Cannot find timer chip\n");
prom_halt();
}
continue;
}
err = prom_getproperty(node, "reg", (char *)clk_reg,
sizeof(clk_reg));
if(err == -1) {
prom_printf("clock_probe: Cannot get Mostek reg property\n");
prom_halt();
}
if(central_bus) {
apply_fhc_ranges(central_bus->child, clk_reg, 1);
apply_central_ranges(central_bus, clk_reg, 1);
}
#ifdef CONFIG_PCI
else if (ebus_chain != NULL) {
struct linux_ebus_device *edev;
for_each_ebusdev(edev, ebus)
if (edev->prom_node == node)
break;
if (edev == NULL) {
if (isa_chain != NULL)
goto try_isa_clock;
prom_printf("%s: Mostek not probed by EBUS\n",
__FUNCTION__);
prom_halt();
}
if (!strcmp(model, "ds1287") ||
!strcmp(model, "m5819")) {
ds1287_regs = edev->resource[0].start;
} else {
mstk48t59_regs = edev->resource[0].start;
mstk48t02_regs = mstk48t59_regs + MOSTEK_48T59_48T02;
}
break;
} else if (isa_chain != NULL) {
struct isa_device *isadev;
try_isa_clock:
for_each_isadev(isadev, isa_br)
if (isadev->prom_node == node)
break;
if (isadev == NULL) {
prom_printf("%s: Mostek not probed by ISA\n");
prom_halt();
}
if (!strcmp(model, "ds1287") ||
!strcmp(model, "m5819")) {
ds1287_regs = isadev->resource.start;
} else {
mstk48t59_regs = isadev->resource.start;
mstk48t02_regs = mstk48t59_regs + MOSTEK_48T59_48T02;
}
break;
}
#endif
else {
if (sbus_root->num_sbus_ranges) {
int nranges = sbus_root->num_sbus_ranges;
int rngc;
for (rngc = 0; rngc < nranges; rngc++)
if (clk_reg[0].which_io ==
sbus_root->sbus_ranges[rngc].ot_child_space)
break;
if (rngc == nranges) {
prom_printf("clock_probe: Cannot find ranges for "
"clock regs.\n");
prom_halt();
}
clk_reg[0].which_io =
sbus_root->sbus_ranges[rngc].ot_parent_space;
clk_reg[0].phys_addr +=
sbus_root->sbus_ranges[rngc].ot_parent_base;
}
}
if(model[5] == '0' && model[6] == '2') {
mstk48t02_regs = (((u64)clk_reg[0].phys_addr) |
(((u64)clk_reg[0].which_io)<<32UL));
} else if(model[5] == '0' && model[6] == '8') {
mstk48t08_regs = (((u64)clk_reg[0].phys_addr) |
(((u64)clk_reg[0].which_io)<<32UL));
mstk48t02_regs = mstk48t08_regs + MOSTEK_48T08_48T02;
} else {
mstk48t59_regs = (((u64)clk_reg[0].phys_addr) |
(((u64)clk_reg[0].which_io)<<32UL));
mstk48t02_regs = mstk48t59_regs + MOSTEK_48T59_48T02;
}
break;
}
if (mstk48t02_regs != 0UL) {
/* Report a low battery voltage condition. */
if (has_low_battery())
prom_printf("NVRAM: Low battery voltage!\n");
/* Kick start the clock if it is completely stopped. */
if (mostek_read(mstk48t02_regs + MOSTEK_SEC) & MSTK_STOP)
kick_start_clock();
}
set_system_time();
__restore_flags(flags);
}
extern void init_timers(void (*func)(int, void *, struct pt_regs *),
unsigned long *);
void __init time_init(void)
{
/* clock_probe() is now done at end of [se]bus_init on sparc64
* so that sbus, fhc and ebus bus information is probed and
* available.
*/
unsigned long clock;
init_timers(timer_interrupt, &clock);
timer_ticks_per_usec_quotient = ((1UL<<32) / (clock / 1000020));
}
static __inline__ unsigned long do_gettimeoffset(void)
{
unsigned long ticks;
if (!SPARC64_USE_STICK) {
__asm__ __volatile__(
" rd %%tick, %%g1\n"
" add %1, %%g1, %0\n"
" sub %0, %2, %0\n"
: "=r" (ticks)
: "r" (timer_tick_offset), "r" (timer_tick_compare)
: "g1", "g2");
} else {
__asm__ __volatile__("rd %%asr24, %%g1\n\t"
"add %1, %%g1, %0\n\t"
"sub %0, %2, %0\n\t"
: "=&r" (ticks)
: "r" (timer_tick_offset), "r" (timer_tick_compare)
: "g1");
}
return (ticks * timer_ticks_per_usec_quotient) >> 32UL;
}
void do_settimeofday(struct timeval *tv)
{
if (this_is_starfire)
return;
write_lock_irq(&xtime_lock);
tv->tv_usec -= do_gettimeoffset();
if(tv->tv_usec < 0) {
tv->tv_usec += 1000000;
tv->tv_sec--;
}
xtime = *tv;
time_adjust = 0; /* stop active adjtime() */
time_status |= STA_UNSYNC;
time_maxerror = NTP_PHASE_LIMIT;
time_esterror = NTP_PHASE_LIMIT;
write_unlock_irq(&xtime_lock);
}
static int set_rtc_mmss(unsigned long nowtime)
{
int real_seconds, real_minutes, chip_minutes;
unsigned long mregs = mstk48t02_regs;
#ifdef CONFIG_PCI
unsigned long dregs = ds1287_regs;
#else
unsigned long dregs = 0UL;
#endif
unsigned long flags;
u8 tmp;
/*
* Not having a register set can lead to trouble.
* Also starfire doesnt have a tod clock.
*/
if (!mregs && !dregs)
return -1;
if (mregs) {
spin_lock_irqsave(&mostek_lock, flags);
/* Read the current RTC minutes. */
tmp = mostek_read(mregs + MOSTEK_CREG);
tmp |= MSTK_CREG_READ;
mostek_write(mregs + MOSTEK_CREG, tmp);
chip_minutes = MSTK_REG_MIN(mregs);
tmp = mostek_read(mregs + MOSTEK_CREG);
tmp &= ~MSTK_CREG_READ;
mostek_write(mregs + MOSTEK_CREG, tmp);
/*
* since we're only adjusting minutes and seconds,
* don't interfere with hour overflow. This avoids
* messing with unknown time zones but requires your
* RTC not to be off by more than 15 minutes
*/
real_seconds = nowtime % 60;
real_minutes = nowtime / 60;
if (((abs(real_minutes - chip_minutes) + 15)/30) & 1)
real_minutes += 30; /* correct for half hour time zone */
real_minutes %= 60;
if (abs(real_minutes - chip_minutes) < 30) {
tmp = mostek_read(mregs + MOSTEK_CREG);
tmp |= MSTK_CREG_WRITE;
mostek_write(mregs + MOSTEK_CREG, tmp);
MSTK_SET_REG_SEC(mregs,real_seconds);
MSTK_SET_REG_MIN(mregs,real_minutes);
tmp = mostek_read(mregs + MOSTEK_CREG);
tmp &= ~MSTK_CREG_WRITE;
mostek_write(mregs + MOSTEK_CREG, tmp);
spin_unlock_irqrestore(&mostek_lock, flags);
return 0;
} else {
spin_unlock_irqrestore(&mostek_lock, flags);
return -1;
}
} else {
int retval = 0;
unsigned char save_control, save_freq_select;
/* Stolen from arch/i386/kernel/time.c, see there for
* credits and descriptive comments.
*/
spin_lock_irqsave(&rtc_lock, flags);
save_control = CMOS_READ(RTC_CONTROL); /* tell the clock it's being set */
CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL);
save_freq_select = CMOS_READ(RTC_FREQ_SELECT); /* stop and reset prescaler */
CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT);
chip_minutes = CMOS_READ(RTC_MINUTES);
if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
BCD_TO_BIN(chip_minutes);
real_seconds = nowtime % 60;
real_minutes = nowtime / 60;
if (((abs(real_minutes - chip_minutes) + 15)/30) & 1)
real_minutes += 30;
real_minutes %= 60;
if (abs(real_minutes - chip_minutes) < 30) {
if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
BIN_TO_BCD(real_seconds);
BIN_TO_BCD(real_minutes);
}
CMOS_WRITE(real_seconds,RTC_SECONDS);
CMOS_WRITE(real_minutes,RTC_MINUTES);
} else {
printk(KERN_WARNING
"set_rtc_mmss: can't update from %d to %d\n",
chip_minutes, real_minutes);
retval = -1;
}
CMOS_WRITE(save_control, RTC_CONTROL);
CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
spin_unlock_irqrestore(&rtc_lock, flags);
return retval;
}
}