From 7669349030886c5c8427dc7cf63d9e28be0023ab Mon Sep 17 00:00:00 2001
From: Doug Szumski <d.s.szumski@gmail.com>
Date: Thu, 4 Feb 2016 19:27:25 +0000
Subject: [PATCH] parts: DS1338 real time clock which runs on the TWI bus

Features:

> External oscillator synced to the AVR core
> Square wave output with scalable frequency
> Leap year correction until 2100 (just like the real thing!)

Should also work for the pin compatible DS1307 device, but that part
hasn't been tested against actual hardware.
---
 examples/parts/ds1338_virt.c | 501 +++++++++++++++++++++++++++++++++++
 examples/parts/ds1338_virt.h | 185 +++++++++++++
 2 files changed, 686 insertions(+)
 create mode 100644 examples/parts/ds1338_virt.c
 create mode 100644 examples/parts/ds1338_virt.h

diff --git a/examples/parts/ds1338_virt.c b/examples/parts/ds1338_virt.c
new file mode 100644
index 0000000..5381a16
--- /dev/null
+++ b/examples/parts/ds1338_virt.c
@@ -0,0 +1,501 @@
+/*
+	ds1338_virt.c
+
+	Copyright 2014 Doug Szumski <d.s.szumski@gmail.com>
+
+	Based on i2c_eeprom example by:
+
+	Copyright 2008, 2009 Michel Pollet <buserror@gmail.com>
+
+	This file is part of simavr.
+
+	simavr is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	simavr is distributed in the hope that 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 simavr.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "avr_twi.h"
+#include "ds1338_virt.h"
+#include "sim_time.h"
+
+
+/*
+ * Increment the ds1338 register address.
+ */
+static void
+ds1338_virt_incr_addr(ds1338_virt_t * const p)
+{
+	if (p->reg_addr < sizeof(p->nvram)) {
+		p->reg_addr++;
+	} else {
+		// TODO Check if this wraps, or if it just stops incrementing
+		p->reg_addr = 0;
+	}
+}
+
+/*
+ * Update the system behaviour after a control register is written to.
+ */
+static void
+ds1338_virt_update(const ds1338_virt_t * const p)
+{
+	// The address of the register which was just updated
+	switch (p->reg_addr)
+	{
+		case DS1338_VIRT_SECONDS:
+			if (ds1338_get_flag(p->nvram[p->reg_addr], DS1338_VIRT_CH) == 0) {
+				printf("DS1338 clock ticking\n");
+			} else {
+				printf("DS1338 clock stopped\n");
+			}
+			break;
+		case DS1338_VIRT_CONTROL:
+			printf("DS1338 control register updated\n");
+			// TODO: Check if changing the prescaler resets the clock counter
+			// and if so do it here?
+			break;
+		default:
+			// No control register updated
+			return;
+	}
+}
+
+/*
+ * Calculate days in month given the year. The year should be specified
+ * in 4 digit format.
+ */
+static uint8_t
+ds1338_virt_days_in_month(uint8_t month, uint16_t year) {
+
+	uint8_t is_leap_year = 1;
+	if ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0))
+		is_leap_year = 0;
+
+	uint8_t days;
+	if (month == 2)
+		days = 28 + is_leap_year;
+	else
+		days = 31 - (month - 1) % 7 % 2;
+
+	return days;
+}
+
+/*
+ * Ticks a BCD register according to the specified constraints.
+ */
+static uint8_t
+ds1338_virt_tick_bcd_reg(bcd_reg_t * bcd_reg)
+{
+	// Unpack BCD
+	uint8_t x = (*bcd_reg->reg & 0x0F) +
+			10*((*bcd_reg->reg & bcd_reg->tens_mask) >> 4);
+
+	// Tick
+	uint8_t cascade = 0;
+	if (++x > bcd_reg->max_val) {
+		x = bcd_reg->min_val;
+		cascade = 1;
+	}
+
+	// Set the BCD part of the register
+	*bcd_reg->reg &= ~(0x0F | bcd_reg->tens_mask);
+	*bcd_reg->reg |= (x / 10 << 4) + x % 10;
+
+	return cascade;
+}
+
+/*
+ * Ticks the time registers. See table 3, p10 of the DS1338 datasheet.
+ */
+static void
+ds1338_virt_tick_time(ds1338_virt_t *p) {
+
+	/*
+	 * Seconds
+	 */
+	bcd_reg_t reg = {
+		.reg = &p->nvram[DS1338_VIRT_SECONDS],
+		.min_val = 0,
+		.max_val = 59,
+		.tens_mask = 0b01110000
+	};
+	uint8_t cascade = ds1338_virt_tick_bcd_reg(&reg);
+	if (!cascade)
+		return;
+
+	/*
+	 * Minutes
+	 */
+	reg.reg = &p->nvram[DS1338_VIRT_MINUTES];
+	cascade = ds1338_virt_tick_bcd_reg(&reg);
+	if (!cascade)
+		return;
+
+	/*
+	 * Hours
+	 */
+	reg.reg = &p->nvram[DS1338_VIRT_HOURS];
+	if (ds1338_get_flag(p->nvram[DS1338_VIRT_HOURS], DS1338_VIRT_12_24_HR)) {
+		// 12 hour mode
+		reg.min_val = 1;
+		reg.max_val = 12;
+		reg.tens_mask = 0b00010000;
+		uint8_t pm = ds1338_get_flag(p->nvram[DS1338_VIRT_HOURS], DS1338_VIRT_AM_PM);
+		cascade = ds1338_virt_tick_bcd_reg(&reg);
+		if (cascade) {
+			if (pm) {
+				// Switch to AM
+				p->nvram[DS1338_VIRT_HOURS] &= !(1 << DS1338_VIRT_AM_PM);
+			} else {
+				// Switch to PM and catch the cascade
+				p->nvram[DS1338_VIRT_HOURS] |= (1 << DS1338_VIRT_AM_PM);
+				cascade = 0;
+			}
+		}
+	} else {
+		// 24 hour mode
+		reg.min_val = 0;
+		reg.max_val = 23;
+		reg.tens_mask = 0b00110000;
+		cascade = ds1338_virt_tick_bcd_reg(&reg);
+	}
+	if (!cascade)
+		return;
+
+	/*
+	 * Day
+	 */
+	reg.reg = &p->nvram[DS1338_VIRT_DAY];
+	reg.min_val = 1;
+	reg.max_val = 7;
+	reg.tens_mask = 0;
+	ds1338_virt_tick_bcd_reg(&reg);
+
+	/*
+	 * Date
+	 */
+	reg.reg = &p->nvram[DS1338_VIRT_DATE];
+	// Insert a y2.1k bug like they do in the original part
+	uint16_t year = 2000 + UNPACK_BCD(p->nvram[DS1338_VIRT_YEAR]);
+	reg.max_val = ds1338_virt_days_in_month(
+	               UNPACK_BCD(p->nvram[DS1338_VIRT_MONTH]),
+	               year);
+	reg.tens_mask = 0b00110000;
+	cascade = ds1338_virt_tick_bcd_reg(&reg);
+	if (!cascade)
+		return;
+
+	/*
+	 * Month
+	 */
+	reg.reg = &p->nvram[DS1338_VIRT_MONTH];
+	reg.max_val = 12;
+	reg.tens_mask = 0b00010000;
+	cascade = ds1338_virt_tick_bcd_reg(&reg);
+	if (!cascade)
+		return;
+
+	/*
+	 * Year
+	 */
+	reg.reg = &p->nvram[DS1338_VIRT_YEAR];
+	reg.min_val = 0;
+	reg.max_val = 99;
+	reg.tens_mask = 0b11110000;
+	cascade = ds1338_virt_tick_bcd_reg(&reg);
+}
+
+static void
+ds1338_virt_cycle_square_wave(ds1338_virt_t *p)
+{
+	if(!ds1338_get_flag(p->nvram[DS1338_VIRT_CONTROL], DS1338_VIRT_SQWE)) {
+		printf("DS1338: SQWE disabled");
+		// Square wave output disabled
+		return;
+	}
+
+	p->square_wave = !p->square_wave;
+
+	if (p->square_wave) {
+		avr_raise_irq(p->irq + DS1338_SQW_IRQ_OUT, 1);
+		//printf ("Tick\n");
+	} else {
+		avr_raise_irq(p->irq + DS1338_SQW_IRQ_OUT, 0);
+		//printf ("Tock\n");
+	}
+}
+
+/*
+ * This function is left in for debugging.
+ */
+static void
+ds1338_print_time(ds1338_virt_t *p)
+{
+	uint8_t seconds = (p->nvram[DS1338_VIRT_SECONDS] & 0xF)
+	                + ((p->nvram[DS1338_VIRT_SECONDS] & 0b01110000) >> 4) * 10;
+	uint8_t minutes = (p->nvram[DS1338_VIRT_MINUTES] & 0xF)
+	                + (p->nvram[DS1338_VIRT_MINUTES] >> 4) * 10;
+
+	uint8_t pm = 0;
+	uint8_t hours;
+	if (ds1338_get_flag (p->nvram[DS1338_VIRT_HOURS], DS1338_VIRT_12_24_HR))
+	{
+		// 12hr mode
+		pm = ds1338_get_flag(p->nvram[DS1338_VIRT_HOURS],
+		                      DS1338_VIRT_AM_PM);
+		hours = (p->nvram[DS1338_VIRT_HOURS] & 0xF)
+		                + ((p->nvram[DS1338_VIRT_HOURS] & 0b00010000) >> 4) * 10;
+	} else {
+		// 24hr mode
+		hours = (p->nvram[DS1338_VIRT_HOURS] & 0xF)
+		                + ((p->nvram[DS1338_VIRT_HOURS] & 0b00110000) >> 4) * 10;
+	}
+
+	uint8_t day = p->nvram[DS1338_VIRT_DAY] & 0b00000111;
+	uint8_t date = (p->nvram[DS1338_VIRT_DATE] & 0xF)
+	                + (p->nvram[DS1338_VIRT_DATE] >> 4) * 10;
+	uint8_t month = (p->nvram[DS1338_VIRT_MONTH] & 0xF)
+	                + (p->nvram[DS1338_VIRT_MONTH] >> 4) * 10;
+	uint8_t year = (p->nvram[DS1338_VIRT_YEAR] & 0xF)
+	                + (p->nvram[DS1338_VIRT_YEAR] >> 4) * 10;
+
+	if(p->verbose)
+		printf("Time: %02i:%02i:%02i  Day: %i Date: %02i:%02i:%02i PM:%01x\n",
+		        hours, minutes, seconds, day, date, month, year, pm);
+}
+
+static avr_cycle_count_t
+ds1338_virt_clock_tick(struct avr_t * avr,
+                       avr_cycle_count_t when,
+                       ds1338_virt_t *p)
+{
+	avr_cycle_count_t next_tick = when + avr_usec_to_cycles(avr, DS1338_CLK_PERIOD_US / 2);
+
+	if (!ds1338_get_flag(p->nvram[DS1338_VIRT_SECONDS], DS1338_VIRT_CH)) {
+		// Oscillator is enabled. Note that this counter is allowed to wrap.
+		p->rtc++;
+	} else {
+		// Avoid a condition match below with the clock switched off
+		return next_tick;
+	}
+
+	/*
+	 * Update the time
+	 */
+	if (p->rtc == 0) {
+		// 1 second has passed
+		ds1338_virt_tick_time(p);
+                if (p->verbose)
+			ds1338_print_time(p);
+	}
+
+	/*
+	 * Deal with the square wave output
+	 */
+	uint8_t prescaler_mode = ds1338_get_flag(p->nvram[DS1338_VIRT_CONTROL],
+	                                         DS1338_VIRT_RS0)
+	                      + (ds1338_get_flag(p->nvram[DS1338_VIRT_CONTROL],
+	                                         DS1338_VIRT_RS1) << 1);
+
+	switch (prescaler_mode)
+	{
+		case DS1338_VIRT_PRESCALER_DIV_32768:
+			if ((p->rtc + 1) % DS1338_CLK_FREQ == 0) {
+				ds1338_virt_cycle_square_wave(p);
+			}
+			break;
+		case DS1338_VIRT_PRESCALER_DIV_8:
+			if ((p->rtc + 1) % (DS1338_CLK_FREQ / 8) == 0)
+				ds1338_virt_cycle_square_wave(p);
+			break;
+		case DS1338_VIRT_PRESCALER_DIV_4:
+			if ((p->rtc + 1) % (DS1338_CLK_FREQ / 4) == 0)
+				ds1338_virt_cycle_square_wave(p);
+			break;
+		case DS1338_VIRT_PRESCALER_OFF:
+			ds1338_virt_cycle_square_wave(p);
+			break;
+		default:
+			printf("DS1338 ERROR: PRESCALER MODE INVALID\n");
+			break;
+	}
+
+	return next_tick;
+}
+
+static void
+ds1338_virt_clock_xtal_init(struct avr_t * avr,
+                            ds1338_virt_t *p)
+{
+	p->rtc = 0;
+
+	/*
+	 * Set a timer for half the clock period to allow reconstruction
+	 * of the square wave output at the maximum possible frequency.
+	 */
+	avr_cycle_timer_register_usec(avr,
+	                              DS1338_CLK_PERIOD_US / 2,
+	                              (void *) ds1338_virt_clock_tick,
+	                              p);
+
+	printf("DS1338 clock crystal period %duS or %d cycles\n",
+			DS1338_CLK_PERIOD_US,
+			(int)avr_usec_to_cycles(avr, DS1338_CLK_PERIOD_US));
+}
+
+/*
+ * Called when a RESET signal is sent
+ */
+static void
+ds1338_virt_in_hook(struct avr_irq_t * irq,
+                    uint32_t value,
+                    void * param)
+{
+	ds1338_virt_t * p = (ds1338_virt_t*)param;
+	avr_twi_msg_irq_t v;
+	v.u.v = value;
+
+	/*
+	 * If we receive a STOP, check it was meant to us, and reset the transaction
+	 */
+	if (v.u.twi.msg & TWI_COND_STOP) {
+		if (p->selected) {
+			// Wahoo, it was us!
+			if (p->verbose)
+				printf("DS1338 stop\n\n");
+		}
+		/* We should not zero the register address here because read mode uses the last
+		 * register address stored and write mode always overwrites it.
+		 */
+		p->selected = 0;
+		p->reg_selected = 0;
+	}
+
+	/*
+	 * If we receive a start, reset status, check if the slave address is
+	 * meant to be us, and if so reply with an ACK bit
+	 */
+	if (v.u.twi.msg & TWI_COND_START) {
+		//printf("DS1338 start attempt: 0x%02x, mask: 0x%02x,
+		//twi: 0x%02x\n", p->addr_base, p->addr_mask, v.u.twi.addr);
+		p->selected = 0;
+		// Ignore the read write bit
+		if ((v.u.twi.addr >> 1) ==  (DS1338_VIRT_TWI_ADDR >> 1)) {
+			// it's us !
+			if (p->verbose)
+				printf("DS1338 start\n");
+			p->selected = v.u.twi.addr;
+			avr_raise_irq(p->irq + TWI_IRQ_INPUT,
+					avr_twi_irq_msg(TWI_COND_ACK, p->selected, 1));
+		}
+	}
+
+	/*
+	 * If it's a data transaction, first check it is meant to be us (we
+	 * received the correct address and are selected)
+	 */
+	if (p->selected) {
+		// Write transaction
+		if (v.u.twi.msg & TWI_COND_WRITE) {
+			// ACK the byte
+			avr_raise_irq(p->irq + TWI_IRQ_INPUT,
+					avr_twi_irq_msg(TWI_COND_ACK, p->selected, 1));
+			// Write to the selected register (see p13. DS1388 datasheet for details)
+			if (p->reg_selected) {
+				if (p->verbose)
+					printf("DS1338 set register 0x%02x to 0x%02x\n", 
+						p->reg_addr, v.u.twi.data);
+				p->nvram[p->reg_addr] = v.u.twi.data;
+				ds1338_virt_update(p);
+				ds1338_virt_incr_addr(p);
+			// No register selected so select one
+			} else {
+				if (p->verbose)
+					printf("DS1338 select register 0x%02x\n",  v.u.twi.data);
+				p->reg_selected = 1;
+				p->reg_addr = v.u.twi.data;
+			}
+		}
+		// Read transaction
+		if (v.u.twi.msg & TWI_COND_READ) {
+			if (p->verbose)
+				printf("DS1338 READ data at 0x%02x: 0x%02x\n",
+					p->reg_addr, p->nvram[p->reg_addr]);
+			uint8_t data = p->nvram[p->reg_addr];
+			ds1338_virt_incr_addr(p);
+			avr_raise_irq(p->irq + TWI_IRQ_INPUT,
+					avr_twi_irq_msg(TWI_COND_READ, p->selected, data));
+		}
+	}
+}
+
+static const char * _ds1338_irq_names[DS1338_IRQ_COUNT] = {
+	[DS1338_TWI_IRQ_INPUT] = "8>ds1338.out",
+	[DS1338_TWI_IRQ_OUTPUT] = "32<ds1338.in",
+	[DS1338_SQW_IRQ_OUT] = ">ds1338_sqw.out",
+};
+
+/*
+ * Initialise the DS1388 virtual part. This should be called before anything else.
+ */
+void
+ds1338_virt_init(struct avr_t * avr,
+                 ds1338_virt_t * p)
+{
+	memset(p, 0, sizeof(*p));
+	memset(p->nvram, 0x00, sizeof(p->nvram));
+	// Default for day counter. Strangely it runs from 1-7.
+	p->nvram[DS1338_VIRT_DAY] = 1;
+
+	p->avr = avr;
+
+	p->irq = avr_alloc_irq(&avr->irq_pool, 0, DS1338_IRQ_COUNT, _ds1338_irq_names);
+	avr_irq_register_notify(p->irq + TWI_IRQ_OUTPUT, ds1338_virt_in_hook, p);
+
+	// Start with the oscillator disabled, at least until there is some "battery backup"
+	p->nvram[DS1338_VIRT_SECONDS] |=  (1 << DS1338_VIRT_CH);
+
+	ds1338_virt_clock_xtal_init(avr, p);
+}
+
+/*
+ *  "Connect" the IRQs of the DS1338 to the TWI/i2c master of the AVR.
+ */
+void
+ds1338_virt_attach_twi(ds1338_virt_t * p,
+                       uint32_t i2c_irq_base)
+{
+	avr_connect_irq(
+		p->irq + TWI_IRQ_INPUT,
+		avr_io_getirq(p->avr, i2c_irq_base, TWI_IRQ_INPUT));
+	avr_connect_irq(
+		avr_io_getirq(p->avr, i2c_irq_base, TWI_IRQ_OUTPUT),
+		p->irq + TWI_IRQ_OUTPUT);
+}
+
+/*
+ * Optionally "connect" the square wave out IRQ to the AVR.
+ */
+void
+ds1338_virt_attach_square_wave_output(ds1338_virt_t * p,
+                                      ds1338_pin_t * wiring)
+{
+	avr_connect_irq(
+		p->irq + DS1338_SQW_IRQ_OUT,
+	        avr_io_getirq(p->avr, AVR_IOCTL_IOPORT_GETIRQ(wiring->port), wiring->pin));
+}
+
diff --git a/examples/parts/ds1338_virt.h b/examples/parts/ds1338_virt.h
new file mode 100644
index 0000000..41044da
--- /dev/null
+++ b/examples/parts/ds1338_virt.h
@@ -0,0 +1,185 @@
+/*
+	ds1338_virt.h
+
+	Copyright 2014 Doug Szumski <d.s.szumski@gmail.com>
+
+	Based on i2c_eeprom example by:
+
+	Copyright 2008, 2009 Michel Pollet <buserror@gmail.com>
+
+ 	This file is part of simavr.
+
+	simavr is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	simavr is distributed in the hope that 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 simavr.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ *  A virtual DS1338 real time clock which runs on the TWI bus.
+ *
+ *  Features:
+ *
+ *  > External oscillator is synced to the AVR core
+ *  > Square wave output with scalable frequency
+ *  > Leap year correction until 2100
+ *
+ *  Should also work for the pin compatible DS1307 device.
+ */
+
+#ifndef DS1338_VIRT_H_
+#define DS1338_VIRT_H_
+
+#include "sim_irq.h"
+#include "sim_avr.h"
+#include "avr_ioport.h"
+
+// TWI address is fixed
+#define DS1338_VIRT_TWI_ADDR		0xD0
+
+/*
+ * Internal registers. Time is in BCD.
+ * See p10 of the DS1388 datasheet.
+ */
+#define DS1338_VIRT_SECONDS		0x00
+#define DS1338_VIRT_MINUTES		0x01
+#define DS1338_VIRT_HOURS		0x02
+#define DS1338_VIRT_DAY			0x03
+#define DS1338_VIRT_DATE		0x04
+#define DS1338_VIRT_MONTH		0x05
+#define DS1338_VIRT_YEAR		0x06
+#define DS1338_VIRT_CONTROL		0x07
+
+/*
+ * Seconds register flag - oscillator is enabled when
+ * this is set to zero. Undefined on startup.
+ */
+#define DS1338_VIRT_CH			7
+
+/*
+ * 12/24 hour select bit. When high clock is in 12 hour
+ * mode and the AM/PM bit is operational. When low the
+ * AM/PM bit becomes part of the tens counter for the
+ * 24 hour clock.
+ */
+#define DS1338_VIRT_12_24_HR		6
+
+/*
+ * AM/PM flag for 12 hour mode. PM is high.
+ */
+#define DS1338_VIRT_AM_PM		5
+
+/*
+ * Control register flags. See p11 of the DS1388 datasheet.
+ *
+ *  +-----+-----+-----+------------+------+
+ *  | OUT | RS1 | RS0 | SQW_OUTPUT | SQWE |
+ *  +-----+-----+-----+------------+------+
+ *  | X   | 0   | 0   | 1Hz        |    1 |
+ *  | X   | 0   | 1   | 4.096kHz   |    1 |
+ *  | X   | 1   | 0   | 8.192kHz   |    1 |
+ *  | X   | 1   | 1   | 32.768kHz  |    1 |
+ *  | 0   | X   | X   | 0          |    0 |
+ *  | 1   | X   | X   | 1          |    0 |
+ *  +-----+-----+-----+------------+------+
+ *
+ *  OSF : Oscillator stop flag. Set to 1 when oscillator
+ *        is interrupted.
+ *
+ *  SQWE : Square wave out, set to 1 to enable.
+ */
+#define DS1338_VIRT_RS0			0
+#define DS1338_VIRT_RS1			1
+#define DS1338_VIRT_SQWE		4
+#define DS1338_VIRT_OSF			5
+#define DS1338_VIRT_OUT			7
+
+#define DS1338_CLK_FREQ 32768
+#define DS1338_CLK_PERIOD_US (1000000 / DS1338_CLK_FREQ)
+
+// Generic unpack of 8bit BCD register. Don't use on seconds or hours.
+#define UNPACK_BCD(x) (((x) & 0x0F) + ((x) >> 4) * 10)
+
+enum {
+	DS1338_TWI_IRQ_OUTPUT = 0,
+	DS1338_TWI_IRQ_INPUT,
+	DS1338_SQW_IRQ_OUT,
+	DS1338_IRQ_COUNT
+};
+
+/*
+ * Square wave out prescaler modes; see p11 of DS1338 datasheet.
+ */
+enum {
+	DS1338_VIRT_PRESCALER_DIV_32768 = 0,
+	DS1338_VIRT_PRESCALER_DIV_8,
+	DS1338_VIRT_PRESCALER_DIV_4,
+	DS1338_VIRT_PRESCALER_OFF,
+};
+
+/*
+ * Describes the behaviour of the specified BCD register.
+ *
+ * The tens mask is used to avoid config bits present in
+ * the same register.
+ */
+typedef struct bcd_reg_t {
+	uint8_t * reg;
+	uint8_t min_val;
+	uint8_t max_val;
+	uint8_t tens_mask;
+} bcd_reg_t;
+
+// TODO: This should be generic, is also used in ssd1306, and maybe elsewhere..
+typedef struct ds1338_pin_t
+{
+	char port;
+	uint8_t pin;
+} ds1338_pin_t;
+
+/*
+ * DS1338 I2C clock
+ */
+typedef struct ds1338_virt_t {
+	struct avr_t * avr;
+	avr_irq_t * irq;		// irq list
+	uint8_t verbose;
+	uint8_t selected;		// selected address
+	uint8_t reg_selected;		// register selected for write
+	uint8_t reg_addr;		// register pointer
+	uint8_t nvram[64];		// battery backed up NVRAM
+	uint16_t rtc;			// RTC counter
+	uint8_t square_wave;
+} ds1338_virt_t;
+
+void
+ds1338_virt_init(struct avr_t * avr,
+                 ds1338_virt_t * p);
+
+/*
+ * Attach the ds1307 to the AVR's TWI master code,
+ * pass AVR_IOCTL_TWI_GETIRQ(0) for example as i2c_irq_base
+ */
+void
+ds1338_virt_attach_twi(ds1338_virt_t * p,
+                       uint32_t i2c_irq_base);
+
+void
+ds1338_virt_attach_square_wave_output(ds1338_virt_t * p,
+                                      ds1338_pin_t * wiring);
+
+static inline int
+ds1338_get_flag(uint8_t reg, uint8_t bit)
+{
+	return (reg & (1 << bit)) != 0;
+}
+
+#endif /* DS1338_VIRT_H_ */
-- 
2.39.5