From 7aca0fefce41f2090ffbd2f7ee2fbfcc9f2d1f0d Mon Sep 17 00:00:00 2001 From: Andre Marques Date: Sat, 5 Sep 2015 21:53:16 +0100 Subject: RaspberryPi: Added I2C and SPI bus support. 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 abb55a6ab8..5f117915b5 100644 --- a/c/src/lib/libbsp/arm/raspberrypi/Makefile.am +++ b/c/src/lib/libbsp/arm/raspberrypi/Makefile.am @@ -46,6 +46,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 @@ -128,6 +130,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 27abe6c09f..2676bef72e 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 0000000000..14a2467001 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 0000000000..4a8dbbf2ac --- /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 + * + * 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 + +#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 ddcd4ffde9..2a4d772dba 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 + * Copyright (c) 2014-2015 Andre Marques * 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 0000000000..1bbbc6d2a4 --- /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 + * + * 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 + +#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 87d6eb9b56..902cc01829 100644 --- a/c/src/lib/libbsp/arm/raspberrypi/preinstall.am +++ b/c/src/lib/libbsp/arm/raspberrypi/preinstall.am @@ -138,6 +138,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 0000000000..ae77f6272d --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#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)); +} -- cgit v1.2.3