diff options
author | Cástor Muñoz <cmvidal@gmail.com> | 2016-08-12 14:03:54 +0200 |
---|---|---|
committer | Cástor Muñoz <cmvidal@gmail.com> | 2016-08-12 14:17:46 +0200 |
commit | 578525b4638f575e30edb963c0de5dde44e250d5 (patch) | |
tree | ae682f078999d2bc3def00fcefb85069f8b7ed23 | |
parent | adbd2969e6e6fd584d46ef60a3fa40bf878d7e00 (diff) | |
download | rockbox-578525b4638f575e30edb963c0de5dde44e250d5.tar.gz rockbox-578525b4638f575e30edb963c0de5dde44e250d5.zip |
iPod Classic: rework on I2C driver
- Some rewrite with the intent to get ride of these random errors
appearing on some builds/devices (not much noticeable on RB but
can ruin bootloader builds).
- Error handling (ACK).
- IIC clock increased to be the same as in OF.
Change-Id: Idf8cfa3c230a0a61ec9c879bf6f0ea8b061a4607
-rw-r--r-- | firmware/export/s5l8702.h | 15 | ||||
-rw-r--r-- | firmware/target/arm/s5l8702/debug-s5l8702.c | 2 | ||||
-rw-r--r-- | firmware/target/arm/s5l8702/i2c-s5l8702.c | 244 |
3 files changed, 135 insertions, 126 deletions
diff --git a/firmware/export/s5l8702.h b/firmware/export/s5l8702.h index 4799b05d4f..f9bf99ed3b 100644 --- a/firmware/export/s5l8702.h +++ b/firmware/export/s5l8702.h | |||
@@ -196,6 +196,20 @@ | |||
196 | #define I2CCLKGATE(i) ((i) == 1 ? CLOCKGATE_I2C1 : \ | 196 | #define I2CCLKGATE(i) ((i) == 1 ? CLOCKGATE_I2C1 : \ |
197 | CLOCKGATE_I2C0) | 197 | CLOCKGATE_I2C0) |
198 | 198 | ||
199 | /* s5l8702 I2C controller is similar to s5l8700, known differences are: | ||
200 | |||
201 | * IICCON[5] is not used in s5l8702. | ||
202 | * IICCON[13:8] are used to enable interrupts. | ||
203 | IICSTA2[13:8] are used to read the status and write-clear interrupts. | ||
204 | Known interrupts: | ||
205 | [13] STOP on bus (TBC) | ||
206 | [12] START on bus (TBC) | ||
207 | [8] byte transmited or received in Master mode (not tested in Slave) | ||
208 | * IICCON[4] does not clear interrupts, it is enabled when a byte is | ||
209 | transmited or received, in Master mode the tx/rx of the next byte | ||
210 | starts when it is written as "1". | ||
211 | */ | ||
212 | |||
199 | #define IICCON(bus) (*((uint32_t volatile*)(0x3C600000 + 0x300000 * (bus)))) | 213 | #define IICCON(bus) (*((uint32_t volatile*)(0x3C600000 + 0x300000 * (bus)))) |
200 | #define IICSTAT(bus) (*((uint32_t volatile*)(0x3C600004 + 0x300000 * (bus)))) | 214 | #define IICSTAT(bus) (*((uint32_t volatile*)(0x3C600004 + 0x300000 * (bus)))) |
201 | #define IICADD(bus) (*((uint32_t volatile*)(0x3C600008 + 0x300000 * (bus)))) | 215 | #define IICADD(bus) (*((uint32_t volatile*)(0x3C600008 + 0x300000 * (bus)))) |
@@ -203,6 +217,7 @@ | |||
203 | #define IICUNK10(bus) (*((uint32_t volatile*)(0x3C600010 + 0x300000 * (bus)))) | 217 | #define IICUNK10(bus) (*((uint32_t volatile*)(0x3C600010 + 0x300000 * (bus)))) |
204 | #define IICUNK14(bus) (*((uint32_t volatile*)(0x3C600014 + 0x300000 * (bus)))) | 218 | #define IICUNK14(bus) (*((uint32_t volatile*)(0x3C600014 + 0x300000 * (bus)))) |
205 | #define IICUNK18(bus) (*((uint32_t volatile*)(0x3C600018 + 0x300000 * (bus)))) | 219 | #define IICUNK18(bus) (*((uint32_t volatile*)(0x3C600018 + 0x300000 * (bus)))) |
220 | #define IICSTA2(bus) (*((uint32_t volatile*)(0x3C600020 + 0x300000 * (bus)))) | ||
206 | 221 | ||
207 | 222 | ||
208 | /////INTERRUPT CONTROLLERS///// | 223 | /////INTERRUPT CONTROLLERS///// |
diff --git a/firmware/target/arm/s5l8702/debug-s5l8702.c b/firmware/target/arm/s5l8702/debug-s5l8702.c index 1bf59e94fe..68b16f39f7 100644 --- a/firmware/target/arm/s5l8702/debug-s5l8702.c +++ b/firmware/target/arm/s5l8702/debug-s5l8702.c | |||
@@ -144,8 +144,6 @@ bool dbg_hw_info(void) | |||
144 | _DEBUG_PRINTF("USB D+: %d mV", adc_read_usbdata_voltage(true)); | 144 | _DEBUG_PRINTF("USB D+: %d mV", adc_read_usbdata_voltage(true)); |
145 | _DEBUG_PRINTF("USB D-: %d mV", adc_read_usbdata_voltage(false)); | 145 | _DEBUG_PRINTF("USB D-: %d mV", adc_read_usbdata_voltage(false)); |
146 | line++; | 146 | line++; |
147 | extern unsigned long i2c_rd_err, i2c_wr_err; | ||
148 | _DEBUG_PRINTF("i2c rd/wr errors: %lu/%lu", i2c_rd_err, i2c_wr_err); | ||
149 | } | 147 | } |
150 | #ifdef UC870X_DEBUG | 148 | #ifdef UC870X_DEBUG |
151 | else if(state==(max_states-1)) | 149 | else if(state==(max_states-1)) |
diff --git a/firmware/target/arm/s5l8702/i2c-s5l8702.c b/firmware/target/arm/s5l8702/i2c-s5l8702.c index a795c9f18c..4f3b012410 100644 --- a/firmware/target/arm/s5l8702/i2c-s5l8702.c +++ b/firmware/target/arm/s5l8702/i2c-s5l8702.c | |||
@@ -25,165 +25,167 @@ | |||
25 | #include "i2c-s5l8702.h" | 25 | #include "i2c-s5l8702.h" |
26 | #include "clocking-s5l8702.h" | 26 | #include "clocking-s5l8702.h" |
27 | 27 | ||
28 | /* Driver for the s5l8700 built-in I2C controller in master mode | 28 | /* Driver for the s5l8702 built-in I2C controller in master mode |
29 | 29 | ||
30 | Both the i2c_read and i2c_write function take the following arguments: | 30 | Both the i2c_read and i2c_write function take the following arguments: |
31 | * slave, the address of the i2c slave device to read from / write to | 31 | * slave, the address of the i2c slave device to read from / write to |
32 | * address, optional sub-address in the i2c slave (unused if -1) | 32 | * address, optional sub-address in the i2c slave (unused if -1) |
33 | * len, number of bytes to be transfered | 33 | * len, number of bytes to be transfered |
34 | * data, pointer to data to be transfered | 34 | * data, pointer to data to be transfered |
35 | A return value < 0 indicates an error. | 35 | A return value > 0 indicates an error. |
36 | 36 | ||
37 | Note: | 37 | Note: |
38 | * blocks the calling thread for the entire duraton of the i2c transfer but | 38 | * blocks the calling thread for the entire duraton of the i2c transfer. |
39 | uses wakeup_wait/wakeup_signal to allow other threads to run. | ||
40 | * ACK from slave is not checked, so functions never return an error | ||
41 | |||
42 | Fixme: | ||
43 | * actually there is no STOP + i2c_off() on error | ||
44 | * very rare random errors when reading and/or(?) writing registers on some | ||
45 | builds/devices, hard to trace, not a 'delay' issue, it seems related | ||
46 | with alignment of STRs and/or(?) LDRs, code cache lines, pipelines... | ||
47 | The new code tries to mix STRs and LDRs at some points but ATM it is | ||
48 | unknown if it might solve or mitigate the problem. Probably it could be | ||
49 | really fixed using wait_rdy() before accessing any register, as OF does. | ||
50 | */ | ||
51 | |||
52 | /* s5l8702 I2C controller is similar to s5l8700, known differences are: | ||
53 | |||
54 | * IICCON[5] is not used in s5l8702. | ||
55 | * IICCON[13:8] are used to enable interrupts. | ||
56 | IICUNK20[13:8] are used to read the status and write-clear interrupts. | ||
57 | Known interrupts: | ||
58 | [13] STOP on bus (TBC) | ||
59 | [12] START on bus (TBC) | ||
60 | [8] byte transmited or received in Master mode (not tested in Slave) | ||
61 | * IICCON[4] does not clear interrupts, it is enabled when a byte is | ||
62 | transmited or received, in Master mode the tx/rx of the next byte | ||
63 | starts when it is written as "1". | ||
64 | */ | 39 | */ |
65 | 40 | ||
66 | static struct mutex i2c_mtx[2]; | 41 | static struct mutex i2c_mtx[2]; |
67 | 42 | ||
43 | void i2c_init() | ||
44 | { | ||
45 | mutex_init(&i2c_mtx[0]); | ||
46 | mutex_init(&i2c_mtx[1]); | ||
47 | } | ||
48 | |||
49 | static void wait_rdy(int bus) | ||
50 | { | ||
51 | while (IICUNK10(bus)); | ||
52 | } | ||
53 | |||
68 | static void i2c_on(int bus) | 54 | static void i2c_on(int bus) |
69 | { | 55 | { |
70 | /* enable I2C clock */ | 56 | /* enable I2C clock */ |
71 | clockgate_enable(I2CCLKGATE(bus), true); | 57 | clockgate_enable(I2CCLKGATE(bus), true); |
72 | |||
73 | IICCON(bus) = (0 << 8) | /* INT_EN = disabled */ | ||
74 | (1 << 7) | /* ACK_GEN */ | ||
75 | (0 << 6) | /* CLKSEL = PCLK/16 */ | ||
76 | (7 << 0); /* CK_REG */ | ||
77 | |||
78 | /* serial output on */ | ||
79 | IICSTAT(bus) = (1 << 4); | ||
80 | } | 58 | } |
81 | 59 | ||
82 | static void i2c_off(int bus) | 60 | static void i2c_off(int bus) |
83 | { | 61 | { |
84 | /* serial output off */ | 62 | /* serial output off */ |
63 | wait_rdy(bus); | ||
85 | IICSTAT(bus) = 0; | 64 | IICSTAT(bus) = 0; |
86 | |||
87 | /* disable I2C clock */ | 65 | /* disable I2C clock */ |
66 | wait_rdy(bus); | ||
88 | clockgate_enable(I2CCLKGATE(bus), false); | 67 | clockgate_enable(I2CCLKGATE(bus), false); |
89 | } | 68 | } |
90 | 69 | ||
91 | void i2c_init() | 70 | /* wait for bus not busy, or tx/rx byte (should return once |
71 | 8 data + 1 ack clocks are generated), or STOP. */ | ||
72 | static void i2c_wait_io(int bus) | ||
92 | { | 73 | { |
93 | mutex_init(&i2c_mtx[0]); | 74 | while (((IICSTAT(bus) & (1 << 5)) != 0) && |
94 | mutex_init(&i2c_mtx[1]); | 75 | ((IICSTA2(bus) & ((1 << 8)|(1 << 13))) == 0)) { |
76 | wait_rdy(bus); | ||
77 | } | ||
78 | IICSTA2(bus) |= (1 << 8)|(1 << 13); | ||
95 | } | 79 | } |
96 | 80 | ||
97 | int i2c_wr(int bus, unsigned char slave, int address, int len, const unsigned char *data) | 81 | static int i2c_start(int bus, unsigned char slave, bool rd) |
98 | { | 82 | { |
99 | i2c_on(bus); | 83 | /* configure port */ |
100 | long timeout = USEC_TIMER + 20000; | 84 | wait_rdy(bus); |
85 | IICCON(bus) = (0 << 8) | /* INT_EN = disabled */ | ||
86 | (1 << 7) | /* ACK_GEN */ | ||
87 | (0 << 6) | /* CLKSEL = PCLK/32 (TBC) */ | ||
88 | (4 << 0); /* CK_REG */ | ||
101 | 89 | ||
102 | /* START */ | 90 | /* START */ |
103 | IICDS(bus) = slave & ~1; | 91 | wait_rdy(bus); |
104 | IICSTAT(bus) = 0xF0; | 92 | IICDS(bus) = slave | rd; |
105 | while ((IICCON(bus) & 0x10) == 0) | 93 | wait_rdy(bus); |
106 | if (TIME_AFTER(USEC_TIMER, timeout)) | 94 | IICSTAT(bus) = rd ? 0xB0 : 0xF0; |
107 | return 1; | ||
108 | |||
109 | if (address >= 0) { | ||
110 | /* write address */ | ||
111 | IICDS(bus) = address; | ||
112 | IICCON(bus) = IICCON(bus); | ||
113 | while ((IICCON(bus) & 0x10) == 0) | ||
114 | if (TIME_AFTER(USEC_TIMER, timeout)) | ||
115 | return 2; | ||
116 | } | ||
117 | 95 | ||
118 | /* write data */ | 96 | i2c_wait_io(bus); |
119 | while (len--) { | ||
120 | IICDS(bus) = *data++; | ||
121 | IICCON(bus) = IICCON(bus); | ||
122 | while ((IICCON(bus) & 0x10) == 0) | ||
123 | if (TIME_AFTER(USEC_TIMER, timeout)) | ||
124 | return 4; | ||
125 | } | ||
126 | 97 | ||
127 | /* STOP */ | 98 | /* check ACK */ |
128 | IICSTAT(bus) = 0xD0; | 99 | if (IICSTAT(bus) & 1) |
129 | IICCON(bus) = IICCON(bus); | 100 | return 1; |
130 | while ((IICSTAT(bus) & (1 << 5)) != 0) | ||
131 | if (TIME_AFTER(USEC_TIMER, timeout)) | ||
132 | return 5; | ||
133 | 101 | ||
134 | i2c_off(bus); | ||
135 | return 0; | 102 | return 0; |
136 | } | 103 | } |
137 | 104 | ||
138 | int i2c_rd(int bus, unsigned char slave, int address, int len, unsigned char *data) | 105 | static void i2c_stop(int bus) |
139 | { | 106 | { |
140 | i2c_on(bus); | 107 | /* STOP */ |
141 | long timeout = USEC_TIMER + 20000; | 108 | wait_rdy(bus); |
142 | 109 | IICSTAT(bus) &= ~0x20; | |
143 | if (address >= 0) { | 110 | wait_rdy(bus); |
144 | /* START */ | 111 | IICCON(bus) = 0x10; |
145 | IICDS(bus) = slave & ~1; | 112 | i2c_wait_io(bus); |
146 | IICSTAT(bus) = 0xF0; | 113 | } |
147 | while ((IICCON(bus) & 0x10) == 0) | 114 | |
148 | if (TIME_AFTER(USEC_TIMER, timeout)) | 115 | static int i2c_wr_internal(int bus, unsigned char slave, |
149 | return 1; | 116 | int address, int len, const unsigned char *data) |
150 | 117 | { | |
151 | /* write address */ | 118 | int rc = 0; |
152 | IICDS(bus) = address; | 119 | |
153 | IICCON(bus) = IICCON(bus); | 120 | if (i2c_start(bus, slave, false) == 0) |
154 | while ((IICCON(bus) & 0x10) == 0) | 121 | { |
155 | if (TIME_AFTER(USEC_TIMER, timeout)) | 122 | /* write address + data */ |
156 | return 2; | 123 | const unsigned char *ptr = data; |
124 | const unsigned char addr = address; | ||
125 | if (address >= 0) { | ||
126 | ptr = &addr; | ||
127 | len++; | ||
128 | } | ||
129 | while (len--) { | ||
130 | wait_rdy(bus); | ||
131 | IICDS(bus) = *ptr; | ||
132 | udelay(5); | ||
133 | wait_rdy(bus); | ||
134 | IICCON(bus) = IICCON(bus); | ||
135 | i2c_wait_io(bus); | ||
136 | /* check ACK */ | ||
137 | if (IICSTAT(bus) & 1) { | ||
138 | rc = 2; | ||
139 | break; | ||
140 | } | ||
141 | if (ptr == &addr) ptr = data; | ||
142 | else ptr++; | ||
143 | } | ||
157 | } | 144 | } |
145 | else | ||
146 | rc = 1; | ||
158 | 147 | ||
159 | /* (repeated) START */ | 148 | i2c_stop(bus); |
160 | IICDS(bus) = slave | 1; | 149 | return rc; |
161 | IICSTAT(bus) = 0xB0; | 150 | } |
162 | IICCON(bus) = IICCON(bus); | 151 | |
163 | while ((IICCON(bus) & 0x10) == 0) | 152 | static int i2c_rd_internal(int bus, unsigned char slave, int len, unsigned char *data) |
164 | if (TIME_AFTER(USEC_TIMER, timeout)) | 153 | { |
165 | return 3; | 154 | int rc = 0; |
166 | 155 | ||
167 | while (len--) { | 156 | if (i2c_start(bus, slave, true) == 0) |
168 | IICCON(bus) &= ~(len ? 0 : 0x80); /* ACK or NAK */ | 157 | { |
169 | while ((IICCON(bus) & 0x10) == 0) | 158 | while (len--) { |
170 | if (TIME_AFTER(USEC_TIMER, timeout)) | 159 | wait_rdy(bus); |
171 | return 4; | 160 | IICCON(bus) &= ~(len ? 0 : 0x80); /* ACK or NAK */ |
172 | *data++ = IICDS(bus); | 161 | i2c_wait_io(bus); |
162 | *data++ = IICDS(bus); | ||
163 | } | ||
173 | } | 164 | } |
165 | else | ||
166 | rc = 3; | ||
174 | 167 | ||
175 | /* STOP */ | 168 | i2c_stop(bus); |
176 | IICSTAT(bus) = 0x90; | 169 | return rc; |
177 | IICCON(bus) = IICCON(bus); | 170 | } |
178 | while ((IICSTAT(bus) & (1 << 5)) != 0) | ||
179 | if (TIME_AFTER(USEC_TIMER, timeout)) | ||
180 | return 5; | ||
181 | 171 | ||
172 | int i2c_wr(int bus, unsigned char slave, int address, int len, const unsigned char *data) | ||
173 | { | ||
174 | i2c_on(bus); | ||
175 | int rc = i2c_wr_internal(bus, slave, address, len, data); | ||
182 | i2c_off(bus); | 176 | i2c_off(bus); |
183 | return 0; | 177 | return rc; |
184 | } | 178 | } |
185 | 179 | ||
186 | unsigned long i2c_rd_err, i2c_wr_err; | 180 | int i2c_rd(int bus, unsigned char slave, int address, int len, unsigned char *data) |
181 | { | ||
182 | i2c_on(bus); | ||
183 | int rc = i2c_wr_internal(bus, slave, address, 0, NULL); | ||
184 | if (rc == 0) | ||
185 | rc = i2c_rd_internal(bus, slave, len, data); | ||
186 | i2c_off(bus); | ||
187 | return rc; | ||
188 | } | ||
187 | 189 | ||
188 | int i2c_write(int bus, unsigned char slave, int address, int len, const unsigned char *data) | 190 | int i2c_write(int bus, unsigned char slave, int address, int len, const unsigned char *data) |
189 | { | 191 | { |
@@ -191,7 +193,6 @@ int i2c_write(int bus, unsigned char slave, int address, int len, const unsigned | |||
191 | mutex_lock(&i2c_mtx[bus]); | 193 | mutex_lock(&i2c_mtx[bus]); |
192 | ret = i2c_wr(bus, slave, address, len, data); | 194 | ret = i2c_wr(bus, slave, address, len, data); |
193 | mutex_unlock(&i2c_mtx[bus]); | 195 | mutex_unlock(&i2c_mtx[bus]); |
194 | if (ret) i2c_wr_err++; | ||
195 | return ret; | 196 | return ret; |
196 | } | 197 | } |
197 | 198 | ||
@@ -201,18 +202,14 @@ int i2c_read(int bus, unsigned char slave, int address, int len, unsigned char * | |||
201 | mutex_lock(&i2c_mtx[bus]); | 202 | mutex_lock(&i2c_mtx[bus]); |
202 | ret = i2c_rd(bus, slave, address, len, data); | 203 | ret = i2c_rd(bus, slave, address, len, data); |
203 | mutex_unlock(&i2c_mtx[bus]); | 204 | mutex_unlock(&i2c_mtx[bus]); |
204 | if (ret) i2c_rd_err++; | ||
205 | return ret; | 205 | return ret; |
206 | } | 206 | } |
207 | 207 | ||
208 | static void wait_rdy(int bus) | ||
209 | { | ||
210 | while (IICUNK10(bus)); | ||
211 | } | ||
212 | |||
213 | void i2c_preinit(int bus) | 208 | void i2c_preinit(int bus) |
214 | { | 209 | { |
215 | clockgate_enable(I2CCLKGATE(bus), true); | 210 | if (bus == 0) PCON3 = (PCON3 & ~0x00000ff0) | 0x00000220; |
211 | /* TBC: else if(bus == 1) PCON6 = (PCON6 & ~0x0ff00000) | 0x02200000; */ | ||
212 | i2c_on(bus); | ||
216 | wait_rdy(bus); | 213 | wait_rdy(bus); |
217 | IICADD(bus) = 0x40; /* own slave address */ | 214 | IICADD(bus) = 0x40; /* own slave address */ |
218 | wait_rdy(bus); | 215 | wait_rdy(bus); |
@@ -220,11 +217,10 @@ void i2c_preinit(int bus) | |||
220 | wait_rdy(bus); | 217 | wait_rdy(bus); |
221 | IICUNK18(bus) = 0; | 218 | IICUNK18(bus) = 0; |
222 | wait_rdy(bus); | 219 | wait_rdy(bus); |
223 | IICSTAT(bus) = 0x80; /* master Rx mode, Tx/Rx off */ | 220 | IICSTAT(bus) = 0x80; /* master Rx mode, serial output off */ |
224 | wait_rdy(bus); | 221 | wait_rdy(bus); |
225 | IICCON(bus) = 0; | 222 | IICCON(bus) = 0; |
226 | wait_rdy(bus); | 223 | wait_rdy(bus); |
227 | IICSTAT(bus) = 0; /* slave Rx mode, Tx/Rx off */ | 224 | IICSTA2(bus) = 0x3f00; |
228 | wait_rdy(bus); | 225 | i2c_off(bus); |
229 | clockgate_enable(I2CCLKGATE(bus), false); | ||
230 | } | 226 | } |