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; |
---|
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 ssize_t eeprom_read( |
---|
52 | i2c_dev *base, |
---|
53 | void *buf, |
---|
54 | size_t n, |
---|
55 | off_t offset |
---|
56 | ) |
---|
57 | { |
---|
58 | eeprom *dev = (eeprom *) base; |
---|
59 | off_t avail = dev->size - offset; |
---|
60 | uint32_t off = (uint32_t) offset; |
---|
61 | uint8_t *in = buf; |
---|
62 | size_t todo; |
---|
63 | |
---|
64 | if (avail <= 0) { |
---|
65 | return 0; |
---|
66 | } |
---|
67 | |
---|
68 | if (n > avail) { |
---|
69 | n = (size_t) avail; |
---|
70 | } |
---|
71 | |
---|
72 | todo = n; |
---|
73 | |
---|
74 | while (todo > 0) { |
---|
75 | uint16_t i2c_addr = eeprom_i2c_addr(dev, off); |
---|
76 | |
---|
77 | /* |
---|
78 | * Limit the transfer size so that it can be stored in 8-bits. This may |
---|
79 | * help some bus controllers. |
---|
80 | */ |
---|
81 | uint16_t cur = (uint16_t) (todo < 255 ? todo : 255); |
---|
82 | |
---|
83 | uint8_t addr[EEPROM_MAX_ADDRESS_BYTES] = { |
---|
84 | (uint8_t) off, |
---|
85 | (uint8_t) (off >> 8), |
---|
86 | (uint8_t) (off >> 16), |
---|
87 | (uint8_t) (off >> 24) |
---|
88 | }; |
---|
89 | i2c_msg msgs[2] = { |
---|
90 | { |
---|
91 | .addr = i2c_addr, |
---|
92 | .flags = 0, |
---|
93 | .len = dev->address_bytes, |
---|
94 | .buf = &addr[0] |
---|
95 | }, { |
---|
96 | .addr = i2c_addr, |
---|
97 | .flags = I2C_M_RD, |
---|
98 | .buf = in, |
---|
99 | .len = cur |
---|
100 | } |
---|
101 | }; |
---|
102 | int err; |
---|
103 | |
---|
104 | err = i2c_bus_transfer(dev->base.bus, &msgs[0], RTEMS_ARRAY_SIZE(msgs)); |
---|
105 | if (err != 0) { |
---|
106 | return err; |
---|
107 | } |
---|
108 | |
---|
109 | todo -= cur; |
---|
110 | off += cur; |
---|
111 | in += cur; |
---|
112 | } |
---|
113 | |
---|
114 | return (ssize_t) n; |
---|
115 | } |
---|
116 | |
---|
117 | static ssize_t eeprom_write( |
---|
118 | i2c_dev *base, |
---|
119 | const void *buf, |
---|
120 | size_t n, |
---|
121 | off_t offset |
---|
122 | ) |
---|
123 | { |
---|
124 | eeprom *dev = (eeprom *) base; |
---|
125 | off_t avail = dev->size - offset; |
---|
126 | uint32_t off = (uint32_t) offset; |
---|
127 | const uint8_t *out = buf; |
---|
128 | size_t todo; |
---|
129 | |
---|
130 | if (avail <= 0) { |
---|
131 | return 0; |
---|
132 | } |
---|
133 | |
---|
134 | if (n > avail) { |
---|
135 | n = (size_t) avail; |
---|
136 | } |
---|
137 | |
---|
138 | todo = n; |
---|
139 | |
---|
140 | while (todo > 0) { |
---|
141 | uint16_t i2c_addr = eeprom_i2c_addr(dev, off); |
---|
142 | uint16_t rem = dev->page_size - (off & (dev->page_size - 1)); |
---|
143 | uint16_t cur = (uint16_t) (todo < rem ? todo : rem); |
---|
144 | uint8_t addr[EEPROM_MAX_ADDRESS_BYTES] = { |
---|
145 | (uint8_t) off, |
---|
146 | (uint8_t) (off >> 8), |
---|
147 | (uint8_t) (off >> 16), |
---|
148 | (uint8_t) (off >> 24) |
---|
149 | }; |
---|
150 | i2c_msg msgs[2] = { |
---|
151 | { |
---|
152 | .addr = i2c_addr, |
---|
153 | .flags = 0, |
---|
154 | .len = dev->address_bytes, |
---|
155 | .buf = &addr[0] |
---|
156 | }, { |
---|
157 | .addr = i2c_addr, |
---|
158 | .flags = I2C_M_NOSTART, |
---|
159 | .buf = RTEMS_DECONST(uint8_t *, out), |
---|
160 | .len = cur |
---|
161 | } |
---|
162 | }; |
---|
163 | uint8_t in[EEPROM_MAX_PAGE_SIZE]; |
---|
164 | int err; |
---|
165 | ssize_t m; |
---|
166 | rtems_interval timeout; |
---|
167 | |
---|
168 | err = i2c_bus_transfer(dev->base.bus, &msgs[0], RTEMS_ARRAY_SIZE(msgs)); |
---|
169 | if (err != 0) { |
---|
170 | return err; |
---|
171 | } |
---|
172 | |
---|
173 | timeout = rtems_clock_tick_later(dev->program_timeout); |
---|
174 | |
---|
175 | do { |
---|
176 | m = eeprom_read(&dev->base, &in[0], cur, off); |
---|
177 | } while (m != cur && rtems_clock_tick_before(timeout)); |
---|
178 | |
---|
179 | if (m != cur) { |
---|
180 | return -ETIMEDOUT; |
---|
181 | } |
---|
182 | |
---|
183 | if (memcmp(&in[0], &out[0], cur) != 0) { |
---|
184 | return -EIO; |
---|
185 | } |
---|
186 | |
---|
187 | todo -= cur; |
---|
188 | off += cur; |
---|
189 | out += cur; |
---|
190 | } |
---|
191 | |
---|
192 | return (ssize_t) n; |
---|
193 | } |
---|
194 | |
---|
195 | static off_t eeprom_get_size(i2c_dev *base) |
---|
196 | { |
---|
197 | eeprom *dev = (eeprom *) base; |
---|
198 | |
---|
199 | return dev->size; |
---|
200 | } |
---|
201 | |
---|
202 | static blksize_t eeprom_get_block_size(i2c_dev *base) |
---|
203 | { |
---|
204 | eeprom *dev = (eeprom *) base; |
---|
205 | |
---|
206 | return dev->page_size; |
---|
207 | } |
---|
208 | |
---|
209 | int i2c_dev_register_eeprom( |
---|
210 | const char *bus_path, |
---|
211 | const char *dev_path, |
---|
212 | uint16_t i2c_address, |
---|
213 | uint16_t address_bytes, |
---|
214 | uint16_t page_size_in_bytes, |
---|
215 | uint32_t size_in_bytes, |
---|
216 | uint32_t program_timeout_in_ms |
---|
217 | ) |
---|
218 | { |
---|
219 | uint32_t extra_address; |
---|
220 | eeprom *dev; |
---|
221 | |
---|
222 | if (address_bytes > EEPROM_MAX_ADDRESS_BYTES) { |
---|
223 | rtems_set_errno_and_return_minus_one(ERANGE); |
---|
224 | } else if (address_bytes == EEPROM_MAX_ADDRESS_BYTES) { |
---|
225 | extra_address = 0; |
---|
226 | } else { |
---|
227 | extra_address = size_in_bytes >> (8 * address_bytes); |
---|
228 | } |
---|
229 | |
---|
230 | if (extra_address != 0 && (extra_address & (extra_address - 1)) != 0) { |
---|
231 | rtems_set_errno_and_return_minus_one(EINVAL); |
---|
232 | } |
---|
233 | |
---|
234 | if (page_size_in_bytes > EEPROM_MAX_PAGE_SIZE) { |
---|
235 | page_size_in_bytes = EEPROM_MAX_PAGE_SIZE; |
---|
236 | } |
---|
237 | |
---|
238 | if (program_timeout_in_ms == 0) { |
---|
239 | program_timeout_in_ms = 1000; |
---|
240 | } |
---|
241 | |
---|
242 | dev = (eeprom *) |
---|
243 | i2c_dev_alloc_and_init(sizeof(*dev), bus_path, i2c_address); |
---|
244 | if (dev == NULL) { |
---|
245 | return -1; |
---|
246 | } |
---|
247 | |
---|
248 | dev->base.read = eeprom_read; |
---|
249 | dev->base.write = eeprom_write; |
---|
250 | dev->base.get_size = eeprom_get_size; |
---|
251 | dev->base.get_block_size = eeprom_get_block_size; |
---|
252 | dev->address_bytes = address_bytes; |
---|
253 | dev->page_size = page_size_in_bytes; |
---|
254 | dev->size = size_in_bytes; |
---|
255 | dev->program_timeout = RTEMS_MILLISECONDS_TO_TICKS(program_timeout_in_ms); |
---|
256 | |
---|
257 | if (extra_address != 0) { |
---|
258 | dev->i2c_address_mask = extra_address - 1; |
---|
259 | dev->i2c_address_shift = (uint16_t) (8 * address_bytes); |
---|
260 | } |
---|
261 | |
---|
262 | return i2c_dev_register(&dev->base, dev_path); |
---|
263 | } |
---|