]> oss.titaniummirror.com Git - tinyos-2.x.git/blobdiff - tos/chips/msp430/usci/Msp430I2CP.nc
Merge tag 'release/2.1.1-4.5' into debian/2.1.1
[tinyos-2.x.git] / tos / chips / msp430 / usci / Msp430I2CP.nc
diff --git a/tos/chips/msp430/usci/Msp430I2CP.nc b/tos/chips/msp430/usci/Msp430I2CP.nc
new file mode 100644 (file)
index 0000000..8ce827d
--- /dev/null
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) 2008, Titanium Mirror, Inc.
+ * 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 Technische Universität Berlin 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.
+ */
+
+/**
+ * USCI I2C implementation.  Currently supports only single master operation.
+ * Repeated start operations are supported, as are using multiple I2CPacket
+ * read or write commands to satisfy a single I2C read or write transaction.
+ *
+ * @author R. Steve McKown <rsmckown@gmail.com>
+ */
+
+generic module Msp430I2CP() {
+  provides {
+    interface I2CPacket<TI2CBasicAddr> as I2CBasicPacket;
+    interface I2CPacket<TI2CExtdAddr> as I2CExtdPacket;
+    interface ResourceConfigure;
+  }
+  uses {
+    interface HplMsp430UsciReg as Registers;
+    interface HplMsp430UsciInt as Interrupts;
+    interface HplMsp430GeneralIO as SDA;
+    interface HplMsp430GeneralIO as SCL;
+    interface AsyncConfigure<const msp430_usci_i2c_t*> as Configure;
+    interface ArbiterInfo;
+    interface BusyWait<TMicro, uint16_t>;
+    interface Counter<TMicro, uint16_t>;
+  }
+}
+implementation {
+  enum {
+    /* Activity timing */
+    SCL_WAIT_TIME = 1024,      /* micros */
+    BUSY_CHECK_TIME = 1024,    /* micros */
+    START_CHECK_TIME = 102,    /* micros */
+    STOP_CHECK_TIME = 102,     /* micros */
+
+    /* Setting new I2C flags is dangerous; manually ensure the bit definitions
+     * below do not overlap with those defined in tos/types/I2C.h.
+     */
+    I2C_EXTENDED = 0x10,
+  };
+
+  i2c_flags_t m_flags;
+  uint8_t* m_buf;
+  uint16_t m_len;
+  uint16_t m_pos;
+
+  inline void setSDA()
+  {
+    call SDA.makeInput();
+  }
+
+  inline void clrSDA()
+  {
+    call SDA.makeOutput();
+  }
+
+  inline void setSCL()
+  {
+    call SCL.makeInput();
+  }
+
+  inline void clrSCL()
+  {
+    call SCL.makeOutput();
+  }
+
+  bool idleBus()
+  {
+    /* Look for bus idle when the I2C pins are in IO mode. */
+    return (call SDA.get() && call SCL.get());
+  }
+
+  bool resetBus()
+  {
+    /* When the I2C pins are in IO mode, verify the I2C bus is idle or attempt
+     * to get the bus into an idle state.  This code is only valid if we are
+     * the only master on the I2C bus and is not suitable for multi-master
+     * setups.
+     *
+     * Return TRUE if the bus was idle or was successfully brought to idle.
+     * Return FALSE if the bus could not be made idle in a reasonable time.
+     */
+    if (idleBus())
+      return TRUE;
+    else {
+      uint16_t i;
+
+      /* Wait a bit if SCL is low.  A save might be clock stretching. */
+      i = call Counter.get();
+      while (call Counter.get() - i <= SCL_WAIT_TIME) {
+       if (!(call SCL.get()))
+         return FALSE;
+      }
+
+      /* If SDA is low, clock SCL in an attempt to release it. */
+      for (i = 0; i < 10 && !call SDA.get(); i++) {
+       clrSCL();
+       call BusyWait.wait(10);
+       setSCL();
+       call BusyWait.wait(10);
+      }
+      if (!idleBus())
+       return FALSE;
+
+      /* Drive a stop condition on the bus to stop any active slaves. */
+      clrSCL();
+      clrSDA();
+      call BusyWait.wait(10);
+      setSCL();
+      call BusyWait.wait(10);
+      setSDA();
+      call BusyWait.wait(10);
+      return idleBus();
+    }
+  }
+
+  bool isConfigured()
+  {
+    return !(call Registers.getCtl1(UCSWRST));
+  }
+
+  /* TRUE if a transaction (we initiated, single master) is in progress. */
+  bool isBusy()
+  {
+    return m_len;
+  }
+
+  /* Wait for an I2C start, if in progress, to complete.  Return FALSE if a
+   * start is pending and did not finish within the allotted time.  Return TRUE
+   * if no start was pending, or it finished within the allotted time.
+   */
+  bool waitStart()
+  {
+    uint16_t t0 = call Counter.get();
+
+    do {
+      if (!(call Registers.getCtl1(UCTXSTT)))
+       return TRUE;
+    } while (call Counter.get() - t0 <= START_CHECK_TIME);
+    return FALSE;
+  }
+
+  /* Wait for an I2C stop, if in progress, to complete.  Return FALSE if a
+   * stop is pending and did not finish within the allotted time.  Return TRUE
+   * if no stop was pending, or it finished within the allotted time.
+   */
+  bool waitStop()
+  {
+    uint16_t t0 = call Counter.get();
+
+    do {
+      if (!(call Registers.getCtl1(UCTXSTP)))
+       return TRUE;
+    } while (call Counter.get() - t0 <= STOP_CHECK_TIME);
+    return FALSE;
+  }
+
+  async command void ResourceConfigure.configure()
+  {
+    atomic {
+      const msp430_usci_i2c_t* config = call Configure.get();
+
+      call Registers.setCtl1(UCSWRST);
+
+      /* Several conditions might find a slave driving the bus at this point.
+       * One such condition is a badly timed PUC of this slave.  Use resetBus()
+       * to attempt rectification of such conditions.
+       */
+      if (!resetBus())
+       return;
+
+      /* Configure USCI registers.  I2C requires UCMODE_3 and UCSYNC */
+      call Registers.assignCtl0(config->ctl0 | UCMODE_3 | UCSYNC);
+      call Registers.assignCtl1(config->ctl1 | UCSWRST);
+      call Registers.assignBr0(config->brx & 0xff);
+      call Registers.assignBr1(config->brx >> 8);
+      call Registers.assignMctl(0);
+      call Registers.assignI2Coa(config->i2coa);
+      call Registers.assignI2Csa(0);
+
+      /* Configure pins for I2C */
+      call SDA.selectModuleFunc();
+      call SCL.selectModuleFunc();
+
+      /* Clear interrupts; we'll add them as needed */
+      call Registers.assignI2Cie(0);
+      call Registers.clrIeRx();
+      call Registers.clrIeTx();
+
+      /* Enable the device */
+      call Registers.clrCtl1(UCSWRST);
+    }
+  }
+
+  void signalDone()
+  {
+    error_t error = (m_pos == m_len) ? SUCCESS : FAIL;
+    uint16_t len = m_len;
+    uint16_t addr;
+
+    /* No more I2C interrupts until the next I2C request arrives */
+    call Registers.assignI2Cie(0);
+    call Registers.clrIeTx();
+    call Registers.clrIeRx();
+
+    /* Wait for stop to finish, if it is in progress. */
+    if (!waitStop())
+      error = FAIL;
+
+    m_len = 0;
+    addr = call Registers.readI2Csa();
+    switch ((call Registers.getCtl0(UCSLA10) ? 2 : 0) +        /* extended addr */
+           (call Registers.getCtl1(UCTR) ? 1 : 0)) {   /* write */
+      case 0:
+       atomic signal I2CBasicPacket.readDone(error, addr, len, m_buf);
+       break;
+      case 1:
+       atomic signal I2CBasicPacket.writeDone(error, addr, len, m_buf);
+       break;
+      case 2:
+       atomic signal I2CExtdPacket.readDone(error, addr, len, m_buf);
+       break;
+      case 3:
+       atomic signal I2CExtdPacket.writeDone(error, addr, len, m_buf);
+       break;
+    }
+  }
+
+  async command void ResourceConfigure.unconfigure()
+  {
+    atomic {
+      /* Signal done if a pending operation */
+      if (isBusy())
+       signalDone();
+
+      /* Disable the device */
+      call Registers.setCtl1(UCSWRST);
+
+      /* Clear interrupts and interrupt flags. */
+      call Registers.assignI2Cie(0);
+      call Registers.clrIeRx();
+      call Registers.clrIfgRx();
+
+      /* Restore I2C pins to IO function */
+      call SDA.selectIOFunc();
+      call SCL.selectIOFunc();
+    }
+  }
+
+  error_t read(i2c_flags_t flags, uint16_t addr, uint8_t len, uint8_t* rxBuf)
+  {
+    /* From the TI family guide SDAU144D, page 17-17:
+     * "Data is received from the slave as long as UCTXSTP or UCTXSTT is not
+     * set.  If UCBxRXBUF is not read the master holds the bus during reception
+     * of the last data bit and until the UCBxRXBUF is read."
+     *
+     * Since the I2C bus can only be suspended during read by *not* reading a
+     * byte out of RXBUF, the only reasonable command after a read() that
+     * did not include I2C_STOP is a read that does not include I2C_START.  A
+     * read that includes I2C_START, or a write (which by definition must
+     * include I2C_START) will effectively create the situation where the
+     * prior read caused the slave to send one more byte than was delivered via
+     * readDone() to the user code.  In many cases, this won't be a problem, as
+     * triggering the slave to read an extra byte will do no harm.  But this
+     * may not always be the case, if for example the slave maintains state
+     * that is changed upon the read of that final byte the user code never
+     * sees.
+     */
+    if (!isConfigured() || isBusy() || len == 0 || !rxBuf)
+      return FAIL;
+
+    m_flags = flags;
+    m_len = len;
+    m_buf = rxBuf;
+    m_pos = 0;
+
+#if 0 /* FIXME: No virtualized alarm for msp430 */
+    /* We require an alarm because we aren't looking at UCALIFG, arbitration
+     * lost.  If this were to happen, the state machine hangs.
+     */
+    call Alarm.start(I2C_TIMEOUT);
+#endif
+    if (m_flags & I2C_EXTENDED)
+      call Registers.setCtl0(UCSLA10);
+    else
+      call Registers.clrCtl0(UCSLA10);
+    call Registers.assignI2Csa(addr);
+    call Registers.clrCtl1(UCTR);
+    if (m_flags & I2C_START) {
+      call Registers.clrIfgRx(); /* see above re: suspending reads */
+      call Registers.setCtl1(UCTXSTT);
+    }
+    if (m_len == 1 && (m_flags & I2C_STOP)) {
+      /* UCTXSTP must assert before hw finishes clocking in the last byte.
+       * FIXME: in reading a single I2C trx using multiple I2C...read() calls,
+       * too much time spent by the user processing the readDone() can cause
+       * the I2C hardware to clock an extra byte's worth of clocks, thereby
+       * issuing 1 byte more of read with the slave, before the stop event.
+       */
+      waitStart();
+      call Registers.setCtl1(UCTXSTP);
+    }
+    call Registers.setI2Cie(UCNACKIE);
+    call Registers.setIeRx();
+    return SUCCESS;
+  }
+
+  async command error_t I2CBasicPacket.read(i2c_flags_t flags, uint16_t addr,
+      uint8_t length, uint8_t* data)
+  {
+    atomic return read(flags & ~I2C_EXTENDED, addr, length, data);
+  }
+
+  async command error_t I2CExtdPacket.read(i2c_flags_t flags, uint16_t addr,
+      uint8_t length, uint8_t* data)
+  {
+    atomic return read(flags | I2C_EXTENDED, addr, length, data);
+  }
+
+  error_t write(i2c_flags_t flags, uint16_t addr, uint8_t len, uint8_t* txBuf)
+  {
+    if (!isConfigured() || isBusy() || len == 0 || !txBuf)
+      return FAIL;
+
+    m_flags = flags;
+    m_len = len;
+    m_buf = txBuf;
+    m_pos = 0;
+
+#if 0 /* FIXME: No virtualized alarm for msp430 */
+    /* We require an alarm because we aren't looking at UCALIFG, arbitration
+     * lost.  If this were to happen, the state machine hangs.
+     */
+    call Alarm.start(I2C_TIMEOUT);
+#endif
+    if (m_flags & I2C_EXTENDED)
+      call Registers.setCtl0(UCSLA10);
+    else
+      call Registers.clrCtl0(UCSLA10);
+    call Registers.assignI2Csa(addr);
+    call Registers.setCtl1((m_flags & I2C_START) ? UCTR + UCTXSTT : UCTR);
+    call Registers.setI2Cie(UCNACKIE);
+    call Registers.setIeTx();
+    return SUCCESS;
+  }
+
+  async command error_t I2CBasicPacket.write(i2c_flags_t flags, uint16_t addr,
+      uint8_t length, uint8_t* data)
+  {
+    atomic return write(flags & ~I2C_EXTENDED, addr, length, data);
+  }
+
+  async command error_t I2CExtdPacket.write(i2c_flags_t flags, uint16_t addr,
+      uint8_t length, uint8_t* data)
+  {
+    atomic return write(flags | I2C_EXTENDED, addr, length, data);
+  }
+
+#if 0 /* FIXME: need a virtualized alarm for msp430 */
+  event void Alarm.fired()
+  {
+    if (m_flags & I2C_STOP) {
+      call Registers.setCtl1(UCTXSTP);
+      call Registers.clrIfgTx();
+    }
+    signalDone();
+  }
+#endif
+
+  async event void Interrupts.tx()
+  {
+    if (m_pos == m_len) {
+      if (m_flags & I2C_STOP) {
+       call Registers.setCtl1(UCTXSTP);
+       call Registers.clrIfgTx();
+      }
+      signalDone();
+    } else
+      call Registers.setTxbuf(m_buf[m_pos++]);
+  }
+
+  async event void Interrupts.rx(uint8_t nobyte)
+  {
+    if (m_len - m_pos == 2 && (m_flags & I2C_STOP)) {
+      /* As soon as we read RXBUF, the hw will begin clocking in the next byte.
+       * To guarantee that a slow uC can still set UCTXSTP before the last
+       * byte is fully clocked, we set it before the second to last byte is
+       * read from RXBUF, when the hw has I2C communications suspended.
+       */
+      call Registers.setCtl1(UCTXSTP);
+    }
+    m_buf[m_pos++] = call Registers.getRxbuf();
+    if (m_pos == m_len)
+      signalDone();
+  }
+
+  async event void Interrupts.i2cNack()
+  {
+    call Registers.setCtl1(UCTXSTP);
+    call Registers.clrStat(UCNACKIFG);
+    signalDone();
+  }
+
+  default async event void I2CBasicPacket.readDone(error_t error, uint16_t addr,
+      uint8_t length, uint8_t* data) {}
+  default async event void I2CBasicPacket.writeDone(error_t error,
+      uint16_t addr, uint8_t length, uint8_t* data) {}
+  default async event void I2CExtdPacket.readDone(error_t error, uint16_t addr,
+      uint8_t length, uint8_t* data) {}
+  default async event void I2CExtdPacket.writeDone(error_t error, uint16_t addr,
+      uint8_t length, uint8_t* data) {}
+
+  default async command const msp430_usci_i2c_t* Configure.get()
+  {
+    const static msp430_usci_i2c_t def = {
+      ctl0: UCSYNC | UCMODE_3 | UCMST, /* I2C master */
+      ctl1: UCSWRST | UCSSEL_3,                /* I2C clock source is SMCLK */
+      brx: 10,                 /* I2C clock=SMCLK/10; ~95KHz if SMCLK=2^20Hz */
+      ren: USCI_REN_NONE
+    };
+
+    return &def;
+  }
+
+  async event void Interrupts.i2cStart() {}
+  async event void Interrupts.i2cStop() {}
+  async event void Interrupts.i2cCal() {}
+  async event void Interrupts.brk() {}
+  async event void Counter.overflow() {}
+}