Commit 7669349030886c5c8427dc7cf63d9e28be0023ab
authorDoug Szumski <d.s.szumski@gmail.com>
Thu, 4 Feb 2016 19:27:25 +0000 (19:27 +0000)
committerDoug Szumski <d.s.szumski@gmail.com>
Thu, 4 Feb 2016 22:11:55 +0000 (22:11 +0000)
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.

2 files changed:
examples/parts/ds1338_virt.c [new file with mode: 0644]
examples/parts/ds1338_virt.h [new file with mode: 0644]

diff --git a/examples/parts/ds1338_virt.c b/examples/parts/ds1338_virt.c
new file mode 100644 (file)
index 0000000..5381a16
--- /dev/null
@@ -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 (file)
index 0000000..41044da
--- /dev/null
@@ -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_ */