[PATCH] RaspberryPi: Added I2C and SPI bus support.

Andre Marques andre.lousa.marques at gmail.com
Sat Sep 5 20:53:16 UTC 2015


Further documentation can be found in

https://devel.rtems.org/wiki/GSoC/2015/RaspberryPi_peripherals_and_SD_card

and test data (including sample user applications, device drivers and wiring schemes) can be found in

https://github.com/asuol/RTEMS_rpi_testing

---
 c/src/lib/libbsp/arm/raspberrypi/Makefile.am       |   6 +
 c/src/lib/libbsp/arm/raspberrypi/configure.ac      |   8 +-
 c/src/lib/libbsp/arm/raspberrypi/i2c/i2c.c         | 421 +++++++++++++
 c/src/lib/libbsp/arm/raspberrypi/include/i2c.h     |  95 +++
 .../libbsp/arm/raspberrypi/include/raspberrypi.h   | 199 ++++---
 c/src/lib/libbsp/arm/raspberrypi/include/spi.h     |  77 +++
 c/src/lib/libbsp/arm/raspberrypi/preinstall.am     |   8 +
 c/src/lib/libbsp/arm/raspberrypi/spi/spi.c         | 657 +++++++++++++++++++++
 8 files changed, 1377 insertions(+), 94 deletions(-)
 create mode 100644 c/src/lib/libbsp/arm/raspberrypi/i2c/i2c.c
 create mode 100644 c/src/lib/libbsp/arm/raspberrypi/include/i2c.h
 create mode 100644 c/src/lib/libbsp/arm/raspberrypi/include/spi.h
 create mode 100644 c/src/lib/libbsp/arm/raspberrypi/spi/spi.c

diff --git a/c/src/lib/libbsp/arm/raspberrypi/Makefile.am b/c/src/lib/libbsp/arm/raspberrypi/Makefile.am
index 03be711..dffe98c 100644
--- a/c/src/lib/libbsp/arm/raspberrypi/Makefile.am
+++ b/c/src/lib/libbsp/arm/raspberrypi/Makefile.am
@@ -45,6 +45,8 @@ include_bsp_HEADERS += include/mmu.h
 include_bsp_HEADERS += include/usart.h
 include_bsp_HEADERS += include/raspberrypi.h
 include_bsp_HEADERS += include/rpi-gpio.h
+include_bsp_HEADERS += include/i2c.h
+include_bsp_HEADERS += include/spi.h
 
 include_libcpu_HEADERS = ../../../libcpu/arm/shared/include/cache_.h \
     ../../../libcpu/arm/shared/include/arm-cp15.h
@@ -127,6 +129,10 @@ libbsp_a_SOURCES += gpio/rpi-gpio.c
 # SSP
 
 # I2C
+libbsp_a_SOURCES += i2c/i2c.c
+
+# SPI
+libbsp_a_SOURCES += spi/spi.c
 
 # Cache
 libbsp_a_SOURCES += ../../../libcpu/shared/src/cache_manager.c
diff --git a/c/src/lib/libbsp/arm/raspberrypi/configure.ac b/c/src/lib/libbsp/arm/raspberrypi/configure.ac
index 27abe6c..2676bef 100644
--- a/c/src/lib/libbsp/arm/raspberrypi/configure.ac
+++ b/c/src/lib/libbsp/arm/raspberrypi/configure.ac
@@ -24,14 +24,18 @@ AM_CONDITIONAL(HAS_NETWORKING,test "$HAS_NETWORKING" = "yes")
 RTEMS_BSPOPTS_SET([BSP_START_RESET_VECTOR],[*],[])
 RTEMS_BSPOPTS_HELP([BSP_START_RESET_VECTOR],[reset vector address for BSP start])
 
+RTEMS_BSPOPTS_SET([I2C_IO_MODE],[*],[1])
+RTEMS_BSPOPTS_HELP([I2C_IO_MODE],[Define to 1 to use interrupt-driven I/O with the Raspberry Pi I2C bus. If defined to other value the access will be polled-driven.])
+
+RTEMS_BSPOPTS_SET([SPI_IO_MODE],[*],[1])
+RTEMS_BSPOPTS_HELP([SPI_IO_MODE],[Define to 1 to use interrupt-driven I/O with the Raspberry Pi SPI bus. If defined to other value the access will be polled-driven.])
+
 # Is this a Raspberry Pi 2?
 RTEMS_BSPOPTS_SET([BSP_IS_RPI2],[raspberrypi2],[1])
 RTEMS_BSPOPTS_SET([BSP_IS_RPI2],[*],[0])
 RTEMS_BSPOPTS_HELP([BSP_IS_RPI2],[Set if the BSP variant is Raspberry Pi 2.])
 AM_CONDITIONAL(RTEMS_RPI2,[test "$BSP_IS_RPI2" = "1"])
 
-
-
 RTEMS_BSP_CLEANUP_OPTIONS(0, 0)
 RTEMS_BSP_LINKCMDS
 
