code: /**
   * Copyright (c) 2004-2005, Regents of the University of California
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * Redistributions of source code must retain the above copyright notice,
   * this list of conditions and the following disclaimer.
   *
   * Redistributions in binary form must reproduce the above copyright
   * notice, this list of conditions and the following disclaimer in the
   * documentation and/or other materials provided with the distribution.
   *
   * Neither the name of the University of California, Los Angeles nor the
   * names of its contributors may be used to endorse or promote products
   * derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  package avrora.sim.mcu;
  
  import avrora.sim.RWRegister;
  import avrora.sim.Simulator;
  import avrora.sim.state.RegisterView;
  import avrora.sim.state.RegisterUtil;
  import avrora.sim.clock.Clock;
  
  /**
   * Base class of 8-bit timers. Timer0 and Timer2 are subclasses of this.
   *
   * @author Daniel Lee
   */
  public abstract class Timer8Bit extends AtmelInternalDevice {
      public static final int MODE_NORMAL = 0;
      public static final int MODE_PWM = 1;
      public static final int MODE_CTC = 2;
      public static final int MODE_FASTPWM = 3;
      public static final int MAX = 0xff;
      public static final int BOTTOM = 0x00;
  
      final ControlRegister TCCRn_reg;
      final ControlRegisterA TCCRnA_reg;
      final ControlRegisterB TCCRnB_reg;
      final TCNTnRegister TCNTn_reg;
      final BufferedRegister OCRn_reg,OCRnB_reg;
  
      protected final int n; // number of timer. 0 for Timer0, 2 for Timer2
  
      protected Simulator.Event ticker;
      protected final Clock externalClock;
      protected Clock timerClock;
  
      protected int period;
  
      final AtmelMicrocontroller.Pin outputComparePin,outputComparePinB;
      final Simulator.Event[] tickers;
  
      /* pg. 93 of manual. Block compareMatch for one period after
       * TCNTn is written to. */
      boolean blockCompareMatch;
      final int OCIEn;
      final int TOIEn;
      final int OCFn;
      final int TOVn;
      final int ab;
  
      protected ATMegaFamily.FlagRegister TIFR_reg;
      protected ATMegaFamily.MaskRegister TIMSK_reg;
  
      final int[] periods;
      //ATMega timers
      protected Timer8Bit(AtmelMicrocontroller m, int ab, int n, int OCIEn, int TOIEn, int OCFn, int TOVn, int[] periods) {
          super("timer"+n, m);
          this.ab = ab;
          this.TOIEn = TOIEn;
          this.TOVn = TOVn;
          TCNTn_reg = new TCNTnRegister();
          OCRn_reg = new BufferedRegister();
          //B enables are the next bit up
          this.OCIEn = OCIEn;
          this.OCFn = OCFn;
  
          if (ab != 0) {
              TCCRn_reg = null; 
              TCCRnA_reg = new ControlRegisterA();
              TCCRnB_reg = new ControlRegisterB();
              OCRnB_reg = new BufferedRegister();
              TIFR_reg = (ATMegaFamily.FlagRegister)m.getIOReg("TIFR"+n);
              TIMSK_reg = (ATMegaFamily.MaskRegister)m.getIOReg("TIMSK"+n);
              outputComparePin = (AtmelMicrocontroller.Pin)microcontroller.getPin("OC"+n+"A");
              if(n!=2) //no pin on atmega128rfa1
                  outputComparePinB = (AtmelMicrocontroller.Pin)microcontroller.getPin("OC"+n+"B");
              else
                  outputComparePinB = null;
              installIOReg("TCCR"+n+"A", TCCRnA_reg);
              installIOReg("TCCR"+n+"B", TCCRnB_reg);
              installIOReg("TCNT"+n, TCNTn_reg);
              installIOReg("OCR"+n+"A", OCRn_reg);
    //          if(n!=2) //no pin on atmega128rfa1
              installIOReg("OCR"+n+"B", OCRnB_reg);
          } else {
              TCCRn_reg = new ControlRegister();
              TCCRnA_reg = null; 
              TCCRnB_reg = null; 
              OCRnB_reg = null;            
              TIFR_reg = (ATMegaFamily.FlagRegister)m.getIOReg("TIFR");
              TIMSK_reg = (ATMegaFamily.MaskRegister)m.getIOReg("TIMSK");
              outputComparePin = (AtmelMicrocontroller.Pin)microcontroller.getPin("OC"+n);
              outputComparePinB = null;
              installIOReg("TCCR"+n, TCCRn_reg);
              installIOReg("TCNT"+n, TCNTn_reg);
              installIOReg("OCR"+n, OCRn_reg);  
          }
  
          externalClock = m.getClock("external");
          timerClock = mainClock;
          this.n = n;
          this.periods = periods;
  
          tickers = new Simulator.Event[4];
          installTickers();
      }
  
      private void installTickers() {
          tickers[MODE_NORMAL] = new Mode_Normal();
          tickers[MODE_CTC] = new Mode_CTC();
          tickers[MODE_FASTPWM] = new Mode_FastPWM();
          tickers[MODE_PWM] = new Mode_PWM();
      }
  
      protected void compareMatch() {
     //   if (devicePrinter != null)  devicePrinter.println("Timer" + n + ".compareMatch (enabled: " + TIMSK_reg.readBit(OCIEn) + ')');
          // set the compare flag for this timer
          TIFR_reg.flagBit(OCFn);
          // if the mode is correct, modify pin OCn. but if the flag is
          // already connected to the pin, does this happen automatically
          // with the last previous call?
          //compareMatchPin();
      }
  
      protected void overflow() {
     //   if (devicePrinter != null) devicePrinter.println("Timer" + n + ".overFlow (enabled: " + TIMSK_reg.readBit(TOIEn) + ')');
          // set the overflow flag for this timer
          TIFR_reg.flagBit(TOVn);
      }
  
      /**
       * Overloads the write behavior of this class of register in order to implement compare match
       * blocking for one timer period.
       */
      protected class TCNTnRegister extends RWRegister {
  
          public void write(byte val) {
              value = val;
              blockCompareMatch = true;
          }
  
      }
  
      /**
       * <code>BufferedRegister</code> implements a register with a write buffer. In PWN modes, writes
       * to this register are not performed until flush() is called. In non-PWM modes, the writes are
       * immediate.
       */
      protected class BufferedRegister extends RWRegister {
          final RWRegister register;
  
          protected BufferedRegister() {
              this.register = new RWRegister();
          }
  
          public void write(byte val) {
              super.write(val);
              if (TCCRn_reg != null) {
                  if (TCCRn_reg.mode == MODE_NORMAL || TCCRn_reg.mode == MODE_CTC) {
                      flush();
                  }
              } else {
                  flush();
             // TODO: handle AB
              }
          }
  
          public byte readBuffer() {
              return super.read();
          }
  
          public byte read() {
              return register.read();
          }
  
          protected void flush() {
              register.write(value);
          }
      }
  
      protected class ControlRegister extends RWRegister {
          public static final int FOCn = 7;
          public static final int WGMn0 = 6;
          public static final int COMn1 = 5;
          public static final int COMn0 = 4;
          public static final int WGMn1 = 3;
          public static final int CSn2 = 2;
          public static final int CSn1 = 1;
          public static final int CSn0 = 0;
  
          final RegisterView CSn = RegisterUtil.bitRangeView(this, 0, 2);
          final RegisterView COMn = RegisterUtil.bitRangeView(this, 4, 5);
          final RegisterView WGMn = RegisterUtil.permutedView(this, new byte[] {6, 3});
  
          int mode = -1;
          int scale = -1;
  
          public void write(byte val) {
              // hardware manual states that high order bit is always read as zero
              value = (byte)(val & 0x7f);
  
              if ((val & 0x80) != 0) {
                  forcedOutputCompare();
              }
  
              // decode modes and update internal state
              int nmode = WGMn.getValue();
              int nscale = CSn.getValue();
              // if the scale or the mode has changed
              if (nmode != mode || nscale != scale) {
                  if (ticker != null) timerClock.removeEvent(ticker);
                  mode = nmode;
                  scale = nscale;
                  ticker = tickers[mode];
                  period = periods[scale];
                  if (period != 0) {
                      timerClock.insertEvent(ticker, period);
                  }
                  if (false && devicePrinter != null) {
                    if (period != 0)
                      devicePrinter.println("Timer" + n + " enabled: period = " + period + " mode = " + mode);
                    else
                      devicePrinter.println("Timer" + n + " disabled");
                  }
              }
          }
  
          private void forcedOutputCompare() {
  
              int count = TCNTn_reg.read() & 0xff;
              int compare = OCRn_reg.read() & 0xff;
  
              // the non-PWM modes are NORMAL and CTC
              // under NORMAL, there is no pin action for a compare match
              // under CTC, the action is to clear the pin.
  
              // TODO: this implementation is probably not correct...
              if (count == compare) {
                  switch (COMn.getValue()) {
                      case 1:
                          if (WGMn.getValue() == MODE_NORMAL || WGMn.getValue() == MODE_CTC)
                            outputComparePin.write(!outputComparePin.read()); // toggle
                          break;
                      case 2:
                          outputComparePin.write(false); // clear
                          break;
                      case 3:
                          outputComparePin.write(true); // set to true
                          break;
                  }
  
              }
          }
      }
  
      //ATMega1284p TCCR2A and B register
      //TODO: perform the action on writes to either A or B
   
      protected class ControlRegisterA extends RWRegister {
          public static final int COMnA1 = 7;
          public static final int COMnA0 = 6;
          public static final int COMnB1 = 5;
          public static final int COMnB0 = 4;
          public static final int WGMn1  = 1;
          public static final int WGMn0  = 0;
          public void write(byte val) {
              super.write(val);
             // System.out.println("ControlregisterA write byte " + val + " to " + this);
          }
      }
   
      protected class ControlRegisterB extends RWRegister {
          public static final int FOCnA  = 7;
          public static final int FOCnB  = 6;
          public static final int WGMn2  = 3;
          public static final int CSn2   = 2;
          public static final int CSn1   = 1;
          public static final int CSn0   = 0;
  
          final RegisterView CSn = RegisterUtil.bitRangeView(this, 0, 2);
  
          int mode = -1;
          int scale = -1;
  
          public void write(byte val) {
              super.write(val);
            //  System.out.println("ControlregisterB write byte " + val + " to " + this);
              //  Combine the WGM bits to get the mode
             // System.out.println("controlregisterA is " + TCCRnA_reg.getValue());
              int nmode = (((value & 4) >>1 ) | (TCCRnA_reg.getValue() & 3));
  
              // Forced output compare only active in a non-PWM mode (WGM0 clear)
              if (((nmode & 1) != 0) && ((val & 0xC0) != 0)) {
                  System.out.println("Forced output compare AB");
                  forcedOutputCompare(mode, val);
              }
  
              // decode modes and update internal state
  
              int nscale = CSn.getValue();
           //   System.out.println("mode is " + nmode + " CSn is " + CSn.getValue());
              // if the scale or the mode has changed
              if (nmode != mode || nscale != scale) {
                  if (ticker != null) timerClock.removeEvent(ticker);
                  mode = nmode;
                  scale = nscale;
                  ticker = tickers[mode];
                  period = periods[scale];
                  if (period != 0) {
                      timerClock.insertEvent(ticker, period);
                  }
                  if (false && devicePrinter != null) {
                    if (period != 0)
                      devicePrinter.println("Timer" + n + " enabled: period = " + period + " mode = " + mode);
                    else
                      devicePrinter.println("Timer" + n + " disabled");
                  }
              }
          }
          private void forcedOutputCompare(int mode, int value) {
  
              int count = TCNTn_reg.read() & 0xff;
              int compare = OCRn_reg.read() & 0xff;
  
              AtmelMicrocontroller.Pin pinAB;
              int com;
              if ((value & 0x80) != 0) {
                  com = (TCCRnA_reg.getValue() & 0xC0) >> 6;
                  pinAB = outputComparePin;
              } else {
                  com = (TCCRnA_reg.getValue() & 0x30) >> 4;
                  pinAB = outputComparePinB;
              }
              if (pinAB == null) return;
              // the non-PWM modes are NORMAL and CTC
              // under NORMAL, there is no pin action for a compare match
              // under CTC, the action is to clear the pin.
  
              // TODO: this implementation is probably not correct...
              if (count == compare) {
                  switch (com) {
                      case 1:
                       //   if (WGMn.getValue() == MODE_NORMAL || WGMn.getValue() == MODE_CTC)
                            pinAB.write(!pinAB.read()); // toggle
                          break;
                      case 2:
                          pinAB.write(false); // clear
                          break;
                      case 3:
                          pinAB.write(true); // set to true
                          break;
                  }
  
              }
          }
      }
  
      class Mode_Normal implements Simulator.Event {
          public void fire() {
              int ncount = (int)TCNTn_reg.read() & 0xff;
          // System.out.println("noamalfire, ncount = " + ncount + " BOTTOM " + BOTTOM + " MAX " + MAX);
              tickerStart(ncount);
              if (ncount >= MAX) {
                  overflow();
                  ncount = BOTTOM;
              }
              else {
                  ncount++;
              }
              tickerFinish(this, ncount);
          }
      }
  
      class Mode_PWM implements Simulator.Event {
          protected byte increment = 1;
          public void fire() {
              // TODO: OCn handling
              int ncount = (int)TCNTn_reg.read() & 0xff;
              tickerStart(ncount);
              if (ncount >= MAX) {
                  increment = -1;
                  ncount = MAX;
                  OCRn_reg.flush(); // pg. 102. update OCRn at TOP
              }
              else if (ncount <= BOTTOM) {
                  overflow();
                  increment = 1;
                  ncount = BOTTOM;
              }
              ncount += increment;
              tickerFinish(this, ncount);
          }
      }
  
      class Mode_CTC implements Simulator.Event {
          public void fire() {
              int ncount = (int)TCNTn_reg.read() & 0xff;
              tickerStart(ncount);
        //      System.out.println("ctcfire, ncount = " + ncount + " BOTTOM " + BOTTOM + " MAX " + MAX + " OCRn " + OCRn_reg.read());
              if (ncount >= MAX) {
                  // OCRn == MAX, then overflow is handled as in normal mode
                  overflow();
                  ncount = BOTTOM;
              }
              else if (ncount == ((int)OCRn_reg.read() & 0xff)) {
                  ncount = BOTTOM;
              }
              else {
                  ncount++;
              }
              tickerFinish(this, ncount);
          }
      }
  
      class Mode_FastPWM implements Simulator.Event {
          public void fire() {
              // TODO: OCn handling
              int ncount = (int)TCNTn_reg.read() & 0xff;
              tickerStart(ncount);
              if (ncount >= MAX) {
                  ncount = BOTTOM;
                  overflow();
                  OCRn_reg.flush(); // pg. 102. update OCRn at TOP
              }
              else {
                  ncount++;
              }
              tickerFinish(this, ncount);
          }
      }
      
      private void tickerStart(int count) {
        if (!blockCompareMatch && count == ((int)OCRn_reg.read() & 0xff)) {
            compareMatch();
        }
      }
      
      private void tickerFinish(Simulator.Event ticker, int ncount) {
          TCNTn_reg.write((byte)ncount);
          // previous write sets the compare, so reset it now
          blockCompareMatch = false;
  
          timerClock.insertEvent(ticker, period);
      }
  }
  