Logo Search packages:      
Sourcecode: faumachine version File versions  Download package

arch_rtc.c

/* $Id: arch_rtc.c,v 1.44 2009-01-28 12:59:16 potyra Exp $ 
 *
 * Copyright (C) 2006-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#ifdef STATE

struct {
      struct storage media;

      uint8_t reg;
      uint8_t reg_ext;

      uint8_t freq_select;
      uint8_t control;
      uint8_t intr_flags;
      
      uint8_t sec;
      uint8_t sec_alarm;
      uint8_t min;
      uint8_t min_alarm;
      uint8_t hour;
      uint8_t hour_alarm;
      uint8_t day_of_week;
      uint8_t day_of_month;
      uint8_t month;
      uint8_t year;

      unsigned long long tsc_call;
      unsigned long long tsc_PI_timer;
} NAME;

#endif /* STATE */
#ifdef BEHAVIOR

#define PATCH_RTC       1
#define DEBUG_RTC       0

#include "system.h" /* FIXME */

/* ------------------------------------------------ */
/* see <linux/mc146818rtc.h> and PC-Hardware p. 753 */
/* ------------------------------------------------ */

/*
 * Clock data registers
 */
#define RTC_SECONDS           0
#define RTC_SECONDS_ALARM     1
#define RTC_MINUTES           2
#define RTC_MINUTES_ALARM     3
#define RTC_HOURS       4
#define RTC_HOURS_ALARM       5
#define RTC_DAY_OF_WEEK       6
#define RTC_DAY_OF_MONTH      7
#define RTC_MONTH       8
#define RTC_YEAR        9

/*
 * Clock control registers
 */
#define RTC_FREQ_SELECT 10
/* update-in-progress  - set to "1" 244 microsecs before RTC goes off the bus,
 * reset after update (may take 1.984ms @ 32768Hz RefClock) is complete,
 * totalling to a max high interval of 2.228 ms.
 */
#define RTC_UIP         0x80
#define RTC_DIV_CTL     0x70
   /* divider control: refclock values 4.194 / 1.049 MHz / 32.768 kHz */
#define RTC_CLCK_4MHZ   0x00
#define RTC_CLCK_1MHZ   0x10
#define RTC_CLCK_32KHZ  0x20
   /* 2 values for divider stage reset, others for "testing purposes only" */
#define RTC_DIV_RESET1  0x60
#define RTC_DIV_RESET2  0x70
  /* Periodic intr. / Square wave rate select. 0=none, 1=32.8kHz,... 15=2Hz */
#define RTC_RATE_SELECT 0x0F

#define RTC_CONTROL     11
#define RTC_SET         0x80 /* disable updates for clock setting */
#define RTC_PIE         0x40 /* periodic interrupt enable */
#define RTC_AIE         0x20 /* alarm interrupt enable */
#define RTC_UIE         0x10 /* update-finished interrupt enable */
#define RTC_SQWE  0x08 /* enable square-wave output */
#define RTC_DM_BINARY   0x04 /* all time/date values are BCD if clear */
#define RTC_24H         0x02 /* 24 hour mode - else hours bit 7 means pm */
#define RTC_DST_EN      0x01 /* auto switch DST - works f. USA only */

#define RTC_INTR_FLAGS  12
                       /* caution - cleared by read */
#define RTC_IRQF  0x80 /* any of the following 3 is active */
#define RTC_PF          0x40
#define RTC_AF          0x20
#define RTC_UF          0x10

#define RTC_VALID 13
#define RTC_VRT         0x80 /* valid RAM and time */