diff --git a/c/src/lib/libbsp/arm/raspberrypi/i2c/i2c.c b/c/src/lib/libbsp/arm/raspberrypi/i2c/i2c.c
new file mode 100644
index 0000000..14a2467
--- /dev/null
+++ b/c/src/lib/libbsp/arm/raspberrypi/i2c/i2c.c
@@ -0,0 +1,421 @@
+/**
+ * @file i2c.c
+ *
+ * @ingroup raspberrypi_i2c
+ *
+ * @brief Support for the I2C bus on the Raspberry Pi GPIO P1 header (model A/B)
+ *        and GPIO J8 header on model B+.
+ */
+
+/*
+ *  Copyright (c) 2014-2015 Andre Marques <andre.lousa.marques at gmail.com>
+ *
+ *  The license and distribution terms for this file may be
+ *  found in the file LICENSE in this distribution or at
+ *  http://www.rtems.org/license/LICENSE.
+ */
+
+/*
+ * STATUS:
+ * - 10-bit slave addressing untested
+ */
+
+#include <bsp.h>
+#include <bsp/raspberrypi.h>
+#include <bsp/gpio.h>
+#include <bsp/rpi-gpio.h>
+#include <bsp/irq.h>
+#include <bsp/i2c.h>
+#include <assert.h>
+
+#define TRANSFER_COUNT(buffer_size) (buffer_size + 0xFFFE) / 0xFFFF
+
+#define ADJUST_TRANSFER_SIZE(transfer_count, remaining_bytes) \
+  transfer_count > 1 ? 0xFFFF : (remaining_bytes & 0xFFFF)
+
+#define I2C_POLLING(condition)                  \
+  while ( condition ) {                         \
+    ;                                           \
+  }
+
+/**
+ * @brief Object containing relevant information about an I2C bus.
+ *
+ * Encapsulates relevant data for a I2C bus transfer.
+ */
+typedef struct
+{
+  i2c_bus base;
+  uint32_t input_clock;
+  rtems_id task_id;
+
+  /* Remaining bytes to read/write on the current bus transfer. */
+  uint32_t remaining_bytes;
+  /* Each transfer has a limit of 0xFFFF bytes, hence larger transfers
+   * have to be divided. Each transfer implies a stop condition, signaled
+   * automatically by the BSC controller. */
+  uint32_t remaining_transfers;
+
+  uint8_t *current_buffer;
+  uint32_t current_buffer_size;
+
+  bool read_transfer;
+} rpi_i2c_bus;
+
+static int rpi_i2c_bus_transfer(rpi_i2c_bus *bus)
+{
+  while ( bus->remaining_bytes >= 1 ) {
+    /* If reading. */
+    if ( bus->read_transfer ) {
+      /* Poll RXD bit until there is data on the RX FIFO to read. */
+      I2C_POLLING((BCM2835_REG(BCM2835_I2C_S) & (1 << 5)) == 0);
+
+      /* Read data from the RX FIFO. */
+      (*(uint8_t *) bus->current_buffer) = BCM2835_REG(BCM2835_I2C_FIFO) & 0xFF;
+
+      ++bus->current_buffer;
+
+      /* Check for acknowledgment or clock stretching errors. */
+      if (
+          (BCM2835_REG(BCM2835_I2C_S) & (1 << 8)) ||
+          (BCM2835_REG(BCM2835_I2C_S) & (1 << 9))
+      ) {
+        return -EIO;
+      }
+    }
+    /* If writing. */
+    else {
+      /* If using the I2C bus in interrupt-driven mode. */
+#if I2C_IO_MODE == 1
+      /* Generate interrupts on the TXW bit condition. */
+      BCM2835_REG(BCM2835_I2C_C) |= (1 << 9);
+
+      /* Sleep until the TX FIFO has free space for a new write. */
+      if (
+          rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout) !=
+          RTEMS_SUCCESSFUL
+      ) {
+        rtems_event_transient_clear();
+
+        return -ETIMEDOUT;
+      }
+
+      /* If using the bus in polling mode. */
+#else
+      /* Poll TXW bit until there is space available to write. */
+      I2C_POLLING((BCM2835_REG(BCM2835_I2C_S) & (1 << 2)) == 0);
+#endif
+
+      /* Write data to the TX FIFO. */
+      BCM2835_REG(BCM2835_I2C_FIFO) = (*(uint8_t *) bus->current_buffer);
+
+      ++bus->current_buffer;
+
+      /* Check for acknowledgment or clock stretching errors. */
+      if (
+          (BCM2835_REG(BCM2835_I2C_S) & (1 << 8)) ||
+          (BCM2835_REG(BCM2835_I2C_S) & (1 << 9))
+      ) {
+        return -EIO;
+      }
+    }
+
+    --bus->remaining_bytes;
+    --bus->current_buffer_size;
+  }
+
+  return 0;
+}
+
+static int rpi_i2c_setup_transfer(rpi_i2c_bus *bus)
+{
+  int rv;
+
+  while ( bus->remaining_transfers > 0 ) {
+    /* Setup the byte size of the current transfer. */
+    bus->remaining_bytes = ADJUST_TRANSFER_SIZE(
+                             bus->remaining_transfers,
+                             bus->current_buffer_size
+                           );
+
+    /* Set the DLEN register, which specifies how many data packets
+     * will be transferred. */
+    BCM2835_REG(BCM2835_I2C_DLEN) = bus->remaining_bytes;
+
+    /* Clear the acknowledgment and clock stretching error status. */
+    BCM2835_REG(BCM2835_I2C_S) |= (3 << 8);
+
+    /* Send start bit. */
+    BCM2835_REG(BCM2835_I2C_C) |= (1 << 7);
+
+    /* Check for an acknowledgment error. */
+    if ( (BCM2835_REG(BCM2835_I2C_S) & (1 << 8)) != 0 ) {
+      return -EIO;
+    }
+
+    rv = rpi_i2c_bus_transfer(bus);
+
+    if ( rv < 0 ) {
+      return rv;
+    }
+
+    /* Wait for the current transfer to finish. */
+
+    /* If using the I2C bus in interrupt-driven mode. */
+#if I2C_IO_MODE == 1
+    /* Generate interrupts on the DONE bit condition. */
+    BCM2835_REG(BCM2835_I2C_C) |= (1 << 8);
+
+    if (
+        rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout) !=
+        RTEMS_SUCCESSFUL
+    ) {
+      rtems_event_transient_clear();
+
+      return -ETIMEDOUT;
+    }
+    /* If using the bus in polling mode. */
+#else
+    /* Poll DONE bit until all data has been sent. */
+    I2C_POLLING((BCM2835_REG(BCM2835_I2C_S) & (1 << 1)) == 0);
+#endif
+
+    --bus->remaining_transfers;
+  }
+
+  return 0;
+}
+
+/* Handler function that is called on any I2C interrupt.
+ *
+ * There are 3 situations that can generate an interrupt:
+ *
+ * 1. Transfer (read/write) complete;
+ * 2. The TX FIFO has space for more data (during a write transfer);
+ * 3. The RX FIFO is full.
+ *
+ * Because the I2C FIFO has a 16 byte size, the 3. situation is not
+ * as useful to many applications as knowing that at least 1 byte can
+ * be read from the RX FIFO. For that reason this information is
+ * got through polling the RXD bit even in interrupt-driven mode.
+ *
+ * This leaves only 2 interrupts to be caught. At any given time
+ * when no I2C bus transfer is taking place no I2C interrupts are
+ * generated, and they do they are only enabled one at a time:
+ *
+ * - When trying to write, the 2. interrupt is enabled to signal that
+ *   data can be written on the TX FIFO, avoiding data loss in case
+ *   it is full. When caught the handler disables that interrupt from
+ *   being generated and sends a waking event to the transfer task,
+ *   which will allow the transfer process to continue
+ *   (by writing to the TX FIFO);
+ *
+ * - When the transfer is done on the Raspberry side, the 1. interrupt is
+ *   enabled for the device to signal it has finished the transfer as
+ *   well. When caught the handler disables that interrupt from being
+ *   generated and sends a waking event to the transfer task, marking
+ *   the end of the transfer.
+ */
+#if I2C_IO_MODE == 1
+static void i2c_handler(void *arg)
+{
+  rpi_i2c_bus *bus = (rpi_i2c_bus *) arg;
+
+  /* If the current enabled interrupt is on the TXW condition, disable it. */
+  if ( (BCM2835_REG(BCM2835_I2C_C) & (1 << 9)) ) {
+    BCM2835_REG(BCM2835_I2C_C) &= ~(1 << 9);
+  }
+  /* If the current enabled interrupt is on the DONE condition, disable it. */
+  else if ( (BCM2835_REG(BCM2835_I2C_C) & (1 << 8)) ) {
+    BCM2835_REG(BCM2835_I2C_C) &= ~(1 << 8);
+  }
+
+  /* Allow the transfer process to continue. */
+  rtems_event_transient_send(bus->task_id);
+}
+#endif
+
+static int rpi_i2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t msg_count)
+{
+  rpi_i2c_bus *bus = (rpi_i2c_bus *) base;
+  uint32_t rv = 0;
+  uint32_t i;
+
+  /* Perform an initial parse through the messages for the I2C_M_RECV_LEN flag,
+   * which the Pi seems to not support and the I2C framework expects the bus
+   * to provide as part of the I2C_FUNC_I2C functionality.
+   *
+   * It states that the slave device sends an initial byte containing the size
+   * of the transfer, and for this to work the Pi will likely require two
+   * transfers, with a stop-start condition in-between. */
+  for ( i = 0; i < msg_count; ++i ) {
+    if ( msgs[i].flags & I2C_M_RECV_LEN ) {
+      return -EINVAL;
+    }
+  }
+
+  for ( i = 0; i < msg_count; ++i ) {
+    /* Clear FIFOs. */
+    BCM2835_REG(BCM2835_I2C_C) |= (3 << 4);
+
+    /* Setup transfer. */
+    bus->current_buffer = msgs[i].buf;
+    bus->current_buffer_size = msgs[i].len;
+    bus->remaining_transfers = TRANSFER_COUNT(bus->current_buffer_size);
+
+    /* If the slave uses 10-bit addressing. */
+    if ( msgs[i].flags & I2C_M_TEN ) {
+      /* Write the 8 least-significative bits of the slave address
+       * to the bus FIFO. */
+      BCM2835_REG(BCM2835_I2C_FIFO) = msgs[i].addr & 0xFF;
+
+      /* Address slave device, with the 2 most-significative bits at the end. */
+      BCM2835_REG(BCM2835_I2C_A) = (0x1E << 2) | (msgs[i].addr >> 8);
+    }
+    /* If using the regular 7-bit slave addressing. */
+    else {
+      /* Address slave device. */
+      BCM2835_REG(BCM2835_I2C_A) = msgs[i].addr;
+    }
+
+    if ( msgs[i].flags & I2C_M_RD ) {
+      /* If the slave uses 10-bit addressing. */
+      if ( msgs[i].flags & I2C_M_TEN ) {
+        /* 10-bit addressing setup for a read transfer. */
+        BCM2835_REG(BCM2835_I2C_DLEN) = 1;
+
+        /* Set write bit. */
+        BCM2835_REG(BCM2835_I2C_C) &= ~(1 << 0);
+
+        /* Send start bit. */
+        BCM2835_REG(BCM2835_I2C_C) |= (1 << 7);
+
+        /* Poll the TA bit until the transfer has started. */
+        I2C_POLLING((BCM2835_REG(BCM2835_I2C_S) & (1 << 0)) == 0);
+      }
+
+      /* Set read bit. */
+      BCM2835_REG(BCM2835_I2C_C) |= (1 << 0);
+
+      bus->read_transfer = true;
+    }
+    else if ( msgs[i].flags == 0 || msgs[i].flags == I2C_M_TEN ) {
+      /* If the slave uses 10-bit addressing. */
+      if ( msgs[i].flags & I2C_M_TEN ) {
+        /* 10-bit addressing setup for a write transfer. */
+        bus->current_buffer_size += 1;
+
+        bus->remaining_transfers = TRANSFER_COUNT(bus->current_buffer_size);
+      }
+
+      /* Set write bit. */
+      BCM2835_REG(BCM2835_I2C_C) &= ~(1 << 0);
+
+      bus->read_transfer = false;
+    }
+
+    rv = rpi_i2c_setup_transfer(bus);
+
+    if ( rv < 0 ) {
+      return rv;
+    }
+  }
+
+  return rv;
+}
+
+/* Calculates a clock divider to be used with the BSC core clock rate
+ * to set a I2C clock rate the closest (<=) to a desired frequency. */
+static int rpi_i2c_set_clock(i2c_bus *base, unsigned long clock)
+{
+  rpi_i2c_bus *bus = (rpi_i2c_bus *) base;
+  uint32_t clock_rate;
+  uint16_t divider;
+
+  /* Calculates an initial clock divider. */
+  divider = BSC_CORE_CLK_HZ / clock;
+
+  clock_rate = BSC_CORE_CLK_HZ / divider;
+
+  /* If the resulting clock rate is greater than desired, try the next greater
+   * divider. */
+  while ( clock_rate > clock ) {
+    ++divider;
+
+    clock_rate = BSC_CORE_CLK_HZ / divider;
+  }
+
+  /* Set clock divider. */
+  BCM2835_REG(BCM2835_I2C_DIV) = divider;
+
+  bus->input_clock = clock_rate;
+
+  return 0;
+}
+
+static void rpi_i2c_destroy(i2c_bus *base)
+{
+  rpi_i2c_bus *bus = (rpi_i2c_bus *) base;
+
+  i2c_bus_destroy_and_free(&bus->base);
+}
+
+int rpi_i2c_register_bus(
+  const char *bus_path,
+  uint32_t bus_clock
+) {
+#if I2C_IO_MODE == 1
+  rtems_status_code sc;
+#endif
+  rpi_i2c_bus *bus;
+  int rv;
+
+  bus = (rpi_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus));
+
+  if ( bus == NULL ) {
+    return -1;
+  }
+
+  /* Enable the I2C BSC interface. */
+  BCM2835_REG(BCM2835_I2C_C) |= (1 << 15);
+
+  /* If the access to the bus is configured to be interrupt-driven. */
+#if I2C_IO_MODE == 1
+  bus->task_id = rtems_task_self();
+
+  sc = rtems_interrupt_handler_install(
+         BCM2835_IRQ_ID_I2C,
+         NULL,
+         RTEMS_INTERRUPT_UNIQUE,
+         (rtems_interrupt_handler) i2c_handler,
+         bus
+       );
+
+  if ( sc != RTEMS_SUCCESSFUL ) {
+    return -EIO;
+  }
+#endif
+
+  rv = rpi_i2c_set_clock(&bus->base, bus_clock);
+
+  if ( rv < 0 ) {
+    (*bus->base.destroy)(&bus->base);
+
+    return -1;
+  }
+
+  bus->base.transfer = rpi_i2c_transfer;
+  bus->base.set_clock = rpi_i2c_set_clock;
+  bus->base.destroy = rpi_i2c_destroy;
+  bus->base.functionality = I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR;
+
+  return i2c_bus_register(&bus->base, bus_path);
+}
+
+void rpi_i2c_init(void)
+{
+  /* Enable the I2C interface on the Raspberry Pi. */
+  rtems_gpio_initialize();
+
+  assert ( rpi_gpio_select_i2c() == RTEMS_SUCCESSFUL );
+}
diff --git a/c/src/lib/libbsp/arm/raspberrypi/include/i2c.h b/c/src/lib/libbsp/arm/raspberrypi/include/i2c.h
new file mode 100644
index 0000000..4a8dbbf
--- /dev/null
+++ b/c/src/lib/libbsp/arm/raspberrypi/include/i2c.h
@@ -0,0 +1,95 @@
+/**
+ * @file i2c.h
+ *
+ * @ingroup raspberrypi_i2c
+ *
+ * @brief Raspberry Pi specific I2C definitions.
+ */
+
+/*
+ *  Copyright (c) 2014-2015 Andre Marques <andre.lousa.marques at gmail.com>
+ *
+ *  The license and distribution terms for this file may be
+ *  found in the file LICENSE in this distribution or at
+ *  http://www.rtems.org/license/LICENSE.
+ */
+
+#ifndef LIBBSP_ARM_RASPBERRYPI_I2C_H
+#define LIBBSP_ARM_RASPBERRYPI_I2C_H
+
+#include <dev/i2c/i2c.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @name I2C constants.
+ *
+ * @{
+ */
+
+/**
+ * @brief BSC controller core clock rate in Hz.
+ *
+ * This is set to 150 MHz as per the BCM2835 datasheet.
+ */
+#define BSC_CORE_CLK_HZ 150000000
+
+/**
+ * @brief Default bus clock.
+ *
+ * This sets the bus with a 100 kHz clock speed.
+ */
+#define DEFAULT_BUS_CLOCK 100000
+
+/** @} */
+
+/**
+ * @name  I2C directives.
+ *
+ * @{
+ */
+
+/**
+ * @brief Setups the Raspberry Pi GPIO header to activate the BSC I2C bus.
+ */
+extern void rpi_i2c_init(void);
+
+/**
+ * @brief Registers the Raspberry Pi BSC I2C bus with the
+ *        Linux I2C User-Space API.
+ *
+ * @param[in] bus_path Path to the bus device file.
+ * @param[in] bus_clock Bus clock in Hz.
+ *
+ * @retval 0 Bus registered successfully.
+ * @retval <0 Could not register the bus. The return value is a negative
+ *            errno code.
+ */
+extern int rpi_i2c_register_bus(
+  const char *bus_path,
+  uint32_t bus_clock
+);
+
+/**
+ * @brief Setups the Raspberry Pi BSC I2C bus (located on the GPIO header)
+ *        on the "/dev/i2c" device file, using the default bus clock.
+ *
+ * @retval 0 Bus configured and registered successfully.
+ * @retval <0 See @see rpi_i2c_register_bus().
+ */
+static inline int rpi_setup_i2c_bus(void)
+{
+  rpi_i2c_init();
+
+  return rpi_i2c_register_bus("/dev/i2c", DEFAULT_BUS_CLOCK);
+}
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* LIBBSP_ARM_RASPBERRYPI_I2C_H */
diff --git a/c/src/lib/libbsp/arm/raspberrypi/include/raspberrypi.h b/c/src/lib/libbsp/arm/raspberrypi/include/raspberrypi.h
index ddcd4ff..2a4d772 100644
--- a/c/src/lib/libbsp/arm/raspberrypi/include/raspberrypi.h
+++ b/c/src/lib/libbsp/arm/raspberrypi/include/raspberrypi.h
@@ -7,7 +7,7 @@
  */
 
 /*
- *  Copyright (c) 2014 Andre Marques <andre.lousa.marques at gmail.com>
+ *  Copyright (c) 2014-2015 Andre Marques <andre.lousa.marques at gmail.com>
  *  Copyright (c) 2013 Alan Cudmore.
  *
  *  The license and distribution terms for this file may be
@@ -68,15 +68,15 @@
 
 #define BCM2835_TIMER_BASE       (RPI_PERIPHERAL_BASE + 0xB400)
 
-#define BCM2835_TIMER_LOD        (BCM2835_TIMER_BASE+0x00)
-#define BCM2835_TIMER_VAL        (BCM2835_TIMER_BASE+0x04)
-#define BCM2835_TIMER_CTL        (BCM2835_TIMER_BASE+0x08)
-#define BCM2835_TIMER_CLI        (BCM2835_TIMER_BASE+0x0C)
-#define BCM2835_TIMER_RIS        (BCM2835_TIMER_BASE+0x10)
-#define BCM2835_TIMER_MIS        (BCM2835_TIMER_BASE+0x14)
-#define BCM2835_TIMER_RLD        (BCM2835_TIMER_BASE+0x18)
-#define BCM2835_TIMER_DIV        (BCM2835_TIMER_BASE+0x1C)
-#define BCM2835_TIMER_CNT        (BCM2835_TIMER_BASE+0x20)
+#define BCM2835_TIMER_LOD        (BCM2835_TIMER_BASE + 0x00)
+#define BCM2835_TIMER_VAL        (BCM2835_TIMER_BASE + 0x04)
+#define BCM2835_TIMER_CTL        (BCM2835_TIMER_BASE + 0x08)
+#define BCM2835_TIMER_CLI        (BCM2835_TIMER_BASE + 0x0C)
+#define BCM2835_TIMER_RIS        (BCM2835_TIMER_BASE + 0x10)
+#define BCM2835_TIMER_MIS        (BCM2835_TIMER_BASE + 0x14)
+#define BCM2835_TIMER_RLD        (BCM2835_TIMER_BASE + 0x18)
+#define BCM2835_TIMER_DIV        (BCM2835_TIMER_BASE + 0x1C)
+#define BCM2835_TIMER_CNT        (BCM2835_TIMER_BASE + 0x20)
 
 #define BCM2835_TIMER_PRESCALE    0xF9
 
@@ -90,19 +90,19 @@
 
 #define BCM2835_GPIO_REGS_BASE   (RPI_PERIPHERAL_BASE + 0x200000)
 
-#define BCM2835_GPIO_GPFSEL1     (BCM2835_GPIO_REGS_BASE+0x04)
-#define BCM2835_GPIO_GPSET0      (BCM2835_GPIO_REGS_BASE+0x1C)
-#define BCM2835_GPIO_GPCLR0      (BCM2835_GPIO_REGS_BASE+0x28)
-#define BCM2835_GPIO_GPLEV0      (BCM2835_GPIO_REGS_BASE+0x34)
-#define BCM2835_GPIO_GPEDS0      (BCM2835_GPIO_REGS_BASE+0x40)
-#define BCM2835_GPIO_GPREN0      (BCM2835_GPIO_REGS_BASE+0x4C)
-#define BCM2835_GPIO_GPFEN0      (BCM2835_GPIO_REGS_BASE+0x58)
-#define BCM2835_GPIO_GPHEN0      (BCM2835_GPIO_REGS_BASE+0x64)
-#define BCM2835_GPIO_GPLEN0      (BCM2835_GPIO_REGS_BASE+0x70)
-#define BCM2835_GPIO_GPAREN0     (BCM2835_GPIO_REGS_BASE+0x7C)
-#define BCM2835_GPIO_GPAFEN0     (BCM2835_GPIO_REGS_BASE+0x88)
-#define BCM2835_GPIO_GPPUD       (BCM2835_GPIO_REGS_BASE+0x94)
-#define BCM2835_GPIO_GPPUDCLK0   (BCM2835_GPIO_REGS_BASE+0x98)  
+#define BCM2835_GPIO_GPFSEL1     (BCM2835_GPIO_REGS_BASE + 0x04)
+#define BCM2835_GPIO_GPSET0      (BCM2835_GPIO_REGS_BASE + 0x1C)
+#define BCM2835_GPIO_GPCLR0      (BCM2835_GPIO_REGS_BASE + 0x28)
+#define BCM2835_GPIO_GPLEV0      (BCM2835_GPIO_REGS_BASE + 0x34)
+#define BCM2835_GPIO_GPEDS0      (BCM2835_GPIO_REGS_BASE + 0x40)
+#define BCM2835_GPIO_GPREN0      (BCM2835_GPIO_REGS_BASE + 0x4C)
+#define BCM2835_GPIO_GPFEN0      (BCM2835_GPIO_REGS_BASE + 0x58)
+#define BCM2835_GPIO_GPHEN0      (BCM2835_GPIO_REGS_BASE + 0x64)
+#define BCM2835_GPIO_GPLEN0      (BCM2835_GPIO_REGS_BASE + 0x70)
+#define BCM2835_GPIO_GPAREN0     (BCM2835_GPIO_REGS_BASE + 0x7C)
+#define BCM2835_GPIO_GPAFEN0     (BCM2835_GPIO_REGS_BASE + 0x88)
+#define BCM2835_GPIO_GPPUD       (BCM2835_GPIO_REGS_BASE + 0x94)
+#define BCM2835_GPIO_GPPUDCLK0   (BCM2835_GPIO_REGS_BASE + 0x98)
 
 /** @} */
 
@@ -114,18 +114,18 @@
 
 #define BCM2835_AUX_BASE         (RPI_PERIPHERAL_BASE + 0x215000)
 
-#define AUX_ENABLES              (BCM2835_AUX_BASE+0x04)
-#define AUX_MU_IO_REG            (BCM2835_AUX_BASE+0x40)
-#define AUX_MU_IER_REG           (BCM2835_AUX_BASE+0x44)
-#define AUX_MU_IIR_REG           (BCM2835_AUX_BASE+0x48)
-#define AUX_MU_LCR_REG           (BCM2835_AUX_BASE+0x4C)
-#define AUX_MU_MCR_REG           (BCM2835_AUX_BASE+0x50)
-#define AUX_MU_LSR_REG           (BCM2835_AUX_BASE+0x54)
-#define AUX_MU_MSR_REG           (BCM2835_AUX_BASE+0x58)
-#define AUX_MU_SCRATCH           (BCM2835_AUX_BASE+0x5C)
-#define AUX_MU_CNTL_REG          (BCM2835_AUX_BASE+0x60)
-#define AUX_MU_STAT_REG          (BCM2835_AUX_BASE+0x64)
-#define AUX_MU_BAUD_REG          (BCM2835_AUX_BASE+0x68)
+#define AUX_ENABLES              (BCM2835_AUX_BASE + 0x04)
+#define AUX_MU_IO_REG            (BCM2835_AUX_BASE + 0x40)
+#define AUX_MU_IER_REG           (BCM2835_AUX_BASE + 0x44)
+#define AUX_MU_IIR_REG           (BCM2835_AUX_BASE + 0x48)
+#define AUX_MU_LCR_REG           (BCM2835_AUX_BASE + 0x4C)
+#define AUX_MU_MCR_REG           (BCM2835_AUX_BASE + 0x50)
+#define AUX_MU_LSR_REG           (BCM2835_AUX_BASE + 0x54)
+#define AUX_MU_MSR_REG           (BCM2835_AUX_BASE + 0x58)
+#define AUX_MU_SCRATCH           (BCM2835_AUX_BASE + 0x5C)
+#define AUX_MU_CNTL_REG          (BCM2835_AUX_BASE + 0x60)
+#define AUX_MU_STAT_REG          (BCM2835_AUX_BASE + 0x64)
+#define AUX_MU_BAUD_REG          (BCM2835_AUX_BASE + 0x68)
 
 /** @} */
 
@@ -137,24 +137,24 @@
 
 #define BCM2835_UART0_BASE       (RPI_PERIPHERAL_BASE + 0x201000)
 
-#define BCM2835_UART0_DR         (BCM2835_UART0_BASE+0x00)
-#define BCM2835_UART0_RSRECR     (BCM2835_UART0_BASE+0x04)
-#define BCM2835_UART0_FR         (BCM2835_UART0_BASE+0x18)
-#define BCM2835_UART0_ILPR       (BCM2835_UART0_BASE+0x20)
-#define BCM2835_UART0_IBRD       (BCM2835_UART0_BASE+0x24)
-#define BCM2835_UART0_FBRD       (BCM2835_UART0_BASE+0x28)
-#define BCM2835_UART0_LCRH       (BCM2835_UART0_BASE+0x2C)
-#define BCM2835_UART0_CR         (BCM2835_UART0_BASE+0x30)
-#define BCM2835_UART0_IFLS       (BCM2835_UART0_BASE+0x34)
-#define BCM2835_UART0_IMSC       (BCM2835_UART0_BASE+0x38)
-#define BCM2835_UART0_RIS        (BCM2835_UART0_BASE+0x3C)
-#define BCM2835_UART0_MIS        (BCM2835_UART0_BASE+0x40)
-#define BCM2835_UART0_ICR        (BCM2835_UART0_BASE+0x44)
-#define BCM2835_UART0_DMACR      (BCM2835_UART0_BASE+0x48)
-#define BCM2835_UART0_ITCR       (BCM2835_UART0_BASE+0x80)
-#define BCM2835_UART0_ITIP       (BCM2835_UART0_BASE+0x84)
-#define BCM2835_UART0_ITOP       (BCM2835_UART0_BASE+0x88)
-#define BCM2835_UART0_TDR        (BCM2835_UART0_BASE+0x8C)
+#define BCM2835_UART0_DR         (BCM2835_UART0_BASE + 0x00)
+#define BCM2835_UART0_RSRECR     (BCM2835_UART0_BASE + 0x04)
+#define BCM2835_UART0_FR         (BCM2835_UART0_BASE + 0x18)
+#define BCM2835_UART0_ILPR       (BCM2835_UART0_BASE + 0x20)
+#define BCM2835_UART0_IBRD       (BCM2835_UART0_BASE + 0x24)
+#define BCM2835_UART0_FBRD       (BCM2835_UART0_BASE + 0x28)
+#define BCM2835_UART0_LCRH       (BCM2835_UART0_BASE + 0x2C)
+#define BCM2835_UART0_CR         (BCM2835_UART0_BASE + 0x30)
+#define BCM2835_UART0_IFLS       (BCM2835_UART0_BASE + 0x34)
+#define BCM2835_UART0_IMSC       (BCM2835_UART0_BASE + 0x38)
+#define BCM2835_UART0_RIS        (BCM2835_UART0_BASE + 0x3C)
+#define BCM2835_UART0_MIS        (BCM2835_UART0_BASE + 0x40)
+#define BCM2835_UART0_ICR        (BCM2835_UART0_BASE + 0x44)
+#define BCM2835_UART0_DMACR      (BCM2835_UART0_BASE + 0x48)
+#define BCM2835_UART0_ITCR       (BCM2835_UART0_BASE + 0x80)
+#define BCM2835_UART0_ITIP       (BCM2835_UART0_BASE + 0x84)
+#define BCM2835_UART0_ITOP       (BCM2835_UART0_BASE + 0x88)
+#define BCM2835_UART0_TDR        (BCM2835_UART0_BASE + 0x8C)
 
 #define BCM2835_UART0_MIS_RX    0x10
 #define BCM2835_UART0_MIS_TX    0x20
@@ -173,16 +173,16 @@
  * @{
  */
 
-#define BCM2835_I2C_BASE           (0x20804000)
+#define BCM2835_I2C_BASE           (RPI_PERIPHERAL_BASE + 0x804000)
 
-#define BCM2835_I2C_C              (BCM2835_I2C_BASE+0x00)
-#define BCM2835_I2C_S              (BCM2835_I2C_BASE+0x04)
-#define BCM2835_I2C_DLEN           (BCM2835_I2C_BASE+0x08)
-#define BCM2835_I2C_A              (BCM2835_I2C_BASE+0x0C)
-#define BCM2835_I2C_FIFO           (BCM2835_I2C_BASE+0x10)
-#define BCM2835_I2C_DIV            (BCM2835_I2C_BASE+0x14)
-#define BCM2835_I2C_DEL            (BCM2835_I2C_BASE+0x18)
-#define BCM2835_I2C_CLKT           (BCM2835_I2C_BASE+0x1C)
+#define BCM2835_I2C_C              (BCM2835_I2C_BASE + 0x00)
+#define BCM2835_I2C_S              (BCM2835_I2C_BASE + 0x04)
+#define BCM2835_I2C_DLEN           (BCM2835_I2C_BASE + 0x08)
+#define BCM2835_I2C_A              (BCM2835_I2C_BASE + 0x0C)
+#define BCM2835_I2C_FIFO           (BCM2835_I2C_BASE + 0x10)
+#define BCM2835_I2C_DIV            (BCM2835_I2C_BASE + 0x14)
+#define BCM2835_I2C_DEL            (BCM2835_I2C_BASE + 0x18)
+#define BCM2835_I2C_CLKT           (BCM2835_I2C_BASE + 0x1C)
 
 /** @} */
 
@@ -192,14 +192,14 @@
  * @{
  */
 
-#define BCM2835_SPI_BASE           (0x20204000)
+#define BCM2835_SPI_BASE           (RPI_PERIPHERAL_BASE + 0x204000)
 
-#define BCM2835_SPI_CS             (BCM2835_SPI_BASE+0x00)
-#define BCM2835_SPI_FIFO           (BCM2835_SPI_BASE+0x04)
-#define BCM2835_SPI_CLK            (BCM2835_SPI_BASE+0x08)
-#define BCM2835_SPI_DLEN           (BCM2835_SPI_BASE+0x0C)
-#define BCM2835_SPI_LTOH           (BCM2835_SPI_BASE+0x10)
-#define BCM2835_SPI_DC             (BCM2835_SPI_BASE+0x14)
+#define BCM2835_SPI_CS             (BCM2835_SPI_BASE + 0x00)
+#define BCM2835_SPI_FIFO           (BCM2835_SPI_BASE + 0x04)
+#define BCM2835_SPI_CLK            (BCM2835_SPI_BASE + 0x08)
+#define BCM2835_SPI_DLEN           (BCM2835_SPI_BASE + 0x0C)
+#define BCM2835_SPI_LTOH           (BCM2835_SPI_BASE + 0x10)
+#define BCM2835_SPI_DC             (BCM2835_SPI_BASE + 0x14)
 
 /** @} */
 
@@ -209,22 +209,22 @@
  * @{
  */
 
-#define BCM2835_I2C_SPI_BASE       (0x20214000)
-
-#define BCM2835_I2C_SPI_DR         (BCM2835_I2C_SPI_BASE+0x00)
-#define BCM2835_I2C_SPI_RSR        (BCM2835_I2C_SPI_BASE+0x04)
-#define BCM2835_I2C_SPI_SLV        (BCM2835_I2C_SPI_BASE+0x08)
-#define BCM2835_I2C_SPI_CR         (BCM2835_I2C_SPI_BASE+0x0C)
-#define BCM2835_I2C_SPI_FR         (BCM2835_I2C_SPI_BASE+0x10)
-#define BCM2835_I2C_SPI_IFLS       (BCM2835_I2C_SPI_BASE+0x14)
-#define BCM2835_I2C_SPI_IMSC       (BCM2835_I2C_SPI_BASE+0x18)
-#define BCM2835_I2C_SPI_RIS        (BCM2835_I2C_SPI_BASE+0x1C)
-#define BCM2835_I2C_SPI_MIS        (BCM2835_I2C_SPI_BASE+0x20)
-#define BCM2835_I2C_SPI_ICR        (BCM2835_I2C_SPI_BASE+0x24)
-#define BCM2835_I2C_SPI_DMACR      (BCM2835_I2C_SPI_BASE+0x28)
-#define BCM2835_I2C_SPI_TDR        (BCM2835_I2C_SPI_BASE+0x2C)
-#define BCM2835_I2C_SPI_GPUSTAT    (BCM2835_I2C_SPI_BASE+0x30)
-#define BCM2835_I2C_SPI_HCTRL      (BCM2835_I2C_SPI_BASE+0x34)
+#define BCM2835_I2C_SPI_BASE       (RPI_PERIPHERAL_BASE + 0x214000)
+
+#define BCM2835_I2C_SPI_DR         (BCM2835_I2C_SPI_BASE + 0x00)
+#define BCM2835_I2C_SPI_RSR        (BCM2835_I2C_SPI_BASE + 0x04)
+#define BCM2835_I2C_SPI_SLV        (BCM2835_I2C_SPI_BASE + 0x08)
+#define BCM2835_I2C_SPI_CR         (BCM2835_I2C_SPI_BASE + 0x0C)
+#define BCM2835_I2C_SPI_FR         (BCM2835_I2C_SPI_BASE + 0x10)
+#define BCM2835_I2C_SPI_IFLS       (BCM2835_I2C_SPI_BASE + 0x14)
+#define BCM2835_I2C_SPI_IMSC       (BCM2835_I2C_SPI_BASE + 0x18)
+#define BCM2835_I2C_SPI_RIS        (BCM2835_I2C_SPI_BASE + 0x1C)
+#define BCM2835_I2C_SPI_MIS        (BCM2835_I2C_SPI_BASE + 0x20)
+#define BCM2835_I2C_SPI_ICR        (BCM2835_I2C_SPI_BASE + 0x24)
+#define BCM2835_I2C_SPI_DMACR      (BCM2835_I2C_SPI_BASE + 0x28)
+#define BCM2835_I2C_SPI_TDR        (BCM2835_I2C_SPI_BASE + 0x2C)
+#define BCM2835_I2C_SPI_GPUSTAT    (BCM2835_I2C_SPI_BASE + 0x30)
+#define BCM2835_I2C_SPI_HCTRL      (BCM2835_I2C_SPI_BASE + 0x34)
 
 /** @} */
 
@@ -262,13 +262,28 @@
  */
 #define BCM2835_GPU_TIMER_BASE    (RPI_PERIPHERAL_BASE + 0x3000)
 
-#define BCM2835_GPU_TIMER_CS      (BCM2835_TIMER_BASE+0x00)
-#define BCM2835_GPU_TIMER_CLO     (BCM2835_TIMER_BASE+0x04)
-#define BCM2835_GPU_TIMER_CHI     (BCM2835_TIMER_BASE+0x08)
-#define BCM2835_GPU_TIMER_C0      (BCM2835_TIMER_BASE+0x0C)
-#define BCM2835_GPU_TIMER_C1      (BCM2835_TIMER_BASE+0x10)
-#define BCM2835_GPU_TIMER_C2      (BCM2835_TIMER_BASE+0x14)
-#define BCM2835_GPU_TIMER_C3      (BCM2835_TIMER_BASE+0x18)
+#define BCM2835_GPU_TIMER_CS      (BCM2835_TIMER_BASE + 0x00)
+#define BCM2835_GPU_TIMER_CLO     (BCM2835_TIMER_BASE + 0x04)
+#define BCM2835_GPU_TIMER_CHI     (BCM2835_TIMER_BASE + 0x08)
+#define BCM2835_GPU_TIMER_C0      (BCM2835_TIMER_BASE + 0x0C)
+#define BCM2835_GPU_TIMER_C1      (BCM2835_TIMER_BASE + 0x10)
+#define BCM2835_GPU_TIMER_C2      (BCM2835_TIMER_BASE + 0x14)
+#define BCM2835_GPU_TIMER_C3      (BCM2835_TIMER_BASE + 0x18)
+
+/** @} */
+
+/**
+ * @name EMMC Registers
+ *
+ * @{
+ */
+
+/**
+ * NOTE: Since the SD controller follows the SDHCI standard,
+ *       the rtems-libbsd tree already provides the remaining registers.
+ */
+
+#define BCM2835_EMMC_BASE              (RPI_PERIPHERAL_BASE + 0x300000)
 
 /** @} */
 
diff --git a/c/src/lib/libbsp/arm/raspberrypi/include/spi.h b/c/src/lib/libbsp/arm/raspberrypi/include/spi.h
new file mode 100644
index 0000000..1bbbc6d
--- /dev/null
+++ b/c/src/lib/libbsp/arm/raspberrypi/include/spi.h
@@ -0,0 +1,77 @@
+/**
+ * @file spi.h
+ *
+ * @ingroup raspberrypi_spi
+ *
+ * @brief Raspberry Pi specific SPI definitions.
+ */
+
+/*
+ *  Copyright (c) 2014-2015 Andre Marques <andre.lousa.marques at gmail.com>
+ *
+ *  The license and distribution terms for this file may be
+ *  found in the file LICENSE in this distribution or at
+ *  http://www.rtems.org/license/LICENSE.
+ */
+
+#ifndef LIBBSP_ARM_RASPBERRYPI_SPI_H
+#define LIBBSP_ARM_RASPBERRYPI_SPI_H
+
+#include <rtems/libi2c.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @name SPI constants.
+ *
+ * @{
+ */
+
+/**
+ * @brief GPU processor core clock rate in Hz.
+ *
+ * Unless configured otherwise on a "config.txt" file present on the SD card
+ * the GPU defaults to 250 MHz. Currently only 250 MHz is supported.
+ */
+
+/* TODO: It would be nice if this value could be probed at startup, probably
+ *       using the Mailbox interface since the usual way of setting this on
+ *       the hardware is through a "config.txt" text file on the SD card.
+ *       Having this setup on the configure.ac script would require changing
+ *       the same setting on two different places. */
+#define GPU_CORE_CLOCK_RATE 250000000
+
+/** @} */
+
+/**
+ * @name SPI directives.
+ *
+ * @{
+ */
+
+/**
+ * @brief Setups the Raspberry Pi SPI bus (located on the GPIO header)
+ *        on the "/dev/spi" device file, and registers the bus on the
+ *        libi2c API.
+ *
+ * @param[in] bidirectional_mode If TRUE sets the SPI bus to use 2-wire SPI,
+ *                               where the MOSI data line doubles as the
+ *                               slave out (SO) and slave in (SI) data lines.
+ *                               If FALSE the bus defaults to the usual
+ *                               3-wire SPI, with 2 separate data lines
+ *                               (MOSI and MISO).
+ *
+ * @retval Returns libi2c bus number.
+ * @retval <0 Could not register the bus. See @see rtems_libi2c_register_bus().
+ */
+extern int rpi_spi_init(bool bidirectional_mode);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* LIBBSP_ARM_RASPBERRYPI_SPI_H */
diff --git a/c/src/lib/libbsp/arm/raspberrypi/preinstall.am b/c/src/lib/libbsp/arm/raspberrypi/preinstall.am
index 122bc00..cb6e46d 100644
--- a/c/src/lib/libbsp/arm/raspberrypi/preinstall.am
+++ b/c/src/lib/libbsp/arm/raspberrypi/preinstall.am
@@ -134,6 +134,14 @@ $(PROJECT_INCLUDE)/bsp/rpi-gpio.h: include/rpi-gpio.h $(PROJECT_INCLUDE)/bsp/$(d
 	$(INSTALL_DATA) $< $(PROJECT_INCLUDE)/bsp/rpi-gpio.h
 PREINSTALL_FILES += $(PROJECT_INCLUDE)/bsp/rpi-gpio.h
 
+$(PROJECT_INCLUDE)/bsp/i2c.h: include/i2c.h $(PROJECT_INCLUDE)/bsp/$(dirstamp)
+	$(INSTALL_DATA) $< $(PROJECT_INCLUDE)/bsp/i2c.h
+PREINSTALL_FILES += $(PROJECT_INCLUDE)/bsp/i2c.h
+
+$(PROJECT_INCLUDE)/bsp/spi.h: include/spi.h $(PROJECT_INCLUDE)/bsp/$(dirstamp)
+	$(INSTALL_DATA) $< $(PROJECT_INCLUDE)/bsp/spi.h
+PREINSTALL_FILES += $(PROJECT_INCLUDE)/bsp/spi.h
+
 $(PROJECT_INCLUDE)/libcpu/cache_.h: ../../../libcpu/arm/shared/include/cache_.h $(PROJECT_INCLUDE)/libcpu/$(dirstamp)
 	$(INSTALL_DATA) $< $(PROJECT_INCLUDE)/libcpu/cache_.h
 PREINSTALL_FILES += $(PROJECT_INCLUDE)/libcpu/cache_.h
diff --git a/c/src/lib/libbsp/arm/raspberrypi/spi/spi.c b/c/src/lib/libbsp/arm/raspberrypi/spi/spi.c
new file mode 100644
index 0000000..ae77f62
--- /dev/null
+++ b/c/src/lib/libbsp/arm/raspberrypi/spi/spi.c
@@ -0,0 +1,657 @@
+/**
+ * @file spi.c
+ *
+ * @ingroup raspberrypi_spi
+ *
+ * @brief Support for the SPI bus on the Raspberry Pi GPIO P1 header (model A/B)
+ *        and GPIO J8 header on model B+.
+ */
+
+/*
+ *  Copyright (c) 2014-2015 Andre Marques <andre.lousa.marques at gmail.com>
+ *
+ *  The license and distribution terms for this file may be
+ *  found in the file LICENSE in this distribution or at
+ *  http://www.rtems.org/license/LICENSE.
+ */
+
+/*
+ * STATUS:
+ * - Bi-directional mode untested
+ * - Write-only devices not supported
+ */
+
+#include <bsp.h>
+#include <bsp/raspberrypi.h>
+#include <bsp/gpio.h>
+#include <bsp/rpi-gpio.h>
+#include <bsp/irq.h>
+#include <bsp/spi.h>
+#include <assert.h>
+
+#define SPI_POLLING(condition)                  \
+  while ( condition ) {                         \
+    ;                                           \
+  }
+
+/**
+ * @brief Object containing the SPI bus configuration settings.
+ *
+ * Encapsulates the current SPI bus configuration.
+ */
+typedef struct
+{
+  int initialized;
+  uint8_t bytes_per_char;
+
+  /* Shift to be applied on data transfers with
+   * least significative bit first (LSB) devices. */
+  uint8_t bit_shift;
+  uint32_t dummy_char;
+  uint32_t current_slave_addr;
+  rtems_id task_id;
+  int irq_write;
+} rpi_spi_softc_t;
+
+/**
+ * @brief Object containing the SPI bus description.
+ *
+ * Encapsulates the current SPI bus description.
+ */
+typedef struct
+{
+  rtems_libi2c_bus_t bus_desc;
+  rpi_spi_softc_t softc;
+} rpi_spi_desc_t;
+
+/* If set to FALSE uses 3-wire SPI, with 2 separate data lines (MOSI and MISO),
+ * if set to TRUE uses 2-wire SPI, where the MOSI data line doubles as the
+ * slave out (SO) and slave in (SI) data lines. */
+static bool bidirectional = false;
+
+/* Calculates a clock divider to be used with the GPU core clock rate
+ * to set a SPI clock rate the closest (<=) to a desired frequency. */
+static rtems_status_code rpi_spi_calculate_clock_divider(
+  uint32_t clock_hz,
+  uint16_t *clock_divider
+) {
+  uint16_t divider;
+  uint32_t clock_rate;
+
+  assert( clock_hz > 0 );
+
+  /* Calculates an initial clock divider. */
+  divider = GPU_CORE_CLOCK_RATE / clock_hz;
+
+  /* Because the divider must be a power of two (as per the BCM2835 datasheet),
+   * calculate the next greater power of two. */
+  --divider;
+
+  divider |= (divider >> 1);
+  divider |= (divider >> 2);
+  divider |= (divider >> 4);
+  divider |= (divider >> 8);
+
+  ++divider;
+
+  clock_rate = GPU_CORE_CLOCK_RATE / divider;
+
+  /* If the resulting clock rate is greater than the desired frequency,
+   * try the next greater power of two divider. */
+  while ( clock_rate > clock_hz ) {
+    divider = (divider << 1);
+
+    clock_rate = GPU_CORE_CLOCK_RATE / divider;
+  }
+
+  *clock_divider = divider;
+
+  return RTEMS_SUCCESSFUL;
+}
+
+/**
+ * @brief Set the SPI bus transfer mode.
+ *
+ * @param[in] bushdl Pointer to the libi2c API bus driver data structure.
+ * @param[in] tfr_mode Pointer to a libi2c API transfer mode data structure.
+ *
+ * @retval RTEMS_SUCCESSFUL Successfully setup the bus transfer mode as desired.
+ * @retval RTEMS_INVALID_NUMBER This can have two meanings:
+ *                              1. The specified number of bytes per char is not
+ *                                 8, 16, 24 or 32;
+ *                              2. @see rpi_spi_calculate_clock_divider()
+ */
+static rtems_status_code rpi_spi_set_tfr_mode(
+  rtems_libi2c_bus_t *bushdl,
+  const rtems_libi2c_tfr_mode_t *tfr_mode
+) {
+  rpi_spi_softc_t *softc_ptr = &(((rpi_spi_desc_t *)(bushdl))->softc);
+  rtems_status_code sc = RTEMS_SUCCESSFUL;
+  uint16_t clock_divider;
+
+  /* Set the dummy character. */
+  softc_ptr->dummy_char = tfr_mode->idle_char;
+
+  /* Calculate the most appropriate clock divider. */
+  sc = rpi_spi_calculate_clock_divider(tfr_mode->baudrate, &clock_divider);
+
+  if ( sc != RTEMS_SUCCESSFUL ) {
+    return sc;
+  }
+
+  /* Set the bus clock divider. */
+  BCM2835_REG(BCM2835_SPI_CLK) = clock_divider;
+
+  /* Calculate how many bytes each character has.
+   * Only multiples of 8 bits are accepted for the transaction. */
+  switch ( tfr_mode->bits_per_char ) {
+    case 8:
+    case 16:
+    case 24:
+    case 32:
+      softc_ptr->bytes_per_char = tfr_mode->bits_per_char / 8;
+      break;
+
+    default:
+      return RTEMS_INVALID_NUMBER;
+  }
+
+  /* Check the data mode (most or least significant bit first) and calculate
+   * the correcting bit shift value to apply on the data before sending. */
+  if ( tfr_mode->lsb_first ) {
+    softc_ptr->bit_shift = 32 - tfr_mode->bits_per_char;
+  }
+  /* If MSB first. */
+  else {
+    softc_ptr->bit_shift = 0;
+  }
+
+  /* Set SPI clock polarity.
+   * If clock_inv is TRUE, the clock is active high.*/
+  if ( tfr_mode->clock_inv ) {
+    /* Rest state of clock is low. */
+    BCM2835_REG(BCM2835_SPI_CS) &= ~(1 << 3);
+  }
+  else {
+    /* Rest state of clock is high. */
+    BCM2835_REG(BCM2835_SPI_CS) |= (1 << 3);
+  }
+
+  /* Set SPI clock phase.
+   * If clock_phs is true, clock starts toggling
+   * at the start of the data transfer. */
+  if ( tfr_mode->clock_phs ) {
+    /* First SCLK transition at beginning of data bit. */
+    BCM2835_REG(BCM2835_SPI_CS) |= (1 << 2);
+  }
+  else {
+    /* First SCLK transition at middle of data bit. */
+    BCM2835_REG(BCM2835_SPI_CS) &= ~(1 << 2);
+  }
+
+  return sc;
+}
+
+/**
+ * @brief Reads/writes to/from the SPI bus.
+ *
+ * @param[in] bushdl Pointer to the libi2c API bus driver data structure.
+ * @param[in] rd_buf Read buffer. If not NULL the function will read from
+ *                   the bus and store the read on this buffer.
+ * @param[in] wr_buf Write buffer. If not NULL the function will write the
+ *                   contents of this buffer to the bus.
+ * @param[in] buffer_size Size of the non-NULL buffer.
+ *
+ * @retval -1 Could not send/receive data to/from the bus.
+ * @retval >=0 The number of bytes read/written.
+ */
+static int rpi_spi_read_write(
+  rtems_libi2c_bus_t * bushdl,
+  unsigned char *rd_buf,
+  const unsigned char *wr_buf,
+  int buffer_size
+) {
+  rpi_spi_softc_t *softc_ptr = &(((rpi_spi_desc_t *)(bushdl))->softc);
+
+  uint8_t bytes_per_char = softc_ptr->bytes_per_char;
+  uint8_t bit_shift =  softc_ptr->bit_shift;
+  uint32_t dummy_char = softc_ptr->dummy_char;
+
+  uint32_t bytes_sent = buffer_size;
+  uint32_t fifo_data;
+
+  /* Clear SPI bus FIFOs. */
+  BCM2835_REG(BCM2835_SPI_CS) |= (3 << 4);
+
+  /* Set SPI transfer active. */
+  BCM2835_REG(BCM2835_SPI_CS) |= (1 << 7);
+
+  /* If using the SPI bus in interrupt-driven mode. */
+#if SPI_IO_MODE == 1
+  softc_ptr->irq_write = 1;
+
+  BCM2835_REG(BCM2835_SPI_CS) |= (1 << 9);
+
+  if ( rtems_event_transient_receive(RTEMS_WAIT, 0) != RTEMS_SUCCESSFUL ) {
+    rtems_event_transient_clear();
+
+    return -1;
+  }
+
+  /* If using the bus in polling mode. */
+#else
+  /* Poll TXD bit until there is space to write at least one byte
+   * on the TX FIFO. */
+  SPI_POLLING((BCM2835_REG(BCM2835_SPI_CS) & (1 << 18)) == 0);
+#endif
+
+  /* While there is data to be transferred. */
+  while ( buffer_size >= bytes_per_char ) {
+    /* If reading from the bus, send a dummy character to the device. */
+    if ( rd_buf != NULL ) {
+      BCM2835_REG(BCM2835_SPI_FIFO) = dummy_char;
+    }
+    /* If writing to the bus, move the buffer data to the TX FIFO. */
+    else {
+      switch ( bytes_per_char ) {
+        case 1:
+          BCM2835_REG(BCM2835_SPI_FIFO) = (((*wr_buf) & 0xFF) << bit_shift);
+          break;
+
+        case 2:
+          BCM2835_REG(BCM2835_SPI_FIFO) = (((*wr_buf) & 0xFFFF) << bit_shift);
+          break;
+
+        case 3:
+          BCM2835_REG(BCM2835_SPI_FIFO) = (((*wr_buf) & 0xFFFFFF) << bit_shift);
+          break;
+
+        case 4:
+          BCM2835_REG(BCM2835_SPI_FIFO) = ((*wr_buf) << bit_shift);
+          break;
+
+        default:
+          return -1;
+      }
+
+      wr_buf += bytes_per_char;
+
+      buffer_size -= bytes_per_char;
+    }
+
+    /* If using bi-directional SPI. */
+    if ( bidirectional ) {
+      /* Change bus direction to read from the slave device. */
+      BCM2835_REG(BCM2835_SPI_CS) |= (1 << 12);
+    }
+
+    /* If using the SPI bus in interrupt-driven mode. */
+#if SPI_IO_MODE == 1
+    softc_ptr->irq_write = 0;
+
+    BCM2835_REG(BCM2835_SPI_CS) |= (1 << 9);
+
+    if ( rtems_event_transient_receive(RTEMS_WAIT, 0) != RTEMS_SUCCESSFUL ) {
+      rtems_event_transient_clear();
+
+      return -1;
+    }
+
+    /* If using the bus in polling mode. */
+#else
+    /* Poll the Done bit until the data transfer is complete. */
+    SPI_POLLING((BCM2835_REG(BCM2835_SPI_CS) & (1 << 16)) == 0);
+
+    /* Poll the RXD bit until there is at least one byte
+     * on the RX FIFO to be read. */
+    SPI_POLLING((BCM2835_REG(BCM2835_SPI_CS) & (1 << 17)) == 0);
+#endif
+
+    /* If writing to the bus, read the dummy char sent by the slave device. */
+    if ( rd_buf == NULL ) {
+      fifo_data = BCM2835_REG(BCM2835_SPI_FIFO) & 0xFF;
+    }
+
+    /* If reading from the bus, retrieve data from the RX FIFO and
+     * store it on the buffer. */
+    if ( rd_buf != NULL ) {
+      switch ( bytes_per_char ) {
+        case 1:
+          fifo_data = BCM2835_REG(BCM2835_SPI_FIFO) & 0xFF;
+          (*rd_buf) = (fifo_data >> bit_shift);
+          break;
+
+        case 2:
+          fifo_data = BCM2835_REG(BCM2835_SPI_FIFO) & 0xFFFF;
+          (*rd_buf) = (fifo_data >> bit_shift);
+          break;
+
+        case 3:
+          fifo_data = BCM2835_REG(BCM2835_SPI_FIFO) & 0xFFFFFF;
+          (*rd_buf) = (fifo_data >> bit_shift);
+          break;
+
+        case 4:
+          fifo_data = BCM2835_REG(BCM2835_SPI_FIFO);
+          (*rd_buf) = (fifo_data >> bit_shift);
+          break;
+
+        default:
+          return -1;
+      }
+
+      rd_buf += bytes_per_char;
+
+      buffer_size -= bytes_per_char;
+    }
+
+    /* If using bi-directional SPI. */
+    if ( bidirectional ) {
+      /* Restore bus direction to write to the slave. */
+      BCM2835_REG(BCM2835_SPI_CS) &= ~(1 << 12);
+    }
+  }
+
+  /* If using the SPI bus in interrupt-driven mode. */
+#if SPI_IO_MODE == 1
+  softc_ptr->irq_write = 1;
+
+  BCM2835_REG(BCM2835_SPI_CS) |= (1 << 9);
+
+  if ( rtems_event_transient_receive(RTEMS_WAIT, 0) != RTEMS_SUCCESSFUL ) {
+    rtems_event_transient_clear();
+
+    return -1;
+  }
+
+  /* If using the bus in polling mode. */
+#else
+  /* Poll the Done bit until the data transfer is complete. */
+  SPI_POLLING((BCM2835_REG(BCM2835_SPI_CS) & (1 << 16)) == 0);
+#endif
+
+  bytes_sent -= buffer_size;
+
+  return bytes_sent;
+}
+
+/**
+ * @brief Handler function that is called on any SPI interrupt.
+ *
+ *        There are 2 situations that can generate an interrupt:
+ *
+ *        1. Transfer (read/write) complete;
+ *        2. RX FIFO full.
+ *
+ *        Because the 2. situation is not useful to many applications,
+ *        the only interrupt that is generated and handled is the
+ *        transfer complete interrupt.
+ *
+ *        The objective of the handler is then, depending on the transfer
+ *        context (reading or writing on the bus), to check if there is enough
+ *        space available on the TX FIFO to send data over the bus (if writing)
+ *        or if the slave device has sent enough data to be fetched from the
+ *        RX FIFO (if reading).
+ *
+ *        When any of these two conditions occur, disables further interrupts
+ *        to be generated and sends a waking event to the transfer task
+ *        which will allow the following transfer to proceed.
+ *
+ * @param[in] arg Void pointer to the bus data structure.
+ */
+#if SPI_IO_MODE == 1
+static void spi_handler(void* arg)
+{
+  rpi_spi_softc_t *softc_ptr = (rpi_spi_softc_t *) arg;
+
+  /* If waiting to write to the bus, expect the TXD bit to be set, or
+   * if waiting to read from the bus, expect the RXD bit to be set
+   * before sending a waking event to the transfer task. */
+  if (
+      ( softc_ptr->irq_write == 1 &&
+        (BCM2835_REG(BCM2835_SPI_CS) & (1 << 18)) != 0
+      ) ||
+      ( softc_ptr->irq_write == 0 &&
+        (BCM2835_REG(BCM2835_SPI_CS) & (1 << 17)) != 0
+      )
+  ) {
+    /* Disable the SPI interrupt generation when a transfer is complete. */
+    BCM2835_REG(BCM2835_SPI_CS) &= ~(1 << 9);
+
+    /* Allow the transfer process to continue. */
+    rtems_event_transient_send(softc_ptr->task_id);
+  }
+}
+#endif
+
+/**
+ * @brief Low level function to initialize the SPI bus.
+ *        This function is used by the libi2c API.
+ *
+ * @param[in] bushdl Pointer to the libi2c API bus driver data structure.
+ *
+ * @retval RTEMS_SUCCESSFUL SPI bus successfully initialized.
+ * @retval Any other status code @see rtems_interrupt_handler_install().
+ */
+static rtems_status_code rpi_libi2c_spi_init(rtems_libi2c_bus_t * bushdl)
+{
+  rpi_spi_softc_t *softc_ptr = &(((rpi_spi_desc_t *)(bushdl))->softc);
+  rtems_status_code sc = RTEMS_SUCCESSFUL;
+
+  if ( softc_ptr->initialized == 1 ) {
+    return sc;
+  }
+
+  softc_ptr->initialized = 1;
+
+  /* If using the SPI bus in interrupt-driven mode. */
+#if SPI_IO_MODE == 1
+  softc_ptr->task_id = rtems_task_self();
+
+  sc = rtems_interrupt_handler_install(
+         BCM2835_IRQ_ID_SPI,
+         NULL,
+         RTEMS_INTERRUPT_UNIQUE,
+         (rtems_interrupt_handler) spi_handler,
+         softc_ptr
+       );
+#endif
+
+  return sc;
+}
+
+/**
+ * @brief Low level function that would send a start condition over an I2C bus.
+ *        As it is not required to access a SPI bus it is here just to satisfy
+ *        the libi2c API, which requires this function.
+ *
+ * @param[in] bushdl Pointer to the libi2c API bus driver data structure.
+ *
+ * @retval RTEMS_SUCCESSFUL
+ */
+static rtems_status_code rpi_libi2c_spi_send_start(rtems_libi2c_bus_t * bushdl)
+{
+  return RTEMS_SUCCESSFUL;
+}
+
+/**
+ * @brief Low level function that terminates a SPI transfer.
+ *        It stops the SPI transfer and unselects the current SPI slave device.
+ *        This function is used by the libi2c API.
+ *
+ * @param[in] bushdl Pointer to the libi2c API bus driver data structure.
+ *
+ * @retval RTEMS_SUCCESSFUL The slave device has been successfully unselected.
+ * @retval RTEMS_INVALID_ADDRESS The stored slave address is neither 0 or 1.
+ */
+static rtems_status_code rpi_libi2c_spi_stop(rtems_libi2c_bus_t * bushdl)
+{
+  rpi_spi_softc_t *softc_ptr = &(((rpi_spi_desc_t *)(bushdl))->softc);
+
+  uint32_t addr = softc_ptr->current_slave_addr;
+  uint32_t chip_select_bit = 21 + addr;
+
+  /* Set SPI transfer as not active. */
+  BCM2835_REG(BCM2835_SPI_CS) &= ~(1 << 7);
+
+  /* Unselect the active SPI slave. */
+  switch ( addr ) {
+    case 0:
+    case 1:
+      BCM2835_REG(BCM2835_SPI_CS) |= (1 << chip_select_bit);
+
+      break;
+
+    default:
+      return RTEMS_INVALID_ADDRESS;
+  }
+
+  return RTEMS_SUCCESSFUL;
+}
+
+/**
+ * @brief Low level function which addresses a SPI slave device.
+ *        This function is used by the libi2c API.
+ *
+ * @param[in] bushdl Pointer to the libi2c API bus driver data structure.
+ * @param[in] addr SPI slave select line address (0 for CE0 or 1 for CE1).
+ * @param[in] rw This values is unnecessary to address a SPI device and its
+ *               presence here is only to fulfill a libi2c requirement.
+ *
+ * @retval RTEMS_SUCCESSFUL The slave device has been successfully addressed.
+ * @retval RTEMS_INVALID_ADDRESS The received address is neither 0 or 1.
+ */
+static rtems_status_code rpi_libi2c_spi_send_addr(
+  rtems_libi2c_bus_t * bushdl,
+  uint32_t addr,
+  int rw
+) {
+  rpi_spi_softc_t *softc_ptr = &(((rpi_spi_desc_t *)(bushdl))->softc);
+
+  /* Calculates the bit corresponding to the received address
+   * on the SPI control register. */
+  uint32_t chip_select_bit = 21 + addr;
+
+  /* Save which slave will be currently addressed,
+   * so it can be unselected later. */
+  softc_ptr->current_slave_addr = addr;
+
+  /* Select one of the two available SPI slave address lines. */
+  switch ( addr ) {
+    case 0:
+    case 1:
+      BCM2835_REG(BCM2835_SPI_CS) &= ~(1 << chip_select_bit);
+      break;
+
+    default:
+      return RTEMS_INVALID_ADDRESS;
+  }
+
+  return RTEMS_SUCCESSFUL;
+}
+
+/**
+ * @brief Low level function that reads a number of bytes from the SPI bus
+ *        on to a buffer.
+ *        This function is used by the libi2c API.
+ *
+ * @param[in] bushdl Pointer to the libi2c API bus driver data structure.
+ * @param[in] bytes Buffer where the data read from the bus will be stored.
+ * @param[in] nbytes Number of bytes to be read from the bus to the bytes buffer.
+ *
+ * @retval @see rpi_spi_read_write().
+ */
+static int rpi_libi2c_spi_read_bytes(
+  rtems_libi2c_bus_t * bushdl,
+  unsigned char *bytes,
+  int nbytes
+) {
+  return rpi_spi_read_write(bushdl, bytes, NULL, nbytes);
+}
+
+/**
+ * @brief Low level function that writes a number of bytes from a buffer
+ *        to the SPI bus.
+ *        This function is used by the libi2c API.
+ *
+ * @param[in] bushdl Pointer to the libi2c API bus driver data structure.
+ * @param[in] bytes Buffer with data to send over the SPI bus.
+ * @param[in] nbytes Number of bytes to be written from the bytes buffer
+                     to the bus.
+ *
+ * @retval @see rpi_spi_read_write().
+ */
+static int rpi_libi2c_spi_write_bytes(
+  rtems_libi2c_bus_t * bushdl,
+  unsigned char *bytes,
+  int nbytes
+) {
+  return rpi_spi_read_write(bushdl, NULL, bytes, nbytes);
+}
+
+/**
+ * @brief Low level function that is used to perform ioctl
+ *        operations on the bus. Currently only setups
+ *        the bus transfer mode.
+ *        This function is used by the libi2c API.
+ *
+ * @param[in] bushdl Pointer to the libi2c API bus driver data structure.
+ * @param[in] cmd IOCTL request command.
+ * @param[in] arg Arguments needed to fulfill the requested IOCTL command.
+ *
+ * @retval -1 Unknown request command.
+ * @retval >=0 @see rpi_spi_set_tfr_mode().
+ */
+static int rpi_libi2c_spi_ioctl(rtems_libi2c_bus_t * bushdl, int cmd, void *arg)
+{
+  switch ( cmd ) {
+    case RTEMS_LIBI2C_IOCTL_SET_TFRMODE:
+      return rpi_spi_set_tfr_mode(
+               bushdl,
+               (const rtems_libi2c_tfr_mode_t *)arg
+             );
+    default:
+      return -1;
+  }
+
+  return 0;
+}
+
+static rtems_libi2c_bus_ops_t rpi_spi_ops = {
+  .init = rpi_libi2c_spi_init,
+  .send_start = rpi_libi2c_spi_send_start,
+  .send_stop = rpi_libi2c_spi_stop,
+  .send_addr = rpi_libi2c_spi_send_addr,
+  .read_bytes = rpi_libi2c_spi_read_bytes,
+  .write_bytes = rpi_libi2c_spi_write_bytes,
+  .ioctl = rpi_libi2c_spi_ioctl
+};
+
+static rpi_spi_desc_t rpi_spi_bus_desc = {
+  {
+    .ops = &rpi_spi_ops,
+    .size = sizeof(rpi_spi_bus_desc)
+  },
+  {
+    .initialized = 0
+  }
+};
+
+int rpi_spi_init(bool bidirectional_mode)
+{
+  /* Initialize the libi2c API. */
+  rtems_libi2c_initialize();
+
+  /* Enable the SPI interface on the Raspberry Pi. */
+  rtems_gpio_initialize();
+
+  assert ( rpi_gpio_select_spi() == RTEMS_SUCCESSFUL );
+
+  bidirectional = bidirectional_mode;
+
+  /* Clear SPI control register and clear SPI FIFOs. */
+  BCM2835_REG(BCM2835_SPI_CS) = (3 << 4);
+
+  /* Register the SPI bus. */
+  return rtems_libi2c_register_bus("/dev/spi", &(rpi_spi_bus_desc.bus_desc));
+}
-- 
2.3.6



More information about the devel mailing list