singrdk/base/Kernel/Singularity.Hal.Omap3430/RTClock.cs

1116 lines
53 KiB
C#

///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: RTClock.cs
//
// #define VERBOSE
using Microsoft.Singularity.Io;
using System;
using System.Threading;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.Singularity.Configuration;
namespace Microsoft.Singularity.Hal
{
//
// NB: The RTC is on the Triton2 companion chip and we talk
// to it on the TI 3430 SDP using the 3430's I2C1 @ 0x4b
//
// declare resources for the kernel manifest
[DriverCategory]
[Signature("/arm/ti/3430/I2C")]
public sealed class I2cResources : DriverCategoryDeclaration
{
// register block
// NB: we may need to share access to i2c1 with peripheral drivers
[IoMemoryRange(0, Default = 0x48070000, Length = 0x80, Shared = true)]
internal readonly IoMemoryRange i2cRegisters;
// interrupt
// NB: we may need to share access to i2c1 with peripheral drivers
// i2c1 - interrupt to drive the i2c controller
[IoIrqRange(1, Default = 56, Shared = true)]
internal readonly IoIrqRange i2cIrq;
// sys_nirq - interrupts directly generated by Triton2
[IoIrqRange(2, Default = 7, Shared = false)]
internal readonly IoIrqRange tritonIrq;
// CTR will create the rest of this class:
public I2cResources(IoConfig config)
{
// dynamic resources
i2cRegisters = (IoMemoryRange)config.DynamicRanges[0];
i2cIrq = (IoIrqRange)config.DynamicRanges[1];
tritonIrq = (IoIrqRange)config.DynamicRanges[2];
}
}
//
// Wrap up control of the I2C bus to allow us to easily access the RTC
//
[CLSCompliant(false)]
internal sealed class I2cOmap3430
{
// size of registers in block
const int Omap3430_I2C1_RegisterSize = 0x00000058; // size of registers in block
// clock frequency
const uint Omap3430_TIMER1_Freq = 32768;
// registers
const uint Omap3430_I2C_REV = 0x00000000; // i2c block revision
const uint Omap3430_I2C_IE = 0x00000004; // interrupt enable
const uint Omap3430_I2C_STAT = 0x00000008; // [interrupt] status
const uint Omap3430_I2C_WE = 0x0000000c; // wakeup enable
const uint Omap3430_I2C_SYSS = 0x00000010; // [non-interrupt] status
const uint Omap3430_I2C_BUF = 0x00000014; // FIFO control
const uint Omap3430_I2C_CNT = 0x00000018; // FIFO data count
const uint Omap3430_I2C_DATA = 0x0000001c; // FIFO data
const uint Omap3430_I2C_SYSC = 0x00000020; // L4 control
const uint Omap3430_I2C_CON = 0x00000024; // control
const uint Omap3430_I2C_OA0 = 0x00000028; // own address 0
const uint Omap3430_I2C_SA = 0x0000002c; // slave address
const uint Omap3430_I2C_PSC = 0x00000030; // prescale sampling clock
const uint Omap3430_I2C_SCLL = 0x00000034; // SCL low time when master
const uint Omap3430_I2C_SCLH = 0x00000038; // SCL high time when master
const uint Omap3430_I2C_SYSTEST = 0x0000003c; // test-mode register
const uint Omap3430_I2C_BUFSTAT = 0x00000040; // FIFO status
const uint Omap3430_I2C_OA1 = 0x00000044; // own address 1
const uint Omap3430_I2C_OA2 = 0x00000048; // own address 2
const uint Omap3430_I2C_OA3 = 0x0000004c; // own address 3
const uint Omap3430_I2C_ACTOA = 0x00000050; // active own addresses
const uint Omap3430_I2C_SBLOCK = 0x00000054; // slave i2c bus locking
// revision register fields
const uint Omap3430_I2C_REV_Mask = 0x000000ff; // i2c block revision
const int Omap3430_I2C_REV_Shift = 0;
const uint Omap3430_I2C_REV_Major_Mask = 0x000000f0; // i2c block major revision
const int Omap3430_I2C_REV_Major_Shift = 4;
const uint Omap3430_I2C_REV_Minor_Mask = 0x0000000f; // i2c block minor revision
const int Omap3430_I2C_REV_Minor_Shift = 0;
// interrupt enable register fields
const uint Omap3430_I2C_IE_XDR_IE = 0x00004000; // transmit draining
const uint Omap3430_I2C_IE_RDR_IE = 0x00002000; // receive draining
const uint Omap3430_I2C_IE_AAS_IE = 0x00000200; // addressed as slave
const uint Omap3430_I2C_IE_BF_IE = 0x00000100; // bus free
const uint Omap3430_I2C_IE_AERR_IE = 0x00000080; // access error
const uint Omap3430_I2C_IE_STC_IE = 0x00000040; // start condition
const uint Omap3430_I2C_IE_GC_IE = 0x00000020; // general call
const uint Omap3430_I2C_IE_XRDY_IE = 0x00000010; // transmit data ready
const uint Omap3430_I2C_IE_RRDY_IE = 0x00000008; // receive data ready
const uint Omap3430_I2C_IE_ARDY_IE = 0x00000004; // register access ready
const uint Omap3430_I2C_IE_NACK_IE = 0x00000002; // no acknowledgment
const uint Omap3430_I2C_IE_AL_IE = 0x00000001; // arbitration lost
// [interrupt] status register fields
const uint Omap3430_I2C_STAT_XDR = 0x00004000; // transmit draining
const uint Omap3430_I2C_STAT_RDR = 0x00002000; // receive draining
const uint Omap3430_I2C_STAT_BB = 0x00001000; // bus busy
const uint Omap3430_I2C_STAT_ROVR = 0x00000800; // read overrun
const uint Omap3430_I2C_STAT_XUDF = 0x00000400; // transmit underflow
const uint Omap3430_I2C_STAT_AAS = 0x00000200; // addressed as slave
const uint Omap3430_I2C_STAT_BF = 0x00000100; // bus free
const uint Omap3430_I2C_STAT_AERR = 0x00000080; // access error
const uint Omap3430_I2C_STAT_STC = 0x00000040; // start condition
const uint Omap3430_I2C_STAT_GC = 0x00000020; // general call
const uint Omap3430_I2C_STAT_XRDY = 0x00000010; // transmit data ready
const uint Omap3430_I2C_STAT_RRDY = 0x00000008; // receive data ready
const uint Omap3430_I2C_STAT_ARDY = 0x00000004; // register access ready
const uint Omap3430_I2C_STAT_NACK = 0x00000002; // no acknowledgment
const uint Omap3430_I2C_STAT_AL = 0x00000001; // arbitration lost
const uint Omap3430_I2C_STAT_Isr_Mask = 0x000063ff; // ISR mask
// wakeup enable register fields
const uint Omap3430_I2C_WE_XDR_WE = 0x00004000; // transmit draining
const uint Omap3430_I2C_WE_RDR_WE = 0x00002000; // receive draining
const uint Omap3430_I2C_WE_AAS_WE = 0x00000200; // addressed as slave
const uint Omap3430_I2C_WE_BF_WE = 0x00000100; // bus free
const uint Omap3430_I2C_WE_STC_WE = 0x00000040; // start condition
const uint Omap3430_I2C_WE_GC_WE = 0x00000020; // general call
const uint Omap3430_I2C_WE_DRDY_WE = 0x00000008; // transmit/receive data ready
const uint Omap3430_I2C_WE_ARDY_WE = 0x00000004; // register access ready
const uint Omap3430_I2C_WE_NACK_WE = 0x00000002; // no acknowledgment
const uint Omap3430_I2C_WE_AL_WE = 0x00000001; // arbitration lost
// [non-interrupt] status register fields
const uint Omap3430_I2C_SSYS_RDONE = 0x00000001; // reset done
// FIFO control register fields
const uint Omap3430_I2C_BUF_RDMA_EN = 0x00008000; // receive DMA enable
const uint Omap3430_I2C_BUF_RXFIFO_CLR = 0x00004000; // receive FIFO clear
const uint Omap3430_I2C_BUF_RTRSH_Mask = 0x00003f00; // receive threshold
const int Omap3430_I2C_BUF_RTRSH_Shift = 8;
const uint Omap3430_I2C_BUF_XDMA_EN = 0x00000080; // transmit DMA enable
const uint Omap3430_I2C_BUF_TXFIFO_CLR = 0x00000040; // transmit FIFO clear
const uint Omap3430_I2C_BUF_XTRSH_Mask = 0x0000003f; // transmit threshold
const int Omap3430_I2C_BUF_XTRSH_Shift = 0;
// FIFO data count register fields
const uint Omap3430_I2C_CNT_Mask = 0x0000ffff; // FIFO count
const int Omap3430_I2C_CNT_Shift = 0;
// FIFO data register fields
const uint Omap3430_I2C_DATA_Mask = 0x000000ff; // FIFO data
const int Omap3430_I2C_DATA_Shift = 0;
// L4 control register fields
const uint Omap3430_I2C_SYSC_CLOCKACTIVIY_Function = 0x00000200; // function clk on in idle
const uint Omap3430_I2C_SYSC_CLOCKACTIVIY_Interface = 0x00000100; // interface clk on in idle
const uint Omap3430_I2C_SYSC_IDLEMODE_Force = 0x00000000; // force idle mode
const uint Omap3430_I2C_SYSC_IDLEMODE_None = 0x00000080; // never idle
const uint Omap3430_I2C_SYSC_IDLEMODE_Smart = 0x00000100; // smart idling
const uint Omap3430_I2C_SYSC_IDLEMODE_Mask = 0x00000180; // idle mode mask
const uint Omap3430_I2C_SYSC_ENAWAKEUP = 0x00000004; // wakeup enable
const uint Omap3430_I2C_SYSC_SRST = 0x00000002; // software reset
const uint Omap3430_I2C_SYSC_AUTOIDLE = 0x00000001; // auto-idle enable
// control register fields
const uint Omap3430_I2C_CON_I2C_EN = 0x00008000; // i2c module enable
const uint Omap3430_I2C_CON_OPMODE_Standard_Fast = 0x00000000; // i2c standard/fast mode
const uint Omap3430_I2C_CON_OPMODE_High_Speed = 0x00001000; // i2c high speed mode
const uint Omap3430_I2C_CON_OPMODE_SCCB = 0x00002000; // SCCB mode
const uint Omap3430_I2C_CON_OPMODE_Mask = 0x00003000; // receive FIFO clear
const uint Omap3430_I2C_CON_STB = 0x00000800; // start byte mode (master)
const uint Omap3430_I2C_CON_MST = 0x00000400; // master/slave select
const uint Omap3430_I2C_CON_TRX = 0x00000200; // transmit/receive select
const uint Omap3430_I2C_CON_XSA = 0x00000100; // expanded slave addresses
const uint Omap3430_I2C_CON_XOA0 = 0x00000080; // expand own address 0
const uint Omap3430_I2C_CON_XOA1 = 0x00000040; // expand own address 1
const uint Omap3430_I2C_CON_XOA2 = 0x00000020; // expand own address 2
const uint Omap3430_I2C_CON_XOA3 = 0x00000010; // expand own address 3
const uint Omap3430_I2C_CON_STP = 0x00000002; // stop condition (master)
const uint Omap3430_I2C_CON_STT = 0x00000001; // start condition (master)
// own address 0 register fields
const uint Omap3430_I2C_OA0_MCODE_Mask = 0x0000e000; // master code
const int Omap3430_I2C_OA0_MCODE_Shift = 13;
// own address 1 register fields
// own address 2 register fields
// own address 3 register fields
const uint Omap3430_I2C_OAn_OA_Mask = 0x000003ff; // own address
const int Omap3430_I2C_OAn_OA_Shift = 0;
// slave address register fields
const uint Omap3430_I2C_SA_SA_Mask = 0x000003ff; // slave address
const int Omap3430_I2C_SA_SA_Shift = 0;
// prescale sampling clock register fields
const uint Omap3430_I2C_PSC_PSC_Mask = 0x0000000f; // prescale sampling clock
const int Omap3430_I2C_PSC_PSC_Shift = 0;
// SCL low time when master register fields
const uint Omap3430_I2C_SCLL_HSSCLL_Mask = 0x0000ff00; // SCL low time when master in h/s mode
const int Omap3430_I2C_SCLL_HSSCLL_Shift = 8;
const uint Omap3430_I2C_SCLL_SCLL_Mask = 0x000000ff; // SCL low time when master in standard/fast mode
const int Omap3430_I2C_SCLL_SCLL_Shift = 0;
// SCL high time when master register fields
const uint Omap3430_I2C_SCLH_HSSCLH_Mask = 0x0000ff00; // SCL high time when master in h/s mode
const int Omap3430_I2C_SCLH_HSSCLH_Shift = 8;
const uint Omap3430_I2C_SCLH_SCLH_Mask = 0x000000ff; // SCL high time when master in standard/fast mode
const int Omap3430_I2C_SCLH_SCLH_Shift = 0;
// test-mode register register fields
const uint Omap3430_I2C_SYSTEST_ST_EN = 0x00008000; // system test enable
const uint Omap3430_I2C_SYSTEST_FREE = 0x00004000; // free-running mode
const uint Omap3430_I2C_SYSTEST_TMODE_Functional = 0x00000000; // functional test
const uint Omap3430_I2C_SYSTEST_TMODE_Counters = 0x00002000; // counters test
const uint Omap3430_I2C_SYSTEST_TMODE_Loopback_Io = 0x00003000; // loopback / sda/scl io test
const uint Omap3430_I2C_SYSTEST_TMODE_Mask = 0x00003000; // test mode mask
const uint Omap3430_I2C_SYSTEST_SSB = 0x00000800; // set status bits
const uint Omap3430_I2C_SYSTEST_SCCBE_O = 0x00000010; // SCCBE line drive output
const uint Omap3430_I2C_SYSTEST_SCL_I = 0x00000008; // sense SCL line input
const uint Omap3430_I2C_SYSTEST_SCL_O = 0x00000004; // drive SCL line output
const uint Omap3430_I2C_SYSTEST_SDA_I = 0x00000002; // sense SDA line input
const uint Omap3430_I2C_SYSTEST_SDA_O = 0x00000001; // drive SDA line output
// FIFO status register fields
const uint Omap3430_I2C_BUFSTAT_FIFODEPTH_Mask = 0x0000c000; // lg2(FIFO depth) - 3
const int Omap3430_I2C_BUFSTAT_FIFODEPTH_Shift = 14;
const uint Omap3430_I2C_BUFSTAT_RXSTAT_Mask = 0x00003f00; // receive count
const int Omap3430_I2C_BUFSTAT_RXSTAT_Shift = 8;
const uint Omap3430_I2C_BUFSTAT_TXSTAT_Mask = 0x0000003f; // transmit count
const int Omap3430_I2C_BUFSTAT_TXSTAT_Shift = 0;
// active own addresses register fields
const uint Omap3430_I2C_ACTOA_OA3_ACT = 0x00000008; // own addresses 3 active
const uint Omap3430_I2C_ACTOA_OA2_ACT = 0x00000004; // own addresses 2 active
const uint Omap3430_I2C_ACTOA_OA1_ACT = 0x00000002; // own addresses 1 active
const uint Omap3430_I2C_ACTOA_OA0_ACT = 0x00000001; // own addresses 0 active
// slave i2c bus locking register fields
const uint Omap3430_I2C_SBLOCK_OA3_EN = 0x00000008; // i2c clk blocked for own address 3
const uint Omap3430_I2C_SBLOCK_OA2_EN = 0x00000004; // i2c clk blocked for own address 2
const uint Omap3430_I2C_SBLOCK_OA1_EN = 0x00000002; // i2c clk blocked for own address 1
const uint Omap3430_I2C_SBLOCK_OA0_EN = 0x00000001; // i2c clk blocked for own address 0
// resource allocation
private PnpConfig config;
private byte irq;
private Pic pic;
// registers
private IoMappedPort i2c_rev; // i2c block revision
private IoMappedPort i2c_ie; // interrupt enable
private IoMappedPort i2c_stat; // [interrupt] status
private IoMappedPort i2c_we; // wakeup enable
private IoMappedPort i2c_syss; // [non-interrupt] status
private IoMappedPort i2c_buf; // FIFO control
private IoMappedPort i2c_cnt; // FIFO data count
private IoMappedPort i2c_data; // FIFO data
private IoMappedPort i2c_sysc; // L4 control
private IoMappedPort i2c_con; // control
private IoMappedPort i2c_oa0; // own address 0
private IoMappedPort i2c_sa; // slave address
private IoMappedPort i2c_psc; // prescale sampling clock
private IoMappedPort i2c_scll; // SCL low time when master
private IoMappedPort i2c_sclh; // SCL high time when master
private IoMappedPort i2c_systest; // test-mode register
private IoMappedPort i2c_bufstat; // FIFO status
private IoMappedPort i2c_oa1; // own address 1
private IoMappedPort i2c_oa2; // own address 2
private IoMappedPort i2c_oa3; // own address 3
private IoMappedPort i2c_actoa; // active own addresses
private IoMappedPort i2c_sblock; // slave i2c bus locking
// Constructor
internal I2cOmap3430(I2cResources ir, Pic pic)
{
DebugStub.Print("I2cOmap3430: create\n");
this.config = config;
this.pic = pic;
this.irq = ir.i2cIrq.Irq;
IoMemory i2cRegisters = ir.i2cRegisters
.MemoryAtOffset(0, Omap3430_I2C1_RegisterSize, Access.ReadWrite);
i2c_rev = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_REV, 4, Access.Read);
i2c_ie = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_IE, 4, Access.ReadWrite);
i2c_stat = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_STAT, 4, Access.ReadWrite);
i2c_we = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_WE, 4, Access.ReadWrite);
i2c_syss = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_SYSS, 4, Access.Read);
i2c_buf = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_BUF, 4, Access.ReadWrite);
i2c_cnt = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_CNT, 4, Access.ReadWrite);
i2c_data = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_DATA, 4, Access.ReadWrite);
i2c_sysc = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_SYSC, 4, Access.ReadWrite);
i2c_con = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_CON, 4, Access.ReadWrite);
i2c_oa0 = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_OA0, 4, Access.ReadWrite);
i2c_sa = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_SA, 4, Access.ReadWrite);
i2c_psc = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_PSC, 4, Access.ReadWrite);
i2c_scll = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_SCLL, 4, Access.ReadWrite);
i2c_sclh = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_SCLH, 4, Access.ReadWrite);
i2c_systest = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_SYSTEST, 4, Access.ReadWrite);
i2c_bufstat = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_BUFSTAT, 4, Access.Read);
i2c_oa1 = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_OA1, 4, Access.ReadWrite);
i2c_oa2 = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_OA2, 4, Access.ReadWrite);
i2c_oa3 = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_OA3, 4, Access.ReadWrite);
i2c_actoa = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_ACTOA, 4, Access.Read);
i2c_sblock = i2cRegisters.MappedPortAtOffset(Omap3430_I2C_SBLOCK, 4, Access.ReadWrite);
}
public byte Initialize()
{
#if VERBOSE
Tracing.Log(Tracing.Debug, "I2COmap3430.Initialize()");
#endif
// Take i2c controller into reset
ushort val16 = i2c_con.Read16();
val16 = (ushort) (val16 & ~((ushort)Omap3430_I2C_CON_I2C_EN));
IoResult result = i2c_con.Write16NoThrow(val16);
DebugStub.Assert(IoResult.Success == result);
// Configure internal sampling frequency for 12Mhz.
result = i2c_psc.Write8NoThrow((byte)((0x7 << Omap3430_I2C_PSC_PSC_Shift) & Omap3430_I2C_PSC_PSC_Mask));
DebugStub.Assert(IoResult.Success == result);
// Configure SCL high/low times for 100 kb/s
result = i2c_scll.Write8NoThrow((byte)((0x36 << Omap3430_I2C_SCLL_SCLL_Shift) & Omap3430_I2C_SCLL_SCLL_Mask));
DebugStub.Assert(IoResult.Success == result);
result = i2c_sclh.Write8NoThrow((byte)((0x38 << Omap3430_I2C_SCLH_SCLH_Shift) & Omap3430_I2C_SCLH_SCLH_Mask));
DebugStub.Assert(IoResult.Success == result);
// Set own address address register
result = i2c_oa0.Write16NoThrow((ushort)((0x21 << Omap3430_I2C_OAn_OA_Shift) & Omap3430_I2C_OAn_OA_Mask));
DebugStub.Assert(IoResult.Success == result);
// Take i2c controller out of reset
result = i2c_con.Read16NoThrow(out val16);
DebugStub.Assert(IoResult.Success == result);
val16 |= (ushort)Omap3430_I2C_CON_I2C_EN;
result = i2c_con.Write16NoThrow(val16);
DebugStub.Assert(IoResult.Success == result);
// Enable i2c interrupts
pic.EnableIrq(irq);
return pic.IrqToInterrupt(irq);
}
[NoHeapAllocation]
public bool MasterSend(byte slaveAddress, byte[] data, bool genStart, bool genStop)
{
return MasterCommon(true, slaveAddress, data, genStart, genStop);
}
[NoHeapAllocation]
public bool MasterReceive(byte slaveAddress, byte[] data, bool genStart, bool genStop)
{
return MasterCommon(false, slaveAddress, data, genStart, genStop);
}
[NoHeapAllocation]
private bool MasterSetup(
bool xmit,
byte slaveAddress,
ushort length,
bool genStart,
bool genStop
)
{
ushort control;
byte retry = 100;
IoResult result = i2c_con.Write16NoThrow((ushort)Omap3430_I2C_CON_I2C_EN);
DebugStub.Assert(IoResult.Success == result);
// Set slave address
result = i2c_sa.Write8NoThrow(slaveAddress);
DebugStub.Assert(IoResult.Success == result);
// Set transfer length
result = i2c_cnt.Write16NoThrow(length);
DebugStub.Assert(IoResult.Success == result);
// Clear status
result = i2c_stat.Write16NoThrow((ushort)(Omap3430_I2C_STAT_ARDY | Omap3430_I2C_STAT_NACK | Omap3430_I2C_STAT_AL));
DebugStub.Assert(IoResult.Success == result);
// Read bus status
ushort status;
result = i2c_stat.Read16NoThrow(out status);
DebugStub.Assert(IoResult.Success == result);
// Wait for free bus
while ((status & Omap3430_I2C_STAT_BB) != 0) {
if (--retry == 0) {
return false;
}
result = i2c_stat.Read16NoThrow(out status);
DebugStub.Assert(IoResult.Success == result);
}
control = (ushort)(
Omap3430_I2C_CON_I2C_EN | // enable i2c
Omap3430_I2C_CON_MST | // i2c master
(xmit ? Omap3430_I2C_CON_TRX : 0) | // transmit/receive
(genStart ? Omap3430_I2C_CON_STT : 0) | // start
(genStop ? Omap3430_I2C_CON_STP : 0) // stop
);
result = i2c_con.Write16NoThrow(control);
DebugStub.Assert(IoResult.Success == result);
return true;
}
[NoHeapAllocation]
private bool MasterTransfer(
bool xmit,
byte[] data
)
{
ushort length = (ushort)data.Length;
ushort index = 0;
for (;;)
{
ushort status;
ushort bytesAvailable;
// Read bus status
IoResult result = i2c_stat.Read16NoThrow(out status);
DebugStub.Assert(IoResult.Success == result);
if ((status & (Omap3430_I2C_STAT_NACK | Omap3430_I2C_STAT_AL)) != 0) {
return false;
}
if ((status & (Omap3430_I2C_STAT_ARDY)) != 0) {
DebugStub.Assert(length == 0);
return true;
}
if (xmit) {
if ((status & Omap3430_I2C_STAT_XDR) != 0) {
ushort bufferStatus;
// Read FIFO status
result = i2c_bufstat.Read16NoThrow(out bufferStatus);
DebugStub.Assert(IoResult.Success == result);
bytesAvailable = (ushort)((bufferStatus & Omap3430_I2C_BUFSTAT_TXSTAT_Mask) >> Omap3430_I2C_BUFSTAT_TXSTAT_Shift);
while (bytesAvailable-- != 0) {
result = i2c_data.Write8NoThrow(data[index]);
DebugStub.Assert(IoResult.Success == result);
index++;
length--;
}
}
if ((status & Omap3430_I2C_STAT_XRDY) != 0) {
ushort bufferControl;
// Read FIFO threshold
result = i2c_buf.Read16NoThrow(out bufferControl);
DebugStub.Assert(IoResult.Success == result);
bytesAvailable = (ushort)((bufferControl & Omap3430_I2C_BUF_XTRSH_Mask) >> Omap3430_I2C_BUF_XTRSH_Shift);
bytesAvailable += 1;
while (bytesAvailable-- != 0) {
result = i2c_data.Write8NoThrow(data[index]);
DebugStub.Assert(IoResult.Success == result);
index++;
length--;
}
}
} else {
if ((status & Omap3430_I2C_STAT_RDR) != 0) {
ushort bufferStatus;
// Read FIFO status
result = i2c_bufstat.Read16NoThrow(out bufferStatus);
DebugStub.Assert(IoResult.Success == result);
bytesAvailable = (ushort)((bufferStatus & Omap3430_I2C_BUFSTAT_RXSTAT_Mask) >> Omap3430_I2C_BUFSTAT_RXSTAT_Shift);
while (bytesAvailable-- != 0) {
result = i2c_data.Read8NoThrow(out data[index]);
DebugStub.Assert(IoResult.Success == result);
index++;
length--;
}
}
if ((status & Omap3430_I2C_STAT_RRDY) != 0) {
ushort bufferControl;
// Read FIFO threshold
result = i2c_buf.Read16NoThrow(out bufferControl);
DebugStub.Assert(IoResult.Success == result);
bytesAvailable = (ushort)((bufferControl & Omap3430_I2C_BUF_RTRSH_Mask) >> Omap3430_I2C_BUF_RTRSH_Shift);
bytesAvailable += 1;
while (bytesAvailable-- != 0) {
result = i2c_data.Read8NoThrow(out data[index]);
DebugStub.Assert(IoResult.Success == result);
index++;
length--;
}
}
}
result = i2c_stat.Write16NoThrow(status);
DebugStub.Assert(IoResult.Success == result);
}
}
[NoHeapAllocation]
private bool MasterCommon(
bool xmit,
byte slaveAddress,
byte[] data,
bool genStart,
bool genStop
)
{
int length = data.Length;
DebugStub.Assert(length <= 0xffffu);
int retry = 10;
while (retry-- != 0) {
MasterSetup(xmit, slaveAddress, (ushort)length, genStart, genStop);
if (MasterTransfer(xmit, data)) {
return true;
}
}
return false;
}
public void Finalize()
{
pic.DisableIrq(irq);
}
}
/// <remarks>
/// Class <c>RTClock</c> represents the system Real-Time Clock.
///
/// RTC chips on ARM are provided by other sources, such as
/// the TWL4030 companion chip. The TWL 4030 RTC provides a
/// timing resolution of 1 second, and can only generate
/// periodic interrupts that frequently.
///
/// By combining information from the <c>TimerOmap3430</c>
/// programmable timer, time can be read with a resolution
/// of xxx microseconds.
/// </remarks>
[CLSCompliant(false)]
internal sealed class RTClockOmap3430
{
private PnpConfig config;
private Pic pic;
private byte irq;
private TimerOmap3430 timer = null;
private I2cOmap3430 i2c1;
private byte[] i2cReadBuffer = new byte[1];
private byte[] i2cWriteBuffer = new byte[2];
const int InterruptGapTicks = 10000000; // units of 100ns
const uint maxAttempts = 1000000;
private RtcPitState rps = null;
private long rtcBootTime;
private volatile uint irqCount = 0;
// Variables for fast GetKernelTicks implementation that uses
// TSC when it is deemed safe.
bool noisyTimer; // Running on VPC? Disable optimization
long pmccntrSnapshot; // TSC value at sync point
long ticksSnapshot; // Kernel ticks at sync point
long lastKernelTicks; // Last kernel ticks reported
bool pmccntrSnapshotValid = false;
bool initedFastTime = false;
int tickScale;
const int tickRoll = 24;
// Triton2 i2c bus addresses
const byte TWL4030_ID1_I2c_Address = 0x48; // i2c address for ID1
const byte TWL4030_ID2_I2c_Address = 0x49; // i2c address for ID2
const byte TWL4030_ID3_I2c_Address = 0x4a; // i2c address for ID3
const byte TWL4030_ID4_I2c_Address = 0x4b; // i2c address for ID4
// Triton2 register addresses
const byte TWL4030_SECONDS_REG = 0x1c; // BCD seconds, 0 -> 59
const byte TWL4030_MINUTES_REG = 0x1d; // BCD minutes, 0 -> 59
const byte TWL4030_HOURS_REG = 0x1e; // BCD hours, 0 -> 23 or 0 -> 11 (am/pm)
const byte TWL4030_DAYS_REG = 0x1f; // BCD days, 0 -> 31
const byte TWL4030_MONTHS_REG = 0x20; // BCD month, 1 -> 12
const byte TWL4030_YEARS_REG = 0x21; // BCD year, [20]00 -> [20]99
const byte TWL4030_WEEKS_REG = 0x22; // BCD day of week, 0 -> 6
const byte TWL4030_ALARM_SECONDS_REG = 0x23; // Alarm time, BCD seconds, 0 -> 59
const byte TWL4030_ALARM_MINUTES_REG = 0x24; // Alarm time, BCD minutes, 0 -> 59
const byte TWL4030_ALARM_HOURS_REG = 0x25; // Alarm time, BCD hours, 0 -> 23 or 0 -> 11 (am/pm)
const byte TWL4030_ALARM_DAYS_REG = 0x26; // Alarm time, BCD days, 0 -> 31
const byte TWL4030_ALARM_MONTHS_REG = 0x27; // Alarm time, BCD month, 1 -> 12
const byte TWL4030_ALARM_YEARS_REG = 0x28; // Alarm time, BCD year, [20]00 -> [20]99
const byte TWL4030_RTC_CTRL_REG = 0x29; // control
const byte TWL4030_RTC_STATUS_REG = 0x2a; // status
const byte TWL4030_RTC_INTERRUPTS_REG = 0x2b; // interrupt status
const byte TWL4030_RTC_COMP_LSB_REG = 0x2c; // clock compensation, comp_reg[15:8], in (1/32768)s
const byte TWL4030_RTC_COMP_MSB_REG = 0x2d; // clock compensation, comp_reg[7:0], in (1/32768)s
const byte TWL4030_PIH_ISR_P1 = 0x81; // programmable interrupt handler ISR for irq1
const byte TWL4030_PIH_ISR_P2 = 0x82; // programmable interrupt handler ISR for irq2
const byte TWL4030_PIH_SIR = 0x83; // software interrupt ISR
const byte TWL4030_PWR_ISR1 = 0x2e; // power ISR
const byte TWL4030_PWR_IMR1 = 0x2f; // power interrupt mask
const byte TWL4030_PWR_EDR1 = 0x33; // power edge detection
const byte TWL4030_PWR_SIH_CTRL = 0x35; // power secondary interrupt handler control
// Triton2 register fields
const byte TWL4030_SECONDS_REG_Mask = 0x7f; // BCD seconds, 0 -> 59
const byte TWL4030_MINUTES_REG_Mask = 0x7f; // BCD minutes, 0 -> 59
const byte TWL4030_HOURS_REG_Mask = 0x3f; // BCD hours, 0 -> 23 or 0 -> 11 (am/pm)
const byte TWL4030_HOURS_REG_PM_NAM = 0x80; // pm/~am flag (am/pm mode)
const byte TWL4030_DAYS_REG_Mask = 0x3f; // BCD days, 0 -> 31
const byte TWL4030_MONTHS_REG_Mask = 0x1f; // BCD month, 1 -> 12
const byte TWL4030_YEARS_REG_Mask = 0xff; // BCD year, [20]00 -> [20]99
const byte TWL4030_WEEKS_REG_Mask = 0x07; // BCD day of week, 0 -> 6
const byte TWL4030_ALARM_SECONDS_REG_Mask = 0x7f; // Alarm time, BCD seconds, 0 -> 59
const byte TWL4030_ALARM_MINUTES_REG_Mask = 0x7f; // Alarm time, BCD minutes, 0 -> 59
const byte TWL4030_ALARM_HOURS_REG_Mask = 0x3f; // Alarm time, BCD hours, 0 -> 23 or 0 -> 11 (am/pm)
const byte TWL4030_ALARM_HOURS_REG_PM_NAM = 0x80; // Alarm time, pm/~am flag (am/pm mode)
const byte TWL4030_ALARM_DAYS_REG_Mask = 0x3f; // Alarm time, BCD days, 0 -> 31
const byte TWL4030_ALARM_MONTHS_REG_Mask = 0x1f; // Alarm time, BCD month, 1 -> 12
const byte TWL4030_ALARM_YEARS_REG_Mask = 0xff; // Alarm time, BCD year, [20]00 -> [20]99
const byte TWL4030_RTC_CTRL_REG_GET_TIME = 0x40; // latch the time into the alarm registers
const byte TWL4030_RTC_CTRL_REG_SET_32_COUNTER = 0x20; // set the 32kHz counter from comp_reg
const byte TWL4030_RTC_CTRL_REG_TEST_MODE = 0x10; // test mode
const byte TWL4030_RTC_CTRL_REG_MODE_12_24 = 0x08; // enable am/pm mode (when set)
const byte TWL4030_RTC_CTRL_REG_AUTO_COMP = 0x04; // enable auto-compensation, as set in TWL4030_RTC_COMP_*_REG
const byte TWL4030_RTC_CTRL_REG_ROUND_30S = 0x02; // round time to closest minute on the next second
const byte TWL4030_RTC_CTRL_REG_STOP_RTC = 0x01; // ~RTC frozen (frozen when low)
const byte TWL4030_RTC_STATUS_REG_POWER_UP = 0x80; // RTC reset
const byte TWL4030_RTC_STATUS_REG_ALARM = 0x40; // alarm interrupt
const byte TWL4030_RTC_STATUS_REG_1D_EVENT = 0x20; // day interval
const byte TWL4030_RTC_STATUS_REG_1H_EVENT = 0x10; // hour interval
const byte TWL4030_RTC_STATUS_REG_1M_EVENT = 0x08; // minute interval
const byte TWL4030_RTC_STATUS_REG_1S_EVENT = 0x04; // second interval
const byte TWL4030_RTC_STATUS_REG_RUN = 0x02; // RTC running
const byte TWL4030_RTC_INTERRUPTS_REG_IT_ALARM = 0x08; // enable alarm interrupt
const byte TWL4030_RTC_INTERRUPTS_REG_IT_TIMER = 0x04; // enable periodic interrupt
const byte TWL4030_RTC_INTERRUPTS_REG_EVERY_Mask = 0x03; // interrupt period mask
const byte TWL4030_RTC_INTERRUPTS_REG_EVERY_Day = 0x03; // daily periodic interrupt
const byte TWL4030_RTC_INTERRUPTS_REG_EVERY_Hour = 0x02; // hourly periodic interrupt
const byte TWL4030_RTC_INTERRUPTS_REG_EVERY_Minute = 0x01; // periodic interrupt every minute
const byte TWL4030_RTC_INTERRUPTS_REG_EVERY_Second = 0x00; // periodic interrupt every second
const byte TWL4030_PIH_ISR_P1_PIH_ISR5 = 0x81; // power management interrupt
const byte TWL4030_PWR_ISR1_RTC_IT = 0x08; // RTC interrupt
const byte TWL4030_PWR_IMR1_RTC_IT = 0x08; // RTC interrupt mask
const byte TWL4030_PWR_EDR1_RTC_IT_RISING = 0x80; // RTC interrupt rising edge detect
const byte TWL4030_PWR_EDR1_RTC_IT_FALLING = 0x40; // RTC interrupt falling edge detect
const byte TWL4030_PWR_SIH_CTRL_COR = 0x04; // ~clear on read (clear on write when set)
// Constructor
internal RTClockOmap3430(PnpConfig config, Pic pic, TimerOmap3430 timer)
{
DebugStub.Print("RTClock: create\n");
I2cResources ir = new I2cResources(config);
this.config = config;
this.pic = pic;
this.irq = ir.tritonIrq.Irq; //sys_nirq
this.timer = timer;
this.i2c1 = new I2cOmap3430(ir, pic);
}
[NoHeapAllocation]
private bool ReadTritonRegister(byte i2c_addr, byte triton_addr, out byte value)
{
this.i2cReadBuffer[0] = triton_addr;
bool success = i2c1.MasterSend(i2c_addr, this.i2cReadBuffer, true, false);
if (success) {
success = i2c1.MasterReceive(i2c_addr, this.i2cReadBuffer, false, true);
}
// NB: value is trash if we don't succeed. We can't raise an exception, etc.
// as we can't allocate on the heap!
value = this.i2cReadBuffer[0];
return success;
}
[NoHeapAllocation]
private bool WriteTritonRegister(byte i2c_addr, byte triton_addr, byte value)
{
this.i2cWriteBuffer[0] = triton_addr;
this.i2cWriteBuffer[1] = value;
return i2c1.MasterSend(i2c_addr, this.i2cReadBuffer, true, true);
}
[NoHeapAllocation]
private byte ReadRtc(byte triton_addr)
{
byte value;
bool success = ReadTritonRegister(TWL4030_ID4_I2c_Address, triton_addr, out value);
DebugStub.Assert(success);
return value;
}
[NoHeapAllocation]
private void WriteRtc(byte triton_addr, byte value)
{
bool success = WriteTritonRegister(TWL4030_ID4_I2c_Address, triton_addr, value);
DebugStub.Assert(success);
}
[NoHeapAllocation]
private byte ReadPih(byte triton_addr)
{
byte value;
bool success = ReadTritonRegister(TWL4030_ID2_I2c_Address, triton_addr, out value);
DebugStub.Assert(success);
return value;
}
[NoHeapAllocation]
private void WritePih(byte triton_addr, byte value)
{
bool success = WriteTritonRegister(TWL4030_ID2_I2c_Address, triton_addr, value);
DebugStub.Assert(success);
}
[NoHeapAllocation]
public void ClearInterrupt()
{
byte status = ReadRtc(TWL4030_RTC_STATUS_REG);
if (((ReadPih(TWL4030_PIH_ISR_P1) & TWL4030_PIH_ISR_P1_PIH_ISR5) == 0) ||
((ReadPih(TWL4030_PWR_ISR1) & TWL4030_PWR_ISR1_RTC_IT) == 0) ||
((status & TWL4030_RTC_STATUS_REG_1S_EVENT) == 0)) {
// Spurious Triton2 interrupt...
pic.AckIrq(irq);
return;
}
ClockLogger.AddEntry(4, rps, timer);
rps.pitLastClock = 0; // timer.Timer2Read();
rps.upTime += InterruptGapTicks;
ClockLogger.AddEntry(5, rps, timer);
irqCount++;
if (timer.InterruptPending == false) {
// This is to keep time progressing if the user has
// not set an interrupt
//timer.SetKeepAliveInterrupt();
}
WritePih(TWL4030_PWR_ISR1, TWL4030_PWR_ISR1_RTC_IT);
pic.AckIrq(irq);
// Invalidate TSC snapshot to force clock synchronization
this.pmccntrSnapshotValid = false;
}
internal byte Initialize()
{
DebugStub.Print("RTClock: initialize\n");
rps = timer.rps;
// Start the RTC (if not already started)
WriteRtc(TWL4030_RTC_CTRL_REG, TWL4030_RTC_CTRL_REG_STOP_RTC);
// Disable interrupts for now
WriteRtc(TWL4030_RTC_INTERRUPTS_REG, 0);
WritePih(TWL4030_PWR_SIH_CTRL, TWL4030_PWR_SIH_CTRL_COR);
WritePih(TWL4030_PWR_IMR1, 0);
// Capture the boot time
rtcBootTime = PullRtcTime().Ticks;
// Enable and clear interrupts
pic.EnableIrq(irq);
return pic.IrqToInterrupt(irq);
}
internal void Start()
{
DebugStub.Print("RTClock::Start()\n");
// Enable interrupt every second
WriteRtc(TWL4030_RTC_INTERRUPTS_REG, (TWL4030_RTC_INTERRUPTS_REG_IT_TIMER | TWL4030_RTC_INTERRUPTS_REG_EVERY_Second));
WritePih(TWL4030_PWR_IMR1, unchecked((byte)~TWL4030_PWR_IMR1_RTC_IT));
}
internal void Finalize()
{
pic.DisableIrq(irq);
}
[NoHeapAllocation]
bool InitializeFastTime()
{
const ulong KernelTicksPerSecond = 10000000;
ulong cps = Processor.CyclesPerSecond;
if (cps >= KernelTicksPerSecond) {
this.pmccntrSnapshot = 0;
this.ticksSnapshot = 0;
this.tickScale =
(int)((1 << tickRoll) * KernelTicksPerSecond / cps);
#if VERBOSE
DebugStub.Print(
"tick scale = {0} (actual {1:d}, fast {2:d})",
__arglist(this.tickScale,
cps,
((1 << tickRoll) * KernelTicksPerSecond /
(ulong)this.tickScale))
);
#endif // VERBOSE
return true;
}
return false;
}
internal void SetNoisyTimer(bool noisy)
{
this.noisyTimer = noisy;
}
[NoHeapAllocation]
private long GetKernelTicksFromHW()
{
bool iflag = Processor.DisableInterrupts();
try {
ClockLogger.AddEntry(100, rps, timer);
long r = rps.upTime; //rps.GetKernelTicks(timer.Timer2Read());
return r;
}
finally {
Processor.RestoreInterrupts(iflag);
}
}
[NoHeapAllocation]
private long GetKernelTicksFromPmccntr()
{
long now = unchecked((long)Processor.CycleCount);
long pmccntrDelta = now - this.pmccntrSnapshot;
long tickDelta = (pmccntrDelta * tickScale) >> tickRoll;
DebugStub.Assert(tickDelta >= 0);
return ticksSnapshot + tickDelta;
}
[NoHeapAllocation]
private long InternalGetKernelTicks()
{
if (!initedFastTime) {
initedFastTime = InitializeFastTime();
}
if (this.noisyTimer || !initedFastTime) {
// Pull time from the hardware as the PMSCCNTR
// is not being used.
return GetKernelTicksFromHW();
}
else {
if (!pmccntrSnapshotValid) {
// When waking up from a processor halt
// event or from a clock interrupt, sync
// with the fixed h/w's concept of time.
this.ticksSnapshot = GetKernelTicksFromHW();
this.pmccntrSnapshot = unchecked((long)Processor.CycleCount);
this.pmccntrSnapshotValid = true;
return ticksSnapshot;
}
else {
return GetKernelTicksFromPmccntr();
}
}
}
[NoHeapAllocation]
public long GetKernelTicks()
{
#if DEBUG_TIMER
{
long cpuT = GetKernelTicksFromPmccntr();
long hwT = GetKernelTicksFromHW();
if (cpuT - hwT > 5000 || hwT - cpuT < -5000) {
DebugStub.Print("Delta = {0:d} (hw {1:d} cpu {2:d})\n",
__arglist(cpuT - hwT, hwT, cpuT));
}
}
#endif // DEBUG_TIMER
// We use the PMCCNTR to provide fast kernel ticks as
// far as possible, and periodically sync against an
// off-chip clock. Because there are different time
// sources and we don't ever want to report time
// going backwards, we need to check that time would
// reported to have advanced or remained the same,
// and that the clock has not wrapped.
long kernelTicks = InternalGetKernelTicks();
if (kernelTicks >= lastKernelTicks) {
lastKernelTicks = kernelTicks;
return kernelTicks;
}
else if (kernelTicks < 0 && lastKernelTicks > 0) {
// clock wrap
return kernelTicks;
}
#if DEBUG_TIMER
// Skew from switching between pmccntr and underlying clock
DebugStub.Print("Backwards delta = {0} (HW {1} PMCCNTR {2})\n",
__arglist(kernelTicks - lastKernelTicks,
GetKernelTicksFromHW(),
GetKernelTicksFromPmccntr())
);
#endif // DEBUG_TIMER
return lastKernelTicks;
}
[NoHeapAllocation]
public void CpuResumeFromHaltEvent()
{
pmccntrSnapshotValid = false;
}
[NoHeapAllocation]
public long GetRtcTime()
{
return rtcBootTime + GetKernelTicks();
}
public void SetRtcTime(long rtcTime)
{
long delta = rtcTime - GetRtcTime();
rtcBootTime = rtcBootTime + delta;
PushRtcTime(new DateTime(rtcTime));
}
[NoHeapAllocation]
internal static byte BcdToHex(byte bcd)
{
return (byte) (10 * (bcd >> 4) + (bcd & 0x0f));
}
[NoHeapAllocation]
internal static byte HexToBcd(int hex)
{
int h = hex / 10;
return (byte) ((h << 4) | (hex % 10));
}
private DateTime PullRtcTime()
{
bool iflag = Processor.DisableInterrupts();
// Read out captured time
byte second = 0xff;
byte minute = 0xff;
byte hour = 0xff;
byte day = 0xff;
byte month = 0xff;
byte year = 0xff;
try {
// Capture current time
byte control = ReadRtc(TWL4030_RTC_CTRL_REG);
control |= TWL4030_RTC_CTRL_REG_GET_TIME;
WriteRtc(TWL4030_RTC_CTRL_REG, control);
// Read out captured time
second = BcdToHex((byte)(ReadRtc(TWL4030_ALARM_SECONDS_REG) & TWL4030_ALARM_SECONDS_REG_Mask));
minute = BcdToHex((byte)(ReadRtc(TWL4030_ALARM_MINUTES_REG) & TWL4030_ALARM_MINUTES_REG_Mask));
hour = BcdToHex((byte)(ReadRtc(TWL4030_ALARM_HOURS_REG) & TWL4030_ALARM_HOURS_REG_Mask));
day = BcdToHex((byte)(ReadRtc(TWL4030_ALARM_DAYS_REG) & TWL4030_ALARM_DAYS_REG_Mask));
month = BcdToHex((byte)(ReadRtc(TWL4030_ALARM_MONTHS_REG) & TWL4030_ALARM_MONTHS_REG_Mask));
year = BcdToHex((byte)(ReadRtc(TWL4030_ALARM_YEARS_REG) & TWL4030_ALARM_YEARS_REG_Mask));
DebugStub.Print("PullRtcTime: " +
"({0:d}:{1:d2}:{2:d2} {3:d}/{4:d}/20{5:d2}" +
")\n",
__arglist(hour,
minute,
second,
day,
month,
year));
return new DateTime(2000 + year,
month, day, hour, minute,
second);
}
catch (ArgumentOutOfRangeException e) {
DebugStub.Print("PullRtcTime failed: {0} " +
"({1:d}:{2:d2}:{3:d2} {4:d}/{5:d}/20{6:d2}" +
")\n",
__arglist(e,
hour,
minute,
second,
day,
month,
year));
}
finally {
Processor.RestoreInterrupts(iflag);
}
return new DateTime(2007, 7, 5, 16, 32, 45);
}
private void PushRtcTime(DateTime dt)
{
bool iflag = Processor.DisableInterrupts();
byte control = ReadRtc(TWL4030_RTC_CTRL_REG);
try {
// Freeze RTC
WriteRtc(TWL4030_RTC_CTRL_REG, (byte)(control & ~TWL4030_RTC_CTRL_REG_STOP_RTC));
// Write new values
WriteRtc(TWL4030_ALARM_SECONDS_REG, HexToBcd(dt.Second));
WriteRtc(TWL4030_ALARM_MINUTES_REG, HexToBcd(dt.Minute));
WriteRtc(TWL4030_ALARM_HOURS_REG, HexToBcd(dt.Hour));
WriteRtc(TWL4030_ALARM_DAYS_REG, HexToBcd(dt.Day));
WriteRtc(TWL4030_ALARM_MONTHS_REG, HexToBcd(dt.Month));
WriteRtc(TWL4030_ALARM_YEARS_REG, HexToBcd(dt.Year % 100));
// to mis-quote Ford:
// "you can have any century you like, as long as it's the 21st..."
DebugStub.Print("PushRtcTime: " +
"({0:d}:{1:d2}:{2:d2} {3:d}/{4:d}/20{5:d2}" +
")\n",
__arglist(dt.Hour,
dt.Minute,
dt.Second,
dt.Day,
dt.Month,
dt.Year % 100));
if ((dt.Year / 100) != 20) {
DebugStub.Print("PushRtcTime: truncating year {0:d4}->20{1:d2}\n",
__arglist(dt.Year, dt.Year % 100));
}
}
finally {
// Run RTC again from new value
WriteRtc(TWL4030_RTC_CTRL_REG, control);
Processor.RestoreInterrupts(iflag);
}
}
}
}