static void
NAME_(umktime)(
      unsigned long long t,
      int *year,
      int *month,
      int *day,
      int *hour,
      int *min,
      int *sec
)
{
      const int secspermin = 60;
      const int secsperhour = 60 * secspermin;
      const int secsperday = 24 * secsperhour;
      unsigned char dayspermonth[12] = {
            31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
      };
      unsigned int daysperyear;

      *year = 1970;
      *month = 0;
      *day = 0;
      *hour = 0;
      *min = 0;
      *sec = 0;
      daysperyear = (*year % 4 == 0) ? 366 : 365;
      while (daysperyear * secsperday <= t) {
            assert(365 <= daysperyear && daysperyear <= 366);
            t -= daysperyear * secsperday;
            (*year)++;
            daysperyear = (*year % 4 == 0) ? 366 : 365;
      }
      assert(1970 <= *year);
      dayspermonth[1] = (*year % 4 == 0) ? 29 : 28;
      while (dayspermonth[*month] * secsperday <= t) {
            t -= dayspermonth[*month] * secsperday;
            (*month)++;
      }
      (*month)++; /* 0->1, 1->2, ... */
      assert(1 <= *month && *month <= 12);
      while (24 * 60 * 60 <= t) {
            t -= 24 * 60 * 60;
            (*day)++;
      }
      (*day)++;   /* 0->1, 1->2, ... */
      assert(1 <= *day && *day <= 31);
      while (60 * 60 <= t) {
            t -= 60 * 60;
            (*hour)++;
      }
      assert(0 <= *hour && *hour <= 23);
      while (60 <= t) {
            t -= 60;
            (*min)++;
      }
      assert(0 <= *min && *min <= 59);
      *sec = t;
      assert(0 <= *sec && *sec <= 59);

#if DEBUG_RTC
      fprintf(stderr, "date=%d-%d-%d %02d:%02d:%02d\n",
                  *year, *month, *day, *hour, *min, *sec);
#endif
}

static void
NAME_(irq_update)(struct cpssp *css)
{
      if ((css->NAME.intr_flags & css->NAME.control) & (RTC_PIE | RTC_AIE | RTC_UIE)) {
            NAME_(irq_set)(css, 1);
            css->NAME.intr_flags |= RTC_IRQF;
      } else {
            NAME_(irq_set)(css, 0);
            css->NAME.intr_flags &= ~RTC_IRQF;
      }
}

static void
NAME_(timer)(void *_css)
{
      struct cpssp *css = (struct cpssp *) _css;

      if (! css->state_power)
            return;

      css->NAME.intr_flags |= RTC_PF;
      NAME_(irq_update)(css);

      if (css->NAME.freq_select & RTC_RATE_SELECT) {
            unsigned int sel;

            sel = css->NAME.freq_select & RTC_RATE_SELECT;
            css->NAME.tsc_PI_timer += TIME_HZ >> (16 - sel);
            time_call_at(css->NAME.tsc_PI_timer, NAME_(timer), css);
      }
}

static uint8_t
NAME_(bcd_add)(
      unsigned int bcd,
      uint8_t val,
      uint8_t first,
      uint8_t last,
      unsigned int *carryp
)
{
      if (bcd) {
            /* Switch to binary mode. */
            val = ((val >> 4) & 0xf) * 10 + ((val >> 0) & 0xf);
      }

      /* Do calculation. */
      val += *carryp;
      if (last < val) {
            *carryp = 1;
            val = first;
      } else {
            *carryp = 0;
      }

      if (bcd) {
            /* Switch back to bcd mode. */
            val = ((val / 10) << 4) | ((val % 10) << 0);
      }

      return val;
}

/*
 * MC146818 needs a max high interval of 2.228 ms to update.
 */
