source: rtems/bsps/arm/beagle/i2c/bbb-i2c.c @ b89d6cc

Last change on this file since b89d6cc was b89d6cc, checked in by Christian Mauderer <christian.mauderer@…>, on Jun 24, 2019 at 8:16:59 PM

bsp/beagle: Partial re-write of I2C driver.

The old driver worked well for EEPROMS with the RTEMS EEPROM driver. But
it had problems with a lot of other situations. Although it's not a
direct port, the new driver is heavily modeled after the FreeBSD ti_i2c
driver.

Closes #3764.

  • Property mode set to 100644
File size: 14.3 KB
Line 
1/**
2 * @file
3 *
4 * @ingroup arm_beagle
5 *
6 * @brief BeagleBoard I2C bus initialization and API Support.
7 */
8
9/*
10 * Copyright (c) 2016 Punit Vara <punitvara@gmail.com>
11 * Copyright (c) 2017 Sichen Zhao <zsc19940506@gmail.com>
12 * Copyright (c) 2019 Christian Mauderer <christian.mauderer@embedded-brains.de>
13 *
14 * The license and distribution terms for this file may be
15 * found in the file LICENSE in this distribution or at
16 * http://www.rtems.org/license/LICENSE.
17 */
18
19#include <rtems/bspIo.h>
20#include <stdio.h>
21#include <bsp/i2c.h>
22#include <libcpu/am335x.h>
23#include <rtems/irq-extension.h>
24#include <rtems/counter.h>
25#include <bsp/bbb-gpio.h>
26#include <rtems/score/assert.h>
27#include <dev/i2c/i2c.h>
28
29typedef struct bbb_i2c_bus {
30  i2c_bus base;
31  volatile bbb_i2c_regs *regs;
32  struct {
33    volatile uint32_t *ctrl_clkctrl;
34    volatile uint32_t *i2c_clkctrl;
35    volatile uint32_t *clkstctrl;
36  } clkregs;
37  struct {
38    volatile uint32_t *conf_sda;
39    uint32_t mmode_sda;
40    volatile uint32_t *conf_scl;
41    uint32_t mmode_scl;
42  } pinregs;
43  rtems_id task_id;
44  rtems_vector_number irq;
45  i2c_msg *buffer;
46  size_t buffer_pos;
47  int error;
48  uint32_t con_reg;
49} bbb_i2c_bus;
50
51#define TRANSFER_TIMEOUT_COUNT 100
52#define FIFO_THRESHOLD 5
53#define min(l,r) ((l) < (r) ? (l) : (r))
54#if 0
55#define debug_print(fmt, args...) printk("bbb-i2c: " fmt, ## args)
56#else
57#define debug_print(fmt, args...)
58#endif
59
60static int am335x_i2c_fill_registers(
61  bbb_i2c_bus *bus,
62  uintptr_t register_base
63)
64{
65  /* FIXME: The pin handling should be replaced by a proper pin handling during
66   * initialization. This one is heavily board specific. */
67#if ! IS_AM335X
68  printk ("The I2C driver currently only works on Beagle Bone. Please add your pin configs.")
69  return EINVAL;
70#endif
71  bus->regs = (volatile bbb_i2c_regs *) register_base;
72  switch ((intptr_t) bus->regs) {
73  case AM335X_I2C0_BASE:
74    bus->clkregs.ctrl_clkctrl = &REG(AM335X_SOC_CM_WKUP_REGS +
75                                 AM335X_CM_WKUP_CONTROL_CLKCTRL);
76    bus->clkregs.i2c_clkctrl = &REG(AM335X_SOC_CM_WKUP_REGS +
77                                 AM335X_CM_WKUP_I2C0_CLKCTRL);
78    bus->clkregs.clkstctrl = &REG(AM335X_SOC_CM_WKUP_REGS +
79                                   AM335X_CM_WKUP_CLKSTCTRL);
80    bus->pinregs.conf_sda = &REG(AM335X_PADCONF_BASE + AM335X_CONF_I2C0_SDA);
81    bus->pinregs.mmode_sda = 0;
82    bus->pinregs.conf_scl = &REG(AM335X_PADCONF_BASE + AM335X_CONF_I2C0_SCL);
83    bus->pinregs.mmode_scl = 0;
84    break;
85  case AM335X_I2C1_BASE:
86    bus->clkregs.ctrl_clkctrl = &REG(AM335X_SOC_CM_WKUP_REGS +
87                                 AM335X_CM_WKUP_CONTROL_CLKCTRL);
88    bus->clkregs.i2c_clkctrl = &REG(AM335X_CM_PER_ADDR +
89                                 AM335X_CM_PER_I2C1_CLKCTRL);
90    bus->clkregs.clkstctrl = NULL;
91    bus->pinregs.conf_sda = &REG(AM335X_PADCONF_BASE + AM335X_CONF_SPI0_D1);
92    bus->pinregs.mmode_sda = 2;
93    bus->pinregs.conf_scl = &REG(AM335X_PADCONF_BASE + AM335X_CONF_SPI0_CS0);
94    bus->pinregs.mmode_scl = 2;
95    break;
96  case AM335X_I2C2_BASE:
97    bus->clkregs.ctrl_clkctrl = &REG(AM335X_SOC_CM_WKUP_REGS +
98                                 AM335X_CM_WKUP_CONTROL_CLKCTRL);
99    bus->clkregs.i2c_clkctrl = &REG(AM335X_CM_PER_ADDR +
100                                 AM335X_CM_PER_I2C2_CLKCTRL);
101    bus->clkregs.clkstctrl = NULL;
102    bus->pinregs.conf_sda = &REG(AM335X_PADCONF_BASE + AM335X_CONF_UART1_CTSN);
103    bus->pinregs.mmode_sda = 3;
104    bus->pinregs.conf_scl = &REG(AM335X_PADCONF_BASE + AM335X_CONF_UART1_RTSN);
105    bus->pinregs.mmode_scl = 3;
106    break;
107  default:
108    return EINVAL;
109  }
110  return 0;
111}
112
113static void am335x_i2c_pinmux( bbb_i2c_bus *bus )
114{
115  *bus->pinregs.conf_sda =
116    ( BBB_RXACTIVE | BBB_SLEWCTRL | bus->pinregs.mmode_sda);
117
118  *bus->pinregs.conf_scl =
119    ( BBB_RXACTIVE | BBB_SLEWCTRL | bus->pinregs.mmode_scl);
120}
121
122static void am335x_i2c_module_clk_enable( bbb_i2c_bus *bus )
123{
124  volatile uint32_t *ctrl_clkctrl = bus->clkregs.ctrl_clkctrl;
125  volatile uint32_t *i2c_clkctrl = bus->clkregs.i2c_clkctrl;
126  volatile uint32_t *clkstctrl = bus->clkregs.clkstctrl;
127
128  /* Writing to MODULEMODE field of AM335X_CM_WKUP_I2C0_CLKCTRL register. */
129  *i2c_clkctrl |= AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE_ENABLE;
130
131  /* Waiting for MODULEMODE field to reflect the written value. */
132  while ( AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE_ENABLE !=
133          ( *i2c_clkctrl & AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE ) )
134  { /* busy wait */ }
135
136  /*
137   * Waiting for IDLEST field in AM335X_CM_WKUP_CONTROL_CLKCTRL
138   * register to attain desired value.
139   */
140  while ( ( AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST_FUNC <<
141            AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST_SHIFT ) !=
142          ( *ctrl_clkctrl & AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST ) )
143  { /* busy wait */ }
144
145  if ( clkstctrl != NULL ) {
146    /*
147     * Waiting for CLKACTIVITY_I2C0_GFCLK field in AM335X_CM_WKUP_CLKSTCTRL
148     * register to attain desired value.
149     */
150    while ( AM335X_CM_WKUP_CLKSTCTRL_CLKACTIVITY_I2C0_GFCLK !=
151            ( *clkstctrl & AM335X_CM_WKUP_CLKSTCTRL_CLKACTIVITY_I2C0_GFCLK ) )
152    { /* busy wait */ }
153  }
154
155  /*
156   * Waiting for IDLEST field in AM335X_CM_WKUP_I2C0_CLKCTRL register to attain
157   * desired value.
158   */
159  while ( ( AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST_FUNC <<
160            AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST_SHIFT ) !=
161          ( *i2c_clkctrl & AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST ) ) ;
162}
163
164static int am335x_i2c_set_clock(
165  i2c_bus      *base,
166  unsigned long clock
167)
168{
169  bbb_i2c_bus           *bus = (bbb_i2c_bus *) base;
170  uint32_t               prescaler, divider;
171
172  prescaler = ( BBB_I2C_SYSCLK / BBB_I2C_INTERNAL_CLK ) - 1;
173  bus->regs->BBB_I2C_PSC = prescaler;
174  divider = BBB_I2C_INTERNAL_CLK / ( 2 * clock );
175  bus->regs->BBB_I2C_SCLL = ( divider - 7 );
176  bus->regs->BBB_I2C_SCLH = ( divider - 5 );
177
178  return 0;
179}
180
181static int am335x_i2c_reset( bbb_i2c_bus *bus )
182{
183  volatile bbb_i2c_regs *regs = bus->regs;
184  int                    timeout = 100;
185  int                    err;
186
187  bus->con_reg = 0;
188  regs->BBB_I2C_CON = bus->con_reg;
189  udelay( 50000 );
190
191  regs->BBB_I2C_SYSC = AM335X_I2C_SYSC_SRST;
192  udelay( 1000 );
193  regs->BBB_I2C_CON = AM335X_I2C_CON_I2C_EN;
194
195  while ( !( regs->BBB_I2C_SYSS & AM335X_I2C_SYSS_RDONE )
196          && timeout >= 0 ) {
197    --timeout;
198    udelay( 100 );
199  }
200
201  if ( timeout <= 0 ) {
202    puts( "ERROR: Timeout in soft-reset\n" );
203    return ETIMEDOUT;
204  }
205
206  /* Disable again after reset */
207  regs->BBB_I2C_CON = bus->con_reg;
208
209  err = am335x_i2c_set_clock( &bus->base, I2C_BUS_CLOCK_DEFAULT );
210  if (err) {
211    return err;
212  }
213
214  regs->BBB_I2C_BUF = AM335X_I2C_BUF_TXTRSH(FIFO_THRESHOLD) |
215                              AM335X_I2C_BUF_RXTRSH(FIFO_THRESHOLD);
216
217  /* Enable the I2C controller in master mode. */
218  bus->con_reg |= AM335X_I2C_CON_I2C_EN | AM335X_I2C_CON_MST;
219  regs->BBB_I2C_CON = bus->con_reg;
220
221  regs->BBB_I2C_IRQENABLE_SET =
222      AM335X_I2C_IRQSTATUS_XDR | AM335X_I2C_IRQSTATUS_XRDY |
223      AM335X_I2C_IRQSTATUS_RDR | AM335X_I2C_IRQSTATUS_RRDY |
224      AM335X_I2C_IRQSTATUS_ARDY | AM335X_I2C_IRQSTATUS_NACK |
225      AM335X_I2C_IRQSTATUS_AL;
226
227  return 0;
228}
229
230/* Return true if done. */
231static bool am335x_i2c_transfer_intr(bbb_i2c_bus *bus, uint32_t status)
232{
233  size_t i;
234  size_t amount = 0;
235  volatile bbb_i2c_regs *regs = bus->regs;
236
237  /* Handle errors */
238  if ((status & AM335X_I2C_IRQSTATUS_NACK) != 0) {
239    debug_print("NACK\n");
240    regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_NACK;
241    bus->error = ENXIO;
242  } else if ((status & AM335X_I2C_IRQSTATUS_AL) != 0) {
243    debug_print("Arbitration lost\n");
244    regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_AL;
245    bus->error = ENXIO;
246  }
247
248  /* Transfer finished? */
249  if ((status & AM335X_I2C_IRQSTATUS_ARDY) != 0) {
250    debug_print("ARDY transaction complete\n");
251    if (bus->error != 0 && (bus->buffer->flags & I2C_M_STOP) == 0) {
252      regs->BBB_I2C_CON = bus->con_reg | AM335X_I2C_CON_STOP;
253    }
254    regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_ARDY |
255                              AM335X_I2C_IRQSTATUS_RDR |
256                              AM335X_I2C_IRQSTATUS_RRDY |
257                              AM335X_I2C_IRQSTATUS_XDR |
258                              AM335X_I2C_IRQSTATUS_XRDY;
259    return true;
260  }
261
262  if (bus->buffer->flags & I2C_M_RD) {
263    if (status & AM335X_I2C_IRQSTATUS_RDR) {
264      debug_print("RDR\n");
265      /* last data received */
266      amount = bus->buffer->len - bus->buffer_pos;
267    } else if (status & AM335X_I2C_IRQSTATUS_RRDY) {
268      debug_print("RRDY\n");
269      /* FIFO threshold reached */
270      amount = min(FIFO_THRESHOLD, bus->buffer->len - bus->buffer_pos);
271    }
272
273    debug_print("Read %d bytes\n", amount);
274    for (i = 0; i < amount; i++) {
275      bus->buffer->buf[bus->buffer_pos] = (uint8_t)(regs->BBB_I2C_DATA);
276      ++bus->buffer_pos;
277    }
278
279    if (status & AM335X_I2C_IRQSTATUS_RDR) {
280      regs->BBB_I2C_IRQSTATUS =AM335X_I2C_IRQSTATUS_RDR;
281    }
282    if (status & AM335X_I2C_IRQSTATUS_RRDY) {
283      regs->BBB_I2C_IRQSTATUS =AM335X_I2C_IRQSTATUS_RRDY;
284    }
285  } else {
286    if (status & AM335X_I2C_IRQSTATUS_XDR) {
287      debug_print("XDR\n");
288      /* Remaining TX data won't reach the FIFO threshold. */
289      amount = bus->buffer->len - bus->buffer_pos;
290    } else if (status & AM335X_I2C_IRQSTATUS_XRDY) {
291      debug_print("XRDY\n");
292      /* FIFO threshold reached */
293      amount = min(FIFO_THRESHOLD, bus->buffer->len - bus->buffer_pos);
294    }
295
296    debug_print("Write %d bytes\n", amount);
297    for (i = 0; i < amount; i++) {
298      regs->BBB_I2C_DATA = bus->buffer->buf[bus->buffer_pos];
299      ++bus->buffer_pos;
300    }
301
302    if (status & AM335X_I2C_IRQSTATUS_XDR) {
303      regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_XDR;
304    }
305    if (status & AM335X_I2C_IRQSTATUS_XRDY) {
306      regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_XRDY;
307    }
308  }
309
310  return false;
311}
312
313static void am335x_i2c_interrupt( void *arg )
314{
315  bbb_i2c_bus *bus = arg;
316  volatile bbb_i2c_regs *regs = bus->regs;
317  uint32_t status;
318
319  status = regs->BBB_I2C_IRQSTATUS;
320
321  debug_print("interrupt: %08x\n", status);
322
323  if (status == 0) {
324    /* Why can this even happen? */
325    return;
326  }
327
328  if (bus->buffer == NULL) {
329    debug_print("Buffer is NULL\n");
330    bus->error = EINVAL;
331  }
332
333  if (bus->buffer == NULL || am335x_i2c_transfer_intr(bus, status)) {
334    rtems_status_code sc;
335    sc = rtems_event_transient_send( bus->task_id );
336    _Assert( sc == RTEMS_SUCCESSFUL );
337    (void) sc; /* suppress warning in case of no assert */
338  }
339}
340
341static int am335x_i2c_transfer(
342  i2c_bus *base,
343  i2c_msg *msgs,
344  uint32_t nmsgs
345)
346{
347  size_t i;
348  int err = 0;
349  bool repstart = false;
350  int timeout = 0;
351  bbb_i2c_bus *bus = (bbb_i2c_bus *) base;
352  volatile bbb_i2c_regs *regs = bus->regs;
353  uint32_t reg;
354  rtems_status_code sc;
355
356  bus->task_id = rtems_task_self();
357
358  for (i = 0; i < nmsgs; i++) {
359    bus->buffer = &msgs[i];
360    bus->buffer_pos = 0;
361    bus->error = 0;
362
363    debug_print("processing %2d/%d: addr: 0x%04x, flags: 0x%04x, len: %d, buf: %p\n",
364        i, nmsgs, msgs[i].addr, msgs[i].flags, msgs[i].len, msgs[i].buf);
365
366    if (bus->buffer == NULL || bus->buffer->buf == NULL ||
367        bus->buffer->len == 0) {
368      err = EINVAL;
369      break;
370    }
371
372    /*
373     * Send START when bus is busy on repeated starts.
374     * Otherwise wait some time.
375     */
376    if (!repstart) {
377      timeout = 0;
378      while ((regs->BBB_I2C_IRQSTATUS_RAW & AM335X_I2C_IRQSTATUS_BB) != 0
379              && timeout <= TRANSFER_TIMEOUT_COUNT) {
380        ++timeout;
381        rtems_task_wake_after(RTEMS_MICROSECONDS_TO_TICKS(1000));
382      }
383      if (timeout > TRANSFER_TIMEOUT_COUNT) {
384        err = EBUSY;
385        break;
386      }
387      timeout = 0;
388    } else {
389      repstart = false;
390    }
391
392    if ((bus->buffer->flags & I2C_M_STOP) == 0) {
393      repstart = true;
394    }
395
396    regs->BBB_I2C_SA = bus->buffer->addr;
397    regs->BBB_I2C_CNT = bus->buffer->len;
398
399    regs->BBB_I2C_BUF |= AM335X_I2C_BUF_RXFIFO_CLR | AM335X_I2C_BUF_TXFIFO_CLR;
400
401    reg = bus->con_reg | AM335X_I2C_CON_START;
402    if (!repstart) {
403      reg |= AM335X_I2C_CON_STOP;
404    }
405    if ((bus->buffer->flags & I2C_M_RD) == 0) {
406      reg |= AM335X_I2C_CON_TRX;
407    }
408    /* Implicit stop on last message. */
409    if (i == nmsgs - 1) {
410      reg |= AM335X_I2C_CON_STOP;
411    }
412    regs->BBB_I2C_CON = reg;
413
414    sc = rtems_event_transient_receive( RTEMS_WAIT, bus->base.timeout );
415    if ( sc != RTEMS_SUCCESSFUL ) {
416      rtems_event_transient_clear();
417      err = ETIMEDOUT;
418      break;
419    }
420    if (bus->error) {
421      err = bus->error;
422      break;
423    }
424  }
425
426  if (timeout == 0) {
427    while ((regs->BBB_I2C_IRQSTATUS_RAW & AM335X_I2C_IRQSTATUS_BB) != 0
428            && timeout <= TRANSFER_TIMEOUT_COUNT) {
429      ++timeout;
430      rtems_task_wake_after(RTEMS_MICROSECONDS_TO_TICKS(1000));
431    }
432  }
433
434  if ((regs->BBB_I2C_CON & AM335X_I2C_CON_MST) == 0) {
435    regs->BBB_I2C_CON = bus->con_reg;
436  }
437
438  bus->buffer = NULL;
439
440  return -err;
441}
442
443static void am335x_i2c_destroy( i2c_bus *base )
444{
445  bbb_i2c_bus      *bus = (bbb_i2c_bus *) base;
446  rtems_status_code sc;
447
448  bus->regs->BBB_I2C_IRQENABLE_CLR = 0xFFFF;
449  bus->regs->BBB_I2C_CON = 0;
450  sc = rtems_interrupt_handler_remove( bus->irq, am335x_i2c_interrupt, bus );
451  _Assert( sc == RTEMS_SUCCESSFUL );
452  (void) sc;
453  i2c_bus_destroy_and_free( &bus->base );
454}
455
456int am335x_i2c_bus_register(
457  const char         *bus_path,
458  uintptr_t           register_base,
459  uint32_t            input_clock,
460  rtems_vector_number irq
461)
462{
463  bbb_i2c_bus      *bus;
464  rtems_status_code sc;
465  int               err;
466
467  (void) input_clock; /* FIXME: Unused. Left for compatibility. */
468
469  bus = (bbb_i2c_bus *) i2c_bus_alloc_and_init( sizeof( *bus ) );
470
471  if ( bus == NULL ) {
472    return -1;
473  }
474
475  bus->irq = irq;
476
477  err = am335x_i2c_fill_registers(bus, register_base);
478  if (err != 0) {
479    ( *bus->base.destroy )( &bus->base );
480    rtems_set_errno_and_return_minus_one( err );
481  }
482  am335x_i2c_module_clk_enable(bus);
483  am335x_i2c_pinmux( bus );
484  err = am335x_i2c_reset( bus );
485  if (err != 0) {
486    ( *bus->base.destroy )( &bus->base );
487    rtems_set_errno_and_return_minus_one( err );
488  }
489
490  sc = rtems_interrupt_handler_install(
491    bus->irq,
492    "BBB_I2C",
493    RTEMS_INTERRUPT_UNIQUE,
494    (rtems_interrupt_handler) am335x_i2c_interrupt,
495    bus
496  );
497
498  if ( sc != RTEMS_SUCCESSFUL ) {
499    ( *bus->base.destroy )( &bus->base );
500    rtems_set_errno_and_return_minus_one( EIO );
501  }
502
503  bus->base.transfer = am335x_i2c_transfer;
504  bus->base.set_clock = am335x_i2c_set_clock;
505  bus->base.destroy = am335x_i2c_destroy;
506
507  return i2c_bus_register( &bus->base, bus_path );
508}
Note: See TracBrowser for help on using the repository browser.