/* ds1338_virt.c Copyright 2014 Doug Szumski Based on i2c_eeprom example by: Copyright 2008, 2009 Michel Pollet 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 . */ #include #include #include #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] = "32nvram, 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)); }