static void
NAME_(clock)(void *_css)
{
      struct cpssp *css = (struct cpssp *) _css;

      if (css->NAME.freq_select & RTC_UIP) {
            /*
             * Generating UIP -> -UIP change.
             */
            css->NAME.freq_select &= ~RTC_UIP;

            /*
             * Interrupt if alarm time is actual time and interrupt
             * is desired for this reason.
             */
            if (((css->NAME.sec_alarm & 0xc0) == 0xc0
                        || css->NAME.sec_alarm == css->NAME.sec)
             && ((css->NAME.min_alarm & 0xc0) == 0xc0
                        || css->NAME.min_alarm == css->NAME.min)
             && ((css->NAME.hour_alarm & 0xc0) == 0xc0
                        || css->NAME.hour_alarm == css->NAME.hour)) {
                  css->NAME.intr_flags |= RTC_AF;
                  NAME_(irq_update)(css);
            }

            /*
             * Interrupt if UIP interrupt is wanted.
             */
            css->NAME.intr_flags |= RTC_UF;
            NAME_(irq_update)(css);

            /* Set new wakeup timer. */
            css->NAME.tsc_call += TIME_HZ - (TIME_HZ * 2228 / 1000000);
            time_call_at(css->NAME.tsc_call, NAME_(clock), css);

      } else {
            /*
             * Generate -UIP -> UIP change.
             */
            css->NAME.freq_select |= RTC_UIP;

            if (! (css->NAME.control & RTC_SET)) {
                  static const int ndays[1 + 0x12] = {
                        0, /* Not used. */
                        31, 29, 31, 30, 31, 30,
                        31, 31, 30,
                        31, 30, 31, /* In case of binary mode. */
                        0, 0, 0,
                        31, 30, 31  /* In case of bcd mode. */
                  };
                  unsigned int bcd;
                  unsigned int carry;
                  unsigned int carry2;

                  bcd = (css->NAME.control & RTC_DM_BINARY) ? 0 : 1;
                  carry = 1;
                  css->NAME.sec = NAME_(bcd_add)(bcd, css->NAME.sec, 0, 59, &carry);
                  css->NAME.min = NAME_(bcd_add)(bcd, css->NAME.min, 0, 59, &carry);
                  if (css->NAME.control & RTC_24H) {
                        css->NAME.hour = NAME_(bcd_add)(bcd, css->NAME.hour, 0, 23, &carry);
                  } else {
                        uint8_t pm;
                        uint8_t hour;

                        pm = (css->NAME.hour >> 7) & 1;
                        hour = (css->NAME.hour >> 0) & 0x7f;

                        hour = NAME_(bcd_add)(bcd, hour, 0, 11, &carry);
                        pm = NAME_(bcd_add)(bcd, pm, 0, 1, &carry);

                        css->NAME.hour = (pm << 7) | (hour << 0);
                  }

                  carry2 = carry;
                  css->NAME.day_of_week = NAME_(bcd_add)(bcd, css->NAME.day_of_week, 1, 7, &carry2);

                  if (css->NAME.month == 2 && css->NAME.year % 4) {
                        css->NAME.day_of_month = NAME_(bcd_add)(bcd,
                              css->NAME.day_of_month,
                              1, 28, &carry);
                  } else {
                        css->NAME.day_of_month = NAME_(bcd_add)(bcd,
                              css->NAME.day_of_month,
                              1, ndays[css->NAME.month], &carry);
                  }
                  css->NAME.year = NAME_(bcd_add)(bcd, css->NAME.year, 0, 99, &carry);
            }

            /* Set new wakeup timer. */
            css->NAME.tsc_call += TIME_HZ * 2228 / 1000000;
            time_call_at(css->NAME.tsc_call, NAME_(clock), css);
      }
}

