time.c 6.49 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
/*
 * Carsten Langgaard, carstenl@mips.com
 * Copyright (C) 1999,2000 MIPS Technologies, Inc.  All rights reserved.
 *
 * Copyright (C) 2003 MontaVista Software Inc.
 * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
 *
 * ########################################################################
 *
 *  This program is free software; you can distribute it and/or modify it
 *  under the terms of the GNU General Public License (Version 2) as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
 *
 * ########################################################################
 *
 * Setting up the clock on the MIPS boards.
 */
#include <linux/init.h>
#include <linux/kernel_stat.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/spinlock.h>

#include <asm/time.h>
#include <asm/mipsregs.h>
#include <asm/ptrace.h>
#include <asm/it8172/it8172.h>
#include <asm/it8172/it8172_int.h>
#include <asm/debug.h>

#define IT8172_RTC_ADR_REG  (IT8172_PCI_IO_BASE + IT_RTC_BASE)
#define IT8172_RTC_DAT_REG  (IT8172_RTC_ADR_REG + 1)
#define IT8172_RTC_CENTURY_REG  (IT8172_PCI_IO_BASE + IT_RTC_CENTURY)

static volatile char *rtc_adr_reg = (char*)KSEG1ADDR(IT8172_RTC_ADR_REG);
static volatile char *rtc_dat_reg = (char*)KSEG1ADDR(IT8172_RTC_DAT_REG);
static volatile char *rtc_century_reg = (char*)KSEG1ADDR(IT8172_RTC_CENTURY_REG);

unsigned char it8172_rtc_read_data(unsigned long addr)
{
	unsigned char retval;

	*rtc_adr_reg = addr;
	retval =  *rtc_dat_reg;
	return retval;
}

void it8172_rtc_write_data(unsigned char data, unsigned long addr)
{
	*rtc_adr_reg = addr;
	*rtc_dat_reg = data;
}

#undef 	CMOS_READ
#undef 	CMOS_WRITE
#define	CMOS_READ(addr)			it8172_rtc_read_data(addr)
#define CMOS_WRITE(data, addr) 		it8172_rtc_write_data(data, addr)

static unsigned char saved_control;	/* remember rtc control reg */
static inline int rtc_24h(void) { return saved_control & RTC_24H; }
static inline int rtc_dm_binary(void) { return saved_control & RTC_DM_BINARY; }

static inline unsigned char
bin_to_hw(unsigned char c)
{
75
	if (rtc_dm_binary())
Linus Torvalds's avatar
Linus Torvalds committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
		return c;
	else
		return ((c/10) << 4) + (c%10);
}

static inline unsigned char
hw_to_bin(unsigned char c)
{
	if (rtc_dm_binary())
		return c;
	else
		return (c>>4)*10 + (c &0xf);
}

/* 0x80 bit indicates pm in 12-hour format */
static inline unsigned char
hour_bin_to_hw(unsigned char c)
{
94
	if (rtc_24h())
Linus Torvalds's avatar
Linus Torvalds committed
95
		return bin_to_hw(c);
96
	if (c >= 12)
Linus Torvalds's avatar
Linus Torvalds committed
97 98 99 100 101 102 103 104 105 106 107
		return 0x80 | bin_to_hw((c==12)?12:c-12);  /* 12 is 12pm */
	else
		return bin_to_hw((c==0)?12:c);	/* 0 is 12 AM, not 0 am */
}

static inline unsigned char
hour_hw_to_bin(unsigned char c)
{
	unsigned char tmp = hw_to_bin(c&0x3f);
	if (rtc_24h())
		return tmp;
108
	if (c & 0x80)
Linus Torvalds's avatar
Linus Torvalds committed
109
		return (tmp==12)?12:tmp+12;  	/* 12pm is 12, not 24 */
110
	else
Linus Torvalds's avatar
Linus Torvalds committed
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
		return (tmp==12)?0:tmp;		/* 12am is 0 */
}

static unsigned long r4k_offset; /* Amount to increment compare reg each time */
static unsigned long r4k_cur;    /* What counter should be at next timer irq */
extern unsigned int mips_hpt_frequency;

/*
 * Figure out the r4k offset, the amount to increment the compare
 * register for each time tick.
 * Use the RTC to calculate offset.
 */
