/*
 * Copyright (c) 2004, 2005
 * The Regents of The University of Michigan
 * All Rights Reserved
 *
 * This code is part of the M5 simulator.
 *
 * Permission is granted to use, copy, create derivative works and
 * redistribute this software and such derivative works for any
 * purpose, so long as the copyright notice above, this grant of
 * permission, and the disclaimer below appear in all copies made; and
 * so long as the name of The University of Michigan is not used in
 * any advertising or publicity pertaining to the use or distribution
 * of this software without specific, written prior authorization.
 *
 * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION FROM THE
 * UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY PURPOSE, AND
 * WITHOUT WARRANTY BY THE UNIVERSITY OF MICHIGAN OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE. THE REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE
 * LIABLE FOR ANY DAMAGES, INCLUDING DIRECT, SPECIAL, INDIRECT,
 * INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
 * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGES.
 *
 * Authors: Ali G. Saidi
 *          Andrew L. Schultz
 *          Miguel J. Serrano
 */

#ifndef __DEV_8254_HH__
#define __DEV_8254_HH__

#include <iostream>
#include <string>

#include "base/bitunion.hh"
#include "base/types.hh"
#include "base/trace.hh"
#include "debug/Intel8254Timer.hh"
#include "sim/eventq_impl.hh"
#include "sim/serialize.hh"

/** Programmable Interval Timer (Intel 8254) */
class Intel8254Timer : public EventManager
{
  protected:
    BitUnion8(CtrlReg)
        Bitfield<7, 6> sel;
        Bitfield<5, 4> rw;
        Bitfield<3, 1> mode;
        Bitfield<0> bcd;
    EndBitUnion(CtrlReg)

    enum SelectVal {
        SelectCounter0,
        SelectCounter1,
        SelectCounter2,
        ReadBackCommand
    };

    enum ReadWriteVal {
        LatchCommand,
        LsbOnly,
        MsbOnly,
        TwoPhase
    };

    enum ModeVal {
        InitTc,
        OneShot,
        RateGen,
        SquareWave,
        SoftwareStrobe,
        HardwareStrobe
    };

    /** Counter element for PIT */
    class Counter
    {
        /** Event for counter interrupt */
        class CounterEvent : public Event
        {
          private:
            /** Pointer back to Counter */
            Counter* counter;
            Tick interval;

          public:
            CounterEvent(Counter*);

            /** Event process */
            void process();

            /** Event description */
            virtual const char *description() const;

            friend class Counter;

            void setTo(int clocks);

            int clocksLeft();

            Tick getInterval();
        };

      private:
        std::string _name;
        const std::string &name() const { return _name; }

        unsigned int num;

        CounterEvent event;

        /** True after startup is called. */
        bool running;

        /** Initial count value */
        uint16_t initial_count;

        /** Latched count */
        uint16_t latched_count;

        /** Interrupt period */
        uint16_t period;

        /** When to start ticking */
        Tick offset;

        /** Current mode of operation */
        uint8_t mode;

        /** Output goes high when the counter reaches zero */
        bool output_high;

        /** State of the count latch */
        bool latch_on;

        /** Set of values for read_byte and write_byte */
        enum {LSB, MSB};

        /** Determine which byte of a 16-bit count value to read/write */
        uint8_t read_byte, write_byte;

        /** Pointer to container */
        Intel8254Timer *parent;

      public:
        Counter(Intel8254Timer *p, const std::string &name, unsigned int num);

        /** Latch the current count (if one is not already latched) */
        void latchCount();

        /** Get the current count for this counter */
        int currentCount();

        /** Set the read/write mode */
        void setRW(int rw_val);

        /** Set operational mode */
        void setMode(int mode_val);

        /** Set count encoding */
        void setBCD(int bcd_val);

        /** Read a count byte */
        uint8_t read();

        /** Write a count byte */
        void write(const uint8_t data);

        /** Is the output high? */
        bool outputHigh();

        /**
         * Serialize this object to the given output stream.
         * @param base The base name of the counter object.
         * @param os   The stream to serialize to.
         */
        void serialize(const std::string &base, CheckpointOut &cp) const;

        /**
         * Reconstruct the state of this object from a checkpoint.
         * @param base The base name of the counter object.
         * @param cp The checkpoint use.
         * @param section The section name of this object
         */
        void unserialize(const std::string &base, CheckpointIn &cp);

        /** Start ticking */
        void startup();
    };

  protected:
    std::string _name;
    const std::string &name() const { return _name; }

    /** PIT has three seperate counters */
    Counter *counter[3];

    virtual void
    counterInterrupt(unsigned int num)
    {
        DPRINTF(Intel8254Timer, "Timer interrupt from counter %d.\n", num);
    }

  public:

    virtual
    ~Intel8254Timer()
    {}

    Intel8254Timer(EventManager *em, const std::string &name,
            Counter *counter0, Counter *counter1, Counter *counter2);

    Intel8254Timer(EventManager *em, const std::string &name);

    /** Write control word */
    void writeControl(const CtrlReg data);

    uint8_t
    readCounter(unsigned int num)
    {
        assert(num < 3);
        return counter[num]->read();
    }

    void
    writeCounter(unsigned int num, const uint8_t data)
    {
        assert(num < 3);
        counter[num]->write(data);
    }

    bool
    outputHigh(unsigned int num)
    {
        assert(num < 3);
        return counter[num]->outputHigh();
    }

    /**
     * Serialize this object to the given output stream.
     * @param base The base name of the counter object.
     * @param os The stream to serialize to.
     */
    void serialize(const std::string &base, CheckpointOut &cp) const;

    /**
     * Reconstruct the state of this object from a checkpoint.
     * @param base The base name of the counter object.
     * @param cp The checkpoint use.
     * @param section The section name of this object
     */
    void unserialize(const std::string &base, CheckpointIn &cp);

    /** Start ticking */
    void startup();
};

#endif // __DEV_8254_HH__