static int
NAME_(inb)(struct cpssp *css, unsigned char *valuep, unsigned short addr)
{
      int ret;

      switch (addr) {
      case 0:
            /* correct? FIXME VOSSI */
            *valuep = css->NAME.reg;
            return 0;

      case 1:
            switch (css->NAME.reg) {
            case RTC_SECONDS:
                  *valuep = css->NAME.sec;
                  break;

            case RTC_SECONDS_ALARM:
                  *valuep = css->NAME.sec_alarm;
                  break;

            case RTC_MINUTES:
                  *valuep = css->NAME.min;
                  break;
                  
            case RTC_MINUTES_ALARM:
                  *valuep = css->NAME.min_alarm;
                  break;

            case RTC_HOURS:
                  *valuep = css->NAME.hour;
                  break;
                  
            case RTC_HOURS_ALARM:
                  *valuep = css->NAME.hour_alarm;
                  break;

            case RTC_DAY_OF_WEEK:
                  *valuep = css->NAME.day_of_week;
                  break;

            case RTC_DAY_OF_MONTH:
                  *valuep = css->NAME.day_of_month;
                  break;

            case RTC_MONTH:
                  *valuep = css->NAME.month;
                  break;

            case RTC_YEAR:
                  *valuep = css->NAME.year;
                  break;

            case RTC_FREQ_SELECT:
                  *valuep = css->NAME.freq_select;
                  break;

            case RTC_CONTROL:
                  *valuep = css->NAME.control;
                  break;

            case RTC_INTR_FLAGS:
                  /* css->NAME.intr_flags register is cleared by read */
                  /* and set by interrupt */
                  *valuep = css->NAME.intr_flags;
                  css->NAME.intr_flags = 0x00;
                  NAME_(irq_update)(css);
                  break;

            case RTC_VALID:
                  *valuep = RTC_VRT;
                  break;

#if PATCH_RTC
            case 16:
                  /* Special Case - FIXME */
                  *valuep = (floppydrive[0] << 4) | floppydrive[1];
                  break;
#endif

            default:
                  /* Those are read from the cmosram-file. */
                  assert(css->NAME.reg < 128);
                  ret = storage_read(&css->NAME.media,
                              valuep, sizeof(*valuep),
                              css->NAME.reg);
                  assert(ret == sizeof(*valuep));
                  break;
            }
#if DEBUG_RTC
            fprintf(stderr, "%s: read 0x%02x from register 0x%02x\n",
                        __FUNCTION__, *valuep, css->NAME.reg);
#endif
            return 0;

      case 2:
            *valuep = css->NAME.reg_ext;
            return 0;

      case 3:
            ret = storage_read(&css->NAME.media,
                        valuep, sizeof(*valuep),
                        css->NAME.reg_ext + 128);
            assert(ret == sizeof(*valuep));
#if DEBUG_RTC
            fprintf(stderr, "%s: read 0x%02x from register 0x%02x\n",
                        __FUNCTION__, *valuep, css->NAME.reg_ext + 128);
#endif
            return 0;

      default:
            return -1;
      }
}

static int
NAME_(outb)(struct cpssp *css, unsigned char value, unsigned short addr)
{
      int ret;

      switch (addr) {
      case 0:
            /* Ignore bit 7 (NMI masking bit) - FIXME VOSSI */
            css->NAME.reg = value & 0x7f;
            return 0;

      case 1:
            switch (css->NAME.reg) {
            case RTC_SECONDS:
                  css->NAME.sec = value;
                  break;

            case RTC_SECONDS_ALARM:
                  css->NAME.sec_alarm = value;
                  break;

            case RTC_MINUTES:
                  css->NAME.min = value;
                  break;

            case RTC_MINUTES_ALARM:
                  css->NAME.min_alarm = value;
                  break;

            case RTC_HOURS:
                  css->NAME.hour = value;
                  break;
                  
            case RTC_HOURS_ALARM:
                  css->NAME.hour_alarm = value;
                  break;

            case RTC_DAY_OF_WEEK:
                  css->NAME.day_of_week = value;
                  break;

            case RTC_DAY_OF_MONTH:
                  css->NAME.day_of_month = value;
                  break;

            case RTC_MONTH:
                  css->NAME.month = value;
                  break;

            case RTC_YEAR:
                  css->NAME.year = value;
                  break;

            case RTC_FREQ_SELECT:
                  /* Bit 7 is read-only. */
                  value &= ~RTC_UIP;
                  value |= css->NAME.freq_select & ~RTC_UIP;

                  /* Start/update/stop timer. */
                  /* Timer update/stop not implemented - FIXME */
                  if (((css->NAME.freq_select & RTC_RATE_SELECT) == 0)
                   && (value & RTC_RATE_SELECT) != 0){
                        unsigned int sel;

                        sel = value & 0xf;
                        css->NAME.tsc_PI_timer = time_virt();
                        css->NAME.tsc_PI_timer += TIME_HZ >> (16 - sel);
                        time_call_at(css->NAME.tsc_PI_timer,
                                    NAME_(timer), css);
                  }

                  css->NAME.freq_select = value;
                  break;

            case RTC_CONTROL:
                  css->NAME.control = value;
                  break;

            case RTC_INTR_FLAGS:
            case RTC_VALID:
                  faum_log(FAUM_LOG_WARNING, "RTC", "",
                              "Writing 0x%02x to read-only register 0x%02x.\n",
                              value, css->NAME.reg);
                  break;

            /* Those are read from the cmosram-file. */
            case 14 ... 127:
                  ret = storage_write(&css->NAME.media,
                              &value, sizeof(value),
                              css->NAME.reg);
                  assert(ret == sizeof(value));
                  break;

            default:
                  assert(0);  /* Cannot happen. */
                  /*NOTREACHED*/
            }

#if DEBUG_RTC
            fprintf(stderr, "%s: wrote 0x%02x to register 0x%02x\n", __FUNCTION__,
                        value, css->NAME.reg);
#endif
            return 0;

      case 2:
            /* Ignore bit 7 */
            css->NAME.reg_ext = value & 0x7f;
            return 0;

      case 3:
            ret = storage_write(&css->NAME.media,
                        &value, sizeof(value),
                        css->NAME.reg_ext + 128);
            assert(ret == sizeof(value));

#if DEBUG_RTC
            fprintf(stderr, "%s: wrote 0x%02x to register 0x%02x\n", __FUNCTION__,
                        value, css->NAME.reg_ext + 128);
#endif
            return 0;

      default:
            return -1;
      }
}

