13446Smrj /*
23446Smrj * CDDL HEADER START
33446Smrj *
43446Smrj * The contents of this file are subject to the terms of the
53446Smrj * Common Development and Distribution License (the "License").
63446Smrj * You may not use this file except in compliance with the License.
73446Smrj *
83446Smrj * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
93446Smrj * or http://www.opensolaris.org/os/licensing.
103446Smrj * See the License for the specific language governing permissions
113446Smrj * and limitations under the License.
123446Smrj *
133446Smrj * When distributing Covered Code, include this CDDL HEADER in each
143446Smrj * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
153446Smrj * If applicable, add the following below this CDDL HEADER, with the
163446Smrj * fields enclosed by brackets "[]" replaced with your own identifying
173446Smrj * information: Portions Copyright [yyyy] [name of copyright owner]
183446Smrj *
193446Smrj * CDDL HEADER END
203446Smrj */
213446Smrj
223446Smrj /*
23*11752STrevor.Thompson@Sun.COM * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
243446Smrj * Use is subject to license terms.
253446Smrj */
263446Smrj
273446Smrj /* Copyright (c) 1990, 1991 UNIX System Laboratories, Inc. */
283446Smrj /* Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T */
293446Smrj /* All Rights Reserved */
303446Smrj
313446Smrj /* Copyright (c) 1987, 1988 Microsoft Corporation */
323446Smrj /* All Rights Reserved */
333446Smrj
343446Smrj #include <sys/param.h>
353446Smrj #include <sys/time.h>
363446Smrj #include <sys/systm.h>
373446Smrj
383446Smrj #include <sys/cpuvar.h>
393446Smrj #include <sys/clock.h>
403446Smrj #include <sys/debug.h>
413446Smrj #include <sys/rtc.h>
423446Smrj #include <sys/archsystm.h>
433446Smrj #include <sys/sysmacros.h>
443446Smrj #include <sys/lockstat.h>
453446Smrj #include <sys/stat.h>
463446Smrj #include <sys/sunddi.h>
473446Smrj
485295Srandyf #include <sys/acpi/acpi.h>
495295Srandyf #include <sys/acpica.h>
505295Srandyf
513446Smrj static int todpc_rtcget(unsigned char *buf);
523446Smrj static void todpc_rtcput(unsigned char *buf);
533446Smrj
545295Srandyf #define CLOCK_RES 1000 /* 1 microsec in nanosecs */
555295Srandyf
565295Srandyf int clock_res = CLOCK_RES;
575295Srandyf
585295Srandyf /*
595295Srandyf * The minimum sleep time till an alarm can be fired.
605295Srandyf * This can be tuned in /etc/system, but if the value is too small,
615295Srandyf * there is a danger that it will be missed if it takes too long to
625295Srandyf * get from the set point to sleep. Or that it can fire quickly, and
635295Srandyf * generate a power spike on the hardware. And small values are
645295Srandyf * probably only usefull for test setups.
655295Srandyf */
665295Srandyf int clock_min_alarm = 4;
675295Srandyf
683446Smrj /*
693446Smrj * Machine-dependent clock routines.
703446Smrj */
713446Smrj
725295Srandyf extern long gmt_lag;
735295Srandyf
745295Srandyf struct rtc_offset {
755295Srandyf int8_t loaded;
765295Srandyf uint8_t day_alrm;
775295Srandyf uint8_t mon_alrm;
785295Srandyf uint8_t century;
795295Srandyf };
805295Srandyf
815295Srandyf static struct rtc_offset pc_rtc_offset = {0, 0, 0, 0};
825295Srandyf
835295Srandyf
845295Srandyf /*
855295Srandyf * Entry point for ACPI to pass RTC or other clock values that
865295Srandyf * are useful to TOD.
875295Srandyf */
885295Srandyf void
pc_tod_set_rtc_offsets(ACPI_TABLE_FADT * fadt)897851SDana.Myers@Sun.COM pc_tod_set_rtc_offsets(ACPI_TABLE_FADT *fadt) {
905295Srandyf int ok = 0;
915295Srandyf
925295Srandyf /*
935295Srandyf * ASSERT is for debugging, but we don't want the machine
945295Srandyf * falling over because for some reason we didn't get a valid
955295Srandyf * pointer.
965295Srandyf */
975295Srandyf ASSERT(fadt);
985295Srandyf if (fadt == NULL) {
995295Srandyf return;
1005295Srandyf }
1015295Srandyf
1027851SDana.Myers@Sun.COM if (fadt->DayAlarm) {
1037851SDana.Myers@Sun.COM pc_rtc_offset.day_alrm = fadt->DayAlarm;
1045295Srandyf ok = 1;
1055295Srandyf }
1065295Srandyf
1077851SDana.Myers@Sun.COM if (fadt->MonthAlarm) {
1087851SDana.Myers@Sun.COM pc_rtc_offset.mon_alrm = fadt->MonthAlarm;
1095295Srandyf ok = 1;
1105295Srandyf }
1115295Srandyf
1125295Srandyf if (fadt->Century) {
1135295Srandyf pc_rtc_offset.century = fadt->Century;
1145295Srandyf ok = 1;
1155295Srandyf }
1165295Srandyf
1175295Srandyf pc_rtc_offset.loaded = ok;
1185295Srandyf }
1195295Srandyf
1205295Srandyf
1213446Smrj /*
1223446Smrj * Write the specified time into the clock chip.
1233446Smrj * Must be called with tod_lock held.
1243446Smrj */
1253446Smrj /*ARGSUSED*/
1263446Smrj static void
todpc_set(tod_ops_t * top,timestruc_t ts)1273446Smrj todpc_set(tod_ops_t *top, timestruc_t ts)
1283446Smrj {
1293446Smrj todinfo_t tod = utc_to_tod(ts.tv_sec - ggmtl());
1303446Smrj struct rtc_t rtc;
1313446Smrj
1323446Smrj ASSERT(MUTEX_HELD(&tod_lock));
1333446Smrj
1343446Smrj if (todpc_rtcget((unsigned char *)&rtc))
1353446Smrj return;
1363446Smrj
1373446Smrj /*
1383446Smrj * rtc bytes are in binary-coded decimal, so we have to convert.
1393446Smrj * We assume that we wrap the rtc year back to zero at 2000.
1403446Smrj */
1413446Smrj /* LINTED: YRBASE = 0 for x86 */
1423446Smrj tod.tod_year -= YRBASE;
1433446Smrj if (tod.tod_year >= 100) {
1443446Smrj tod.tod_year -= 100;
1453446Smrj rtc.rtc_century = BYTE_TO_BCD(20); /* 20xx year */
1463446Smrj } else
1473446Smrj rtc.rtc_century = BYTE_TO_BCD(19); /* 19xx year */
1483446Smrj rtc.rtc_yr = BYTE_TO_BCD(tod.tod_year);
1493446Smrj rtc.rtc_mon = BYTE_TO_BCD(tod.tod_month);
1503446Smrj rtc.rtc_dom = BYTE_TO_BCD(tod.tod_day);
1513446Smrj /* dow < 10, so no conversion */
1523446Smrj rtc.rtc_dow = (unsigned char)tod.tod_dow;
1533446Smrj rtc.rtc_hr = BYTE_TO_BCD(tod.tod_hour);
1543446Smrj rtc.rtc_min = BYTE_TO_BCD(tod.tod_min);
1553446Smrj rtc.rtc_sec = BYTE_TO_BCD(tod.tod_sec);
1563446Smrj
1573446Smrj todpc_rtcput((unsigned char *)&rtc);
1583446Smrj }
1593446Smrj
1603446Smrj /*
1613446Smrj * Read the current time from the clock chip and convert to UNIX form.
1623446Smrj * Assumes that the year in the clock chip is valid.
1633446Smrj * Must be called with tod_lock held.
1643446Smrj */
1653446Smrj /*ARGSUSED*/
1663446Smrj static timestruc_t
todpc_get(tod_ops_t * top)1673446Smrj todpc_get(tod_ops_t *top)
1683446Smrj {
1693446Smrj timestruc_t ts;
1703446Smrj todinfo_t tod;
1713446Smrj struct rtc_t rtc;
1723446Smrj int compute_century;
1733446Smrj static int century_warn = 1; /* only warn once, not each time called */
1743446Smrj static int range_warn = 1;
1753446Smrj
1763446Smrj ASSERT(MUTEX_HELD(&tod_lock));
1773446Smrj
1783446Smrj if (todpc_rtcget((unsigned char *)&rtc)) {
1793446Smrj ts.tv_sec = 0;
1803446Smrj ts.tv_nsec = 0;
181*11752STrevor.Thompson@Sun.COM tod_status_set(TOD_GET_FAILED);
1823446Smrj return (ts);
1833446Smrj }
1843446Smrj
1853446Smrj /* assume that we wrap the rtc year back to zero at 2000 */
1863446Smrj tod.tod_year = BCD_TO_BYTE(rtc.rtc_yr);
1873446Smrj if (tod.tod_year < 69) {
1883446Smrj if (range_warn && tod.tod_year > 38) {
1893446Smrj cmn_err(CE_WARN, "hardware real-time clock is out "
1905295Srandyf "of range -- time needs to be reset");
1913446Smrj range_warn = 0;
1923446Smrj }
1933446Smrj tod.tod_year += 100 + YRBASE; /* 20xx year */
1943446Smrj compute_century = 20;
1953446Smrj } else {
1963446Smrj /* LINTED: YRBASE = 0 for x86 */
1973446Smrj tod.tod_year += YRBASE; /* 19xx year */
1983446Smrj compute_century = 19;
1993446Smrj }
2003446Smrj if (century_warn && BCD_TO_BYTE(rtc.rtc_century) != compute_century) {
2013446Smrj cmn_err(CE_NOTE,
2025295Srandyf "The hardware real-time clock appears to have the "
2035295Srandyf "wrong century: %d.\nSolaris will still operate "
2045295Srandyf "correctly, but other OS's/firmware agents may "
2055295Srandyf "not.\nUse date(1) to set the date to the current "
2065295Srandyf "time to correct the RTC.",
2075295Srandyf BCD_TO_BYTE(rtc.rtc_century));
2083446Smrj century_warn = 0;
2093446Smrj }
2103446Smrj tod.tod_month = BCD_TO_BYTE(rtc.rtc_mon);
2113446Smrj tod.tod_day = BCD_TO_BYTE(rtc.rtc_dom);
2123446Smrj tod.tod_dow = rtc.rtc_dow; /* dow < 10, so no conversion needed */
2133446Smrj tod.tod_hour = BCD_TO_BYTE(rtc.rtc_hr);
2143446Smrj tod.tod_min = BCD_TO_BYTE(rtc.rtc_min);
2153446Smrj tod.tod_sec = BCD_TO_BYTE(rtc.rtc_sec);
2163446Smrj
217*11752STrevor.Thompson@Sun.COM /* read was successful so ensure failure flag is clear */
218*11752STrevor.Thompson@Sun.COM tod_status_clear(TOD_GET_FAILED);
219*11752STrevor.Thompson@Sun.COM
2203446Smrj ts.tv_sec = tod_to_utc(tod) + ggmtl();
2213446Smrj ts.tv_nsec = 0;
2223446Smrj
2233446Smrj return (ts);
2243446Smrj }
2253446Smrj
2265295Srandyf #include <sys/promif.h>
2275295Srandyf /*
2285295Srandyf * Write the specified wakeup alarm into the clock chip.
2295295Srandyf * Must be called with tod_lock held.
2305295Srandyf */
2315295Srandyf void
2325295Srandyf /*ARGSUSED*/
todpc_setalarm(tod_ops_t * top,int nsecs)2335295Srandyf todpc_setalarm(tod_ops_t *top, int nsecs)
2345295Srandyf {
2355295Srandyf struct rtc_t rtc;
2365295Srandyf int delta, asec, amin, ahr, adom, amon;
2375295Srandyf int day_alrm = pc_rtc_offset.day_alrm;
2385295Srandyf int mon_alrm = pc_rtc_offset.mon_alrm;
2395295Srandyf
2405295Srandyf ASSERT(MUTEX_HELD(&tod_lock));
2415295Srandyf
2425295Srandyf /* A delay of zero is not allowed */
2435295Srandyf if (nsecs == 0)
2445295Srandyf return;
2455295Srandyf
2465295Srandyf /* Make sure that we delay no less than the minimum time */
2475295Srandyf if (nsecs < clock_min_alarm)
2485295Srandyf nsecs = clock_min_alarm;
2495295Srandyf
2505295Srandyf if (todpc_rtcget((unsigned char *)&rtc))
2515295Srandyf return;
2525295Srandyf
2535295Srandyf /*
2545295Srandyf * Compute alarm secs, mins and hrs, and where appropriate, dom
2555295Srandyf * and mon. rtc bytes are in binary-coded decimal, so we have
2565295Srandyf * to convert.
2575295Srandyf */
2585295Srandyf delta = nsecs + BCD_TO_BYTE(rtc.rtc_sec);
2595295Srandyf asec = delta % 60;
2605295Srandyf
2615295Srandyf delta = (delta / 60) + BCD_TO_BYTE(rtc.rtc_min);
2625295Srandyf amin = delta % 60;
2635295Srandyf
2645295Srandyf delta = (delta / 60) + BCD_TO_BYTE(rtc.rtc_hr);
2655295Srandyf ahr = delta % 24;
2665295Srandyf
2675295Srandyf if (day_alrm == 0 && delta >= 24) {
2685295Srandyf prom_printf("No day alarm - set to end of today!\n");
2695295Srandyf asec = 59;
2705295Srandyf amin = 59;
2715295Srandyf ahr = 23;
2725295Srandyf } else {
2735295Srandyf int mon = BCD_TO_BYTE(rtc.rtc_mon);
2745295Srandyf static int dpm[] =
2755295Srandyf {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
2765295Srandyf
2775295Srandyf adom = (delta / 24) + BCD_TO_BYTE(rtc.rtc_dom);
2785295Srandyf
2795295Srandyf if (mon_alrm == 0) {
2805295Srandyf if (adom > dpm[mon]) {
2815295Srandyf prom_printf("No mon alarm - "
2825295Srandyf "set to end of current month!\n");
2835295Srandyf asec = 59;
2845295Srandyf amin = 59;
2855295Srandyf ahr = 23;
2865295Srandyf adom = dpm[mon];
2875295Srandyf }
2885295Srandyf } else {
2895295Srandyf for (amon = mon;
2905295Srandyf amon <= 12 && adom > dpm[amon]; amon++) {
2915295Srandyf adom -= dpm[amon];
2925295Srandyf }
2935295Srandyf if (amon > 12) {
2945295Srandyf prom_printf("Alarm too far in future - "
2955295Srandyf "set to end of current year!\n");
2965295Srandyf asec = 59;
2975295Srandyf amin = 59;
2985295Srandyf ahr = 23;
2995295Srandyf adom = dpm[12];
3005295Srandyf amon = 12;
3015295Srandyf }
3025295Srandyf rtc.rtc_amon = BYTE_TO_BCD(amon);
3035295Srandyf }
3045295Srandyf
3055295Srandyf rtc.rtc_adom = BYTE_TO_BCD(adom);
3065295Srandyf }
3075295Srandyf
3085295Srandyf rtc.rtc_asec = BYTE_TO_BCD(asec);
3095295Srandyf rtc.rtc_amin = BYTE_TO_BCD(amin);
3105295Srandyf rtc.rtc_ahr = BYTE_TO_BCD(ahr);
3115295Srandyf
3125295Srandyf rtc.rtc_statusb |= RTC_AIE; /* Enable alarm interrupt */
3135295Srandyf
3145295Srandyf todpc_rtcput((unsigned char *)&rtc);
3155295Srandyf }
3165295Srandyf
3175295Srandyf /*
3185295Srandyf * Clear an alarm. This is effectively setting an alarm of 0.
3195295Srandyf */
3205295Srandyf void
3215295Srandyf /*ARGSUSED*/
todpc_clralarm(tod_ops_t * top)3225295Srandyf todpc_clralarm(tod_ops_t *top)
3235295Srandyf {
3245295Srandyf mutex_enter(&tod_lock);
3255295Srandyf todpc_setalarm(top, 0);
3265295Srandyf mutex_exit(&tod_lock);
3275295Srandyf }
3285295Srandyf
3293446Smrj /*
3303446Smrj * Routine to read contents of real time clock to the specified buffer.
3313446Smrj * Returns ENXIO if clock not valid, or EAGAIN if clock data cannot be read
3323446Smrj * else 0.
3333446Smrj * The routine will busy wait for the Update-In-Progress flag to clear.
3343446Smrj * On completion of the reads the Seconds register is re-read and the
3353446Smrj * UIP flag is rechecked to confirm that an clock update did not occur
3363446Smrj * during the accesses. Routine will error exit after 256 attempts.
3373446Smrj * (See bugid 1158298.)
3383446Smrj * Routine returns RTC_NREG (which is 15) bytes of data, as given in the
3393446Smrj * technical reference. This data includes both time and status registers.
3403446Smrj */
3413446Smrj
3423446Smrj static int
todpc_rtcget(unsigned char * buf)3433446Smrj todpc_rtcget(unsigned char *buf)
3443446Smrj {
3453446Smrj unsigned char reg;
3463446Smrj int i;
3473446Smrj int retries = 256;
3483446Smrj unsigned char *rawp;
3495295Srandyf unsigned char century = RTC_CENTURY;
3505295Srandyf unsigned char day_alrm;
3515295Srandyf unsigned char mon_alrm;
3523446Smrj
3533446Smrj ASSERT(MUTEX_HELD(&tod_lock));
3543446Smrj
3555295Srandyf day_alrm = pc_rtc_offset.day_alrm;
3565295Srandyf mon_alrm = pc_rtc_offset.mon_alrm;
3575295Srandyf if (pc_rtc_offset.century != 0) {
3585295Srandyf century = pc_rtc_offset.century;
3595295Srandyf }
3605295Srandyf
3613446Smrj outb(RTC_ADDR, RTC_D); /* check if clock valid */
3623446Smrj reg = inb(RTC_DATA);
3633446Smrj if ((reg & RTC_VRT) == 0)
3643446Smrj return (ENXIO);
3653446Smrj
3663446Smrj checkuip:
3673446Smrj if (retries-- < 0)
3683446Smrj return (EAGAIN);
3693446Smrj outb(RTC_ADDR, RTC_A); /* check if update in progress */
3703446Smrj reg = inb(RTC_DATA);
3713446Smrj if (reg & RTC_UIP) {
3723446Smrj tenmicrosec();
3733446Smrj goto checkuip;
3743446Smrj }
3753446Smrj
3763446Smrj for (i = 0, rawp = buf; i < RTC_NREG; i++) {
3773446Smrj outb(RTC_ADDR, i);
3783446Smrj *rawp++ = inb(RTC_DATA);
3793446Smrj }
3805295Srandyf outb(RTC_ADDR, century); /* do century */
3813446Smrj ((struct rtc_t *)buf)->rtc_century = inb(RTC_DATA);
3825295Srandyf
3835295Srandyf if (day_alrm > 0) {
3845295Srandyf outb(RTC_ADDR, day_alrm);
3855295Srandyf ((struct rtc_t *)buf)->rtc_adom = inb(RTC_DATA) & 0x3f;
3865295Srandyf }
3875295Srandyf if (mon_alrm > 0) {
3885295Srandyf outb(RTC_ADDR, mon_alrm);
3895295Srandyf ((struct rtc_t *)buf)->rtc_amon = inb(RTC_DATA);
3905295Srandyf }
3915295Srandyf
3923446Smrj outb(RTC_ADDR, 0); /* re-read Seconds register */
3933446Smrj reg = inb(RTC_DATA);
3943446Smrj if (reg != ((struct rtc_t *)buf)->rtc_sec ||
3953446Smrj (((struct rtc_t *)buf)->rtc_statusa & RTC_UIP))
3963446Smrj /* update occured during reads */
3973446Smrj goto checkuip;
3983446Smrj
3993446Smrj return (0);
4003446Smrj }
4013446Smrj
4023446Smrj /*
4033446Smrj * This routine writes the contents of the given buffer to the real time
4043446Smrj * clock. It is given RTC_NREGP bytes of data, which are the 10 bytes used
4053446Smrj * to write the time and set the alarm. It should be called with the priority
4063446Smrj * raised to 5.
4073446Smrj */
4083446Smrj static void
todpc_rtcput(unsigned char * buf)4093446Smrj todpc_rtcput(unsigned char *buf)
4103446Smrj {
4113446Smrj unsigned char reg;
4123446Smrj int i;
4135295Srandyf unsigned char century = RTC_CENTURY;
4145295Srandyf unsigned char day_alrm = pc_rtc_offset.day_alrm;
4155295Srandyf unsigned char mon_alrm = pc_rtc_offset.mon_alrm;
4168936SDan.Mick@Sun.COM unsigned char tmp;
4175295Srandyf
4185295Srandyf if (pc_rtc_offset.century != 0) {
4195295Srandyf century = pc_rtc_offset.century;
4205295Srandyf }
4213446Smrj
4223446Smrj outb(RTC_ADDR, RTC_B);
4233446Smrj reg = inb(RTC_DATA);
4243446Smrj outb(RTC_ADDR, RTC_B);
4253446Smrj outb(RTC_DATA, reg | RTC_SET); /* allow time set now */
4263446Smrj for (i = 0; i < RTC_NREGP; i++) { /* set the time */
4273446Smrj outb(RTC_ADDR, i);
4283446Smrj outb(RTC_DATA, buf[i]);
4293446Smrj }
4305295Srandyf outb(RTC_ADDR, century); /* do century */
4313446Smrj outb(RTC_DATA, ((struct rtc_t *)buf)->rtc_century);
4325295Srandyf
4335295Srandyf if (day_alrm > 0) {
4345295Srandyf outb(RTC_ADDR, day_alrm);
4358936SDan.Mick@Sun.COM /* preserve RTC_VRT bit; some virt envs accept writes there */
4368936SDan.Mick@Sun.COM tmp = inb(RTC_DATA) & RTC_VRT;
4378936SDan.Mick@Sun.COM tmp |= ((struct rtc_t *)buf)->rtc_adom & ~RTC_VRT;
4388936SDan.Mick@Sun.COM outb(RTC_DATA, tmp);
4395295Srandyf }
4405295Srandyf if (mon_alrm > 0) {
4415295Srandyf outb(RTC_ADDR, mon_alrm);
4425295Srandyf outb(RTC_DATA, ((struct rtc_t *)buf)->rtc_amon);
4435295Srandyf }
4445295Srandyf
4455295Srandyf outb(RTC_ADDR, RTC_B);
4465295Srandyf reg = inb(RTC_DATA);
4473446Smrj outb(RTC_ADDR, RTC_B);
4483446Smrj outb(RTC_DATA, reg & ~RTC_SET); /* allow time update */
4493446Smrj }
4503446Smrj
4513446Smrj static tod_ops_t todpc_ops = {
4523446Smrj TOD_OPS_VERSION,
4533446Smrj todpc_get,
4543446Smrj todpc_set,
4555295Srandyf NULL,
4565295Srandyf NULL,
4575295Srandyf todpc_setalarm,
4585295Srandyf todpc_clralarm,
4593446Smrj NULL
4603446Smrj };
4613446Smrj
4623446Smrj /*
4633446Smrj * Initialize for the default TOD ops vector for use on hardware.
4643446Smrj */
4653446Smrj
4663446Smrj tod_ops_t *tod_ops = &todpc_ops;
467