diff options
Diffstat (limited to 'chips/mc_clock.c')
-rw-r--r-- | chips/mc_clock.c | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/chips/mc_clock.c b/chips/mc_clock.c new file mode 100644 index 00000000..15fa049d --- /dev/null +++ b/chips/mc_clock.c @@ -0,0 +1,516 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * File: mc_clock.c + * Author: Alessandro Forin + * Date: 8/90 + * + * Driver for the MC146818 Clock + */ + +#include <mc.h> +#if NMC > 0 +#include <platforms.h> + +#include <mach/std_types.h> +#include <machine/machspl.h> /* spl definitions */ +#include <chips/busses.h> + +#include <sys/time.h> +#include <kern/time_out.h> +#include <chips/mc_clock.h> + +#ifdef DECSTATION +#include <mips/mips_cpu.h> +#include <mips/clock.h> +#endif /*DECSTATION*/ + +#ifdef FLAMINGO +#include <alpha/clock.h> +#endif /*FLAMINGO*/ + +#define private static +#define public + + +/* Architecture-specific defines */ + +#ifdef DECSTATION + +#define MC_DEFAULT_ADDRESS (mc_clock_ram_t *)PHYS_TO_K1SEG(0x1d000000) +#define MC_DOES_DELAYS 1 + +/* + * Both the Pmax and the 3max implementations of the chip map + * bytes of the chip's RAM to 32 bit words (low byte). + * For convenience, we redefine here the chip's RAM layout + * making padding explicit. + */ + +typedef struct { + volatile unsigned char mc_second; + char pad0[3]; + volatile unsigned char mc_alarm_second; + char pad1[3]; + volatile unsigned char mc_minute; + char pad2[3]; + volatile unsigned char mc_alarm_minute; + char pad3[3]; + volatile unsigned char mc_hour; + char pad4[3]; + volatile unsigned char mc_alarm_hour; + char pad5[3]; + volatile unsigned char mc_day_of_week; + char pad6[3]; + volatile unsigned char mc_day_of_month; + char pad7[3]; + volatile unsigned char mc_month; + char pad8[3]; + volatile unsigned char mc_year; + char pad9[3]; + volatile unsigned char mc_register_A; + char pad10[3]; + volatile unsigned char mc_register_B; + char pad11[3]; + volatile unsigned char mc_register_C; + char pad12[3]; + volatile unsigned char mc_register_D; + char pad13[3]; + unsigned char mc_non_volatile_ram[50 * 4]; /* unused */ +} mc_clock_ram_t; + +#define MC_CLOCK_PADDED 1 + +#endif /*DECSTATION*/ + + +#ifdef FLAMINGO +#define MC_DEFAULT_ADDRESS 0L + +/* padded, later */ + +#endif /* FLAMINGO */ + + + +#ifndef MC_CLOCK_PADDED +typedef mc_clock_t mc_clock_ram_t; /* No padding needed */ +#endif + +/* + * Functions provided herein + */ +int mc_probe( vm_offset_t addr, struct bus_ctlr * ); +private void mc_attach(); + +int mc_intr(); + +void mc_open(), mc_close(), mc_write(); +private unsigned int mc_read(); + +private void mc_wait_for_uip( mc_clock_ram_t *clock ); + + +/* + * Status + */ +boolean_t mc_running = FALSE; +boolean_t mc_new_century = FALSE; /* "year" info overfloweth */ + +private int days_per_month[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +private unsigned int mc_read(); /* forward */ +private void mc_wait_for_uip(); + +/* + * Where is the chip's RAM mapped + */ +private mc_clock_ram_t *rt_clock = MC_DEFAULT_ADDRESS; + +/* + * (Auto?)Configuration + */ +private vm_offset_t mc_std[NMC] = { 0 }; +private struct bus_device *mc_info[NMC]; + +struct bus_driver mc_driver = + { mc_probe, 0, mc_attach, 0, mc_std, "mc", mc_info, }; + + +mc_probe(vm_offset_t addr, struct bus_ctlr *ui) +{ + rt_clock = (mc_clock_ram_t *)addr; + return 1; +} + +private void +mc_attach() +{ + printf(": MC146818 or like Time-Of-Year chip"); +} + +/* + * Interrupt routine + */ +#if MC_DOES_DELAYS + +private int config_step = 3; +private volatile int had_intr; + +mc_intr(spllevel) + spl_t spllevel; +{ + /* + * Interrupt flags are read-to-clear. + */ + if (config_step > 2) + return (rt_clock->mc_register_C & MC_REG_C_IRQF); + had_intr = (rt_clock->mc_register_C & MC_REG_C_IRQF) ? 1 : 0; + if (config_step++ == 0) + accurate_config_delay(spllevel); + return had_intr; +} +#else /* MC_DOES_DELAYS */ + +mc_intr() +{ + return (rt_clock->mc_register_C); /* clear intr */ +} + +#endif /* MC_DOES_DELAYS */ + +/* + * Start real-time clock. + */ +void +mc_open() +{ + /* + * All we should need to do is to enable interrupts, but + * since we do not know what OS last ran on this box + * we'll reset it all over again. Just kidding.. + */ + unsigned unix_seconds_now; + + /* + * Check for battery backup power. If we do not have it, + * warn the user. Time will be bogus only after power up. + */ + if ((rt_clock->mc_register_D & MC_REG_D_VRT) == 0) + printf("WARNING: clock batteries are low\n"); + + /* + * Read the current time settings, check if the year info + * has been screwed up. + */ + unix_seconds_now = mc_read(); + + if (unix_seconds_now < (SECYR * (1990 - YRREF))) + printf("The prom has clobbered the clock\n"); + + time.tv_sec = (long)unix_seconds_now; + mc_write(); + + mc_running = TRUE; +} + +void +mc_close() +{ + /* + * Disable interrupts, but keep the chip running. + * Note we are called at splhigh and an interrupt + * might be pending already. + */ + + mc_intr(0); + rt_clock->mc_register_B &= ~(MC_REG_B_UIE|MC_REG_B_AIE|MC_REG_B_PIE); + mc_running = FALSE; +#if MC_DOES_DELAYS + config_step = 0; +#endif +} + + +/* + * Set time-of-day. Must be called at splhigh() + */ +void +mc_write() +{ + register mc_clock_ram_t *clock = rt_clock; + register unsigned years, months, days, hours, minutes, seconds; + register unsigned unix_seconds = time.tv_sec; + int frequence_selector, temp; + int bogus_hz = 0; + + /* + * Convert U*x time into absolute time + */ + + years = YRREF; + while (1) { + seconds = SECYR; + if (LEAPYEAR(years)) + seconds += SECDAY; + if (unix_seconds < seconds) + break; + unix_seconds -= seconds; + years++; + } + + months = 0; + while (1) { + seconds = days_per_month[months++] * SECDAY; + if (months == 2 /* February */ && LEAPYEAR(years)) + seconds += SECDAY; + if (unix_seconds < seconds) + break; + unix_seconds -= seconds; + } + + days = unix_seconds / SECDAY; + unix_seconds -= SECDAY * days++; + + hours = unix_seconds / SECHOUR; + unix_seconds -= SECHOUR * hours; + + minutes = unix_seconds / SECMIN; + unix_seconds -= SECMIN * minutes; + + seconds = unix_seconds; + + /* + * Trim years into 0-99 range. + */ + if ((years -= 1900) > 99) { + years -= 100; + mc_new_century = TRUE; + } + + /* + * Check for "hot dates" + */ + if (days >= 28 && days <= 30 && + hours == 23 && minutes == 59 && + seconds >= 58) + seconds = 57; + + /* + * Select the interrupt frequency based on system params + */ + switch (hz) { + case 1024: + frequence_selector = MC_BASE_32_KHz | MC_RATE_1024_Hz; + break; + case 512: + frequence_selector = MC_BASE_32_KHz | MC_RATE_512_Hz; + break; + case 256: + frequence_selector = MC_BASE_32_KHz | MC_RATE_256_Hz; + break; + case 128: + frequence_selector = MC_BASE_32_KHz | MC_RATE_128_Hz; + break; + case 64: +default_frequence: + frequence_selector = MC_BASE_32_KHz | MC_RATE_64_Hz; + break; + default: + bogus_hz = hz; + hz = 64; + tick = 1000000 / 64; + goto default_frequence; + } + + /* + * Stop updates while we fix it + */ + mc_wait_for_uip(clock); + clock->mc_register_B = MC_REG_B_STOP; + wbflush(); + + /* + * Ack any pending interrupts + */ + temp = clock->mc_register_C; + + /* + * Reset the frequency divider, in case we are changing it. + */ + clock->mc_register_A = MC_BASE_RESET; + + /* + * Now update the time + */ + clock->mc_second = seconds; + clock->mc_minute = minutes; + clock->mc_hour = hours; + clock->mc_day_of_month = days; + clock->mc_month = months; + clock->mc_year = years; + + /* + * Spec says the VRT bit can be validated, but does not say how. I + * assume it is via reading the register. + */ + temp = clock->mc_register_D; + + /* + * Reconfigure the chip and get it started again + */ + clock->mc_register_A = frequence_selector; + clock->mc_register_B = MC_REG_B_24HM | MC_REG_B_DM | MC_REG_B_PIE; + + /* + * Print warnings, if we have to + */ + if (bogus_hz != 0) + printf("Unacceptable value (%d Hz) for hz, reset to %d Hz\n", + bogus_hz, hz); +} + + +/* + * Internal functions + */ + +private void +mc_wait_for_uip(clock) + mc_clock_ram_t *clock; +{ + while (clock->mc_register_A & MC_REG_A_UIP) + delay(MC_UPD_MINIMUM >> 2); +} + +private unsigned int +mc_read() +{ + /* + * Note we only do this at boot time + */ + register unsigned years, months, days, hours, minutes, seconds; + register mc_clock_ram_t *clock = rt_clock;; + + /* + * If the chip is updating, wait + */ + mc_wait_for_uip(clock); + + years = clock->mc_year; + months = clock->mc_month; + days = clock->mc_day_of_month; + hours = clock->mc_hour; + minutes = clock->mc_minute; + seconds = clock->mc_second; + + /* + * Convert to Unix time + */ + seconds += minutes * SECMIN; + seconds += hours * SECHOUR; + seconds += (days - 1) * SECDAY; + if (months > 2 /* February */ && LEAPYEAR(years)) + seconds += SECDAY; + while (months > 1) + seconds += days_per_month[--months - 1]; + + /* + * Note that in ten years from today (Aug,1990) the new century will + * cause the trouble that mc_new_century attempts to avoid. + */ + if (mc_new_century) + years += 100; + years += 1900; /* chip base year in YRREF's century */ + + for (--years; years >= YRREF; years--) { + seconds += SECYR; + if (LEAPYEAR(years)) + seconds += SECDAY; + } + + return seconds; +} + +#ifdef MC_DOES_DELAYS + +/* + * Timed delays + */ +extern unsigned int cpu_speed; + +void +config_delay(speed) +{ + /* + * This is just an initial estimate, later on with the clock + * running we'll tune it more accurately. + */ + cpu_speed = speed; +} + +accurate_config_delay(spllevel) + spl_t spllevel; +{ + register unsigned int i; + register spl_t s; + int inner_loop_count; + +#ifdef mips + /* find "spllevel - 1" */ + s = spllevel | ((spllevel >> 1) & SR_INT_MASK); + splx(s); +#else +#endif + + /* wait till we have an interrupt pending */ + had_intr = 0; + while (!had_intr) + continue; + + had_intr = 0; + i = delay_timing_function(1, &had_intr, &inner_loop_count); + + splx(spllevel); + + i *= hz; + cpu_speed = i / (inner_loop_count * 1000000); + + /* roundup clock speed */ + i /= 100000; + if ((i % 10) >= 5) + i += 5; + printf("Estimating CPU clock at %d Mhz\n", i / 10); + if (isa_pmax() && cpu_speed != MC_DELAY_PMAX) { + printf("%s\n", "This machine looks like a DEC 2100"); + machine_slot[cpu_number()].cpu_subtype = CPU_SUBTYPE_MIPS_R2000; + } +} +#endif /* MC_DOES_DELAYS */ + +#endif NMC > 0 |