diff options
| author | tsutsui <tsutsui@NetBSD.org> | 2008-06-01 01:13:18 +0000 |
|---|---|---|
| committer | tsutsui <tsutsui@NetBSD.org> | 2008-06-01 01:13:18 +0000 |
| commit | 3a70013d4eb99c2f2c79c0b0ed947fa7fc1f2601 (patch) | |
| tree | aea43b6d25ee75e1e2f5fc799bc6e1251bb8ebe2 /sys/dev | |
| parent | ff6926e860486fee2006c5744efafc4113f27323 (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.c | 98 |
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); |
