diff options
Diffstat (limited to 'cpu/mpc824x/drivers/i2c/i2c1.c')
-rw-r--r-- | cpu/mpc824x/drivers/i2c/i2c1.c | 1228 |
1 files changed, 1228 insertions, 0 deletions
diff --git a/cpu/mpc824x/drivers/i2c/i2c1.c b/cpu/mpc824x/drivers/i2c/i2c1.c new file mode 100644 index 0000000..be6ec60 --- /dev/null +++ b/cpu/mpc824x/drivers/i2c/i2c1.c @@ -0,0 +1,1228 @@ +/************************************************************* + * + * Copyright @ Motorola, 1999 + * + ************************************************************/ +#include <common.h> + +#ifdef CONFIG_HARD_I2C +#include <i2c.h> +#include "i2c_export.h" +#include "i2c.h" + +#undef I2CDBG0 +#undef DEBUG + +/* Define a macro to use an optional application-layer print function, if + * one was passed to the I2C library during initialization. If there was + * no function pointer passed, this protects against calling it. Also define + * the global variable that holds the passed pointer. + */ +#define TIMEOUT (CFG_HZ/4) +#define PRINT if ( app_print ) app_print +static int (*app_print) (char *, ...); + +/******************* Internal to I2C Driver *****************/ +static unsigned int ByteToXmit = 0; +static unsigned int XmitByte = 0; +static unsigned char *XmitBuf = 0; +static unsigned int XmitBufEmptyStop = 0; +static unsigned int ByteToRcv = 0; +static unsigned int RcvByte = 0; +static unsigned char *RcvBuf = 0; +static unsigned int RcvBufFulStop = 0; +static unsigned int MasterRcvAddress = 0; + +/* Set by call to get_eumbbar during I2C_Initialize. + * This could be globally available to the I2C library, but there is + * an advantage to passing it as a parameter: it is already in a register + * and doesn't have to be loaded from memory. Also, that is the way the + * I2C library was already implemented and I don't want to change it without + * a more detailed analysis. + * It is being set as a global variable in I2C_Initialize to hide it from + * the DINK application layer, because it is Kahlua-specific. I think that + * get_eumbbar, load_runtime_reg, and store_runtime_reg should be defined in + * a Kahlua-specific library dealing with the embedded utilities memory block. + * Right now, get_eumbbar is defined in dink32/kahlua.s. The other two are + * defined in dink32/drivers/i2c/i2c2.s. + */ +static unsigned int Global_eumbbar = 0; + +extern unsigned int load_runtime_reg (unsigned int eumbbar, + unsigned int reg); + +extern unsigned int store_runtime_reg (unsigned int eumbbar, + unsigned int reg, unsigned int val); + +/************************** API *****************/ + +/* Application Program Interface (API) are the calls provided by the I2C + * library to upper layer applications (i.e., DINK) to access the Kahlua + * I2C bus interface. The functions and values that are part of this API + * are declared in i2c_export.h. + */ + +/* Initialize I2C unit with the following: + * driver's slave address + * interrupt enabled + * optional pointer to application layer print function + * + * These parameters may be added: + * desired clock rate + * digital filter frequency sampling rate + * + * This function must be called before I2C unit can be used. + */ +I2C_Status I2C_Initialize (unsigned char addr, + I2C_INTERRUPT_MODE en_int, + int (*p) (char *, ...)) +{ + I2CStatus status; + + /* establish the pointer, if there is one, to the application's "printf" */ + app_print = p; + + /* If this is the first call, get the embedded utilities memory block + * base address. I'm not sure what to do about error handling here: + * if a non-zero value is returned, accept it. + */ + if (Global_eumbbar == 0) + Global_eumbbar = get_eumbbar (); + if (Global_eumbbar == 0) { + PRINT ("I2C_Initialize: can't find EUMBBAR\n"); + return I2C_ERROR; + } + + /* validate the I2C address */ + if (addr & 0x80) { + PRINT ("I2C_Initialize, I2C address invalid: %d 0x%x\n", + (unsigned int) addr, (unsigned int) addr); + return I2C_ERROR; + } + + /* Call the internal I2C library function to perform work. + * Accept the default frequency sampling rate (no way to set it currently, + * via I2C_Init) and set the clock frequency to something reasonable. + */ + status = I2C_Init (Global_eumbbar, (unsigned char) 0x31, addr, en_int); + if (status != I2CSUCCESS) { + PRINT ("I2C_Initialize: error in initiation\n"); + return I2C_ERROR; + } + + /* all is well */ + return I2C_SUCCESS; +} + + +/* Perform the given I2C transaction, only MASTER_XMIT and MASTER_RCV + * are implemented. Both are only in polling mode. + * + * en_int controls interrupt/polling mode + * act is the type of transaction + * i2c_addr is the I2C address of the slave device + * data_addr is the address of the data on the slave device + * len is the length of data to send or receive + * buffer is the address of the data buffer + * stop = I2C_NO_STOP, don't signal STOP at end of transaction + * I2C_STOP, signal STOP at end of transaction + * retry is the timeout retry value, currently ignored + * rsta = I2C_NO_RESTART, this is not continuation of existing transaction + * I2C_RESTART, this is a continuation of existing transaction + */ +I2C_Status I2C_do_transaction ( I2C_INTERRUPT_MODE en_int, + I2C_TRANSACTION_MODE act, + unsigned char i2c_addr, + unsigned char data_addr, + int len, + char *buffer, + I2C_STOP_MODE stop, + int retry, I2C_RESTART_MODE rsta) +{ + I2C_Status status; + unsigned char data_addr_buffer[1]; + +#if 1 +/* This is a temporary work-around. The I2C library breaks the protocol + * if it attempts to handle a data transmission in more than one + * transaction, so the data address and the actual data bytes are put + * into a single buffer before sending it to the library internal functions. + * The problem is related to being able to restart a transaction without + * sending the I2C device address or repeating the data address. It may take + * a day or two to sort it all out, so I'll have to get back to it later. + * Look at I2C_Start to see about using some status flags (I'm not sure that + * "stop" and "rsta" are enough to reflect the states, maybe so; but the logic + * in the library is insufficient) to control correct handling of the protocol. + */ + unsigned char dummy_buffer[257]; + + if (act == I2C_MASTER_XMIT) { + int i; + + if (len > 256) + return I2C_ERROR; + for (i = 1; i <= len; i++) + dummy_buffer[i] = buffer[i - 1]; + dummy_buffer[0] = data_addr; + status = I2C_do_buffer (en_int, act, i2c_addr, 1 + len, + dummy_buffer, stop, retry, rsta); + if (status != I2C_SUCCESS) { + PRINT ("I2C_do_transaction: can't perform data transfer\n"); + return I2C_ERROR; + } + return I2C_SUCCESS; + } +#endif /* end of temp work-around */ + + /* validate requested transaction type */ + if ((act != I2C_MASTER_XMIT) && (act != I2C_MASTER_RCV)) { + PRINT ("I2C_do_transaction, invalid transaction request: %d\n", + act); + return I2C_ERROR; + } + + /* range check the I2C address */ + if (i2c_addr & 0x80) { + PRINT ("I2C_do_transaction, I2C address out of range: %d 0x%x\n", + (unsigned int) i2c_addr, (unsigned int) i2c_addr); + return I2C_ERROR; + } else { + data_addr_buffer[0] = data_addr; + } + + /* + * We first have to contact the slave device and transmit the + * data address. Be careful about the STOP and restart stuff. + * We don't want to signal STOP after sending the data + * address, but this could be a continuation if the + * application didn't release the bus after the previous + * transaction, by not sending a STOP after it. + */ + status = I2C_do_buffer (en_int, I2C_MASTER_XMIT, i2c_addr, 1, + data_addr_buffer, I2C_NO_STOP, retry, rsta); + if (status != I2C_SUCCESS) { + PRINT ("I2C_do_transaction: can't send data address for read\n"); + return I2C_ERROR; + } + + /* The data transfer will be a continuation. */ + rsta = I2C_RESTART; + + /* now handle the user data */ + status = I2C_do_buffer (en_int, act, i2c_addr, len, + buffer, stop, retry, rsta); + if (status != I2C_SUCCESS) { + PRINT ("I2C_do_transaction: can't perform data transfer\n"); + return I2C_ERROR; + } + + /* all is well */ + return I2C_SUCCESS; +} + +/* This function performs the work for I2C_do_transaction. The work is + * split into this function to enable I2C_do_transaction to first transmit + * the data address to the I2C slave device without putting the data address + * into the first byte of the buffer. + * + * en_int controls interrupt/polling mode + * act is the type of transaction + * i2c_addr is the I2C address of the slave device + * len is the length of data to send or receive + * buffer is the address of the data buffer + * stop = I2C_NO_STOP, don't signal STOP at end of transaction + * I2C_STOP, signal STOP at end of transaction + * retry is the timeout retry value, currently ignored + * rsta = I2C_NO_RESTART, this is not continuation of existing transaction + * I2C_RESTART, this is a continuation of existing transaction + */ +static I2C_Status I2C_do_buffer (I2C_INTERRUPT_MODE en_int, + I2C_TRANSACTION_MODE act, + unsigned char i2c_addr, + int len, + unsigned char *buffer, + I2C_STOP_MODE stop, + int retry, I2C_RESTART_MODE rsta) +{ + I2CStatus rval; + unsigned int dev_stat; + + if (act == I2C_MASTER_RCV) { + /* set up for master-receive transaction */ + rval = I2C_get (Global_eumbbar, i2c_addr, buffer, len, stop, rsta); + } else { + /* set up for master-transmit transaction */ + rval = I2C_put (Global_eumbbar, i2c_addr, buffer, len, stop, rsta); + } + + /* validate the setup */ + if (rval != I2CSUCCESS) { + dev_stat = load_runtime_reg (Global_eumbbar, I2CSR); + PRINT ("Error(I2C_do_buffer): control phase, code(0x%08x), status(0x%08x)\n", rval, dev_stat); + I2C_Stop (Global_eumbbar); + return I2C_ERROR; + } + + if (en_int == 1) { + /* this should not happen, no interrupt handling yet */ + return I2C_SUCCESS; + } + + /* this performs the polling action, when the transfer is completed, + * the status returned from I2C_Timer_Event will be I2CBUFFFULL or + * I2CBUFFEMPTY (rcv or xmit), I2CSUCCESS or I2CADDRESS indicates the + * transaction is not yet complete, anything else is an error. + */ + while (rval == I2CSUCCESS || rval == I2CADDRESS) { + int timeval = get_timer (0); + + /* poll the device until something happens */ + do { + rval = I2C_Timer_Event (Global_eumbbar, 0); + } + while (rval == I2CNOEVENT && get_timer (timeval) < TIMEOUT); + + /* check for error condition */ + if (rval == I2CSUCCESS || + rval == I2CBUFFFULL || + rval == I2CBUFFEMPTY || + rval == I2CADDRESS) { + ; /* do nothing */ + } else { + /* report the error condition */ + dev_stat = load_runtime_reg (Global_eumbbar, I2CSR); + PRINT ("Error(I2C_do_buffer): code(0x%08x), status(0x%08x)\n", + rval, dev_stat); + return I2C_ERROR; + } + } + + /* all is well */ + return I2C_SUCCESS; +} + +/** + * Note: + * + * In all following functions, + * the caller shall pass the configured embedded utility memory + * block base, EUMBBAR. + **/ + +/*********************************************************** + * function: I2C_put + * + * description: + Send a buffer of data to the intended rcv_addr. + * If stop_flag is set, after the whole buffer + * is sent, generate a STOP signal provided that the + * receiver doesn't signal the STOP in the middle. + * I2C is the master performing transmitting. If + * no STOP signal is generated at the end of current + * transaction, the master can generate a START signal + * to another slave addr. + * + * note: this is master xmit API + *********************************************************/ +static I2CStatus I2C_put (unsigned int eumbbar, unsigned char rcv_addr, /* receiver's address */ + unsigned char *buffer_ptr, /* pointer of data to be sent */ + unsigned int length, /* number of byte of in the buffer */ + unsigned int stop_flag, /* 1 - signal STOP when buffer is empty + * 0 - no STOP signal when buffer is empty + */ + unsigned int is_cnt) +{ /* 1 - this is a restart, don't check MBB + * 0 - this is a new start, check MBB + */ + if (buffer_ptr == 0 || length == 0) { + return I2CERROR; + } +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_put\n", __FILE__, __LINE__); +#endif + + XmitByte = 0; + ByteToXmit = length; + XmitBuf = buffer_ptr; + XmitBufEmptyStop = stop_flag; + + RcvByte = 0; + ByteToRcv = 0; + RcvBuf = 0; + + /* we are the master, start transaction */ + return I2C_Start (eumbbar, rcv_addr, XMIT, is_cnt); +} + +/*********************************************************** + * function: I2C_get + * + * description: + * Receive a buffer of data from the desired sender_addr + * If stop_flag is set, when the buffer is full and the + * sender does not signal STOP, generate a STOP signal. + * I2C is the master performing receiving. If no STOP signal + * is generated, the master can generate a START signal + * to another slave addr. + * + * note: this is master receive API + **********************************************************/ +static I2CStatus I2C_get (unsigned int eumbbar, unsigned char rcv_from, /* sender's address */ + unsigned char *buffer_ptr, /* pointer of receiving buffer */ + unsigned int length, /* length of the receiving buffer */ + unsigned int stop_flag, /* 1 - signal STOP when buffer is full + * 0 - no STOP signal when buffer is full + */ + unsigned int is_cnt) +{ /* 1 - this is a restart, don't check MBB + * 0 - this is a new start, check MBB + */ + if (buffer_ptr == 0 || length == 0) { + return I2CERROR; + } +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_get\n", __FILE__, __LINE__); +#endif + + RcvByte = 0; + ByteToRcv = length; + RcvBuf = buffer_ptr; + RcvBufFulStop = stop_flag; + + XmitByte = 0; + ByteToXmit = 0; + XmitBuf = 0; + + /* we are the master, start the transaction */ + return I2C_Start (eumbbar, rcv_from, RCV, is_cnt); + +} + +#if 0 /* turn off dead code */ +/********************************************************* + * function: I2C_write + * + * description: + * Send a buffer of data to the requiring master. + * If stop_flag is set, after the whole buffer is sent, + * generate a STOP signal provided that the requiring + * receiver doesn't signal the STOP in the middle. + * I2C is the slave performing transmitting. + * + * Note: this is slave xmit API. + * + * due to the current Kahlua design, slave transmitter + * shall not signal STOP since there is no way + * for master to detect it, causing I2C bus hung. + * + * For the above reason, the stop_flag is always + * set, i.e., 0. + * + * programmer shall use the timer on Kahlua to + * control the interval of data byte at the + * master side. + *******************************************************/ +static I2CStatus I2C_write (unsigned int eumbbar, unsigned char *buffer_ptr, /* pointer of data to be sent */ + unsigned int length, /* number of byte of in the buffer */ + unsigned int stop_flag) +{ /* 1 - signal STOP when buffer is empty + * 0 - no STOP signal when buffer is empty + */ + if (buffer_ptr == 0 || length == 0) { + return I2CERROR; + } + + XmitByte = 0; + ByteToXmit = length; + XmitBuf = buffer_ptr; + XmitBufEmptyStop = 0; /* in order to avoid bus hung, ignored the user's stop_flag */ + + RcvByte = 0; + ByteToRcv = 0; + RcvBuf = 0; + + /* we are the slave, just wait for being called, or pull */ + /* I2C_Timer_Event( eumbbar ); */ +} + +/****************************************************** + * function: I2C_read + * + * description: + * Receive a buffer of data from the sending master. + * If stop_flag is set, when the buffer is full and the + * sender does not signal STOP, generate a STOP signal. + * I2C is the slave performing receiving. + * + * note: this is slave receive API + ****************************************************/ +static I2CStatus I2C_read (unsigned int eumbbar, unsigned char *buffer_ptr, /* pointer of receiving buffer */ + unsigned int length, /* length of the receiving buffer */ + unsigned int stop_flag) +{ /* 1 - signal STOP when buffer is full + * 0 - no STOP signal when buffer is full + */ + if (buffer_ptr == 0 || length == 0) { + return I2CERROR; + } + + RcvByte = 0; + ByteToRcv = length; + RcvBuf = buffer_ptr; + RcvBufFulStop = stop_flag; + + XmitByte = 0; + ByteToXmit = 0; + XmitBuf = 0; + + /* wait for master to call us, or poll */ + /* I2C_Timer_Event( eumbbar ); */ +} +#endif /* turn off dead code */ + +/********************************************************* + * function: I2c_Timer_Event + * + * description: + * if interrupt is not used, this is the timer event handler. + * After each fixed time interval, this function can be called + * to check the I2C status and call appropriate function to + * handle the status event. + ********************************************************/ +static I2CStatus I2C_Timer_Event (unsigned int eumbbar, + I2CStatus (*handler) (unsigned int)) +{ + I2C_STAT stat; + +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_Timer_Event\n", __FILE__, __LINE__); +#endif + + stat = I2C_Get_Stat (eumbbar); + + if (stat.mif == 1) { + if (handler == 0) { + return I2C_ISR (eumbbar); + } else { + return (*handler) (eumbbar); + } + } + + return I2CNOEVENT; +} + + +/****************** Device I/O function *****************/ + +/****************************************************** + * function: I2C_Start + * + * description: Generate a START signal in the desired mode. + * I2C is the master. + * + * Return I2CSUCCESS if no error. + * + * note: + ****************************************************/ +static I2CStatus I2C_Start (unsigned int eumbbar, unsigned char slave_addr, /* address of the receiver */ + I2C_MODE mode, /* XMIT(1) - put (write) + * RCV(0) - get (read) + */ + unsigned int is_cnt) +{ /* 1 - this is a restart, don't check MBB + * 0 - this is a new start + */ + unsigned int tmp = 0; + I2C_STAT stat; + I2C_CTRL ctrl; + +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_Start addr 0x%x mode %d cnt %d\n", __FILE__, + __LINE__, slave_addr, mode, is_cnt); +#endif + + ctrl = I2C_Get_Ctrl (eumbbar); + + /* first make sure I2C has been initialized */ + if (ctrl.men == 0) { + return I2CERROR; + } + + /* next make sure bus is idle */ + stat = I2C_Get_Stat (eumbbar); + + if (is_cnt == 0 && stat.mbb == 1) { + /* sorry, we lost */ + return I2CBUSBUSY; + } else if (is_cnt == 1 && stat.mif == 1 && stat.mal == 0) { + /* sorry, we lost the bus */ + return I2CALOSS; + } + + + /* OK, I2C is enabled and we have the bus */ + + /* prepare to write the slave address */ + ctrl.msta = 1; + ctrl.mtx = 1; + ctrl.txak = 0; + ctrl.rsta = is_cnt; /* set the repeat start bit */ + I2C_Set_Ctrl (eumbbar, ctrl); + + /* write the slave address and xmit/rcv mode bit */ + tmp = load_runtime_reg (eumbbar, I2CDR); + tmp = (tmp & 0xffffff00) | + ((slave_addr & 0x007f) << 1) | + (mode == XMIT ? 0x0 : 0x1); + store_runtime_reg (eumbbar, I2CDR, tmp); + + if (mode == RCV) { + MasterRcvAddress = 1; + } else { + MasterRcvAddress = 0; + } + +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_Start exit\n", __FILE__, __LINE__); +#endif + + /* wait for the interrupt or poll */ + return I2CSUCCESS; +} + +/*********************************************************** + * function: I2c_Stop + * + * description: Generate a STOP signal to terminate the master + * transaction. + * return I2CSUCCESS + * + **********************************************************/ +static I2CStatus I2C_Stop (unsigned int eumbbar) +{ + I2C_CTRL ctrl; + +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_Stop enter\n", __FILE__, __LINE__); +#endif + + ctrl = I2C_Get_Ctrl (eumbbar); + ctrl.msta = 0; + I2C_Set_Ctrl (eumbbar, ctrl); + +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_Stop exit\n", __FILE__, __LINE__); +#endif + + return I2CSUCCESS; +} + +/**************************************************** + * function: I2C_Master_Xmit + * + * description: Master sends one byte of data to + * slave target + * + * return I2CSUCCESS if the byte transmitted. + * Otherwise no-zero + * + * Note: condition must meet when this function is called: + * I2CSR(MIF) == 1 && I2CSR(MCF) == 1 && I2CSR(RXAK) == 0 + * I2CCR(MSTA) == 1 && I2CCR(MTX) == 1 + * + ***************************************************/ +static I2CStatus I2C_Master_Xmit (unsigned int eumbbar) +{ + unsigned int val; + + if (ByteToXmit > 0) { + + if (ByteToXmit == XmitByte) { + /* all xmitted */ + ByteToXmit = 0; + + if (XmitBufEmptyStop == 1) { + I2C_Stop (eumbbar); + } + + return I2CBUFFEMPTY; + + } +#ifdef I2CDBG0 + PRINT ("%s(%d): xmit 0x%02x\n", __FILE__, __LINE__, + *(XmitBuf + XmitByte)); +#endif + + val = *(XmitBuf + XmitByte); + val &= 0x000000ff; + store_runtime_reg (eumbbar, I2CDR, val); + XmitByte++; + + return I2CSUCCESS; + + } + + return I2CBUFFEMPTY; +} + +/*********************************************** + * function: I2C_Master_Rcv + * + * description: master reads one byte data + * from slave source + * + * return I2CSUCCESS if no error + * + * Note: condition must meet when this function is called: + * I2CSR(MIF) == 1 && I2CSR(MCF) == 1 && + * I2CCR(MSTA) == 1 && I2CCR(MTX) == 0 + * + ***********************************************/ +static I2CStatus I2C_Master_Rcv (unsigned int eumbbar) +{ + I2C_CTRL ctrl; + unsigned int val; + + if (ByteToRcv > 0) { + + if (ByteToRcv - RcvByte == 2 && RcvBufFulStop == 1) { + /* master requests more than or equal to 2 bytes + * we are reading 2nd to last byte + */ + + /* we need to set I2CCR(TXAK) to generate a STOP */ + ctrl = I2C_Get_Ctrl (eumbbar); + ctrl.txak = 1; + I2C_Set_Ctrl (eumbbar, ctrl); + + /* Kahlua will automatically generate a STOP + * next time a transaction happens + */ + + /* note: the case of master requesting one byte is + * handled in I2C_ISR + */ + } + + /* generat a STOP before reading the last byte */ + if (RcvByte + 1 == ByteToRcv && RcvBufFulStop == 1) { + I2C_Stop (eumbbar); + } + + val = load_runtime_reg (eumbbar, I2CDR); + *(RcvBuf + RcvByte) = val & 0xFF; + +#ifdef I2CDBG0 + PRINT ("%s(%d): rcv 0x%02x\n", __FILE__, __LINE__, + *(RcvBuf + RcvByte)); +#endif + + RcvByte++; + + if (ByteToRcv == RcvByte) { + ByteToRcv = 0; + + return I2CBUFFFULL; + } + + return I2CSUCCESS; + } + + return I2CBUFFFULL; + +} + +/**************************************************** + * function: I2C_Slave_Xmit + * + * description: Slave sends one byte of data to + * requesting destination + * + * return SUCCESS if the byte transmitted. Otherwise + * No-zero + * + * Note: condition must meet when this function is called: + * I2CSR(MIF) == 1 && I2CSR(MCF) == 1 && I2CSR(RXAK) = 0 + * I2CCR(MSTA) == 0 && I2CCR(MTX) == 1 + * + ***************************************************/ +static I2CStatus I2C_Slave_Xmit (unsigned int eumbbar) +{ + unsigned int val; + + if (ByteToXmit > 0) { + + if (ByteToXmit == XmitByte) { + /* no more data to send */ + ByteToXmit = 0; + + /* + * do not toggle I2CCR(MTX). Doing so will + * cause bus-hung since current Kahlua design + * does not give master a way to detect slave + * stop. It is always a good idea for master + * to use timer to prevent the long long + * delays + */ + + return I2CBUFFEMPTY; + } +#ifdef I2CDBG + PRINT ("%s(%d): xmit 0x%02x\n", __FILE__, __LINE__, + *(XmitBuf + XmitByte)); +#endif + + val = *(XmitBuf + XmitByte); + val &= 0x000000ff; + store_runtime_reg (eumbbar, I2CDR, val); + XmitByte++; + + return I2CSUCCESS; + } + + return I2CBUFFEMPTY; +} + +/*********************************************** + * function: I2C_Slave_Rcv + * + * description: slave reads one byte data + * from master source + * + * return I2CSUCCESS if no error otherwise non-zero + * + * Note: condition must meet when this function is called: + * I2CSR(MIF) == 1 && I2CSR(MCF) == 1 && + * I2CCR(MSTA) == 0 && I2CCR(MTX) = 0 + * + ***********************************************/ +static I2CStatus I2C_Slave_Rcv (unsigned int eumbbar) +{ + unsigned int val; + I2C_CTRL ctrl; + + if (ByteToRcv > 0) { + val = load_runtime_reg (eumbbar, I2CDR); + *(RcvBuf + RcvByte) = val & 0xff; +#ifdef I2CDBG + PRINT ("%s(%d): rcv 0x%02x\n", __FILE__, __LINE__, + *(RcvBuf + RcvByte)); +#endif + RcvByte++; + + if (ByteToRcv == RcvByte) { + if (RcvBufFulStop == 1) { + /* all done */ + ctrl = I2C_Get_Ctrl (eumbbar); + ctrl.txak = 1; + I2C_Set_Ctrl (eumbbar, ctrl); + } + + ByteToRcv = 0; + return I2CBUFFFULL; + } + + return I2CSUCCESS; + } + + return I2CBUFFFULL; +} + +/****************** Device Control Function *************/ + +/********************************************************* + * function: I2C_Init + * + * description: Initialize I2C unit with desired frequency divider, + * master's listening address, with interrupt enabled + * or disabled. + * + * note: + ********************************************************/ +static I2CStatus I2C_Init (unsigned int eumbbar, unsigned char fdr, /* frequency divider */ + unsigned char slave_addr, /* driver's address used for receiving */ + unsigned int en_int) +{ /* 1 - enable I2C interrupt + * 0 - disable I2C interrup + */ + I2C_CTRL ctrl; + unsigned int tmp; + +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_Init enter\n", __FILE__, __LINE__); +#endif + + ctrl = I2C_Get_Ctrl (eumbbar); + /* disable the I2C module before we change everything */ + ctrl.men = 0; + I2C_Set_Ctrl (eumbbar, ctrl); + + /* set the frequency diver */ + tmp = load_runtime_reg (eumbbar, I2CFDR); + tmp = (tmp & 0xffffffc0) | (fdr & 0x3f); + store_runtime_reg (eumbbar, I2CFDR, tmp); + + /* Set our listening (slave) address */ + tmp = load_runtime_reg (eumbbar, I2CADR); + tmp = (tmp & 0xffffff01) | ((slave_addr & 0x7f) << 1); + store_runtime_reg (eumbbar, I2CADR, tmp); + + /* enable I2C with desired interrupt setting */ + ctrl.men = 1; + ctrl.mien = en_int & 0x1; + I2C_Set_Ctrl (eumbbar, ctrl); +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_Init exit\n", __FILE__, __LINE__); +#endif + + return I2CSUCCESS; + +} + +/***************************************** + * function I2c_Get_Stat + * + * description: Query I2C Status, i.e., read I2CSR + * + ****************************************/ +static I2C_STAT I2C_Get_Stat (unsigned int eumbbar) +{ + unsigned int temp; + I2C_STAT stat; + + temp = load_runtime_reg (eumbbar, I2CSR); + +#ifdef I2CDBG0 + PRINT ("%s(%d): get stat = 0x%08x\n", __FILE__, __LINE__, temp); +#endif + + stat.rsrv0 = (temp & 0xffffff00) >> 8; + stat.mcf = (temp & 0x00000080) >> 7; + stat.maas = (temp & 0x00000040) >> 6; + stat.mbb = (temp & 0x00000020) >> 5; + stat.mal = (temp & 0x00000010) >> 4; + stat.rsrv1 = (temp & 0x00000008) >> 3; + stat.srw = (temp & 0x00000004) >> 2; + stat.mif = (temp & 0x00000002) >> 1; + stat.rxak = (temp & 0x00000001); + return stat; +} + +/********************************************* + * function: I2c_Set_Ctrl + * + * description: Change I2C Control bits, + * i.e., write to I2CCR + * + ********************************************/ +static void I2C_Set_Ctrl (unsigned int eumbbar, I2C_CTRL ctrl) +{ /* new control value */ + unsigned int temp = load_runtime_reg (eumbbar, I2CCR); + + temp &= 0xffffff03; + temp |= ((ctrl.men & 0x1) << 7); + temp |= ((ctrl.mien & 0x1) << 6); + temp |= ((ctrl.msta & 0x1) << 5); + temp |= ((ctrl.mtx & 0x1) << 4); + temp |= ((ctrl.txak & 0x1) << 3); + temp |= ((ctrl.rsta & 0x1) << 2); +#ifdef I2CDBG0 + PRINT ("%s(%d): set ctrl = 0x%08x\n", __FILE__, __LINE__, temp); +#endif + store_runtime_reg (eumbbar, I2CCR, temp); + +} + +/***************************************** + * function: I2C_Get_Ctrl + * + * description: Query I2C Control bits, + * i.e., read I2CCR + *****************************************/ +static I2C_CTRL I2C_Get_Ctrl (unsigned int eumbbar) +{ + union { + I2C_CTRL ctrl; + unsigned int temp; + } s; + + s.temp = load_runtime_reg (eumbbar, I2CCR); +#ifdef I2CDBG0 + PRINT ("%s(%d): get ctrl = 0x%08x\n", __FILE__, __LINE__, s.temp); +#endif + + return s.ctrl; +} + + +/**************************************** + * function: I2C_Slave_Addr + * + * description: Process slave address phase. + * return I2CSUCCESS if no error + * + * note: Precondition for calling this function: + * I2CSR(MIF) == 1 && + * I2CSR(MAAS) == 1 + ****************************************/ +static I2CStatus I2C_Slave_Addr (unsigned int eumbbar) +{ + I2C_STAT stat = I2C_Get_Stat (eumbbar); + I2C_CTRL ctrl = I2C_Get_Ctrl (eumbbar); + + if (stat.srw == 1) { + /* we are asked to xmit */ + ctrl.mtx = 1; + I2C_Set_Ctrl (eumbbar, ctrl); /* set MTX */ + return I2C_Slave_Xmit (eumbbar); + } + + /* we are asked to receive data */ + ctrl.mtx = 0; + I2C_Set_Ctrl (eumbbar, ctrl); + (void) load_runtime_reg (eumbbar, I2CDR); /* do a fake read to start */ + + return I2CADDRESS; +} + +/*********************************************** + * function: I2C_ISR + * + * description: I2C Interrupt service routine + * + * note: Precondition: + * I2CSR(MIF) == 1 + **********************************************/ +static I2CStatus I2C_ISR (unsigned int eumbbar) +{ + I2C_STAT stat; + I2C_CTRL ctrl; + +#ifdef I2CDBG0 + PRINT ("%s(%d): I2C_ISR\n", __FILE__, __LINE__); +#endif + + stat = I2C_Get_Stat (eumbbar); + ctrl = I2C_Get_Ctrl (eumbbar); + + /* clear MIF */ + stat.mif = 0; + + /* Now let see what kind of event this is */ + if (stat.mcf == 1) { + /* transfer compete */ + + /* clear the MIF bit */ + I2C_Set_Stat (eumbbar, stat); + + if (ctrl.msta == 1) { + /* master */ + if (ctrl.mtx == 1) { + /* check if this is the address phase for master receive */ + if (MasterRcvAddress == 1) { + /* Yes, it is the address phase of master receive */ + ctrl.mtx = 0; + /* now check how much we want to receive */ + if (ByteToRcv == 1 && RcvBufFulStop == 1) { + ctrl.txak = 1; + } + + I2C_Set_Ctrl (eumbbar, ctrl); + (void) load_runtime_reg (eumbbar, I2CDR); /* fake read first */ + + MasterRcvAddress = 0; + return I2CADDRESS; + + } + + /* master xmit */ + if (stat.rxak == 0) { + /* slave has acknowledged */ + return I2C_Master_Xmit (eumbbar); + } + + /* slave has not acknowledged yet, generate a STOP */ + if (XmitBufEmptyStop == 1) { + ctrl.msta = 0; + I2C_Set_Ctrl (eumbbar, ctrl); + } + + return I2CSUCCESS; + } + + /* master receive */ + return I2C_Master_Rcv (eumbbar); + } + + /* slave */ + if (ctrl.mtx == 1) { + /* slave xmit */ + if (stat.rxak == 0) { + /* master has acknowledged */ + return I2C_Slave_Xmit (eumbbar); + } + + /* master has not acknowledged, wait for STOP */ + /* do nothing for preventing bus from hung */ + return I2CSUCCESS; + } + + /* slave rcv */ + return I2C_Slave_Rcv (eumbbar); + + } else if (stat.maas == 1) { + /* received a call from master */ + + /* clear the MIF bit */ + I2C_Set_Stat (eumbbar, stat); + + /* master is calling us, process the address phase */ + return I2C_Slave_Addr (eumbbar); + } else { + /* has to be arbitration lost */ + stat.mal = 0; + I2C_Set_Stat (eumbbar, stat); + + ctrl.msta = 0; /* return to receive mode */ + I2C_Set_Ctrl (eumbbar, ctrl); + } + + return I2CSUCCESS; + +} + +/****************************************************** + * function: I2C_Set_Stat + * + * description: modify the I2CSR + * + *****************************************************/ +static void I2C_Set_Stat (unsigned int eumbbar, I2C_STAT stat) +{ + union { + unsigned int val; + I2C_STAT stat; + } s_tmp; + union { + unsigned int val; + I2C_STAT stat; + } s; + + s.val = load_runtime_reg (eumbbar, I2CSR); + s.val &= 0xffffff08; + s_tmp.stat = stat; + s.val |= (s_tmp.val & 0xf7); + +#ifdef I2CDBG0 + PRINT ("%s(%d): set stat = 0x%08x\n", __FILE__, __LINE__, s.val); +#endif + + store_runtime_reg (eumbbar, I2CSR, s.val); + +} + +/****************************************************** + * The following are routines to glue the rest of + * U-Boot to the Sandpoint I2C driver. + *****************************************************/ + +void i2c_init (int speed, int slaveadd) +{ +#ifdef DEBUG + I2C_Initialize (0x7f, 0, (void *) printf); +#else + I2C_Initialize (0x7f, 0, 0); +#endif +} + +int i2c_probe (uchar chip) +{ + int tmp; + + /* + * Try to read the first location of the chip. The underlying + * driver doesn't appear to support sending just the chip address + * and looking for an <ACK> back. + */ + udelay(10000); + return i2c_read (chip, 0, 1, (char *)&tmp, 1); +} + +int i2c_read (uchar chip, uint addr, int alen, uchar * buffer, int len) +{ + I2CStatus status; + uchar xaddr[4]; + + if (alen > 0) { + xaddr[0] = (addr >> 24) & 0xFF; + xaddr[1] = (addr >> 16) & 0xFF; + xaddr[2] = (addr >> 8) & 0xFF; + xaddr[3] = addr & 0xFF; + + status = I2C_do_buffer (0, I2C_MASTER_XMIT, chip, alen, + &xaddr[4 - alen], I2C_NO_STOP, 1, + I2C_NO_RESTART); + if (status != I2C_SUCCESS) { + PRINT ("i2c_read: can't send data address for read\n"); + return 1; + } + } + + /* The data transfer will be a continuation. */ + status = I2C_do_buffer (0, I2C_MASTER_RCV, chip, len, + buffer, I2C_STOP, 1, (alen > 0 ? I2C_RESTART : + I2C_NO_RESTART)); + + if (status != I2C_SUCCESS) { + PRINT ("i2c_read: can't perform data transfer\n"); + return 1; + } + + return 0; +} + +int i2c_write (uchar chip, uint addr, int alen, uchar * buffer, int len) +{ + I2CStatus status; + unsigned char dummy_buffer[I2C_RXTX_LEN + 2]; + int i; + + dummy_buffer[0] = addr & 0xFF; + if (alen == 2) + dummy_buffer[1] = (addr >> 8) & 0xFF; + for (i = 0; i < len; i++) + dummy_buffer[i + alen] = buffer[i]; + + status = I2C_do_buffer (0, I2C_MASTER_XMIT, chip, alen + len, + dummy_buffer, I2C_STOP, 1, I2C_NO_RESTART); + +#ifdef CFG_EEPROM_PAGE_WRITE_DELAY_MS + udelay(CFG_EEPROM_PAGE_WRITE_DELAY_MS * 1000); +#endif + if (status != I2C_SUCCESS) { + PRINT ("i2c_write: can't perform data transfer\n"); + return 1; + } + + return 0; +} + +uchar i2c_reg_read (uchar i2c_addr, uchar reg) +{ + char buf[1]; + + i2c_init (0, 0); + + i2c_read (i2c_addr, reg, 1, buf, 1); + + return (buf[0]); +} + +void i2c_reg_write (uchar i2c_addr, uchar reg, uchar val) +{ + i2c_init (0, 0); + + i2c_write (i2c_addr, reg, 1, &val, 1); +} + +#endif /* CONFIG_HARD_I2C */ |