X-Git-Url: https://oss.titaniummirror.com/gitweb/?a=blobdiff_plain;f=tos%2Fchips%2Fmsp430%2Fusci%2FMsp430I2CP.nc;fp=tos%2Fchips%2Fmsp430%2Fusci%2FMsp430I2CP.nc;h=8ce827d0eb1eaa0706498d268430aa9d6d5f9015;hb=c62b18891808f1958e9e04708f91c5ea6fb2c765;hp=0000000000000000000000000000000000000000;hpb=d4339168075e5b8367972937a71f9665ff3cc4f0;p=tinyos-2.x.git diff --git a/tos/chips/msp430/usci/Msp430I2CP.nc b/tos/chips/msp430/usci/Msp430I2CP.nc new file mode 100644 index 00000000..8ce827d0 --- /dev/null +++ b/tos/chips/msp430/usci/Msp430I2CP.nc @@ -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 + */ + +generic module Msp430I2CP() { + provides { + interface I2CPacket as I2CBasicPacket; + interface I2CPacket as I2CExtdPacket; + interface ResourceConfigure; + } + uses { + interface HplMsp430UsciReg as Registers; + interface HplMsp430UsciInt as Interrupts; + interface HplMsp430GeneralIO as SDA; + interface HplMsp430GeneralIO as SCL; + interface AsyncConfigure as Configure; + interface ArbiterInfo; + interface BusyWait; + interface Counter; + } +} +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() {} +}