diff --git a/src/drivers/bitbash/i2c_bit.c b/src/drivers/bitbash/i2c_bit.c index a3af610b..b85057af 100644 --- a/src/drivers/bitbash/i2c_bit.c +++ b/src/drivers/bitbash/i2c_bit.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ static void i2c_delay ( void ) { * @v state New state of SCL */ static void setscl ( struct bit_basher *basher, int state ) { + DBG2 ( "%c", ( state ? '/' : '\\' ) ); write_bit ( basher, I2C_BIT_SCL, state ); i2c_delay(); } @@ -60,6 +62,7 @@ static void setscl ( struct bit_basher *basher, int state ) { * @v state New state of SDA */ static void setsda ( struct bit_basher *basher, int state ) { + DBG2 ( "%c", ( state ? '1' : '0' ) ); write_bit ( basher, I2C_BIT_SDA, state ); i2c_delay(); } @@ -71,7 +74,10 @@ static void setsda ( struct bit_basher *basher, int state ) { * @ret state State of SDA */ static int getsda ( struct bit_basher *basher ) { - return read_bit ( basher, I2C_BIT_SDA ); + int state; + state = read_bit ( basher, I2C_BIT_SDA ); + DBG2 ( "%c", ( state ? '+' : '-' ) ); + return state; } /** @@ -137,15 +143,20 @@ static void i2c_stop ( struct bit_basher *basher ) { */ static int i2c_send_byte ( struct bit_basher *basher, uint8_t byte ) { int i; - + int ack; + /* Send byte */ + DBG2 ( "[send %02x]", byte ); for ( i = 8 ; i ; i-- ) { i2c_send_bit ( basher, byte & 0x80 ); byte <<= 1; } /* Check for acknowledgement from slave */ - return ( i2c_recv_bit ( basher ) == 0 ? 0 : -EIO ); + ack = ( i2c_recv_bit ( basher ) == 0 ); + DBG2 ( "%s", ( ack ? "[acked]" : "[not acked]" ) ); + + return ( ack ? 0 : -EIO ); } /** @@ -157,19 +168,20 @@ static int i2c_send_byte ( struct bit_basher *basher, uint8_t byte ) { * Receives a byte via the I2C bus and sends NACK to the slave device. */ static uint8_t i2c_recv_byte ( struct bit_basher *basher ) { - uint8_t value = 0; + uint8_t byte = 0; int i; /* Receive byte */ for ( i = 8 ; i ; i-- ) { - value <<= 1; - value |= ( i2c_recv_bit ( basher ) & 0x1 ); + byte <<= 1; + byte |= ( i2c_recv_bit ( basher ) & 0x1 ); } /* Send NACK */ i2c_send_bit ( basher, 1 ); - return value; + DBG2 ( "[rcvd %02x]", byte ); + return byte; } /** @@ -177,36 +189,75 @@ static uint8_t i2c_recv_byte ( struct bit_basher *basher ) { * * @v basher Bit-bashing interface * @v i2cdev I2C device + * @v offset Starting offset within the device * @v direction I2C_READ or I2C_WRITE * @ret rc Return status code */ static int i2c_select ( struct bit_basher *basher, struct i2c_device *i2cdev, - unsigned int direction ) { + unsigned int offset, unsigned int direction ) { unsigned int address; + int shift; + unsigned int byte; int rc; i2c_start ( basher ); - /* First byte of the address */ - address = i2cdev->address; - if ( i2cdev->tenbit ) { - address |= I2C_TENBIT_ADDRESS; - address >>= 8; - } - if ( ( rc = i2c_send_byte ( basher, - ( ( address << 1 ) | direction ) ) ) != 0 ) - return rc; + /* Calculate address to appear on bus */ + address = ( ( ( i2cdev->dev_addr | + ( offset >> ( 8 * i2cdev->word_addr_len ) ) ) << 1 ) + | direction ); - /* Second byte of the address (10-bit addresses only) */ - if ( i2cdev->tenbit ) { - if ( ( rc = i2c_send_byte ( basher, - ( i2cdev->address & 0xff ) ) ) !=0) + /* Send address a byte at a time */ + for ( shift = ( 8 * ( i2cdev->dev_addr_len - 1 ) ) ; + shift >= 0 ; shift -= 8 ) { + byte = ( ( address >> shift ) & 0xff ); + if ( ( rc = i2c_send_byte ( basher, byte ) ) != 0 ) return rc; } return 0; } +/** + * Reset I2C bus + * + * @v basher Bit-bashing interface + * @ret rc Return status code + * + * i2c devices often don't have a reset line, so even a reboot or + * system power cycle is sometimes not enough to bring them back to a + * known state. + */ +static int i2c_reset ( struct bit_basher *basher ) { + unsigned int i; + int sda; + + /* Clock through several cycles, waiting for an opportunity to + * pull SDA low while SCL is high (which creates a start + * condition). + */ + setscl ( basher, 0 ); + setsda ( basher, 1 ); + for ( i = 0 ; i < I2C_RESET_MAX_CYCLES ; i++ ) { + setscl ( basher, 1 ); + sda = getsda ( basher ); + if ( sda ) { + /* Now that the device will see a start, issue it */ + i2c_start ( basher ); + /* Stop the bus to leave it in a known good state */ + i2c_stop ( basher ); + DBGC ( basher, "I2CBIT %p reset after %d attempts\n", + basher, ( i + 1 ) ); + return 0; + } + setscl ( basher, 0 ); + } + + DBGC ( basher, "I2CBIT %p could not reset after %d attempts\n", + basher, i ); + return -ETIMEDOUT; +} + /** * Read data from I2C device via bit-bashing interface * @@ -228,12 +279,14 @@ static int i2c_bit_read ( struct i2c_interface *i2c, struct bit_basher *basher = &i2cbit->basher; int rc = 0; - DBG ( "Reading from I2C device %x: ", i2cdev->address ); + DBGC ( basher, "I2CBIT %p reading from device %x: ", + basher, i2cdev->dev_addr ); - while ( 1 ) { + for ( ; ; data++, offset++ ) { /* Select device for writing */ - if ( ( rc = i2c_select ( basher, i2cdev, I2C_WRITE ) ) != 0 ) + if ( ( rc = i2c_select ( basher, i2cdev, offset, + I2C_WRITE ) ) != 0 ) break; /* Abort at end of data */ @@ -241,19 +294,20 @@ static int i2c_bit_read ( struct i2c_interface *i2c, break; /* Select offset */ - if ( ( rc = i2c_send_byte ( basher, offset++ ) ) != 0 ) + if ( ( rc = i2c_send_byte ( basher, offset ) ) != 0 ) break; /* Select device for reading */ - if ( ( rc = i2c_select ( basher, i2cdev, I2C_READ ) ) != 0 ) + if ( ( rc = i2c_select ( basher, i2cdev, offset, + I2C_READ ) ) != 0 ) break; /* Read byte */ - *data++ = i2c_recv_byte ( basher ); - DBG ( "%02x ", *(data - 1) ); + *data = i2c_recv_byte ( basher ); + DBGC ( basher, "%02x ", *data ); } - DBG ( "%s\n", ( rc ? "failed" : "" ) ); + DBGC ( basher, "%s\n", ( rc ? "failed" : "" ) ); i2c_stop ( basher ); return rc; } @@ -279,12 +333,14 @@ static int i2c_bit_write ( struct i2c_interface *i2c, struct bit_basher *basher = &i2cbit->basher; int rc = 0; - DBG ( "Writing to I2C device %x: ", i2cdev->address ); + DBGC ( basher, "I2CBIT %p writing to device %x: ", + basher, i2cdev->dev_addr ); - while ( 1 ) { + for ( ; ; data++, offset++ ) { /* Select device for writing */ - if ( ( rc = i2c_select ( basher, i2cdev, I2C_WRITE ) ) != 0 ) + if ( ( rc = i2c_select ( basher, i2cdev, offset, + I2C_WRITE ) ) != 0 ) break; /* Abort at end of data */ @@ -292,16 +348,16 @@ static int i2c_bit_write ( struct i2c_interface *i2c, break; /* Select offset */ - if ( ( rc = i2c_send_byte ( basher, offset++ ) ) != 0 ) + if ( ( rc = i2c_send_byte ( basher, offset ) ) != 0 ) break; /* Write data to device */ - DBG ( "%02x ", *data ); - if ( ( rc = i2c_send_byte ( basher, *data++ ) ) != 0 ) + DBGC ( basher, "%02x ", *data ); + if ( ( rc = i2c_send_byte ( basher, *data ) ) != 0 ) break; } - DBG ( "%s\n", ( rc ? "failed" : "" ) ); + DBGC ( basher, "%s\n", ( rc ? "failed" : "" ) ); i2c_stop ( basher ); return rc; } @@ -310,13 +366,26 @@ static int i2c_bit_write ( struct i2c_interface *i2c, * Initialise I2C bit-bashing interface * * @v i2cbit I2C bit-bashing interface + * @v bash_op Bit-basher operations */ -void init_i2c_bit_basher ( struct i2c_bit_basher *i2cbit ) { +int init_i2c_bit_basher ( struct i2c_bit_basher *i2cbit, + struct bit_basher_operations *bash_op ) { struct bit_basher *basher = &i2cbit->basher; - + int rc; + + /* Initialise data structures */ + basher->op = bash_op; assert ( basher->op->read != NULL ); assert ( basher->op->write != NULL ); i2cbit->i2c.read = i2c_bit_read; i2cbit->i2c.write = i2c_bit_write; - i2c_stop ( basher ); + + /* Reset I2C bus */ + if ( ( rc = i2c_reset ( basher ) ) != 0 ) { + DBGC ( basher, "I2CBIT %p could not reset I2C bus: %s\n", + basher, strerror ( rc ) ); + return rc; + } + + return 0; } diff --git a/src/drivers/net/etherfabric.c b/src/drivers/net/etherfabric.c index 8a6b1a17..d9af625a 100644 --- a/src/drivers/net/etherfabric.c +++ b/src/drivers/net/etherfabric.c @@ -1069,9 +1069,9 @@ static struct bit_basher_operations ef1002_basher_ops = { }; static void ef1002_init_eeprom ( struct efab_nic *efab ) { - efab->ef1002_i2c.basher.op = &ef1002_basher_ops; - init_i2c_bit_basher ( &efab->ef1002_i2c ); - efab->ef1002_eeprom.address = EF1_EEPROM_I2C_ID; + init_i2c_bit_basher ( &efab->ef1002_i2c, + &ef1002_basher_ops ); + init_i2c_eeprom ( &efab->ef1002_eeprom, EF1_EEPROM_I2C_ID ); } /** diff --git a/src/include/gpxe/i2c.h b/src/include/gpxe/i2c.h index bfaee8fb..9d229546 100644 --- a/src/include/gpxe/i2c.h +++ b/src/include/gpxe/i2c.h @@ -8,6 +8,7 @@ */ #include +#include /** An I2C device * @@ -15,10 +16,35 @@ * is accessed via an I2C interface. */ struct i2c_device { - /** Address of this device */ - unsigned int address; - /** Flag indicating a ten-bit address format */ - int tenbit; + /** Address of this device + * + * The actual address sent on the bus will look like + * + * + * + * The "word address overflow" is any excess bits from the + * word address, i.e. any portion that does not fit within the + * defined word address length. + */ + unsigned int dev_addr; + /** Device address length, in bytes + * + * This is the number of bytes that comprise the device + * address, defined to be the portion that terminates with the + * read/write bit. + */ + unsigned int dev_addr_len; + /** Word adddress length, in bytes + * + * This is the number of bytes that comprise the word address, + * defined to be the portion that starts after the read/write + * bit and ends before the first data byte. + * + * For some devices, this length will be zero (i.e. the word + * address is contained entirely within the "word address + * overflow"). + */ + unsigned int word_addr_len; }; /** An I2C interface @@ -91,6 +117,9 @@ enum { /** Delay required for bit-bashing operation */ #define I2C_UDELAY 5 +/** Maximum number of cycles to use when attempting a bus reset */ +#define I2C_RESET_MAX_CYCLES 32 + /** * Check presence of I2C device * @@ -106,6 +135,35 @@ static inline int i2c_check_presence ( struct i2c_interface *i2c, return i2c->write ( i2c, i2cdev, 0, NULL, 0 ); } -extern void init_i2c_bit_basher ( struct i2c_bit_basher *i2cbit ); +extern int init_i2c_bit_basher ( struct i2c_bit_basher *i2cbit, + struct bit_basher_operations *bash_op ); + +/** + * Initialise generic I2C EEPROM device + * + * @v i2cdev I2C device + */ +static inline __always_inline void +init_i2c_eeprom ( struct i2c_device *i2cdev, unsigned int dev_addr ) { + i2cdev->dev_addr = dev_addr; + i2cdev->dev_addr_len = 1; + i2cdev->word_addr_len = 1; +} + +/** + * Initialise Atmel AT24C11 + * + * @v i2cdev I2C device + */ +static inline __always_inline void +init_at24c11 ( struct i2c_device *i2cdev ) { + /* This chip has no device address; it must be the only chip + * on the bus. The word address is contained entirely within + * the device address field. + */ + i2cdev->dev_addr = 0; + i2cdev->dev_addr_len = 1; + i2cdev->word_addr_len = 0; +} #endif /* _GPXE_I2C_H */