diff options
Diffstat (limited to 'common/soft_i2c.c')
| -rw-r--r-- | common/soft_i2c.c | 416 | 
1 files changed, 416 insertions, 0 deletions
| diff --git a/common/soft_i2c.c b/common/soft_i2c.c new file mode 100644 index 000000000..77774801e --- /dev/null +++ b/common/soft_i2c.c @@ -0,0 +1,416 @@ +/* + * (C) Copyright 2001, 2002 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 (at your option) 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * This has been changed substantially by Gerald Van Baren, Custom IDEAS, + * vanbaren@cideas.com.  It was heavily influenced by LiMon, written by + * Neil Russell. + */ + +#include <common.h> +#ifdef	CONFIG_MPC8260			/* only valid for MPC8260 */ +#include <ioports.h> +#endif +#include <i2c.h> + +#if defined(CONFIG_SOFT_I2C) + +/* #define	DEBUG_I2C	*/ + + +/*----------------------------------------------------------------------- + * Definitions + */ + +#define RETRIES		0 + + +#define I2C_ACK		0		/* PD_SDA level to ack a byte */ +#define I2C_NOACK	1		/* PD_SDA level to noack a byte */ + + +#ifdef DEBUG_I2C +#define PRINTD(fmt,args...)	do {	\ +	DECLARE_GLOBAL_DATA_PTR;	\ +	if (gd->have_console)		\ +		printf (fmt ,##args);	\ +	} while (0) +#else +#define PRINTD(fmt,args...) +#endif + +/*----------------------------------------------------------------------- + * Local functions + */ +static void  send_reset	(void); +static void  send_start	(void); +static void  send_stop	(void); +static void  send_ack	(int); +static int   write_byte	(uchar byte); +static uchar read_byte	(int); + + +/*----------------------------------------------------------------------- + * Send a reset sequence consisting of 9 clocks with the data signal high + * to clock any confused device back into an idle state.  Also send a + * <stop> at the end of the sequence for belts & suspenders. + */ +static void send_reset(void) +{ +#ifdef	CONFIG_MPC8260 +	volatile ioport_t *iop = ioport_addr((immap_t *)CFG_IMMR, I2C_PORT); +#endif +#ifdef	CONFIG_8xx +	volatile immap_t *immr = (immap_t *)CFG_IMMR; +#endif +	int j; + +	I2C_ACTIVE; +	I2C_SDA(1); +	for(j = 0; j < 9; j++) { +		I2C_SCL(0); +		I2C_DELAY; +		I2C_DELAY; +		I2C_SCL(1); +		I2C_DELAY; +		I2C_DELAY; +	} +	send_stop(); +	I2C_TRISTATE; +} + +/*----------------------------------------------------------------------- + * START: High -> Low on SDA while SCL is High + */ +static void send_start(void) +{ +#ifdef	CONFIG_MPC8260 +	volatile ioport_t *iop = ioport_addr((immap_t *)CFG_IMMR, I2C_PORT); +#endif +#ifdef	CONFIG_8xx +	volatile immap_t *immr = (immap_t *)CFG_IMMR; +#endif + +	I2C_DELAY; +	I2C_SDA(1); +	I2C_ACTIVE; +	I2C_DELAY; +	I2C_SCL(1); +	I2C_DELAY; +	I2C_SDA(0); +	I2C_DELAY; +} + +/*----------------------------------------------------------------------- + * STOP: Low -> High on SDA while SCL is High + */ +static void send_stop(void) +{ +#ifdef	CONFIG_MPC8260 +	volatile ioport_t *iop = ioport_addr((immap_t *)CFG_IMMR, I2C_PORT); +#endif +#ifdef	CONFIG_8xx +	volatile immap_t *immr = (immap_t *)CFG_IMMR; +#endif + +	I2C_SCL(0); +	I2C_DELAY; +	I2C_SDA(0); +	I2C_ACTIVE; +	I2C_DELAY; +	I2C_SCL(1); +	I2C_DELAY; +	I2C_SDA(1); +	I2C_DELAY; +	I2C_TRISTATE; +} + + +/*----------------------------------------------------------------------- + * ack should be I2C_ACK or I2C_NOACK + */ +static void send_ack(int ack) +{ +#ifdef	CONFIG_MPC8260 +	volatile ioport_t *iop = ioport_addr((immap_t *)CFG_IMMR, I2C_PORT); +#endif +#ifdef	CONFIG_8xx +	volatile immap_t *immr = (immap_t *)CFG_IMMR; +#endif + +	I2C_ACTIVE; +	I2C_SCL(0); +	I2C_DELAY; + +	I2C_SDA(ack); + +	I2C_ACTIVE; +	I2C_DELAY; +	I2C_SCL(1); +	I2C_DELAY; +	I2C_DELAY; +	I2C_SCL(0); +	I2C_DELAY; +} + + +/*----------------------------------------------------------------------- + * Send 8 bits and look for an acknowledgement. + */ +static int write_byte(uchar data) +{ +#ifdef	CONFIG_MPC8260 +	volatile ioport_t *iop = ioport_addr((immap_t *)CFG_IMMR, I2C_PORT); +#endif +#ifdef	CONFIG_8xx +	volatile immap_t *immr = (immap_t *)CFG_IMMR; +#endif +	int j; +	int nack; + +	I2C_ACTIVE; +	for(j = 0; j < 8; j++) { +		I2C_SCL(0); +		I2C_DELAY; +		I2C_SDA(data & 0x80); +		I2C_DELAY; +		I2C_SCL(1); +		I2C_DELAY; +		I2C_DELAY; + +		data <<= 1; +	} + +	/* +	 * Look for an <ACK>(negative logic) and return it. +	 */ +	I2C_SCL(0); +	I2C_DELAY; +	I2C_SDA(1); +	I2C_TRISTATE; +	I2C_DELAY; +	I2C_SCL(1); +	I2C_DELAY; +	I2C_DELAY; +	nack = I2C_READ; +	I2C_SCL(0); +	I2C_DELAY; +	I2C_ACTIVE; + +	return(nack);	/* not a nack is an ack */ +} + + +/*----------------------------------------------------------------------- + * if ack == I2C_ACK, ACK the byte so can continue reading, else + * send I2C_NOACK to end the read. + */ +static uchar read_byte(int ack) +{ +#ifdef	CONFIG_MPC8260 +	volatile ioport_t *iop = ioport_addr((immap_t *)CFG_IMMR, I2C_PORT); +#endif +#ifdef	CONFIG_8xx +	volatile immap_t *immr = (immap_t *)CFG_IMMR; +#endif +	int  data; +	int  j; + +	/* +	 * Read 8 bits, MSB first. +	 */ +	I2C_TRISTATE; +	data = 0; +	for(j = 0; j < 8; j++) { +		I2C_SCL(0); +		I2C_DELAY; +		I2C_SCL(1); +		I2C_DELAY; +		data <<= 1; +		data |= I2C_READ; +		I2C_DELAY; +	} +	send_ack(ack); + +	return(data); +} + +/*=====================================================================*/ +/*                         Public Functions                            */ +/*=====================================================================*/ + +/*----------------------------------------------------------------------- + * Initialization + */ +void i2c_init (int speed, int slaveaddr) +{ +#ifdef	CONFIG_8xx +	volatile immap_t *immr = (immap_t *)CFG_IMMR; +#endif + +#ifdef	I2C_INIT +	I2C_INIT; +#endif +	/* +         * WARNING: Do NOT save speed in a static variable: if the +         * I2C routines are called before RAM is initialized (to read +         * the DIMM SPD, for instance), RAM won't be usable and your +         * system will crash. +	 */ +	send_reset (); +} + +/*----------------------------------------------------------------------- + * Probe to see if a chip is present.  Also good for checking for the + * completion of EEPROM writes since the chip stops responding until + * the write completes (typically 10mSec). + */ +int i2c_probe(uchar addr) +{ +	int rc; + +	send_start(); +	rc = write_byte ((addr << 1) | 1); +	send_stop(); + +	return (rc ? 1 : 0); +} + +/*----------------------------------------------------------------------- + * Read bytes + */ +int  i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ +	int shift; +	PRINTD("i2c_read: chip %02X addr %02X alen %d buffer %p len %d\n", +		chip, addr, alen, buffer, len); + +#ifdef CFG_I2C_EEPROM_ADDR_OVERFLOW +	/* +	 * EEPROM chips that implement "address overflow" are ones +	 * like Catalyst 24WC04/08/16 which has 9/10/11 bits of +	 * address and the extra bits end up in the "chip address" +	 * bit slots. This makes a 24WC08 (1Kbyte) chip look like +	 * four 256 byte chips. +	 * +	 * Note that we consider the length of the address field to +	 * still be one byte because the extra address bits are +	 * hidden in the chip address. +	 */ +	chip |= ((addr >> (alen * 8)) & CFG_I2C_EEPROM_ADDR_OVERFLOW); + +	PRINTD("i2c_read: fix addr_overflow: chip %02X addr %02X\n", +		chip, addr); +#endif + +	/* +	 * Do the addressing portion of a write cycle to set the +	 * chip's address pointer.  If the address length is zero, +	 * don't do the normal write cycle to set the address pointer, +	 * there is no address pointer in this chip. +	 */ +	send_start(); +	if(alen > 0) { +		if(write_byte(chip << 1)) {	/* write cycle */ +			send_stop(); +			PRINTD("i2c_read, no chip responded %02X\n", chip); +			return(1); +		} +		shift = (alen-1) * 8; +		while(alen-- > 0) { +			if(write_byte(addr >> shift)) { +				PRINTD("i2c_read, address not <ACK>ed\n"); +				return(1); +			} +			shift -= 8; +		} +		send_stop();	/* reportedly some chips need a full stop */ +		send_start(); +	} +	/* +	 * Send the chip address again, this time for a read cycle. +	 * Then read the data.  On the last byte, we do a NACK instead +	 * of an ACK(len == 0) to terminate the read. +	 */ +	write_byte((chip << 1) | 1);	/* read cycle */ +	while(len-- > 0) { +		*buffer++ = read_byte(len == 0); +	} +	send_stop(); +	return(0); +} + +/*----------------------------------------------------------------------- + * Write bytes + */ +int  i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ +	int shift, failures = 0; + +	PRINTD("i2c_write: chip %02X addr %02X alen %d buffer %p len %d\n", +		chip, addr, alen, buffer, len); + +	send_start(); +	if(write_byte(chip << 1)) {	/* write cycle */ +		send_stop(); +		PRINTD("i2c_write, no chip responded %02X\n", chip); +		return(1); +	} +	shift = (alen-1) * 8; +	while(alen-- > 0) { +		if(write_byte(addr >> shift)) { +			PRINTD("i2c_write, address not <ACK>ed\n"); +			return(1); +		} +		shift -= 8; +	} + +	while(len-- > 0) { +		if(write_byte(*buffer++)) { +			failures++; +		} +	} +	send_stop(); +	return(failures); +} + +/*----------------------------------------------------------------------- + * Read a register + */ +uchar i2c_reg_read(uchar i2c_addr, uchar reg) +{ +	char buf; + +	i2c_read(i2c_addr, reg, 1, &buf, 1); + +	return(buf); +} + +/*----------------------------------------------------------------------- + * Write a register + */ +void i2c_reg_write(uchar i2c_addr, uchar reg, uchar val) +{ +	i2c_write(i2c_addr, reg, 1, &val, 1); +} + + +#endif	/* CONFIG_SOFT_I2C */ |