source: rtems/cpukit/dev/i2c/eeprom.c @ 05015dc1

5
Last change on this file since 05015dc1 was 9c063df, checked in by Sebastian Huber <sebastian.huber@…>, on 10/02/17 at 11:28:22

i2c: Fix EEPROM driver program timeout handling

The RTEMS_MILLISECONDS_TO_TICKS() macro doesn't round up. Do not use it
to calculate the program timeout in ticks. Check program done condition
after the timeout check to account for pre-emptions.

Update #3162.

  • Property mode set to 100644
File size: 5.7 KB
Line 
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
35typedef 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
45static 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
51static 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
68static 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
130static 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
210static off_t eeprom_get_size(i2c_dev *base)
211{
212  eeprom *dev = (eeprom *) base;
213
214  return dev->size;
215}
216
217static blksize_t eeprom_get_block_size(i2c_dev *base)
218{
219  eeprom *dev = (eeprom *) base;
220
221  return dev->page_size;
222}
223
224int 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}
Note: See TracBrowser for help on using the repository browser.