1 | /** |
---|
2 | * @file |
---|
3 | * |
---|
4 | * @brief EEPROM Driver Implementation |
---|
5 | * |
---|
6 | * @ingroup I2CEEPROM |
---|
7 | */ |
---|
8 | |
---|
9 | /* |
---|
10 | * Copyright (c) 2014 embedded brains GmbH. All rights reserved. |
---|
11 | * |
---|
12 | * embedded brains GmbH |
---|
13 | * Dornierstr. 4 |
---|
14 | * 82178 Puchheim |
---|
15 | * Germany |
---|
16 | * <rtems@embedded-brains.de> |
---|
17 | * |
---|
18 | * The license and distribution terms for this file may be |
---|
19 | * found in the file LICENSE in this distribution or at |
---|
20 | * http://www.rtems.org/license/LICENSE. |
---|
21 | */ |
---|
22 | |
---|
23 | #if HAVE_CONFIG_H |
---|
24 | #include "config.h" |
---|
25 | #endif |
---|
26 | |
---|
27 | #include <dev/i2c/eeprom.h> |
---|
28 | |
---|
29 | #include <string.h> |
---|
30 | |
---|
31 | #define EEPROM_MAX_ADDRESS_BYTES 4 |
---|
32 | |
---|
33 | #define EEPROM_MAX_PAGE_SIZE 128 |
---|
34 | |
---|
35 | typedef struct { |
---|
36 | i2c_dev base; |
---|
37 | uint16_t address_bytes; |
---|
38 | uint16_t page_size; |
---|
39 | uint32_t size; |
---|
40 | uint16_t i2c_address_mask; |
---|
41 | uint16_t i2c_address_shift; |
---|
42 | rtems_interval program_timeout_in_ticks; |
---|
43 | } eeprom; |
---|
44 | |
---|
45 | static uint16_t eeprom_i2c_addr(eeprom *dev, uint32_t off) |
---|
46 | { |
---|
47 | return dev->base.address |
---|
48 | | ((off >> dev->i2c_address_shift) & dev->i2c_address_mask); |
---|
49 | } |
---|
50 | |
---|
51 | static void eeprom_set_addr( |
---|
52 | const eeprom *dev, |
---|
53 | uint32_t off, |
---|
54 | uint8_t addr[EEPROM_MAX_ADDRESS_BYTES] |
---|
55 | ) |
---|
56 | { |
---|
57 | int shift = 24 - (4 - dev->address_bytes) * 8; |
---|
58 | |
---|
59 | addr[0] = (uint8_t) (off >> shift); |
---|
60 | shift -= 8; |
---|
61 | addr[1] = (uint8_t) (off >> shift); |
---|
62 | shift -= 8; |
---|
63 | addr[2] = (uint8_t) (off >> shift); |
---|
64 | shift -= 8; |
---|
65 | addr[3] = (uint8_t) (off >> shift); |
---|
66 | } |
---|
67 | |
---|
68 | static ssize_t eeprom_read( |
---|
69 | i2c_dev *base, |
---|
70 | void *buf, |
---|
71 | size_t n, |
---|
72 | off_t offset |
---|
73 | ) |
---|
74 | { |
---|
75 | eeprom *dev = (eeprom *) base; |
---|
76 | off_t avail = dev->size - offset; |
---|
77 | uint32_t off = (uint32_t) offset; |
---|
78 | uint8_t *in = buf; |
---|
79 | size_t todo; |
---|
80 | |
---|
81 | if (avail <= 0) { |
---|
82 | return 0; |
---|
83 | } |
---|
84 | |
---|
85 | if (n > avail) { |
---|
86 | n = (size_t) avail; |
---|
87 | } |
---|
88 | |
---|
89 | todo = n; |
---|
90 | |
---|
91 | while (todo > 0) { |
---|
92 | uint16_t i2c_addr = eeprom_i2c_addr(dev, off); |
---|
93 | |
---|
94 | /* |
---|
95 | * Limit the transfer size so that it can be stored in 8-bits. This may |
---|
96 | * help some bus controllers. |
---|
97 | */ |
---|
98 | uint16_t cur = (uint16_t) (todo < 255 ? todo : 255); |
---|
99 | |
---|
100 | uint8_t addr[EEPROM_MAX_ADDRESS_BYTES]; |
---|
101 | i2c_msg msgs[2] = { |
---|
102 | { |
---|
103 | .addr = i2c_addr, |
---|
104 | .flags = 0, |
---|
105 | .len = dev->address_bytes, |
---|
106 | .buf = &addr[0] |
---|
107 | }, { |
---|
108 | .addr = i2c_addr, |
---|
109 | .flags = I2C_M_RD, |
---|
110 | .buf = in, |
---|
111 | .len = cur |
---|
112 | } |
---|
113 | }; |
---|
114 | int err; |
---|
115 | |
---|
116 | eeprom_set_addr(dev, off, addr); |
---|
117 | err = i2c_bus_transfer(dev->base.bus, &msgs[0], RTEMS_ARRAY_SIZE(msgs)); |
---|
118 | if (err != 0) { |
---|
119 | return err; |
---|
120 | } |
---|
121 | |
---|
122 | todo -= cur; |
---|
123 | off += cur; |
---|
124 | in += cur; |
---|
125 | } |
---|
126 | |
---|
127 | return (ssize_t) n; |
---|
128 | } |
---|
129 | |
---|
130 | static ssize_t eeprom_write( |
---|
131 | i2c_dev *base, |
---|
132 | const void *buf, |
---|
133 | size_t n, |
---|
134 | off_t offset |
---|
135 | ) |
---|
136 | { |
---|
137 | eeprom *dev = (eeprom *) base; |
---|
138 | off_t avail = dev->size - offset; |
---|
139 | uint32_t off = (uint32_t) offset; |
---|
140 | const uint8_t *out = buf; |
---|
141 | size_t todo; |
---|
142 | |
---|
143 | if (avail <= 0) { |
---|
144 | return 0; |
---|
145 | } |
---|
146 | |
---|
147 | if (n > avail) { |
---|
148 | n = (size_t) avail; |
---|
149 | } |
---|
150 | |
---|
151 | todo = n; |
---|
152 | |
---|
153 | while (todo > 0) { |
---|
154 | uint16_t i2c_addr = eeprom_i2c_addr(dev, off); |
---|
155 | uint16_t rem = dev->page_size - (off & (dev->page_size - 1)); |
---|
156 | uint16_t cur = (uint16_t) (todo < rem ? todo : rem); |
---|
157 | uint8_t addr[EEPROM_MAX_ADDRESS_BYTES]; |
---|
158 | i2c_msg msgs[2] = { |
---|
159 | { |
---|
160 | .addr = i2c_addr, |
---|
161 | .flags = 0, |
---|
162 | .len = dev->address_bytes, |
---|
163 | .buf = &addr[0] |
---|
164 | }, { |
---|
165 | .addr = i2c_addr, |
---|
166 | .flags = I2C_M_NOSTART, |
---|
167 | .buf = RTEMS_DECONST(uint8_t *, out), |
---|
168 | .len = cur |
---|
169 | } |
---|
170 | }; |
---|
171 | uint8_t in[EEPROM_MAX_PAGE_SIZE]; |
---|
172 | int err; |
---|
173 | ssize_t m; |
---|
174 | rtems_interval timeout; |
---|
175 | bool before; |
---|
176 | |
---|
177 | eeprom_set_addr(dev, off, addr); |
---|
178 | err = i2c_bus_transfer(dev->base.bus, &msgs[0], RTEMS_ARRAY_SIZE(msgs)); |
---|
179 | if (err != 0) { |
---|
180 | return err; |
---|
181 | } |
---|
182 | |
---|
183 | timeout = rtems_clock_tick_later(dev->program_timeout_in_ticks); |
---|
184 | |
---|
185 | do { |
---|
186 | before = rtems_clock_tick_before(timeout); |
---|
187 | |
---|
188 | m = eeprom_read(&dev->base, &in[0], cur, off); |
---|
189 | if (m == cur) { |
---|
190 | break; |
---|
191 | } |
---|
192 | } while (before); |
---|
193 | |
---|
194 | if (m != cur) { |
---|
195 | return -ETIMEDOUT; |
---|
196 | } |
---|
197 | |
---|
198 | if (memcmp(&in[0], &out[0], cur) != 0) { |
---|
199 | return -EIO; |
---|
200 | } |
---|
201 | |
---|
202 | todo -= cur; |
---|
203 | off += cur; |
---|
204 | out += cur; |
---|
205 | } |
---|
206 | |
---|
207 | return (ssize_t) n; |
---|
208 | } |
---|
209 | |
---|
210 | static off_t eeprom_get_size(i2c_dev *base) |
---|
211 | { |
---|
212 | eeprom *dev = (eeprom *) base; |
---|
213 | |
---|
214 | return dev->size; |
---|
215 | } |
---|
216 | |
---|
217 | static blksize_t eeprom_get_block_size(i2c_dev *base) |
---|
218 | { |
---|
219 | eeprom *dev = (eeprom *) base; |
---|
220 | |
---|
221 | return dev->page_size; |
---|
222 | } |
---|
223 | |
---|
224 | int i2c_dev_register_eeprom( |
---|
225 | const char *bus_path, |
---|
226 | const char *dev_path, |
---|
227 | uint16_t i2c_address, |
---|
228 | uint16_t address_bytes, |
---|
229 | uint16_t page_size_in_bytes, |
---|
230 | uint32_t size_in_bytes, |
---|
231 | uint32_t program_timeout_in_ms |
---|
232 | ) |
---|
233 | { |
---|
234 | uint32_t extra_address; |
---|
235 | uint32_t ms_per_tick; |
---|
236 | eeprom *dev; |
---|
237 | |
---|
238 | if (address_bytes > EEPROM_MAX_ADDRESS_BYTES) { |
---|
239 | rtems_set_errno_and_return_minus_one(ERANGE); |
---|
240 | } else if (address_bytes == EEPROM_MAX_ADDRESS_BYTES) { |
---|
241 | extra_address = 0; |
---|
242 | } else { |
---|
243 | extra_address = size_in_bytes >> (8 * address_bytes); |
---|
244 | } |
---|
245 | |
---|
246 | if (extra_address != 0 && (extra_address & (extra_address - 1)) != 0) { |
---|
247 | rtems_set_errno_and_return_minus_one(EINVAL); |
---|
248 | } |
---|
249 | |
---|
250 | if (page_size_in_bytes > EEPROM_MAX_PAGE_SIZE) { |
---|
251 | page_size_in_bytes = EEPROM_MAX_PAGE_SIZE; |
---|
252 | } |
---|
253 | |
---|
254 | if (program_timeout_in_ms == 0) { |
---|
255 | program_timeout_in_ms = 1000; |
---|
256 | } |
---|
257 | |
---|
258 | dev = (eeprom *) |
---|
259 | i2c_dev_alloc_and_init(sizeof(*dev), bus_path, i2c_address); |
---|
260 | if (dev == NULL) { |
---|
261 | return -1; |
---|
262 | } |
---|
263 | |
---|
264 | dev->base.read = eeprom_read; |
---|
265 | dev->base.write = eeprom_write; |
---|
266 | dev->base.get_size = eeprom_get_size; |
---|
267 | dev->base.get_block_size = eeprom_get_block_size; |
---|
268 | dev->address_bytes = address_bytes; |
---|
269 | dev->page_size = page_size_in_bytes; |
---|
270 | dev->size = size_in_bytes; |
---|
271 | ms_per_tick = rtems_configuration_get_milliseconds_per_tick(); |
---|
272 | dev->program_timeout_in_ticks = (program_timeout_in_ms + ms_per_tick - 1) |
---|
273 | / ms_per_tick + 1; |
---|
274 | |
---|
275 | if (extra_address != 0) { |
---|
276 | dev->i2c_address_mask = extra_address - 1; |
---|
277 | dev->i2c_address_shift = (uint16_t) (8 * address_bytes); |
---|
278 | } |
---|
279 | |
---|
280 | return i2c_dev_register(&dev->base, dev_path); |
---|
281 | } |
---|