static uint8_t
NAME_(bin_to_bcd)(uint8_t val)
{
      val = ((val / 10) << 4) | ((val % 10) << 0);

      return val;
}

static void
NAME_(reset)(struct cpssp *css)
{
      css->NAME.freq_select = 0x00;
}

static void
NAME_(init)(struct cpssp *css)
{
      int ret;

      storage_init(&css->NAME.media);
      ret = storage_open(&css->NAME.media, 1);
      assert(0 <= ret);

      css->NAME.tsc_call = TIME_HZ;
      time_call_at(css->NAME.tsc_call, NAME_(clock), css);
}

static void
NAME_(create)(struct cpssp *css, const char *rtc_start)
{
      int year;
      int month;
      int day_of_month;
      int hour;
      int min;
      int sec;
      int ret;
      int seconds;      /* FIXME should be unsigned */
      char cmos[128+128];

      if (rtc_start == NULL) {
            seconds = -1;
      } else {
            seconds = strtol(rtc_start, NULL, 0);
      }
      if (seconds < 0) {
            /* use current time to initialize clock */
            struct timeval tv;

            ret = gettimeofday(&tv, (struct timezone *) 0);
            assert(0 <= ret);

            seconds = tv.tv_sec;
      }

      NAME_(umktime)(seconds,
                  &year, &month, &day_of_month, &hour, &min, &sec);

      year %= 100;

      css->NAME.sec = NAME_(bin_to_bcd)(sec);
      css->NAME.min = NAME_(bin_to_bcd)(min);
      css->NAME.hour = NAME_(bin_to_bcd)(hour);
      css->NAME.day_of_month = NAME_(bin_to_bcd)(day_of_month);
      /* Note: 1970-01-01 was a thursday. */
      css->NAME.day_of_week = ((seconds / 60 / 24) + 5) % 7 + 1;
      css->NAME.month = NAME_(bin_to_bcd)(month);
      css->NAME.year = NAME_(bin_to_bcd)(year);

      css->NAME.freq_select = 0;
      css->NAME.reg = -1;
      css->NAME.control = RTC_24H;
      css->NAME.intr_flags = 0x00;

      storage_create(&css->NAME.media, "cmosram",
                  1, "", 1, sizeof(cmos), 0, 0, 0, 0);
}

static void
NAME_(destroy)(struct cpssp *css)
{
      int ret;

      ret = storage_close(&css->NAME.media);
      assert(0 <= ret);
}

#undef DEBUG_RTC
#undef PATCH_RTC

#endif /* BEHAVIOR */

Generated by  Doxygen 1.6.0   Back to index