[U-Boot] [PATCH 19/31] i2c, mpc83xx: add CONFIG_SYS_I2C_INIT_BOARD for fsl_i2c

Joakim Tjernlund
2009-01-28T11:28:36Z

Heiko Schocher <hs@denx.de> wrote on 28/01/2009 11:54:22:

> Hello Joakim,
>
> Joakim Tjernlund wrote:
> >> This patch adds the possibility to call a board specific
> >> i2c bus reset routine for the fsl_i2c bus driver, and adds
> >> this option for the keymile kmeter1 board.
> >>
> [...]
> >> @@ -478,6 +480,17 @@ static int i2c_make_abort (void)
> >> */
> >> void i2c_init_board(void)
> >> {
> >> +#if defined(CONFIG_KMETER1)
> >> + struct fsl_i2c *dev;
> >> + dev = (struct fsl_i2c *) (CONFIG_SYS_IMMR +
CONFIG_SYS_I2C_OFFSET);
> >> + uchar dummy;
> >> +
> >> + out_8 (&dev->cr, (I2C_CR_MSTA));
> >> + out_8 (&dev->cr, (I2C_CR_MEN | I2C_CR_MSTA));
> >> + dummy = in_8(&dev->dr);
> >> + out_8 (&dev->cr, (I2C_CR_MEN));
> >
> > Are you sure this will generate a proper I2C reset sequence? We also
> > use this controller and I tried to do it too but didn't find a way. I
then
> > asked
> > Freescale and they could not come up with a solution either.
>
> ?
> This routine is decribed in the MPC8260ERM.pdf ?15.5.7 on page 15-23
> from Freescale!
>
> 15.5.7 Generation of SCLn when SDAn is Negated
> It is sometimes necessary to force the I2C module to become the I2C bus
master out of reset and drive
> SCLn (even though SDAn may already be driven, which indicates that the
bus is busy). This can occur
> when a system reset does not cause all I2C devices to be reset. Thus,
SDAn can be negated low by another
> I2C device while this I2C module is coming out of reset and will stay
low indefinitely. The following
> procedure can be used to force this I2C module to generate SCLn so that
the device driving SDAn can
> finish its transaction:
> 1. Disable the I2C module and set the master bit by setting I2CnCR to
0x20.
> 2. Enable the I2C module by setting I2CnCR to 0xA0.
> 3. Read I2CnDR.
> 4. Return the I2C module to slave mode by setting I2CnCR to 0x80.
>
> And this worked fine on our Hardware ...

Ahh, memory slowly returns. The problem is that this does not generate a
reset
sequence that will work in all cases. Consider the case when the
CPU is reset half-way through writing or reading a byte from the device.

I once researched this(can't remember the exact details now) but the
only reset sequence that works in all cases is:

static void send_start(void)
{
I2C_DELAY;
I2C_TRISTATE;
I2C_SDA(1);
I2C_DELAY;
I2C_SCL(1);
I2C_DELAY;
I2C_SDA(0);
I2C_ACTIVE;
I2C_DELAY;
}

static void send_stop(void)
{
I2C_SCL(0);
I2C_DELAY;
I2C_SDA(0);
I2C_ACTIVE;
I2C_DELAY;
I2C_SCL(1);
I2C_DELAY;
I2C_TRISTATE;
I2C_SDA(1);
I2C_DELAY;
}

/*-----------------------------------------------------------------------
* 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.
*/
void tm_i2c_reset(int bus)
{
int j;

I2C_INIT;
I2C_TRISTATE;
for(j = 0; j < 9; j++) {
if(I2C_READ)
send_start();
I2C_SCL(0);
I2C_DELAY;
I2C_TRISTATE;
I2C_SDA(1);
I2C_DELAY;
I2C_SCL(1);
I2C_DELAY;
I2C_DELAY;
}
send_stop();
if(!I2C_READ)
printf("I2C SDA is low! I2C bus:%d is stuck!!!\n", bus);
}