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.*;
  import avrora.sim.state.*;
  import cck.util.Arithmetic;
  
  /**
   * Serial Peripheral Interface. Used on the <code>Mica2</code> platform for radio communication.
   *
   * @author Daniel Lee, Simon Han
   */
  public class SPI extends AtmelInternalDevice implements SPIDevice, InterruptTable.Notification {
  
      final SPDReg SPDR_reg;
      final SPCRReg SPCR_reg;
      final SPSReg SPSR_reg;
  
      SPIDevice connectedDevice;
  
      final TransferEvent transferEvent = new TransferEvent();
  
      boolean spifAccessed;
  
      int interruptNum;
  
      protected int period;
  
      /**
       * A single byte data frame for the SPI.
       */
      public static class Frame {
          public final byte data;
  
          protected Frame(byte data) {
              this.data = data;
          }
      }
  
      private static final Frame[] frameCache = new Frame[256];
      public static final Frame ZERO_FRAME;
      public static final Frame FF_FRAME;
  
      static {
          for ( int cntr = 0; cntr < 256; cntr++ ) {
              frameCache[cntr] = new Frame((byte)cntr);
          }
          ZERO_FRAME = frameCache[0];
          FF_FRAME = frameCache[0xff];
      }
  
      public static Frame newFrame(byte data) {
          return frameCache[data & 0xff];
      }
  
      @Override
      public void connect(SPIDevice d) {
          connectedDevice = d;
      }
  
      @Override
      public Frame exchange(Frame frame) {
          Frame result = newFrame(SPDR_reg.transmitReg.read());
          receive(frame);
          return result;
      }
  
      public void receive(Frame frame) {
          SPDR_reg.receiveReg.write(frame.data);
          if (!SPCR_reg._master.getValue() && !transferEvent.transmitting) {
              postSPIInterrupt();
          }
      }
  
      public SPI(AtmelMicrocontroller m) {
          super("spi", m);
          SPDR_reg = new SPDReg();
          SPCR_reg = new SPCRReg();
          SPSR_reg = new SPSReg();
  
          interruptNum = m.getProperties().getInterrupt("SPI, STC");
  
          installIOReg("SPDR", SPDR_reg);
          installIOReg("SPSR", SPSR_reg);
          installIOReg("SPCR", SPCR_reg);
  
          interpreter.getInterruptTable().registerInternalNotification(this, interruptNum);
      }
  
      /**
       * Post SPI interrupt
       */
      private void postSPIInterrupt() {
          interpreter.setPosted(interruptNum, true);
          SPSR_reg.setSPIF();
      }
  
      private void unpostSPIInterrupt() {
          interpreter.setPosted(interruptNum, false);
          SPSR_reg.clearSPIF();
      }
  
  
      /**
       * The SPI transfer event. Upon firing delivers frames in both directions.
       */
      protected class TransferEvent implements Simulator.Event {
  
          Frame frame;
          boolean transmitting;
  
          protected void enableTransfer() {
  
              if (SPCR_reg._master.getValue() && SPCR_reg._enabled.getValue() && !transmitting) {
                  //if (devicePrinter != null) {
                  //    devicePrinter.println("SPI: Master mode. Enabling transfer. ");
                  //}
                  SPSR_reg.clearSPIF();
                  transmitting = true;
                  frame = newFrame(SPDR_reg.transmitReg.read());
                  mainClock.insertEvent(this, period);
              }
          }
  
          @Override
          public void fire() {
              if (SPCR_reg._enabled.getValue()) {
                  SPSR_reg.clearSPIF();//after every reading SPSR must be a cleared
                  if (connectedDevice == null) {
                      throw new Error("No device connected to SPI - did you specify a platform?");
                  }
                  receive(connectedDevice.exchange(frame));
                  transmitting = false;
                  postSPIInterrupt();
              }
          }
      }
  
      @Override
      public void force(int inum) {
          SPSR_reg.setSPIF();
      }
  
      @Override
      public void invoke(int inum) {
          unpostSPIInterrupt();
      }
  
      /**
       * SPI data register. Writes to this register are transmitted to the connected device and reads
       * from the register read the data received from the connected device.
       */
      class SPDReg implements ActiveRegister {
  
          protected final RWRegister receiveReg;
          protected final TransmitRegister transmitReg;
  
          protected class TransmitRegister extends RWRegister {
  
              @Override
              public void write(byte val) {
                  super.write(val);
                  transferEvent.enableTransfer();
              }
          }
  
          SPDReg() {
              receiveReg = new RWRegister();
              transmitReg = new TransmitRegister();
          }
  
          /**
           * The <code>read()</code> method
           *
           * @return the value from the receive buffer
           */
          @Override
          public byte read() {
              if ( spifAccessed ) unpostSPIInterrupt();
              return receiveReg.read();
          }
  
          /**
           * The <code>write()</code> method
           *
           * @param val the value to transmit buffer
           */
          @Override
          public void write(byte val) {
              // TODO: implement write collision detection
              transmitReg.write(val);
          }
      }
  
      /**
       * SPI control register.
       */
      protected class SPCRReg extends RWRegister {
  
          static final int SPIE = 7;
          static final int SPE = 6;
          static final int MSTR = 4;
          static final int SPR1 = 1;
          static final int SPR0 = 0;
  
          boolean prev_spie;
  
          final BooleanView _master = RegisterUtil.booleanView(this, MSTR);
          final BooleanView _enabled = RegisterUtil.booleanView(this, SPE);
          final RegisterView _spr = RegisterUtil.bitRangeView(this, SPR0, SPR1);
  
          @Override
          public void write(byte val) {
              super.write(val);
              //if (devicePrinter != null)
              //      devicePrinter.println("SPI: wrote " + StringUtil.toMultirepString(val, 8) + " to SPCR");
              decode(val);
          }
  
          protected void decode(byte val) {
  
              // Reset spi interrupt flag when enabling SPI interrupt
              boolean spie = Arithmetic.getBit(val, SPIE);
              interpreter.setEnabled(interruptNum, spie);
              if (spie && !prev_spie) {
                  prev_spie = true;
                  SPSR_reg.clearSPIF();
              }
              if (!spie && prev_spie) {
                  prev_spie = false;
              }
  
              // calculate the period of the clock
              int divider = 0;
              switch ( _spr.getValue() ) {
                  case 0:
                      divider = 4;
                      break;
                  case 1:
                      divider = 16;
                      break;
                  case 2:
                      divider = 64;
                      break;
                  case 3:
                      divider = 128;
                      break;
              }
  
              if ( SPSR_reg._spi2x.getValue() ) {
                  divider /= 2;
              }
              period = divider * 8;
          }
      }
  
      /**
       * SPI status register.
       */
      class SPSReg extends RWRegister {
  
          static final int SPIF = 7;
          static final int WCOL = 6;
  
          final BooleanView _spif = RegisterUtil.booleanView(this, SPIF);
          final BooleanView _spi2x = RegisterUtil.booleanView(this, 0);
  
          byte prev_value;
  
          @Override
          public void write(byte val) {
              super.write(val);
              //if (devicePrinter != null)
              // devicePrinter.println("SPI: wrote " + val + " to SPSR");
              decode(val);
          }
  
          @Override
          public byte read() {
              if (_spif.getValue()) {
                spifAccessed = true;
              }
              return super.read();
          }
  
          protected void decode(byte val) {
  
              if (!Arithmetic.getBit(prev_value, SPIF) && Arithmetic.getBit(val, SPIF)) {
                  postSPIInterrupt();
              }
  
              spifAccessed = false;
              prev_value = val;
          }
  
          public void setSPIF() {
              _spif.setValue(true);
              spifAccessed = false;
          }
  
          public void clearSPIF() {
              _spif.setValue(false);
              spifAccessed = false;
          }
  
      }
  }
  