/******************************************************************* * * Copyright (c) 2007 by Silicon Motion, Inc. (SMI) * * All rights are reserved. Reproduction or in part is prohibited * without the written consent of the copyright owner. * * swi2c.c --- SM750/SM718 DDK * This file contains the source code for I2C using software * implementation. * *******************************************************************/ #include "ddk750_help.h" #include "ddk750_reg.h" #include "ddk750_swi2c.h" #include "ddk750_power.h" /******************************************************************* * I2C Software Master Driver: * =========================== * Each i2c cycle is split into 4 sections. Each of these section marks * a point in time where the SCL or SDA may be changed. * * 1 Cycle == | Section I. | Section 2. | Section 3. | Section 4. | * +-------------+-------------+-------------+-------------+ * | SCL set LOW |SCL no change| SCL set HIGH|SCL no change| * * ____________ _____________ * SCL == XXXX _____________ ____________ / * * I.e. the SCL may only be changed in section 1. and section 3. while * the SDA may only be changed in section 2. and section 4. The table * below gives the changes for these 2 lines in the varios sections. * * Section changes Table: * ====================== * blank = no change, L = set bit LOW, H = set bit HIGH * * | 1.| 2.| 3.| 4.| * ---------------+---+---+---+---+ * Tx Start SDA | | H | | L | * SCL | L | | H | | * ---------------+---+---+---+---+ * Tx Stop SDA | | L | | H | * SCL | L | | H | | * ---------------+---+---+---+---+ * Tx bit H SDA | | H | | | * SCL | L | | H | | * ---------------+---+---+---+---+ * Tx bit L SDA | | L | | | * SCL | L | | H | | * ---------------+---+---+---+---+ * ******************************************************************/ /* GPIO pins used for this I2C. It ranges from 0 to 63. */ static unsigned char g_i2cClockGPIO = DEFAULT_I2C_SCL; static unsigned char g_i2cDataGPIO = DEFAULT_I2C_SDA; /* * Below is the variable declaration for the GPIO pin register usage * for the i2c Clock and i2c Data. * * Note: * Notice that the GPIO usage for the i2c clock and i2c Data are * separated. This is to make this code flexible enough when * two separate GPIO pins for the clock and data are located * in two different GPIO register set (worst case). */ /* i2c Clock GPIO Register usage */ static unsigned long g_i2cClkGPIOMuxReg = GPIO_MUX; static unsigned long g_i2cClkGPIODataReg = GPIO_DATA; static unsigned long g_i2cClkGPIODataDirReg = GPIO_DATA_DIRECTION; /* i2c Data GPIO Register usage */ static unsigned long g_i2cDataGPIOMuxReg = GPIO_MUX; static unsigned long g_i2cDataGPIODataReg = GPIO_DATA; static unsigned long g_i2cDataGPIODataDirReg = GPIO_DATA_DIRECTION; /* * This function puts a delay between command */ static void swI2CWait(void) { /* find a bug: * peekIO method works well before suspend/resume * but after suspend, peekIO(0x3ce,0x61) & 0x10 * always be non-zero,which makes the while loop * never finish. * use non-ultimate for loop below is safe * */ #if 0 /* Change wait algorithm to use PCI bus clock, it's more reliable than counter loop .. write 0x61 to 0x3ce and read from 0x3cf */ while(peekIO(0x3ce,0x61) & 0x10); #else int i, Temp; for(i=0; i<600; i++) { Temp = i; Temp += i; } #endif } /* * This function set/reset the SCL GPIO pin * * Parameters: * value - Bit value to set to the SCL or SDA (0 = low, 1 = high) * * Notes: * When setting SCL to high, just set the GPIO as input where the pull up * resistor will pull the signal up. Do not use software to pull up the * signal because the i2c will fail when other device try to drive the * signal due to SM50x will drive the signal to always high. */ void swI2CSCL(unsigned char value) { unsigned long ulGPIOData; unsigned long ulGPIODirection; ulGPIODirection = PEEK32(g_i2cClkGPIODataDirReg); if (value) /* High */ { /* Set direction as input. This will automatically pull the signal up. */ ulGPIODirection &= ~(1 << g_i2cClockGPIO); POKE32(g_i2cClkGPIODataDirReg, ulGPIODirection); } else /* Low */ { /* Set the signal down */ ulGPIOData = PEEK32(g_i2cClkGPIODataReg); ulGPIOData &= ~(1 << g_i2cClockGPIO); POKE32(g_i2cClkGPIODataReg, ulGPIOData); /* Set direction as output */ ulGPIODirection |= (1 << g_i2cClockGPIO); POKE32(g_i2cClkGPIODataDirReg, ulGPIODirection); } } /* * This function set/reset the SDA GPIO pin * * Parameters: * value - Bit value to set to the SCL or SDA (0 = low, 1 = high) * * Notes: * When setting SCL to high, just set the GPIO as input where the pull up * resistor will pull the signal up. Do not use software to pull up the * signal because the i2c will fail when other device try to drive the * signal due to SM50x will drive the signal to always high. */ void swI2CSDA(unsigned char value) { unsigned long ulGPIOData; unsigned long ulGPIODirection; ulGPIODirection = PEEK32(g_i2cDataGPIODataDirReg); if (value) /* High */ { /* Set direction as input. This will automatically pull the signal up. */ ulGPIODirection &= ~(1 << g_i2cDataGPIO); POKE32(g_i2cDataGPIODataDirReg, ulGPIODirection); } else /* Low */ { /* Set the signal down */ ulGPIOData = PEEK32(g_i2cDataGPIODataReg); ulGPIOData &= ~(1 << g_i2cDataGPIO); POKE32(g_i2cDataGPIODataReg, ulGPIOData); /* Set direction as output */ ulGPIODirection |= (1 << g_i2cDataGPIO); POKE32(g_i2cDataGPIODataDirReg, ulGPIODirection); } } /* * This function read the data from the SDA GPIO pin * * Return Value: * The SDA data bit sent by the Slave */ static unsigned char swI2CReadSDA(void) { unsigned long ulGPIODirection; unsigned long ulGPIOData; /* Make sure that the direction is input (High) */ ulGPIODirection = PEEK32(g_i2cDataGPIODataDirReg); if ((ulGPIODirection & (1 << g_i2cDataGPIO)) != (~(1 << g_i2cDataGPIO))) { ulGPIODirection &= ~(1 << g_i2cDataGPIO); POKE32(g_i2cDataGPIODataDirReg, ulGPIODirection); } /* Now read the SDA line */ ulGPIOData = PEEK32(g_i2cDataGPIODataReg); if (ulGPIOData & (1 << g_i2cDataGPIO)) return 1; else return 0; } /* * This function sends ACK signal */ static void swI2CAck(void) { return; /* Single byte read is ok without it. */ } /* * This function sends the start command to the slave device */ static void swI2CStart(void) { /* Start I2C */ swI2CSDA(1); swI2CSCL(1); swI2CSDA(0); } /* * This function sends the stop command to the slave device */ static void swI2CStop(void) { /* Stop the I2C */ swI2CSCL(1); swI2CSDA(0); swI2CSDA(1); } /* * This function writes one byte to the slave device * * Parameters: * data - Data to be write to the slave device * * Return Value: * 0 - Success * -1 - Fail to write byte */ static long swI2CWriteByte(unsigned char data) { unsigned char value = data; int i; /* Sending the data bit by bit */ for (i=0; i<8; i++) { /* Set SCL to low */ swI2CSCL(0); /* Send data bit */ if ((value & 0x80) != 0) swI2CSDA(1); else swI2CSDA(0); swI2CWait(); /* Toggle clk line to one */ swI2CSCL(1); swI2CWait(); /* Shift byte to be sent */ value = value << 1; } /* Set the SCL Low and SDA High (prepare to get input) */ swI2CSCL(0); swI2CSDA(1); /* Set the SCL High for ack */ swI2CWait(); swI2CSCL(1); swI2CWait(); /* Read SDA, until SDA==0 */ for(i=0; i<0xff; i++) { if (!swI2CReadSDA()) break; swI2CSCL(0); swI2CWait(); swI2CSCL(1); swI2CWait(); } /* Set the SCL Low and SDA High */ swI2CSCL(0); swI2CSDA(1); if (i<0xff) return 0; else return -1; } /* * This function reads one byte from the slave device * * Parameters: * ack - Flag to indicate either to send the acknowledge * message to the slave device or not * * Return Value: * One byte data read from the Slave device */ static unsigned char swI2CReadByte(unsigned char ack) { int i; unsigned char data = 0; for(i=7; i>=0; i--) { /* Set the SCL to Low and SDA to High (Input) */ swI2CSCL(0); swI2CSDA(1); swI2CWait(); /* Set the SCL High */ swI2CSCL(1); swI2CWait(); /* Read data bits from SDA */ data |= (swI2CReadSDA() << i); } if (ack) swI2CAck(); /* Set the SCL Low and SDA High */ swI2CSCL(0); swI2CSDA(1); return data; } /* * This function initializes GPIO port for SW I2C communication. * * Parameters: * i2cClkGPIO - The GPIO pin to be used as i2c SCL * i2cDataGPIO - The GPIO pin to be used as i2c SDA * * Return Value: * -1 - Fail to initialize the i2c * 0 - Success */ static long swI2CInit_SM750LE(unsigned char i2cClkGPIO, unsigned char i2cDataGPIO) { int i; /* Initialize the GPIO pin for the i2c Clock Register */ g_i2cClkGPIODataReg = GPIO_DATA_SM750LE; g_i2cClkGPIODataDirReg = GPIO_DATA_DIRECTION_SM750LE; /* Initialize the Clock GPIO Offset */ g_i2cClockGPIO = i2cClkGPIO; /* Initialize the GPIO pin for the i2c Data Register */ g_i2cDataGPIODataReg = GPIO_DATA_SM750LE; g_i2cDataGPIODataDirReg = GPIO_DATA_DIRECTION_SM750LE; /* Initialize the Data GPIO Offset */ g_i2cDataGPIO = i2cDataGPIO; /* Note that SM750LE don't have GPIO MUX and power is always on */ /* Clear the i2c lines. */ for(i=0; i<9; i++) swI2CStop(); return 0; } /* * This function initializes the i2c attributes and bus * * Parameters: * i2cClkGPIO - The GPIO pin to be used as i2c SCL * i2cDataGPIO - The GPIO pin to be used as i2c SDA * * Return Value: * -1 - Fail to initialize the i2c * 0 - Success */ long swI2CInit( unsigned char i2cClkGPIO, unsigned char i2cDataGPIO ) { int i; /* Return 0 if the GPIO pins to be used is out of range. The range is only from [0..63] */ if ((i2cClkGPIO > 31) || (i2cDataGPIO > 31)) return -1; if (getChipType() == SM750LE) return swI2CInit_SM750LE(i2cClkGPIO, i2cDataGPIO); /* Initialize the GPIO pin for the i2c Clock Register */ g_i2cClkGPIOMuxReg = GPIO_MUX; g_i2cClkGPIODataReg = GPIO_DATA; g_i2cClkGPIODataDirReg = GPIO_DATA_DIRECTION; /* Initialize the Clock GPIO Offset */ g_i2cClockGPIO = i2cClkGPIO; /* Initialize the GPIO pin for the i2c Data Register */ g_i2cDataGPIOMuxReg = GPIO_MUX; g_i2cDataGPIODataReg = GPIO_DATA; g_i2cDataGPIODataDirReg = GPIO_DATA_DIRECTION; /* Initialize the Data GPIO Offset */ g_i2cDataGPIO = i2cDataGPIO; /* Enable the GPIO pins for the i2c Clock and Data (GPIO MUX) */ POKE32(g_i2cClkGPIOMuxReg, PEEK32(g_i2cClkGPIOMuxReg) & ~(1 << g_i2cClockGPIO)); POKE32(g_i2cDataGPIOMuxReg, PEEK32(g_i2cDataGPIOMuxReg) & ~(1 << g_i2cDataGPIO)); /* Enable GPIO power */ enableGPIO(1); /* Clear the i2c lines. */ for(i=0; i<9; i++) swI2CStop(); return 0; } /* * This function reads the slave device's register * * Parameters: * deviceAddress - i2c Slave device address which register * to be read from * registerIndex - Slave device's register to be read * * Return Value: * Register value */ unsigned char swI2CReadReg( unsigned char deviceAddress, unsigned char registerIndex ) { unsigned char data; /* Send the Start signal */ swI2CStart(); /* Send the device address */ swI2CWriteByte(deviceAddress); /* Send the register index */ swI2CWriteByte(registerIndex); /* Get the bus again and get the data from the device read address */ swI2CStart(); swI2CWriteByte(deviceAddress + 1); data = swI2CReadByte(1); /* Stop swI2C and release the bus */ swI2CStop(); return data; } /* * This function writes a value to the slave device's register * * Parameters: * deviceAddress - i2c Slave device address which register * to be written * registerIndex - Slave device's register to be written * data - Data to be written to the register * * Result: * 0 - Success * -1 - Fail */ long swI2CWriteReg( unsigned char deviceAddress, unsigned char registerIndex, unsigned char data ) { long returnValue = 0; /* Send the Start signal */ swI2CStart(); /* Send the device address and read the data. All should return success in order for the writing processed to be successful */ if ((swI2CWriteByte(deviceAddress) != 0) || (swI2CWriteByte(registerIndex) != 0) || (swI2CWriteByte(data) != 0)) { returnValue = -1; } /* Stop i2c and release the bus */ swI2CStop(); return returnValue; }