///////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   Timer8254.cs
//
//  Useful reference URLs:
//    http://developer.intel.com/design/archives/periphrl/index.htm
//    http://developer.intel.com/design/archives/periphrl/docs/7203.htm
//    http://developer.intel.com/design/archives/periphrl/docs/23124406.htm
//

using Microsoft.Singularity.Io;
using Microsoft.Singularity.Configuration;

using System;
using System.Threading;
using System.Diagnostics;

namespace Microsoft.Singularity.Hal
{
    [DriverCategory]
    [Signature("/pnp/PNP0100")]
    public sealed class Timer8254Resources : DriverCategoryDeclaration
    {
        [IoPortRange(0, Default = 0x0040, Length = 0x04)]
        public IoPortRange port1;

        [IoIrqRange(1, Default = 0x00, Length = 0x01)]
        public IoIrqRange irq1;

        [IoFixedPortRange(Base = 0x0061, Length = 0x01)]
        public IoPortRange port2;
    }

    //
    // Note on the i8254 timer:
    //
    // The clock is driven by a 14.318 MHz clock through a divide-by-12
    // counter.
    // This gives a clock frequency of 1.193 MHz.
    // Each tick is therefore 0.838 microseconds long.
    // Thus a 16 bit timer gives us a maximum delay of 0.05 seconds.
    // 
    [CLSCompliant(false)]
    public sealed class Timer8254Apic
    {
        // Registers   
        const byte  i8254_C2     = 0x02;    // counter 2       
        const byte  i8254_CW     = 0x03;    // control word      

        //
        //The control word to the 8254 is composed of four fields:
        //bits 6-7: select the counter
        //bits 4-5: select read/write
        //bits 1-3: mode to use
        //bit  0  : BCD mode
        //

        // bits 6-7 select the counter   
        const byte i8254_CW_SEL0      = 0x00;  // select counter 0         
        const byte i8254_CW_SEL1      = 0x40;  // select counter 1         
        const byte i8254_CW_SEL2      = 0x80;  // select counter 2         
        const byte i8254_CW_RBC       = 0xc0;  // read-back command        

        // bits 4-5 select transaction type   
        const byte i8254_CW_CLC       = 0x00;  // counter latch comm.      
        const byte i8254_CW_LSB       = 0x10;  // r/w lsb only             
        const byte i8254_CW_MSB       = 0x20;  // r/w msb only             
        const byte i8254_CW_BOTH      = 0x30;  // r/w lsb, then msb        

        // bits 1-3 select the mode. bit 3 is sometimes a don't care   
        const byte i8254_CW_MODE0     = 0x00;  // int. on term. count      
        const byte i8254_CW_MODE1     = 0x02;  // h/w retrig. one-shot     
        const byte i8254_CW_MODE2     = 0x04;  // rate generator           
        const byte i8254_CW_MODE3     = 0x06;  // square wave mode         
        const byte i8254_CW_MODE4     = 0x08;  // s/w trig. strobe         
        const byte i8254_CW_MODE5     = 0x0a;  // h/w trig. strobe         

        // bit 0 sets BCD mode, if you really must   
        const byte i8254_CW_BCD       = 0x01;  // set BCD operation        

        // read-back commands use bits 4 and 5 to return status these
        // are the wrong way round since the bits are inverted (RTFDS).
        // 
        const byte i8254_RB_NOSTATUS  = 0xd0;  // Don't get latched count   
        const byte i8254_RB_NOCOUNT   = 0xe0;  // Don't get status         
        const byte i8254_RB_ALL       = 0xc0;  // Get status and count     

        // read-back commands use bits 3-1 to select counter   
        const byte i8254_RB_SEL2      = 0x08;  // counter 2                

        // status from read-back is returned in bits 6-7   
        const byte i8254_RB_OUT       = 0x80;  // out pin value            
        const byte i8254_RB_NULL      = 0x40;  // 1 = count null           

        private static readonly int DefaultTicksPerSecond = 1193182;
        private static readonly int SystemTicksPerSecond  = 10000000;

        private PnpConfig config;
        private byte      irq;
        private int       frequency = DefaultTicksPerSecond;

        // IOPorts   
        private IoPort  C2Port;
        private IoPort  CWPort;

        // Constructor
        internal Timer8254Apic(PnpConfig config)
        {
            // /pnp/08/02/01/PNP0100 0003 cfg dis : ISA 8254 Timer : AT Timer
            // IRQ mask=0001 type=47
            // I/O Port inf=01 min=0040 max=0040 aln=01 len=04 0040..0043
            this.config = config;
            this.irq    = ((IoIrqRange)config.DynamicRanges[1]).Irq;

            IoPortRange ioPorts = (IoPortRange) config.DynamicRanges[0];

            C2Port = ioPorts.PortAtOffset(i8254_C2, 1, Access.ReadWrite);
            CWPort = ioPorts.PortAtOffset(i8254_CW, 1, Access.ReadWrite);

            // Use Timer2 as it's not connected as interrupt source.
            //
            // Lower 2 bits of port 61 are described in:
            // The Indispensable PC Hardware Book (3rd Edition) p.724
            ioPorts = (IoPortRange) config.FixedRanges[0];
            IoPort p = ioPorts.PortAtOffset(0, 1, Access.ReadWrite);

            //
            // Also clear the NMI RAM parity error to enable the PCI card
            // to generate NMI if the button is depressed
            //

            p.Write8((byte) ((p.Read8() & 0xf8) | 0x01));
        }

        internal void Start(ushort pitTicks)
        {
            CWPort.Write8(i8254_CW_MODE2 | i8254_CW_BOTH | i8254_CW_SEL2);
            C2Port.Write8((byte)(pitTicks & 0xff));
            C2Port.Write8((byte)(pitTicks >> 8));
            do {
                CWPort.Write8(i8254_RB_NOCOUNT | i8254_RB_SEL2);
            } while ((C2Port.Read8() & i8254_RB_NULL) == i8254_RB_NULL);
        }

        internal ushort Read()
        {
            CWPort.Write8(i8254_RB_NOSTATUS | i8254_RB_SEL2);
            return (ushort)(C2Port.Read8() | (C2Port.Read8() << 8));
        }

        internal void Stop()
        {
            CWPort.Write8(i8254_CW_MODE2 | i8254_CW_BOTH | i8254_CW_SEL2);
            C2Port.Write8((byte)(0));    // LSB   
            // C2Port.Write8((byte)(0));  MSB DON'T!   
        }

        internal void SetFrequency(uint newFrequency)
        {
            frequency = (int)newFrequency;
            DebugStub.Print("i8254 frequency {0} Hz\n", __arglist(frequency));
        }

        internal uint TimeSpanToTimerTicks(uint ts)
        {
            return (uint) ((ulong)ts * (uint)(frequency / SystemTicksPerSecond));
        }

        internal uint TimerToTimeSpanTicks(uint ts)
        {
            return (uint) ((ulong)ts * (uint)(SystemTicksPerSecond / frequency));
        }

#if FIXED_FREQUENCY
        internal static int TimeSpanTicksToPitTicks(int ts)
        {
            // t = 1193182 * T / (10^7) = 0.1193182 * T
            //   = T * (1/0x10 + 0xe8ba/0x100000)
            int tmp = (ts * 0xe8ba) >> 20;
            return tmp + (ts >> 4);
        }

        internal static int PitTicksToTimeSpanTicks(int delta)
        {
            // T = 10^7 * t / 1193182 = 8.3809511 * t
            //   = t * (8 + 0x6186/0x10000)
            return (delta << 3) + ((delta * 0x6186) >> 16);
        }
#endif
    }
}