static unsigned long __init cal_r4koff(void)
{
	unsigned int flags;

	local_irq_save(flags);

	/* Start counter exactly on falling edge of update flag */
	while (CMOS_READ(RTC_REG_A) & RTC_UIP);
	while (!(CMOS_READ(RTC_REG_A) & RTC_UIP));

	/* Start r4k counter. */
	write_c0_count(0);

	/* Read counter exactly on falling edge of update flag */
	while (CMOS_READ(RTC_REG_A) & RTC_UIP);
	while (!(CMOS_READ(RTC_REG_A) & RTC_UIP));

	mips_hpt_frequency = read_c0_count();

	/* restore interrupts */
	local_irq_restore(flags);

	return (mips_hpt_frequency / HZ);
}

148
static unsigned long
Linus Torvalds's avatar
Linus Torvalds committed
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
it8172_rtc_get_time(void)
{
	unsigned int year, mon, day, hour, min, sec;
	unsigned int flags;

	/* avoid update-in-progress. */
	for (;;) {
		local_irq_save(flags);
		if (! (CMOS_READ(RTC_REG_A) & RTC_UIP))
			break;
		/* don't hold intr closed all the time */
		local_irq_restore(flags);
	}

	/* Read regs. */
	sec = hw_to_bin(CMOS_READ(RTC_SECONDS));
	min = hw_to_bin(CMOS_READ(RTC_MINUTES));
	hour = hour_hw_to_bin(CMOS_READ(RTC_HOURS));
	day = hw_to_bin(CMOS_READ(RTC_DAY_OF_MONTH));
	mon = hw_to_bin(CMOS_READ(RTC_MONTH));
169
	year = hw_to_bin(CMOS_READ(RTC_YEAR)) +
Linus Torvalds's avatar
Linus Torvalds committed
170 171 172 173
		hw_to_bin(*rtc_century_reg) * 100;

	/* restore interrupts */
	local_irq_restore(flags);
174

Linus Torvalds's avatar
Linus Torvalds committed
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
	return mktime(year, mon, day, hour, min, sec);
}

static int
it8172_rtc_set_time(unsigned long t)
{
	struct rtc_time tm;
	unsigned int flags;

	/* convert */
	to_tm(t, &tm);

	/* avoid update-in-progress. */
	for (;;) {
		local_irq_save(flags);
		if (! (CMOS_READ(RTC_REG_A) & RTC_UIP))
			break;
		/* don't hold intr closed all the time */
		local_irq_restore(flags);
	}

	*rtc_century_reg = bin_to_hw(tm.tm_year/100);
	CMOS_WRITE(bin_to_hw(tm.tm_sec), RTC_SECONDS);
	CMOS_WRITE(bin_to_hw(tm.tm_min), RTC_MINUTES);
	CMOS_WRITE(hour_bin_to_hw(tm.tm_hour), RTC_HOURS);
	CMOS_WRITE(bin_to_hw(tm.tm_mday), RTC_DAY_OF_MONTH);
	CMOS_WRITE(bin_to_hw(tm.tm_mon+1), RTC_MONTH);	/* tm_mon starts from 0 */
	CMOS_WRITE(bin_to_hw(tm.tm_year%100), RTC_YEAR);

	/* restore interrupts */
	local_irq_restore(flags);

	return 0;
}

void __init it8172_time_init(void)
{
        unsigned int est_freq, flags;

	local_irq_save(flags);

	saved_control = CMOS_READ(RTC_CONTROL);

	printk("calculating r4koff... ");
	r4k_offset = cal_r4koff();
	printk("%08lx(%d)\n", r4k_offset, (int) r4k_offset);

	est_freq = 2*r4k_offset*HZ;
	est_freq += 5000;    /* round */
	est_freq -= est_freq%10000;
	printk("CPU frequency %d.%02d MHz\n", est_freq/1000000,
	       (est_freq%1000000)*100/1000000);

	local_irq_restore(flags);

	rtc_get_time = it8172_rtc_get_time;
	rtc_set_time = it8172_rtc_set_time;
}

#define ALLINTS (IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3 | IE_IRQ4 | IE_IRQ5)
void __init it8172_timer_setup(struct irqaction *irq)
{
	puts("timer_setup\n");
	put32(NR_IRQS);
	puts("");
        /* we are using the cpu counter for timer interrupts */
	setup_irq(MIPS_CPU_TIMER_IRQ, irq);

        /* to generate the first timer interrupt */
	r4k_cur = (read_c0_count() + r4k_offset);
	write_c0_compare(r4k_cur);
	set_c0_status(ALLINTS);
}