--- /dev/null
+/*
+ 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(®);
+ if (!cascade)
+ return;
+
+ /*
+ * Minutes
+ */
+ reg.reg = &p->nvram[DS1338_VIRT_MINUTES];
+ cascade = ds1338_virt_tick_bcd_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(®);
+ 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(®);
+ }
+ 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(®);
+
+ /*
+ * 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(®);
+ 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(®);
+ 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(®);
+}
+
+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));
+}
+
--- /dev/null
+/*
+ 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_ */