source: rtems/c/src/lib/libbsp/arm/imx/i2c/imx-i2c.c @ 8bdbefe

5
Last change on this file since 8bdbefe was f043b9b, checked in by Sebastian Huber <sebastian.huber@…>, on 09/29/17 at 08:43:03

bsp/imx: Add I2C bus driver

Update #3090.

  • Property mode set to 100644
File size: 9.4 KB
Line 
1/*
2 * Copyright (c) 2017 embedded brains GmbH.  All rights reserved.
3 *
4 *  embedded brains GmbH
5 *  Dornierstr. 4
6 *  82178 Puchheim
7 *  Germany
8 *  <info@embedded-brains.de>
9 *
10 * The license and distribution terms for this file may be
11 * found in the file LICENSE in this distribution or at
12 * http://www.rtems.org/license/LICENSE.
13 */
14
15#include <bsp.h>
16#include <bsp/fdt.h>
17#include <libfdt.h>
18#include <arm/freescale/imx/imx_ccmvar.h>
19#include <arm/freescale/imx/imx_i2creg.h>
20#include <dev/i2c/i2c.h>
21#include <rtems/irq-extension.h>
22
23#define IMX_I2C_TRANSMIT (IMX_I2C_I2CR_IEN | IMX_I2C_I2CR_IIEN \
24  | IMX_I2C_I2CR_MSTA | IMX_I2C_I2CR_MTX)
25
26#define IMX_I2C_RECEIVE (IMX_I2C_I2CR_IEN | IMX_I2C_I2CR_IIEN \
27  | IMX_I2C_I2CR_MSTA)
28
29typedef struct {
30  i2c_bus base;
31  volatile imx_i2c *regs;
32  uint32_t msg_todo;
33  const i2c_msg *msg;
34  bool read;
35  bool start;
36  uint16_t restart;
37  uint32_t chunk_total;
38  uint32_t chunk_done;
39  uint16_t buf_todo;
40  uint8_t *buf;
41  rtems_id task_id;
42  int eno;
43  rtems_vector_number irq;
44} imx_i2c_bus;
45
46typedef struct {
47  uint16_t divisor;
48  uint8_t ifdr;
49} imx_i2c_clock_divisor;
50
51static const imx_i2c_clock_divisor imx_i2c_clock_divisor_table[] = {
52  {    0, 0x20 }, {   22, 0x20 }, {   24, 0x21 }, {     26, 0x22 },
53  {   28, 0x23 }, {   30, 0x00 }, {   32, 0x24 }, {     36, 0x25 },
54  {   40, 0x26 }, {   42, 0x03 }, {   44, 0x27 }, {     48, 0x28 },
55  {   52, 0x05 }, {   56, 0x29 }, {   60, 0x06 }, {     64, 0x2a },
56  {   72, 0x2b }, {   80, 0x2c }, {   88, 0x09 }, {     96, 0x2d },
57  {  104, 0x0a }, {  112, 0x2e }, {  128, 0x2f }, {    144, 0x0c },
58  {  160, 0x30 }, {  192, 0x31 }, {  224, 0x32 }, {    240, 0x0f },
59  {  256, 0x33 }, {  288, 0x10 }, {  320, 0x34 }, {    384, 0x35 },
60  {  448, 0x36 }, {  480, 0x13 }, {  512, 0x37 }, {    576, 0x14 },
61  {  640, 0x38 }, {  768, 0x39 }, {  896, 0x3a }, {    960, 0x17 },
62  { 1024, 0x3b }, { 1152, 0x18 }, { 1280, 0x3c }, {   1536, 0x3d },
63  { 1792, 0x3e }, { 1920, 0x1b }, { 2048, 0x3f }, {   2304, 0x1c },
64  { 2560, 0x1d }, { 3072, 0x1e }, { 3840, 0x1f }, { 0xffff, 0x1f }
65};
66
67static void imx_i2c_stop(volatile imx_i2c *regs)
68{
69  regs->i2cr = IMX_I2C_I2CR_IEN;
70  regs->i2sr = 0;
71  regs->i2sr;
72}
73
74static void imx_i2c_trigger_receive(imx_i2c_bus *bus, volatile imx_i2c *regs)
75{
76  uint16_t i2cr;
77
78  i2cr = IMX_I2C_RECEIVE;
79
80  if (bus->chunk_total == 1) {
81    i2cr |= IMX_I2C_I2CR_TXAK;
82  }
83
84  regs->i2cr = i2cr;
85  regs->i2dr;
86}
87
88static void imx_i2c_done(imx_i2c_bus *bus, int eno)
89{
90  /*
91   * Generates a stop in case of transmit, otherwise, only disables interrupts
92   * (IMX_I2C_I2CR_MSTA is already cleared).
93   */
94  imx_i2c_stop(bus->regs);
95
96  bus->eno = eno;
97  rtems_event_transient_send(bus->task_id);
98}
99
100static const i2c_msg *imx_i2c_msg_inc(imx_i2c_bus *bus)
101{
102  const i2c_msg *next;
103
104  next = bus->msg + 1;
105  bus->msg = next;
106  --bus->msg_todo;
107  return next;
108}
109
110static void imx_i2c_msg_inc_and_set_buf(imx_i2c_bus *bus)
111{
112  const i2c_msg *next;
113
114  next = imx_i2c_msg_inc(bus);
115  bus->buf_todo = next->len;
116  bus->buf = next->buf;
117}
118
119static void imx_i2c_buf_inc(imx_i2c_bus *bus)
120{
121  ++bus->buf;
122  --bus->buf_todo;
123  ++bus->chunk_done;
124}
125
126static void imx_i2c_buf_push(imx_i2c_bus *bus, uint8_t c)
127{
128  while (true) {
129    if (bus->buf_todo > 0) {
130      bus->buf[0] = c;
131      imx_i2c_buf_inc(bus);
132      break;
133    }
134
135    imx_i2c_msg_inc_and_set_buf(bus);
136  }
137}
138
139static uint8_t imx_i2c_buf_pop(imx_i2c_bus *bus)
140{
141  while (true) {
142    if (bus->buf_todo > 0) {
143      uint8_t c;
144
145      c = bus->buf[0];
146      imx_i2c_buf_inc(bus);
147      return c;
148    }
149
150    imx_i2c_msg_inc_and_set_buf(bus);
151  }
152}
153
154RTEMS_STATIC_ASSERT(I2C_M_RD == 1, imx_i2c_read_flag);
155
156static void imx_i2c_setup_chunk(imx_i2c_bus *bus, volatile imx_i2c *regs)
157{
158  while (true) {
159    const i2c_msg *msg;
160    int flags;
161    int can_continue;
162    uint32_t i;
163
164    if (bus->msg_todo == 0) {
165      imx_i2c_done(bus, 0);
166      break;
167    }
168
169    msg = bus->msg;
170    flags = msg->flags;
171
172    bus->read = (flags & I2C_M_RD) != 0;
173    bus->start = (flags & I2C_M_NOSTART) == 0;
174    bus->chunk_total = msg->len;
175    bus->chunk_done = 0;
176    bus->buf_todo = msg->len;
177    bus->buf = msg->buf;
178
179    can_continue = (flags & I2C_M_RD) | I2C_M_NOSTART;
180
181    for (i = 1; i < bus->msg_todo; ++i) {
182      if ((msg[i].flags & (I2C_M_RD | I2C_M_NOSTART)) != can_continue) {
183        break;
184      }
185
186      bus->chunk_total += msg[i].len;
187    }
188
189    if (bus->start) {
190      regs->i2cr = IMX_I2C_TRANSMIT | bus->restart;
191      regs->i2dr = (uint8_t) ((msg->addr << 1) | (flags & I2C_M_RD));
192      bus->restart = IMX_I2C_I2CR_RSTA;
193      break;
194    } else if (bus->chunk_total > 0) {
195      if (bus->read) {
196        imx_i2c_trigger_receive(bus, regs);
197      } else {
198        regs->i2cr = IMX_I2C_TRANSMIT;
199        regs->i2dr = imx_i2c_buf_pop(bus);
200      }
201
202      break;
203    } else {
204      ++bus->msg;
205      --bus->msg_todo;
206    }
207  }
208}
209
210static void imx_i2c_transfer_complete(
211  imx_i2c_bus *bus,
212  volatile imx_i2c *regs,
213  uint16_t i2sr
214)
215{
216  if (bus->start) {
217    bus->start = false;
218
219    if ((i2sr & IMX_I2C_I2SR_RXAK) != 0) {
220      imx_i2c_done(bus, EIO);
221      return;
222    }
223
224    if (bus->read) {
225      imx_i2c_trigger_receive(bus, regs);
226      return;
227    }
228  }
229
230  if (bus->chunk_done < bus->chunk_total) {
231    if (bus->read) {
232      if (bus->chunk_done + 2 == bus->chunk_total) {
233        /* Receive second last byte with NACK */
234        regs->i2cr = IMX_I2C_RECEIVE | IMX_I2C_I2CR_TXAK;
235      } else if (bus->chunk_done + 1 == bus->chunk_total) {
236        /* Receive last byte with STOP */
237        bus->restart = 0;
238        regs->i2cr = (IMX_I2C_RECEIVE | IMX_I2C_I2CR_TXAK)
239          & ~IMX_I2C_I2CR_MSTA;
240      }
241
242      imx_i2c_buf_push(bus, (uint8_t) regs->i2dr);
243
244      if (bus->chunk_done == bus->chunk_total) {
245        imx_i2c_msg_inc(bus);
246        imx_i2c_setup_chunk(bus, regs);
247      }
248    } else {
249      if (bus->chunk_done > 0 && (i2sr & IMX_I2C_I2SR_RXAK) != 0) {
250        imx_i2c_done(bus, EIO);
251        return;
252      }
253
254      regs->i2dr = imx_i2c_buf_pop(bus);
255    }
256  } else {
257    imx_i2c_msg_inc(bus);
258    imx_i2c_setup_chunk(bus, regs);
259  }
260}
261
262static void imx_i2c_interrupt(void *arg)
263{
264  imx_i2c_bus *bus;
265  volatile imx_i2c *regs;
266  uint16_t i2sr;
267
268  bus = arg;
269  regs = bus->regs;
270
271  i2sr = regs->i2sr;
272  regs->i2sr = 0;
273
274  if ((i2sr & (IMX_I2C_I2SR_IAL | IMX_I2C_I2SR_ICF)) == IMX_I2C_I2SR_ICF) {
275    imx_i2c_transfer_complete(bus, regs, i2sr);
276  } else {
277    imx_i2c_done(bus, EIO);
278  }
279}
280
281static int imx_i2c_wait_for_not_busy(volatile imx_i2c *regs)
282{
283  rtems_interval timeout;
284  bool before;
285
286  if ((regs->i2sr & IMX_I2C_I2SR_IBB) == 0) {
287    return 0;
288  }
289
290  timeout = rtems_clock_tick_later(10);
291
292  do {
293    before = rtems_clock_tick_before(timeout);
294
295    if ((regs->i2sr & IMX_I2C_I2SR_IBB) == 0) {
296      return 0;
297    }
298  } while (before);
299
300  return ETIMEDOUT;
301}
302
303static int imx_i2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t n)
304{
305  imx_i2c_bus *bus;
306  int supported_flags;
307  uint32_t i;
308  volatile imx_i2c *regs;
309  int eno;
310  rtems_status_code sc;
311
312  supported_flags = I2C_M_RD;
313
314  for (i = 0; i < n; ++i) {
315    if ((msgs[i].flags & ~supported_flags) != 0) {
316      return -EINVAL;
317    }
318
319    supported_flags |= I2C_M_NOSTART;
320  }
321
322  bus = (imx_i2c_bus *) base;
323  regs = bus->regs;
324
325  eno = imx_i2c_wait_for_not_busy(regs);
326  if (eno != 0) {
327    return -eno;
328  }
329
330  bus->msg_todo = n;
331  bus->msg = &msgs[0];
332  bus->restart = 0;
333  bus->task_id = rtems_task_self();
334  bus->eno = 0;
335
336  regs->i2sr = 0;
337  imx_i2c_setup_chunk(bus, regs);
338
339  sc = rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout);
340  if (sc != RTEMS_SUCCESSFUL) {
341    imx_i2c_stop(bus->regs);
342    rtems_event_transient_clear();
343    return -ETIMEDOUT;
344  }
345
346  return -bus->eno;
347}
348
349static int imx_i2c_set_clock(i2c_bus *base, unsigned long clock)
350{
351  imx_i2c_bus *bus;
352  uint32_t ipg_clock;
353  uint16_t div;
354  size_t i;
355  const imx_i2c_clock_divisor *clock_divisor;
356
357  bus = (imx_i2c_bus *) base;
358  ipg_clock = imx_ccm_ipg_hz();
359  div = (uint16_t) ((ipg_clock + clock - 1) / clock);
360
361  for (i = 0; i < RTEMS_ARRAY_SIZE(imx_i2c_clock_divisor_table); ++i) {
362    clock_divisor = &imx_i2c_clock_divisor_table[i];
363
364    if (clock_divisor->divisor >= div) {
365      break;
366    }
367  }
368
369  bus->regs->ifdr = clock_divisor->ifdr;
370  return 0;
371}
372
373static void imx_i2c_destroy(i2c_bus *base)
374{
375  imx_i2c_bus *bus;
376
377  bus = (imx_i2c_bus *) base;
378  rtems_interrupt_handler_remove(bus->irq, imx_i2c_interrupt, bus);
379  i2c_bus_destroy_and_free(&bus->base);
380}
381
382static int imx_i2c_init(imx_i2c_bus *bus)
383{
384  rtems_status_code sc;
385
386  imx_i2c_set_clock(&bus->base, I2C_BUS_CLOCK_DEFAULT);
387  bus->regs->i2cr = IMX_I2C_I2CR_IEN;
388
389  sc = rtems_interrupt_handler_install(
390    bus->irq,
391    "I2C",
392    RTEMS_INTERRUPT_UNIQUE,
393    imx_i2c_interrupt,
394    bus
395  );
396  if (sc != RTEMS_SUCCESSFUL) {
397    return EAGAIN;
398  }
399
400  return 0;
401}
402
403int i2c_bus_register_imx(const char *bus_path, const char *alias)
404{
405  const void *fdt;
406  int node;
407  imx_i2c_bus *bus;
408  int eno;
409
410  fdt = bsp_fdt_get();
411  alias = fdt_get_alias(fdt, alias);
412
413  if (alias == NULL) {
414    rtems_set_errno_and_return_minus_one(ENXIO);
415  }
416
417  bus = (imx_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus));
418  if (bus == NULL){
419    return -1;
420  }
421
422  node = fdt_path_offset(fdt, alias);
423  bus->regs = imx_get_reg_of_node(fdt, node);
424  bus->irq = imx_get_irq_of_node(fdt, node, 0);
425
426  eno = imx_i2c_init(bus);
427  if (eno != 0) {
428    (*bus->base.destroy)(&bus->base);
429    rtems_set_errno_and_return_minus_one(eno);
430  }
431
432  bus->base.transfer = imx_i2c_transfer;
433  bus->base.set_clock = imx_i2c_set_clock;
434  bus->base.destroy = imx_i2c_destroy;
435
436  return i2c_bus_register(&bus->base, bus_path);
437}
Note: See TracBrowser for help on using the repository browser.