From 3ed40686c9cc0d05e5925890d157aeed7871fa70 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 11 Jun 2006 00:57:00 +0000 Subject: [PATCH] Added bit-bashing i2c interface code --- src/drivers/bitbash/i2c_bit.c | 313 ++++++++++++++++++++++++++++++++++ src/include/gpxe/i2c.h | 109 ++++++++++++ 2 files changed, 422 insertions(+) create mode 100644 src/drivers/bitbash/i2c_bit.c create mode 100644 src/include/gpxe/i2c.h diff --git a/src/drivers/bitbash/i2c_bit.c b/src/drivers/bitbash/i2c_bit.c new file mode 100644 index 00000000..9524d631 --- /dev/null +++ b/src/drivers/bitbash/i2c_bit.c @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2006 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +/** @file + * + * I2C bit-bashing interface + * + * This implements a simple I2C master via a bit-bashing interface + * that provides two lines: SCL (clock) and SDA (data). + */ + +/** + * Set state of I2C SCL line + * + * @v basher Bit-bashing interface + * @v state New state of SCL + */ +static inline __attribute__ (( always_inline )) void +setscl ( struct bit_basher *basher, int state ) { + write_bit ( basher, I2C_BIT_SCL, state ); +} + +/** + * Set state of I2C SDA line + * + * @v basher Bit-bashing interface + * @v state New state of SDA + */ +static inline __attribute__ (( always_inline )) void +setsda ( struct bit_basher *basher, int state ) { + write_bit ( basher, I2C_BIT_SDA, state ); +} + +/** + * Get state of I2C SDA line + * + * @v basher Bit-bashing interface + * @ret state State of SDA + */ +static inline __attribute__ (( always_inline )) int +getsda ( struct bit_basher *basher ) { + return read_bit ( basher, I2C_BIT_SDA ); +} + +/** + * Send an I2C start condition + * + * @v basher Bit-bashing interface + */ +static void i2c_start ( struct bit_basher *basher ) { + setscl ( basher, 1 ); + setsda ( basher, 0 ); + setscl ( basher, 0 ); + setsda ( basher, 1 ); +} + +/** + * Send an I2C data bit + * + * @v basher Bit-bashing interface + * @v bit Bit to send + */ +static void i2c_send_bit ( struct bit_basher *basher, int bit ) { + setsda ( basher, bit ); + setscl ( basher, 1 ); + setscl ( basher, 0 ); + setsda ( basher, 1 ); +} + +/** + * Receive an I2C data bit + * + * @v basher Bit-bashing interface + * @ret bit Received bit + */ +static int i2c_recv_bit ( struct bit_basher *basher ) { + int bit; + + setscl ( basher, 1 ); + bit = getsda ( basher ); + setscl ( basher, 0 ); + return bit; +} + +/** + * Send an I2C stop condition + * + * @v basher Bit-bashing interface + */ +static void i2c_stop ( struct bit_basher *basher ) { + setsda ( basher, 0 ); + setscl ( basher, 1 ); + setsda ( basher, 1 ); +} + +/** + * Send byte via I2C bus and check for acknowledgement + * + * @v basher Bit-bashing interface + * @v byte Byte to send + * @ret rc Return status code + * + * Sends a byte via the I2C bus and checks for an acknowledgement from + * the slave device. + */ +static int i2c_send_byte ( struct bit_basher *basher, uint8_t byte ) { + int i; + + /* Send 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 ); +} + +/** + * Receive byte via I2C bus + * + * @v basher Bit-bashing interface + * @ret byte Received 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; + int i; + + /* Receive byte */ + for ( i = 8 ; i ; i-- ) { + value <<= 1; + value |= i2c_recv_bit ( basher ); + } + + /* Send NACK */ + i2c_send_bit ( basher, 1 ); + + return value; +} + +/** + * Select I2C device for reading or writing + * + * @v basher Bit-bashing interface + * @v i2cdev I2C 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 address; + 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; + + /* Second byte of the address (10-bit addresses only) */ + if ( i2cdev->tenbit ) { + if ( ( rc = i2c_send_byte ( basher, + ( i2cdev->address & 0xff ) ) ) !=0) + return rc; + } + + return 0; +} + +/** + * Read data from I2C device via bit-bashing interface + * + * @v i2c I2C interface + * @v i2cdev I2C device + * @v offset Starting offset within the device + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + * + * Note that attempting to read zero bytes of data is a valid way to + * check for I2C device presence. + */ +static int i2c_bit_read ( struct i2c_interface *i2c, + struct i2c_device *i2cdev, unsigned int offset, + uint8_t *data, unsigned int len ) { + struct i2c_bit_basher *i2cbit + = container_of ( i2c, struct i2c_bit_basher, i2c ); + struct bit_basher *basher = &i2cbit->basher; + int rc = 0; + + DBG ( "Reading from I2C device %x: ", i2cdev->address ); + + while ( 1 ) { + + /* Select device for writing */ + if ( ( rc = i2c_select ( basher, i2cdev, I2C_WRITE ) ) != 0 ) + break; + + /* Abort at end of data */ + if ( ! ( len-- ) ) + break; + + /* Select offset */ + if ( ( rc = i2c_send_byte ( basher, offset++ ) ) != 0 ) + break; + + /* Select device for reading */ + if ( ( rc = i2c_select ( basher, i2cdev, I2C_READ ) ) != 0 ) + break; + + /* Read byte */ + *data++ = i2c_recv_byte ( basher ); + DBG ( "%02x ", *(data - 1) ); + } + + DBG ( "%s\n", ( rc ? "failed" : "" ) ); + i2c_stop ( basher ); + return rc; +} + +/** + * Write data to I2C device via bit-bashing interface + * + * @v i2c I2C interface + * @v i2cdev I2C device + * @v offset Starting offset within the device + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + * + * Note that attempting to write zero bytes of data is a valid way to + * check for I2C device presence. + */ +static int i2c_bit_write ( struct i2c_interface *i2c, + struct i2c_device *i2cdev, unsigned int offset, + const uint8_t *data, unsigned int len ) { + struct i2c_bit_basher *i2cbit + = container_of ( i2c, struct i2c_bit_basher, i2c ); + struct bit_basher *basher = &i2cbit->basher; + int rc = 0; + + DBG ( "Writing to I2C device %x: ", i2cdev->address ); + + while ( 1 ) { + + /* Select device for writing */ + if ( ( rc = i2c_select ( basher, i2cdev, I2C_WRITE ) ) != 0 ) + break; + + /* Abort at end of data */ + if ( ! ( len-- ) ) + break; + + /* Select offset */ + if ( ( rc = i2c_send_byte ( basher, offset++ ) ) != 0 ) + break; + + /* Write data to device */ + DBG ( "%02x ", *data ); + if ( ( rc = i2c_send_byte ( basher, *data++ ) ) != 0 ) + break; + } + + DBG ( "%s\n", ( rc ? "failed" : "" ) ); + i2c_stop ( basher ); + return rc; +} + +/** + * Initialise I2C bit-bashing interface + * + * @v i2cbit I2C bit-bashing interface + */ +void init_i2c_bit_basher ( struct i2c_bit_basher *i2cbit ) { + struct bit_basher *basher = &i2cbit->basher; + + assert ( basher->read != NULL ); + assert ( basher->write != NULL ); + i2cbit->i2c.read = i2c_bit_read; + i2cbit->i2c.write = i2c_bit_write; + basher->udelay = I2C_UDELAY; + i2c_stop ( basher ); +} diff --git a/src/include/gpxe/i2c.h b/src/include/gpxe/i2c.h new file mode 100644 index 00000000..e282810b --- /dev/null +++ b/src/include/gpxe/i2c.h @@ -0,0 +1,109 @@ +#ifndef _GPXE_I2C_H +#define _GPXE_I2C_H + +/** @file + * + * I2C interface + * + */ + +#include + +/** An I2C device + * + * An I2C device represents a specific slave device on an I2C bus. It + * 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; +}; + +/** An I2C interface + * + * An I2C interface provides access to an I2C bus, via which I2C + * devices may be reached. + */ +struct i2c_interface { + /** + * Read data from I2C device + * + * @v i2c I2C interface + * @v i2cdev I2C device + * @v offset Starting offset within the device + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ + int ( * read ) ( struct i2c_interface *i2c, struct i2c_device *i2cdev, + unsigned int offset, uint8_t *data, + unsigned int len ); + /** + * Write data to I2C device + * + * @v i2c I2C interface + * @v i2cdev I2C device + * @v offset Starting offset within the device + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ + int ( * write ) ( struct i2c_interface *i2c, struct i2c_device *i2cdev, + unsigned int offset, const uint8_t *data, + unsigned int len ); +}; + +/** A bit-bashing I2C interface + * + * This provides a standardised way to construct I2C buses via a + * bit-bashing interface. + */ +struct i2c_bit_basher { + /** I2C interface */ + struct i2c_interface i2c; + /** Bit-bashing interface */ + struct bit_basher basher; +}; + +/** Ten-bit address marker + * + * This value is ORed with the I2C device address to indicate a + * ten-bit address format on the bus. + */ +#define I2C_TENBIT_ADDRESS 0x7800 + +/** An I2C write command */ +#define I2C_WRITE 0 + +/** An I2C read command */ +#define I2C_READ 1 + +/** Bit indices used for I2C bit-bashing interface */ +enum { + I2C_BIT_SCL = 0, + I2C_BIT_SDA, +}; + +/** Delay required for bit-bashing operation */ +#define I2C_UDELAY 5 + +/** + * Check presence of I2C device + * + * @v i2c I2C interface + * @v i2cdev I2C device + * @ret rc Return status code + * + * Checks for the presence of the device on the I2C bus by attempting + * a zero-length write. + */ +static inline int i2c_check_presence ( struct i2c_interface *i2c, + struct i2c_device *i2cdev ) { + return i2c->write ( i2c, i2cdev, 0, NULL, 0 ); +} + +extern void init_i2c_bit_basher ( struct i2c_bit_basher *i2cbit ); + +#endif /* _GPXE_I2C_H */