summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
authortsutsui <tsutsui@NetBSD.org>2008-06-01 01:13:18 +0000
committertsutsui <tsutsui@NetBSD.org>2008-06-01 01:13:18 +0000
commit3a70013d4eb99c2f2c79c0b0ed947fa7fc1f2601 (patch)
treeaea43b6d25ee75e1e2f5fc799bc6e1251bb8ebe2 /sys/dev
parentff6926e860486fee2006c5744efafc4113f27323 (diff)
Reorganize I2C bitbang ops:
- don't set DIR(INPUT) in i2c_wait_for_scl() because it could cause unexpected stop condition in SCL=H, SDL=L and DIR(OUTPUT) case, and DIR() should be used only to switch SDA direction, not SCL that is always output on the master device (I'm not sure if reading SCL on the master is defined in the I2C spec) - set SDA right after SCL H->L edge on Tx, fetch SDA at SCL L->H edge on Rx, and make sure not to change SDA during SCL=H except start/stop conditions so that we don't have to rely on data setup time which might be critical - don't set DIR(OUTPUT) during SDA bit read in i2c_bitbang_read_byte() - explicitly specify both SDA and SCL bits in SETBITS() for readability - sprinkle comments Fixes "tl0: error reading Ethernet address" problem on tl(4).
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/i2c/i2c_bitbang.c98
1 files changed, 52 insertions, 46 deletions
diff --git a/sys/dev/i2c/i2c_bitbang.c b/sys/dev/i2c/i2c_bitbang.c
index 930cd9bdd60..8e953896ee1 100644
--- a/sys/dev/i2c/i2c_bitbang.c
+++ b/sys/dev/i2c/i2c_bitbang.c
@@ -1,4 +1,4 @@
-/* $NetBSD: i2c_bitbang.c,v 1.10 2008/05/31 18:26:43 tsutsui Exp $ */
+/* $NetBSD: i2c_bitbang.c,v 1.11 2008/06/01 01:13:18 tsutsui Exp $ */
/*
* Copyright (c) 2003 Wasabi Systems, Inc.
@@ -40,7 +40,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: i2c_bitbang.c,v 1.10 2008/05/31 18:26:43 tsutsui Exp $");
+__KERNEL_RCSID(0, "$NetBSD: i2c_bitbang.c,v 1.11 2008/06/01 01:13:18 tsutsui Exp $");
#include <sys/param.h>
@@ -67,8 +67,6 @@ i2c_wait_for_scl(void *v, i2c_bitbang_ops_t ops)
{
int bail = 0;
- DIR(INPUT);
-
while (((READ & SCL) == 0) && (bail < SCL_BAIL_COUNT)) {
delay(1);
bail++;
@@ -85,18 +83,18 @@ int
i2c_bitbang_send_start(void *v, int flags, i2c_bitbang_ops_t ops)
{
+ /* start condition: put SDL H->L edge during SCL=H */
+
DIR(OUTPUT);
SETBITS(SDA | SCL);
delay(5); /* bus free time (4.7 us) */
- SETBITS( SCL);
-
+ SETBITS( 0 | SCL);
if (i2c_wait_for_scl(v, ops) != 0)
return EIO;
delay(4); /* start hold time (4.0 us) */
- DIR(OUTPUT);
- SETBITS( 0);
- delay(5); /* clock low time (4.7 us) */
+ /* leave SCL=L and SDL=L to avoid unexpected start/stop condition */
+ SETBITS( 0 | 0);
return 0;
}
@@ -106,8 +104,11 @@ int
i2c_bitbang_send_stop(void *v, int flags, i2c_bitbang_ops_t ops)
{
+ /* stop condition: put SDL L->H edge during SCL=H */
+
+ /* assume SCL=L, SDL=L here */
DIR(OUTPUT);
- SETBITS( SCL);
+ SETBITS( 0 | SCL);
delay(4); /* stop setup time (4.0 us) */
SETBITS(SDA | SCL);
@@ -160,46 +161,47 @@ i2c_bitbang_read_byte(void *v, uint8_t *valp, int flags, i2c_bitbang_ops_t ops)
uint8_t val = 0;
uint32_t bit;
- DIR(OUTPUT);
- SETBITS(SDA );
+ /* assume SCL=L, SDA=L here */
+
+ DIR(INPUT);
for (i = 0; i < 8; i++) {
val <<= 1;
- DIR(OUTPUT);
- SETBITS(SDA | SCL);
+ /* data is set at SCL H->L edge */
+ SETBITS( 0 | 0);
+ delay(5); /* clock low time (4.7 us) */
+ /* read data at SCL L->H edge */
+ SETBITS( 0 | SCL);
if (i2c_wait_for_scl(v, ops) != 0)
return EIO;
- delay(4); /* clock high time (4.0 us) */
-
- DIR(INPUT);
if (READ & SDA)
val |= 1;
-
- DIR(OUTPUT);
- SETBITS(SDA );
- delay(5); /* clock low time (4.7 us) */
+ delay(4); /* clock high time (4.0 us) */
}
+ /* set SCL H->L before set SDA direction OUTPUT */
+ SETBITS( 0 | 0);
+ /* set ack after SCL H->L edge */
bit = (flags & I2C_F_LAST) ? SDA : 0;
-
DIR(OUTPUT);
- SETBITS(bit );
- delay(1); /* data setup time (250 ns) */
- SETBITS(bit | SCL);
+ SETBITS(bit | 0);
+ delay(5); /* clock low time (4.7 us) */
+ /* ack is checked at SCL L->H edge */
+ SETBITS(bit | SCL);
if (i2c_wait_for_scl(v, ops) != 0)
return EIO;
delay(4); /* clock high time (4.0 us) */
- DIR(OUTPUT);
- SETBITS(bit );
- delay(5); /* clock low time (4.7 us) */
+ /* set SCL H->L for next data; don't change SDA here */
+ SETBITS(bit | 0);
+ /* leave SCL=L and SDL=L to avoid unexpected start/stop condition */
DIR(INPUT);
- SETBITS(SDA );
- delay(5);
+ SETBITS( 0 | 0);
+
if ((flags & (I2C_F_STOP | I2C_F_LAST)) == (I2C_F_STOP | I2C_F_LAST))
(void) i2c_bitbang_send_stop(v, flags, ops);
@@ -215,38 +217,42 @@ i2c_bitbang_write_byte(void *v, uint8_t val, int flags, i2c_bitbang_ops_t ops)
uint8_t mask;
int error;
+ /* assume at SCL=L, SDA=L here */
+
+ DIR(OUTPUT);
+
for (mask = 0x80; mask != 0; mask >>= 1) {
bit = (val & mask) ? SDA : 0;
- DIR(OUTPUT);
- SETBITS(bit );
- delay(1); /* data setup time (250 ns) */
- SETBITS(bit | SCL);
+ /* set data after SCL H->L edge */
+ SETBITS(bit | 0);
+ delay(5); /* clock low time (4.7 us) */
+ /* data is fetched at SCL L->H edge */
+ SETBITS(bit | SCL);
if (i2c_wait_for_scl(v, ops))
return EIO;
delay(4); /* clock high time (4.0 us) */
- DIR(OUTPUT);
- SETBITS(bit );
- delay(5); /* clock low time (4.7 us) */
+ /* put SCL H->L edge; don't change SDA here */
+ SETBITS(bit | 0);
}
- DIR(OUTPUT);
- SETBITS(SDA );
- delay(5);
- SETBITS(SDA | SCL);
+ /* ack is set at H->L edge */
+ DIR(INPUT);
+ delay(5); /* clock low time (4.7 us) */
+ /* read ack at L->H edge */
+ SETBITS( 0 | SCL);
if (i2c_wait_for_scl(v, ops) != 0)
return EIO;
- delay(4);
-
- DIR(INPUT);
error = (READ & SDA) ? EIO : 0;
+ delay(4); /* clock high time (4.0 us) */
+ /* leave SCL=L and SDL=L to avoid unexpected start/stop condition */
+ /* set SCL H->L before set SDA direction OUTPUT */
+ SETBITS( 0 | 0);
DIR(OUTPUT);
- SETBITS(SDA );
- delay(5);
if (flags & I2C_F_STOP)
(void) i2c_bitbang_send_stop(v, flags, ops);