summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCástor Muñoz <cmvidal@gmail.com>2016-08-12 14:03:54 +0200
committerCástor Muñoz <cmvidal@gmail.com>2016-08-12 14:17:46 +0200
commit578525b4638f575e30edb963c0de5dde44e250d5 (patch)
treeae682f078999d2bc3def00fcefb85069f8b7ed23
parentadbd2969e6e6fd584d46ef60a3fa40bf878d7e00 (diff)
downloadrockbox-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.h15
-rw-r--r--firmware/target/arm/s5l8702/debug-s5l8702.c2
-rw-r--r--firmware/target/arm/s5l8702/i2c-s5l8702.c244
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
66static struct mutex i2c_mtx[2]; 41static struct mutex i2c_mtx[2];
67 42
43void i2c_init()
44{
45 mutex_init(&i2c_mtx[0]);
46 mutex_init(&i2c_mtx[1]);
47}
48
49static void wait_rdy(int bus)
50{
51 while (IICUNK10(bus));
52}
53
68static void i2c_on(int bus) 54static 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
82static void i2c_off(int bus) 60static 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
91void 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. */
72static 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
97int i2c_wr(int bus, unsigned char slave, int address, int len, const unsigned char *data) 81static 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
138int i2c_rd(int bus, unsigned char slave, int address, int len, unsigned char *data) 105static 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)) 115static 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) 152static 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
172int 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
186unsigned long i2c_rd_err, i2c_wr_err; 180int 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
188int i2c_write(int bus, unsigned char slave, int address, int len, const unsigned char *data) 190int 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
208static void wait_rdy(int bus)
209{
210 while (IICUNK10(bus));
211}
212
213void i2c_preinit(int bus) 208void 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}