blob: be730a397cd76eb9db531b7633a00e5da24dbb84 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ADK_INTERNAL
#include "fwk.h"
#include "I2C.h"
#include "coop.h"
#include "dbg.h"
// the hardware interface in this chip is as pleasant to use as cuddling a pirahna, hence the bit-bang solution.
// to use the hardware, set I2C_BITBANG to 0.
// to use the peripheral DMA with the I2C hardware, set I2C_PDC to 1.
#define I2C_BITBANG 0
#define I2C_PDC 1
#define I2C_INT_PDC 1
#define I2C_STAT_DONE 0
#define I2C_STAT_LASTREAD 2
#define I2C_STAT_BUSY 1
#define I2C_STAT_NACK 3
typedef struct _i2cInst
{
Twi *pTwi;
volatile uint8_t status;
uint8_t *lastByte;
} i2c_i;
static i2c_i i2cInst[I2C_NUM_INSTANCES] = { { TWI0, 0, 0 }, { TWI1, 0, 0 } };
static char sdaRd(uint8_t instance);
static void sdaLo(uint8_t instance);
static void sdaHi(uint8_t instance);
static void sclLo(uint8_t instance);
static void sclHi(uint8_t instance);
static void i2cBitTx(uint8_t instance, char bit);
static void i2cStop(uint8_t instance);
static void i2cPdcDisable(Twi *pTwi);
//sda first please
static const uint8_t pins[I2C_NUM_INSTANCES][2] = { { PORTA(17), PORTA(18) }, { PORTB(12), PORTB(13) } };
static uint32_t delayTicks[I2C_NUM_INSTANCES];
static void i2cDelay(uint8_t instance){
fwkDelay(delayTicks[instance]);
}
void i2cClock(uint8_t instance, uint32_t clock){
#if I2C_BITBANG
delayTicks[instance] = (TICKS_PER_MS * 1000) / (8 * clock * 2);
#else
Twi *pTwi;
uint32_t dwCkDiv = 0;
uint32_t dwClDiv;
uint32_t dwOk = 0;
if (instance == 0)
pTwi = TWI0;
else
pTwi = TWI1;
while (!dwOk) {
dwClDiv = ((BOARD_MCK / (2 * clock)) - 4) / (1 << dwCkDiv);
if (dwClDiv <= 255)
dwOk = 1;
else
dwCkDiv++;
}
pTwi->TWI_CWGR = 0;
pTwi->TWI_CWGR = (dwCkDiv << 16) | (dwClDiv << 8) | dwClDiv;
#endif
}
// i2c interrupt handler used only for PDC mode
// to prevent runaway operations caused by not responding
// fast enough and setting the stop bit.
void i2c_handler( i2c_i *pInst )
{
uint32_t status;
Twi *pTwi = pInst->pTwi;
status = pTwi->TWI_SR & pTwi->TWI_IMR; // Masked status
if (TWI_SR_TXBUFE & status) { // Transmit has completed.
i2cPdcDisable(pTwi);
pTwi->TWI_CR = TWI_CR_STOP;
pTwi->TWI_IDR = TWI_IDR_TXBUFE;
pTwi->TWI_IER = TWI_IER_TXCOMP;
} else if (TWI_SR_RXBUFF & status) { // Receive has completed
if (pInst->status == I2C_STAT_LASTREAD) {
i2cPdcDisable(pTwi);
pTwi->TWI_IDR = TWI_IDR_RXBUFF;
pTwi->TWI_IER = TWI_IER_TXCOMP;
} else {
pInst->status = I2C_STAT_LASTREAD;
i2cPdcDisable(pTwi);
pTwi->TWI_CR = TWI_CR_STOP;
pTwi->TWI_RPR = pInst->lastByte;
pTwi->TWI_RCR = 1;
pTwi->TWI_PTCR = TWI_PTCR_RXTEN;
}
} else if (TWI_SR_TXCOMP & status) {
pInst->status = I2C_STAT_DONE;
pTwi->TWI_IDR = TWI_IDR_TXCOMP;
} else if (TWI_SR_NACK & status) {
pInst->status = I2C_STAT_NACK;
i2cPdcDisable(pTwi);
}
}
void TWI0_Handler(void)
{
i2c_handler(&i2cInst[0]);
}
void TWI1_Handler(void)
{
i2c_handler(&i2cInst[1]);
}
uint8_t i2cInit(uint8_t instance, uint32_t clock){
uint8_t i;
if(instance >= I2C_NUM_INSTANCES) return I2C_ERR_INVAL;
gpioSetFun(pins[instance][0], GPIO_FUNC_GPIO);
gpioSetFun(pins[instance][1], GPIO_FUNC_GPIO);
gpioSetPullup(pins[instance][0], 1);
gpioSetPullup(pins[instance][1], 1);
gpioSetDir(pins[instance][0], 0);
gpioSetDir(pins[instance][1], 0);
gpioSetVal(pins[instance][0], 0);
gpioSetVal(pins[instance][1], 0);
sdaHi(instance);
sclHi(instance);
delayTicks[instance] = (TICKS_PER_MS * 1000) / (8 * clock * 2);
i2cDelay(instance);
i2cDelay(instance);
for (i = 0; i < 100; i++) // Try to reset the bus
i2cBitTx(instance, 1);
i2cStop(instance);
#if !I2C_BITBANG
PMC_EnablePeripheral(instance ? ID_TWI1 : ID_TWI0);
gpioSetFun(pins[instance][0], GPIO_FUNC_A);
gpioSetFun(pins[instance][1], GPIO_FUNC_A);
TWI_ConfigureMaster(instance ? TWI1 : TWI0, clock, BOARD_MCK);
#if I2C_INT_PDC
NVIC_EnableIRQ(instance ? TWI1_IRQn : TWI0_IRQn);
if (instance)
TWI1->TWI_IDR = 0x00F77;
else
TWI0->TWI_IDR = 0x00F77;
#endif
#endif
return I2C_ALL_OK;
}
static void i2cStart(uint8_t instance){
sdaHi(instance);
sclHi(instance);
i2cDelay(instance);
sdaLo(instance);
i2cDelay(instance);
sclLo(instance);
i2cDelay(instance);
}
static void i2cStop(uint8_t instance){
i2cDelay(instance);
sdaLo(instance);
i2cDelay(instance);
sclHi(instance);
i2cDelay(instance);
sdaHi(instance);
}
static char i2cBitRx(uint8_t instance){
char ret;
sdaHi(instance);
i2cDelay(instance);
sclHi(instance);
i2cDelay(instance);
ret = sdaRd(instance);
sclLo(instance);
return ret;
}
static void i2cBitTx(uint8_t instance, char bit){
if(bit) sdaHi(instance);
else sdaLo(instance);
i2cDelay(instance);
sclHi(instance);
i2cDelay(instance);
sclLo(instance);
}
static char i2cSend(uint8_t instance, uint8_t val){ //return 1 -> ack, 0->nak
uint8_t i;
for(i = 0; i < 8; i++){
i2cBitTx(instance, !!(val & 0x80));
val <<= 1;
}
return !i2cBitRx(instance);
}
static uint8_t i2cRecv(uint8_t instance, char ack){
uint8_t i, v = 0;
for(i = 0; i < 8; i++){
v <<= 1;
if(i2cBitRx(instance)) v |= 1;
}
i2cBitTx(instance, !ack);
return v;
}
static void i2cWaitComplete(Twi *pTwi)
{
while(!(pTwi->TWI_SR & TWI_SR_TXCOMP)) coopYield();
}
static uint32_t i2cWaitRx(Twi *pTwi)
{
uint32_t stat;
do {
coopYield();
stat = pTwi->TWI_SR;
if (stat & TWI_SR_NACK) {
return stat;
}
} while (!(stat & TWI_SR_RXRDY));
}
static uint32_t i2cWaitTx(Twi *pTwi)
{
uint32_t stat;
do {
coopYield();
stat = pTwi->TWI_SR;
if (stat & TWI_SR_NACK) {
return stat;
}
} while (!(stat & TWI_SR_TXRDY));
return 0;
}
static void i2cPdcDisable(Twi *pTwi)
{
pTwi->TWI_PTCR = TWI_PTCR_RXTDIS;
pTwi->TWI_PTCR = TWI_PTCR_TXTDIS;
pTwi->TWI_RPR = 0;
pTwi->TWI_RCR = 0;
pTwi->TWI_TPR = 0;
pTwi->TWI_TCR = 0;
}
static uint32_t i2cWaitPdcTx(Twi *pTwi)
{
uint32_t stat;
do {
coopYield();
stat = pTwi->TWI_SR;
if (stat & TWI_SR_NACK) {
return stat;
}
} while (!(stat & TWI_SR_TXBUFE));
return stat;
}
static uint32_t i2cWaitPdcRx(Twi *pTwi)
{
uint32_t stat;
do {
coopYield();
stat = pTwi->TWI_SR;
if ((stat & TWI_SR_NACK) || ((stat & TWI_SR_TXCOMP) && !(stat & TWI_SR_RXBUFF))) {
return stat;
}
} while(!(stat & TWI_SR_RXBUFF));
return stat;
}
uint8_t i2cOp(uint8_t instance, uint8_t addr, const uint8_t* sendData, uint8_t sendSz, uint8_t* recvData, uint8_t recvSz){
uint8_t ret = I2C_ERR_NAK;
uint32_t stat;
#if I2C_PDC
uint8_t dat[256];
uint8_t i;
uint32_t tmp;
if (sendData != NULL && (uint32_t)sendData < 0x20000000) { /* Undocumented feature: PDC can't read from flash */
if (sendSz > 256)
return I2C_ERR_INVAL;
for (i = 0; i < sendSz; i++)
dat[i] = *sendData++;
sendData = dat;
}
#endif
#if !I2C_BITBANG
Twi *pTwi;
#endif
if(instance >= I2C_NUM_INSTANCES) return I2C_ERR_INVAL;
#if !I2C_BITBANG
pTwi = (instance == 0) ? TWI0 : TWI1;
#endif
#if I2C_BITBANG
addr <<= 1;
if(sendSz || !recvSz){ //if neither send nor recv requested, do a single send -> this is for a scan
i2cStart(instance);
if(!i2cSend(instance, addr)) goto out;
while(sendSz--) if(!i2cSend(instance, *sendData++)) goto out;
}
if(recvSz){
i2cStart(instance); //if we already sent something, restart
if(!i2cSend(instance, addr | 1)) goto out;
while(recvSz){
recvSz--;
*recvData++ = i2cRecv(instance, !!recvSz);
}
}
ret = I2C_ALL_OK;
#else
if (sendSz == 0 && recvSz == 0) { // Quick (for a scan)
pTwi->TWI_MMR = 0;
pTwi->TWI_MMR = (addr << 16);
pTwi->TWI_CR = TWI_CR_QUICK;
if (TWI_SR_NACK & i2cWaitTx(pTwi)) {
i2cWaitComplete(pTwi);
ret = I2C_ERR_NAK;
goto out;
}
i2cWaitComplete(pTwi);
}
#if I2C_PDC
if (sendSz != 0) {
pTwi->TWI_TPR = (uint32_t)sendData;
pTwi->TWI_TCR = sendSz;
pTwi->TWI_MMR = 0;
pTwi->TWI_MMR = (addr << 16);
pTwi->TWI_IADR = 0;
pTwi->TWI_IADR = 0;
pTwi->TWI_PTCR = TWI_PTCR_TXTEN;
#if I2C_INT_PDC
i2cInst[instance].status = I2C_STAT_BUSY;
pTwi->TWI_IER = TWI_IER_TXBUFE | TWI_IER_NACK;
while((i2cInst[instance].status != I2C_STAT_DONE) &&
(i2cInst[instance].status != I2C_STAT_NACK))
coopYield();
if (i2cInst[instance].status == I2C_STAT_NACK) {
ret = I2C_ERR_NAK;
goto out;
}
#else
if (TWI_SR_NACK & i2cWaitPdcTx(pTwi)) {
i2cPdcDisable(pTwi);
i2cWaitComplete(pTwi);
ret = I2C_ERR_NAK;
goto out;
}
i2cPdcDisable(pTwi);
pTwi->TWI_CR = TWI_CR_STOP;
i2cWaitComplete(pTwi);
#endif
}
if (recvSz == 1) { // We are forced to do the one byte reads manually.
pTwi->TWI_RPR = (uint32_t)recvData;
pTwi->TWI_RCR = 1;
pTwi->TWI_MMR = 0;
pTwi->TWI_MMR = TWI_MMR_MREAD | (addr << 16);
pTwi->TWI_CR = TWI_CR_START | TWI_CR_STOP;
pTwi->TWI_PTCR = TWI_PTCR_RXTEN;
#if I2C_INT_PDC
i2cInst[instance].status = I2C_STAT_LASTREAD;
pTwi->TWI_IER = TWI_IER_RXBUFF | TWI_IER_NACK;
while((i2cInst[instance].status != I2C_STAT_DONE) &&
(i2cInst[instance].status != I2C_STAT_NACK))
coopYield();
if (i2cInst[instance].status == I2C_STAT_NACK) {
ret = I2C_ERR_NAK;
goto out;
}
#else
if (TWI_SR_NACK & i2cWaitPdcRx(pTwi)) {
i2cPdcDisable(pTwi);
i2cWaitComplete(pTwi);
ret = I2C_ERR_NAK;
goto out;
}
i2cPdcDisable(pTwi);
i2cWaitComplete(pTwi);
#endif
}
if (recvSz > 1) {
pTwi->TWI_RPR = (uint32_t)recvData;
pTwi->TWI_RCR = recvSz - 1; // Last byte read is handled manually
pTwi->TWI_MMR = 0;
pTwi->TWI_MMR = TWI_MMR_MREAD | (addr << 16);
pTwi->TWI_CR = TWI_CR_START;
pTwi->TWI_PTCR = TWI_PTCR_RXTEN;
#if I2C_INT_PDC
i2cInst[instance].lastByte = recvData + recvSz - 1;
i2cInst[instance].status = I2C_STAT_BUSY;
pTwi->TWI_IER = TWI_IER_RXBUFF | TWI_IER_NACK;
while((i2cInst[instance].status != I2C_STAT_DONE) &&
(i2cInst[instance].status != I2C_STAT_NACK))
coopYield();
if (i2cInst[instance].status == I2C_STAT_NACK) {
ret = I2C_ERR_NAK;
goto out;
}
#else
if (TWI_SR_NACK & i2cWaitPdcRx(pTwi)) {
i2cPdcDisable(pTwi);
i2cWaitComplete(pTwi);
ret = I2C_ERR_NAK;
goto out;
}
i2cPdcDisable(pTwi);
pTwi->TWI_CR = TWI_CR_STOP;
pTwi->TWI_RPR = (uint32_t)(recvData + recvSz - 1);
pTwi->TWI_RCR = 1;
pTwi->TWI_PTCR = TWI_PTCR_RXTEN;
if (TWI_SR_NACK & i2cWaitPdcRx(pTwi)) {
i2cPdcDisable(pTwi);
i2cWaitComplete(pTwi);
ret = I2C_ERR_NAK;
goto out;
}
i2cPdcDisable(pTwi);
i2cWaitComplete(pTwi);
#endif
}
ret = I2C_ALL_OK;
#else
if (sendSz != 0) {
pTwi->TWI_MMR = 0;
pTwi->TWI_MMR = (addr << 16);
pTwi->TWI_IADR = 0;
pTwi->TWI_IADR = 0;
while (sendSz--) {
pTwi->TWI_THR = *sendData++;
stat = i2cWaitTx(pTwi);
if (stat & TWI_SR_NACK) {
ret = I2C_ERR_NAK;
goto out;
}
}
pTwi->TWI_CR = TWI_CR_STOP;
i2cWaitComplete(pTwi);
}
if (recvSz == 1) {
pTwi->TWI_MMR = 0;
pTwi->TWI_MMR = TWI_MMR_MREAD | (addr << 16);
pTwi->TWI_IADR = 0;
pTwi->TWI_IADR = 0;
pTwi->TWI_CR = TWI_CR_START | TWI_CR_STOP;
if (TWI_SR_NACK & i2cWaitRx(pTwi)) {
ret = I2C_ERR_NAK;
goto out;
}
*recvData = pTwi->TWI_RHR;
i2cWaitComplete(pTwi);
} else if (recvSz >= 1) {
pTwi->TWI_MMR = 0;
pTwi->TWI_MMR = TWI_MMR_MREAD | (addr << 16);
pTwi->TWI_IADR = 0;
pTwi->TWI_IADR = 0;
pTwi->TWI_CR = TWI_CR_START;
while (recvSz--) {
if (TWI_SR_NACK & i2cWaitRx(pTwi)) {
ret = I2C_ERR_NAK;
goto out;
}
dbgPrintf("\0");
*recvData++ = pTwi->TWI_RHR;
if (recvSz == 1) pTwi->TWI_CR = TWI_CR_STOP;
}
i2cWaitComplete(pTwi);
}
ret = I2C_ALL_OK;
#endif
#endif
out:
#if I2C_BITBANG
i2cStop(instance);
#else
if (ret != I2C_ALL_OK) {
pTwi->TWI_CR = TWI_CR_STOP;
i2cWaitComplete(pTwi);
}
#endif
return ret;
}
uint8_t i2cSingleWrite(uint8_t instance, uint8_t addr, uint8_t reg, uint8_t val){
unsigned char data[2] = {reg, val};
return i2cOp(instance, addr, data, 2, NULL, 0);
}
uint8_t i2cSingleRead(uint8_t instance, uint8_t addr, uint8_t reg){
uint8_t ret, val;
ret = i2cOp(instance, addr, &reg, 1, &val, 1);
if(ret != I2C_ALL_OK) val = 0xFF;
return val;
}
uint8_t i2cQuick(uint8_t instance, uint8_t addr, uint8_t byte){
return i2cOp(instance, addr, &byte, 1, NULL, 0);
}
uint8_t i2cScan(uint8_t instance, uint8_t addr){
return i2cOp(instance, addr, NULL, 0, NULL, 0);
}
static char sdaRd(uint8_t instance){
return gpioGetVal(pins[instance][0]);
}
static void sdaLo(uint8_t instance){
gpioSetDir(pins[instance][0], 0);
}
static void sdaHi(uint8_t instance){
gpioSetDir(pins[instance][0], 1);
}
static void sclLo(uint8_t instance){
gpioSetDir(pins[instance][1], 0);
}
static void sclHi(uint8_t instance){
gpioSetDir(pins[instance][1], 1);
while(!gpioGetVal(pins[instance][1]));
}