text: /*
   * Copyright (c) 2019 Intel Corp.
   * SPDX-License-Identifier: Apache-2.0
   *
   * This barebones driver enables the use of the PC AT-style RTC
   * (the so-called "CMOS" clock) as a primitive, 1Hz monotonic counter.
   *
   * Reading a reliable value from the RTC is a fairly slow process, because
   * we use legacy I/O ports and do a lot of iterations with spinlocks to read
   * the RTC state. Plus we have to read the state multiple times because we're
   * crossing clock domains (no pun intended). Use accordingly.
   */
  
  #include <drivers/counter.h>
  #include <device.h>
  #include <soc.h>
  
  /* The "CMOS" device is accessed via an address latch and data port. */
  
  #define X86_CMOS_ADDR 0x70
  #define X86_CMOS_DATA 0x71
  
  /*
   * A snapshot of the RTC state, or at least the state we're
   * interested in. This struct should not be modified without
   * serious consideraton, for two reasons:
   *
   *	1. Order of the element is important, and must correlate
   *	   with addrs[] and NR_BCD_VALS (see below), and
   *	2. if it doesn't remain exactly 8 bytes long, the
   *	   type-punning to compare states will break.
   */
  
  struct state {
  	u8_t second,
  	     minute,
  	     hour,
  	     day,
  	     month,
  	     year,
  	     status_a,
  	     status_b;
  };
  
  /*
   * If the clock is in BCD mode, the first NR_BCD_VALS
   * valies in 'struct state' are BCD-encoded.
   */
  
  #define NR_BCD_VALS 6
  
  /*
   * Indices into the CMOS address space that correspond to
   * the members of 'struct state'.
   */
  
  const u8_t addrs[] = { 0, 2, 4, 7, 8, 9, 10, 11 };
  
  /*
   * Interesting bits in 'struct state'.
   */
  
  #define STATUS_B_24HR	0x02	/* 24-hour (vs 12-hour) mode */
  #define STATUS_B_BIN	0x01	/* binary (vs BCD) mode */
  #define HOUR_PM		0x80	/* high bit of hour set = PM */
  
  /*
   * Read a value from the CMOS. Because of the address latch,
   * we have to spinlock to make the access atomic.
   */
  
  static u8_t read_register(u8_t addr)
  {
  	static struct k_spinlock lock;
  	k_spinlock_key_t k;
  	u8_t val;
  
  	k = k_spin_lock(&lock);
  	sys_out8(addr, X86_CMOS_ADDR);
  	val = sys_in8(X86_CMOS_DATA);
  	k_spin_unlock(&lock, k);
  
  	return val;
  }
  
  /* Populate 'state' with current RTC state. */
  
  void read_state(struct state *state)
  {
  	int i;
  	u8_t *p;
  
  	p = (u8_t *) state;
  	for (i = 0; i < sizeof(*state); ++i) {
  		*p++ = read_register(addrs[i]);
  	}
  }
  
  /* Convert 8-bit (2-digit) BCD to binary equivalent. */
  
  static inline u8_t decode_bcd(u8_t val)
  {
  	return (((val >> 4) & 0x0F) * 10) + (val & 0x0F);
  }
  
  /*
   * Hinnant's algorithm to calculate the number of days offset from the epoch.
   */
  
  static u32_t hinnant(int y, int m, int d)
  {
  	unsigned yoe;
  	unsigned doy;
  	unsigned doe;
  	int era;
  
  	y -= (m <= 2);
  	era = ((y >= 0) ? y : (y - 399)) / 400;
  	yoe = y - era * 400;
  	doy = (153 * (m + ((m > 2) ? -3 : 9)) + 2)/5 + d - 1;
  	doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
  
  	return era * 146097 + ((int) doe) - 719468;
  }
  
  /*
   * Returns the Unix epoch time (assuming UTC) read from the CMOS RTC.
   * This function is long, but linear and easy to follow.
   */
  
  u32_t read(struct device *dev)
  {
  	struct state state, state2;
  	u64_t *pun = (u64_t *) &state;
  	u64_t *pun2 = (u64_t *) &state2;
  	bool pm;
  	u32_t epoch;
  
  	ARG_UNUSED(dev);
  
  	/*
  	 * Read the state until we see the same state twice in a row.
  	 */
  
  	read_state(&state2);
  	do {
  		state = state2;
  		read_state(&state2);
  	} while (*pun != *pun2);
  
  	/*
  	 * Normalize the state; 12hr -> 24hr, BCD -> decimal.
  	 * The order is a bit awkward because we need to interpret
  	 * the HOUR_PM flag before we adjust for BCD.
  	 */
  
  	if (state.status_b & STATUS_B_24HR) {
  		pm = false;
  	} else {
  		pm = ((state.hour & HOUR_PM) == HOUR_PM);
  		state.hour &= ~HOUR_PM;
  	}
  
  	if (!(state.status_b & STATUS_B_BIN)) {
  		u8_t *cp = (u8_t *) &state;
  		int i;
  
  		for (i = 0; i < NR_BCD_VALS; ++i) {
  			*cp = decode_bcd(*cp);
  			++cp;
  		}
  	}
  
  	if (pm) {
  		state.hour = (state.hour + 12) % 24;
  	}
  
  	/*
  	 * Convert date/time to epoch time. We don't care about
  	 * timezones here, because we're just creating a mapping
  	 * that results in a monotonic clock; the absolute value
  	 * is irrelevant.
  	 */
  
  	epoch = hinnant(state.year + 2000, state.month, state.day);
  	epoch *= 86400; /* seconds per day */
  	epoch += state.hour * 3600; /* seconds per hour */
  	epoch += state.minute * 60; /* seconds per minute */
  	epoch += state.second;
  
  	return epoch;
  }
  
  static int init(struct device *dev)
  {
  	ARG_UNUSED(dev);
  
  	return 0;
  }
  
  static const struct counter_driver_api api = {
  	.read = read
  };
  
  DEVICE_AND_API_INIT(counter_cmos, "CMOS", init, NULL, NULL,
  		    POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &api);
  
  