This patch addresses gedare's comments regardig coding conventions and a few other issues. As to Pavel's concerns on the interrupt handling I currently lack the time to address them properly, but when possible I and possibly Alan will have a look at that and on the new I2C framework. --- c/src/lib/libbsp/arm/raspberrypi/Makefile.am | 11 +- c/src/lib/libbsp/arm/raspberrypi/configure.ac | 12 + c/src/lib/libbsp/arm/raspberrypi/gpio/gpio.c | 727 +++++++++++++++++++++ c/src/lib/libbsp/arm/raspberrypi/i2c/i2c.c | 480 ++++++++++++++ c/src/lib/libbsp/arm/raspberrypi/i2c/i2c_init.c | 84 +++ c/src/lib/libbsp/arm/raspberrypi/i2c/spi.c | 608 +++++++++++++++++ c/src/lib/libbsp/arm/raspberrypi/i2c/spi_init.c | 85 +++ c/src/lib/libbsp/arm/raspberrypi/include/gpio.h | 210 ++++++ c/src/lib/libbsp/arm/raspberrypi/include/i2c.h | 183 ++++++ c/src/lib/libbsp/arm/raspberrypi/include/irq.h | 6 +- .../libbsp/arm/raspberrypi/include/raspberrypi.h | 79 ++- c/src/lib/libbsp/arm/raspberrypi/irq/irq.c | 103 ++- c/src/lib/libbsp/arm/raspberrypi/preinstall.am | 8 + .../lib/libbsp/arm/raspberrypi/startup/bspstart.c | 17 +- 14 files changed, 2591 insertions(+), 22 deletions(-) create mode 100644 c/src/lib/libbsp/arm/raspberrypi/gpio/gpio.c create mode 100644 c/src/lib/libbsp/arm/raspberrypi/i2c/i2c.c create mode 100644 c/src/lib/libbsp/arm/raspberrypi/i2c/i2c_init.c create mode 100644 c/src/lib/libbsp/arm/raspberrypi/i2c/spi.c create mode 100644 c/src/lib/libbsp/arm/raspberrypi/i2c/spi_init.c create mode 100644 c/src/lib/libbsp/arm/raspberrypi/include/gpio.h create mode 100644 c/src/lib/libbsp/arm/raspberrypi/include/i2c.h
diff --git a/c/src/lib/libbsp/arm/raspberrypi/Makefile.am b/c/src/lib/libbsp/arm/raspberrypi/Makefile.am index ed134fa..9363bc4 100644 --- a/c/src/lib/libbsp/arm/raspberrypi/Makefile.am +++ b/c/src/lib/libbsp/arm/raspberrypi/Makefile.am @@ -44,6 +44,8 @@ include_bsp_HEADERS += include/irq.h include_bsp_HEADERS += include/mmu.h include_bsp_HEADERS += include/usart.h include_bsp_HEADERS += include/raspberrypi.h +include_bsp_HEADERS += include/gpio.h +include_bsp_HEADERS += include/i2c.h include_libcpu_HEADERS = ../../../libcpu/arm/shared/include/cache_.h \ ../../../libcpu/arm/shared/include/arm-cp15.h @@ -79,7 +81,6 @@ libbsp_a_SOURCES += ../../shared/bspclean.c libbsp_a_SOURCES += ../../shared/bspgetworkarea.c libbsp_a_SOURCES += ../../shared/bsplibc.c libbsp_a_SOURCES += ../../shared/bsppost.c -libbsp_a_SOURCES += ../../shared/bsppredriverhook.c libbsp_a_SOURCES += ../../shared/bsppretaskinghook.c libbsp_a_SOURCES += ../../shared/cpucounterread.c libbsp_a_SOURCES += ../../shared/cpucounterdiff.c @@ -118,11 +119,19 @@ libbsp_a_SOURCES += clock/clockdrv.c ../../../shared/clockdrv_shell.h # Timer libbsp_a_SOURCES += misc/timer.c +# GPIO + +libbsp_a_SOURCES += gpio/gpio.c + # RTC # SSP # I2C +libbsp_a_SOURCES += i2c/i2c.c +libbsp_a_SOURCES += i2c/i2c_init.c +libbsp_a_SOURCES += i2c/spi.c +libbsp_a_SOURCES += i2c/spi_init.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 9bd6883..92fa903 100644 --- a/c/src/lib/libbsp/arm/raspberrypi/configure.ac +++ b/c/src/lib/libbsp/arm/raspberrypi/configure.ac @@ -24,6 +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.]) + +RTEMS_BSPOPTS_SET([BSP_ENABLE_SPI],[*],[0]) +RTEMS_BSPOPTS_HELP([BSP_ENABLE_SPI],[Define to 1 to use the SPI bus. This will register the bus in /dev/spi and also any device driver mentioned in i2c/spi_init.c .]) + +RTEMS_BSPOPTS_SET([BSP_ENABLE_I2C],[*],[0]) +RTEMS_BSPOPTS_HELP([BSP_ENABLE_I2C],[Define to 1 to use the I2C bus. This will register the bus in /dev/i2c and also any device driver mentioned in i2c/i2c_init.c .]) + RTEMS_BSP_CLEANUP_OPTIONS(0, 0) RTEMS_BSP_LINKCMDS diff --git a/c/src/lib/libbsp/arm/raspberrypi/gpio/gpio.c b/c/src/lib/libbsp/arm/raspberrypi/gpio/gpio.c new file mode 100644 index 0000000..40ac3b0 --- /dev/null +++ b/c/src/lib/libbsp/arm/raspberrypi/gpio/gpio.c @@ -0,0 +1,727 @@ +/** + * @file gpio.c + * + * @ingroup raspberrypi_gpio + * + * @brief Support for the Raspberry PI GPIO. + */ + +/* + * Copyright (c) 2014 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. + */ + +#include <bsp/raspberrypi.h> +#include <bsp/irq.h> +#include <bsp/gpio.h> +#include <assert.h> + +/* Calculates a bitmask to assign an alternate function to a given pin. */ +#define SELECT_PIN_FUNCTION(fn, pn) (fn << ((pn % 10) * 3)) + +static bool is_initialized = false; + +rpi_gpio_pin gpio_pin[GPIO_PIN_COUNT]; + +/** + * @brief Waits a number of CPU cycles. + * + * @param[in] cycles The number of CPU cycles to wait. + * + */ +static void arm_delay (int cycles) +{ + int i; + + for ( i = 0; i < cycles; i++ ) { + asm volatile ("nop"); + } +} + +/** + * @brief Initializes the GPIO API and sets every pin as NOT_USED. + * If the API has already been initialized silently exits. + */ +void gpio_initialize(void) +{ + int i; + + if ( is_initialized ) { + return; + } + + is_initialized = true; + + for ( i = 0; i < GPIO_PIN_COUNT; i++ ) { + gpio_pin[i].pin_type = NOT_USED; + gpio_pin[i].enabled_interrupt = NONE; + + gpio_pin[i].h_args.debouncing_tick_count = 0; + } +} + +/** + * @brief Gives an output GPIO pin the logical value of 1. + * + * @param[in] pin The Raspberry Pi GPIO pin label number (not is position + * on the header). + * + * @retval RTEMS_SUCCESSFUL Pin was set successfully. + * @retval RTEMS_NOT_CONFIGURED The received pin is not configured + * as an digital output. + */ +rtems_status_code gpio_set(int pin) +{ + assert( pin >= 0 && pin < GPIO_PIN_COUNT ); + + if ( gpio_pin[pin - 1].pin_type != DIGITAL_OUTPUT ) { + return RTEMS_NOT_CONFIGURED; + } + + BCM2835_REG(BCM2835_GPIO_GPSET0) = (1 << pin); + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Gives an output GPIO pin the logical value of 0. + * + * @param[in] pin The Raspberry Pi GPIO pin label number (not is position + * on the header). + * + * @retval RTEMS_SUCCESSFUL Pin was cleared successfully. + * @retval RTEMS_NOT_CONFIGURED The received pin is not configured + * as an digital output. + */ +rtems_status_code gpio_clear(int pin) +{ + assert( pin >= 0 && pin < GPIO_PIN_COUNT ); + + if ( gpio_pin[pin - 1].pin_type != DIGITAL_OUTPUT ) { + return RTEMS_NOT_CONFIGURED; + } + + BCM2835_REG(BCM2835_GPIO_GPCLR0) = (1 << pin); + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Gets the value (level) of a GPIO input pin. + * + * @param[in] pin The Raspberry Pi GPIO pin label number (not is position + * on the header). + * + * @retval The function returns 0 or 1 depending on the pin current + * logical value. + */ +int gpio_get_value(int pin) +{ + assert( pin >= 0 && pin < GPIO_PIN_COUNT ); + + return (BCM2835_REG(BCM2835_GPIO_GPLEV0) & (1 << pin)); +} + +/** + * @brief Configures a GPIO pin to perform a certain function. + * + * @param[in] pin The Raspberry Pi GPIO pin label number (not is position + * on the header). + * @param[in] type The new function of the pin. + * + * @retval RTEMS_SUCCESSFUL Pin was configured successfully. + * @retval RTEMS_RESOURCE_IN_USE The received pin is already being used. + */ +rtems_status_code gpio_select_pin(int pin, rpi_pin type) +{ + assert( pin >= 0 && pin < GPIO_PIN_COUNT ); + + /* Calculate the pin function select register address. */ + volatile unsigned int *pin_addr = (unsigned int *)BCM2835_GPIO_REGS_BASE + + (pin / 10); + + /* If the pin is already being used returns with an error. */ + if ( gpio_pin[pin - 1].pin_type != NOT_USED ) { + return RTEMS_RESOURCE_IN_USE; + } + + /* Sets pin function select bits.*/ + if ( type == DIGITAL_INPUT ) { + *(pin_addr) &= ~SELECT_PIN_FUNCTION(7, pin); + } + else { + *(pin_addr) |= SELECT_PIN_FUNCTION(type, pin); + } + + /* If the alternate function was successfuly assigned to the pin, + * record that information on the gpio_pin structure. */ + gpio_pin[pin - 1].pin_type = type; + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Configures the pull resistor setting of an array of GPIO pins. + * + * @param[in] pins Array of Raspberry Pi GPIO pin label numbers (not their + * position on the header). + * @param[in] pin_count Number of pins on the @var pins array. + * @param[in] mode The pull resistor mode. + * + * @retval RTEMS_SUCCESSFUL Pull resistor successfully configured. + * @retval RTEMS_NOT_DEFINED Unknown pull resistor mode. + */ +static rtems_status_code +set_input_mode(int *pins, int pin_count, int pin_mask, rpi_gpio_input_mode mode) +{ + int i; + + /* Set control signal. */ + switch ( mode ) { + case PULL_UP: + BCM2835_REG(BCM2835_GPIO_GPPUD) = (1 << 1); + break; + + case PULL_DOWN: + BCM2835_REG(BCM2835_GPIO_GPPUD) = (1 << 0); + break; + + case NO_PULL_RESISTOR: + BCM2835_REG(BCM2835_GPIO_GPPUD) = 0; + break; + + default: + return RTEMS_NOT_DEFINED; + } + + /* Wait 150 cyles, as per BCM2835 documentation. */ + arm_delay(150); + + /* Setup clock for the control signal. */ + BCM2835_REG(BCM2835_GPIO_GPPUDCLK0) = pin_mask; + + arm_delay(150); + + /* Remove the control signal. */ + BCM2835_REG(BCM2835_GPIO_GPPUD) = 0; + + /* Remove the clock. */ + BCM2835_REG(BCM2835_GPIO_GPPUDCLK0) = 0; + + /* If the operation was successful, record that information + * on the gpio_pin structure so it can be recalled later. */ + for ( i = 0; i < pin_count; i++ ) { + gpio_pin[pins[i] - 1].input_mode = mode; + } + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Configures a single GPIO pin pull resistor. + * + * @param[in] pin Raspberry Pi GPIO pin label number (not its position + * on the header). + * @param[in] mode The pull resistor mode. + * + * @retval RTEMS_SUCCESSFUL Pull resistor successfully configured. + * @retval RTEMS_NOT_DEFINED @see set_input_mode(). + */ +rtems_status_code gpio_input_mode(int pin, rpi_gpio_input_mode mode) +{ + assert( pin >= 0 && pin < GPIO_PIN_COUNT ); + + int pin_mask = (1 << pin); + int pins[1]; + + /* If the desired actuation mode is already set, silently exits. */ + if ( gpio_pin[pin - 1].input_mode == mode ) { + return RTEMS_SUCCESSFUL; + } + + pins[0] = pin; + + return set_input_mode(pins, 1, pin_mask, mode); +} + +/** + * @brief Sets the same pull-up/down resistors actuation mode to multiple GPIO + * input pins. There is a maximum number of 32 pins per call, + * which is enough for Raspberry Pi models A and B (17 GPIOs on + * P1 GPIO header) and also model B+ (28 GPIOs on J8 GPIO header). + * + * @param[in] pins Array of Raspberry Pi GPIO pin label numbers + * (not their position on the header). + * @param[in] pin_count Number of pins on the @var pins array. + * @param[in] mode The pull resistor mode. + * + * @retval RTEMS_SUCCESSFUL Pull resistor successfully configured. + * @retval RTEMS_INVALID_ID Unknown pull resistor mode. + */ +rtems_status_code +gpio_setup_input_mode(int *pins, int pin_count, rpi_gpio_input_mode mode) +{ + uint32_t pin_mask = 0; + int diff_mode_counter = 0; + int i; + + if ( pin_count > GPIO_EXTERNAL_TOP_PIN ) { + return RTEMS_INVALID_ID; + } + + /* Cycle through the given pins to check if this operation will have an effect + * on the resistor actuation mode of any one of the pins. + * Every pin that currently uses a different pull resistor mode sets a bit + * in its corresponding place on a bitmask. If the mode for a pin will not + * change then the diff_mode_counter variable is increased. */ + for ( i = 0; i < pin_count; i++ ) { + assert( pins[i] >= 0 && pins[i] < GPIO_PIN_COUNT ); + + if ( gpio_pin[pins[i] - 1].input_mode != mode ) { + pin_mask |= (1 << pins[i]); + } + else { + ++diff_mode_counter; + } + } + + /* If no pin will have its resistor mode changed silently exits, avoiding an + * unnecessary access to the Rasberry Pi memory registers. */ + if ( diff_mode_counter == 0 ) { + return RTEMS_SUCCESSFUL; + } + + return set_input_mode(pins, pin_count, pin_mask, mode); +} + +/** + * @brief Disables a GPIO pin on the APiI, making it available to be used + * by anyone on the system. + * + * @param[in] dev_pin Raspberry Pi GPIO pin label number (not its position + * on the header). + * + * @retval RTEMS_SUCCESSFUL Pin successfully disabled on the API. + * @retval RTEMS_UNSATISFIED Could not disable an ative interrupt on this pin, + * @see gpio_disable_interrupt(), + */ +rtems_status_code gpio_disable_pin(int dev_pin) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rpi_gpio_pin *pin; + + assert( dev_pin >= 1 && dev_pin <= GPIO_PIN_COUNT ); + + pin = &gpio_pin[dev_pin - 1]; + + pin->pin_type = NOT_USED; + + /* If the pin has an enabled interrupt then remove the handler. */ + if ( pin->enabled_interrupt != NONE ) { + sc = gpio_disable_interrupt(dev_pin); + } + + return sc; +} + +/** + * @brief Setups a JTAG interface using the P1 GPIO pin header + * for the models A/B and J8 header on the B+. + * The following pins should be unused before calling this function: + * GPIO 4, 22, 24, 25 and 27. + * + * @param[in] dev_pin Raspberry Pi GPIO pin label number (not its position + * on the header). + * + * @retval RTEMS_SUCCESSFUL JTAG interface successfully configured. + * @retval RTEMS_RESOURCE_IN_USE At least one of the required pins is currently + * occupied, @see gpio_select_pin(). + */ +rtems_status_code gpio_select_jtag(void) +{ + rtems_status_code sc; + + /* Setup gpio 4 alt5 ARM_TDI. */ + if ( (sc = gpio_select_pin(4, ALT_FUNC_5)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* Setup gpio 22 alt4 ARM_TRST. */ + if ( (sc = gpio_select_pin(22, ALT_FUNC_4)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* Setup gpio 24 alt4 ARM_TDO. */ + if ( (sc = gpio_select_pin(24, ALT_FUNC_4)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* Setup gpio 25 alt4 ARM_TCK. */ + if ( (sc = gpio_select_pin(25, ALT_FUNC_4)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* Setup gpio 27 alt4 ARM_TMS. */ + if ( (sc = gpio_select_pin(27, ALT_FUNC_4)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Setups a SPI interface using the P1 GPIO pin header + * for the models A/B and J8 header on the B+. + * The following pins should be unused before calling this function: + * GPIO 7, 8, 9, 10 and 11. + * + * @param[in] dev_pin Raspberry Pi GPIO pin label number (not its position + * on the header). + * + * @retval RTEMS_SUCCESSFUL SPI interface successfully configured. + * @retval RTEMS_RESOURCE_IN_USE At least one of the required pins is currently + * occupied, @see gpio_select_pin(). + */ +rtems_status_code gpio_select_spi_p1(void) +{ + rtems_status_code sc; + + /* SPI master 0 MISO data line. */ + if ( (sc = gpio_select_pin(9, ALT_FUNC_0)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* SPI master 0 MOSI data line. */ + if ( (sc = gpio_select_pin(10, ALT_FUNC_0)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* SPI master 0 SCLK clock line. */ + if ( (sc = gpio_select_pin(11, ALT_FUNC_0)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* SPI master 0 CE_0 chip enable line. */ + if ( (sc = gpio_select_pin(8, ALT_FUNC_0)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* SPI master 0 CE_1 chip enable line. */ + if ( (sc = gpio_select_pin(7, ALT_FUNC_0)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Setups a I2C interface using the P1 GPIO pin header + * for the models A/B and J8 header on the B+. + * The following pins should be unused before calling this function: + * GPIO 2 and 3. + * + * @param[in] dev_pin Raspberry Pi GPIO pin label number (not its position + * on the header). + * + * @retval RTEMS_SUCCESSFUL JTAG interface successfully configured. + * @retval RTEMS_RESOURCE_IN_USE At least one of the required pins is currently + * occupied, @see gpio_select_pin(). + */ +rtems_status_code gpio_select_i2c_p1_rev2(void) +{ + rtems_status_code sc; + int pins[] = {2,3}; + + /* I2C BSC1 SDA data line. */ + if ( (sc = gpio_select_pin(2, ALT_FUNC_0)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* I2C BSC1 SCL clock line. */ + if ( (sc = gpio_select_pin(3, ALT_FUNC_0)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* Enable pins 2 and 3 pull-up resistors. */ + if ( (sc = gpio_setup_input_mode(pins, 2, PULL_UP)) != RTEMS_SUCCESSFUL ) { + return sc; + } + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief De-bounces a switch by requiring a certain time to pass between + * interrupts. Any interrupt fired too close to the last will be + * ignored as it is probably the result of a involuntary switch/button + * bounce after being released. + * + * @param[in] dev_pin Raspberry Pi GPIO pin label number (not its position + * on the header). + * + * @retval 0 Interrupt is likely provoked by a user press on the switch. + * @retval -1 Interrupt was generated too close to the last one. + * Probably a switch bounce. + */ +static int debounce_switch(int dev_pin) +{ + rtems_interval time; + rpi_gpio_pin *pin; + + assert( dev_pin >= 1 && dev_pin <= GPIO_PIN_COUNT ); + + pin = &gpio_pin[dev_pin - 1]; + + time = rtems_clock_get_ticks_since_boot(); + + if ( (time - pin->h_args.last_isr_tick) < pin->h_args.debouncing_tick_count ) { + return -1; + } + + pin->h_args.last_isr_tick = time; + + return 0; +} + +/** + * @brief Generic ISR that clears the event register on the Raspberry Pi and + * calls an user defined ISR. + * + * @param[in] arg Void pointer to a handler_arguments structure. + */ +static void generic_handler(void* arg) +{ + handler_arguments* handler_args; + int rv = 0; + int pin = 0; + + handler_args = (handler_arguments*) arg; + + pin = handler_args->pin_number; + + /* If the interrupt was generated by the pin attached to this ISR clear it. */ + if ( BCM2835_REG(BCM2835_GPIO_GPEDS0) & (1 << pin) ) { + BCM2835_REG(BCM2835_GPIO_GPEDS0) &= (1 << pin); + } + /* If not lets the next ISR process the interrupt. */ + else { + return; + } + + /* If this pin has the deboucing function attached, call it. */ + if ( handler_args->debouncing_tick_count > 0 ) { + rv = debounce_switch(pin); + + if ( rv < 0 ) { + return; + } + } + + /* Call the user's ISR. */ + (handler_args->handler) (); +} + +/** + * @brief Defines for a GPIO input pin the number of clock ticks that must pass + * before an generated interrupt is garanteed to be generated by the user + * and not by a bouncing switch/button. + * + * @param[in] dev_pin Raspberry Pi GPIO pin label number (not its position + * on the header). + * + * @retval RTEMS_SUCCESSFUL De-bounce function successfully attached to the pin. + * @retval RTEMS_NOT_CONFIGURED The current pin is not configured as a digital + * input, hence it can not be connected to a switch. + */ +rtems_status_code gpio_debounce_switch(int dev_pin, int ticks) +{ + assert( dev_pin >= 1 && dev_pin <= GPIO_PIN_COUNT ); + + if ( gpio_pin[dev_pin - 1].pin_type != DIGITAL_INPUT ) { + return RTEMS_NOT_CONFIGURED; + } + + gpio_pin[dev_pin - 1].h_args.debouncing_tick_count = ticks; + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Enables interrupts to be generated on a given GPIO pin. + * When fired that interrupt will call the given handler. + * + * @param[in] dev_pin Raspberry Pi GPIO pin label number (not its position + * on the header). + * @param[in] interrupt Type of interrupt to enable for the pin. + * @param[in] handler Pointer to a function that will be called every time + * @var interrupt is generated. This function must have + * no receiving parameters and return void. + * + * @retval RTEMS_SUCCESSFUL Interrupt successfully enabled for this pin. + * @retval RTEMS_UNSATISFIED Could not replace the currently active + * interrupt on this pin. + */ +rtems_status_code gpio_enable_interrupt( +int dev_pin, +gpio_interrupt interrupt, +void (*handler)(void) +) +{ + rtems_status_code sc; + rpi_gpio_pin *pin; + + assert( dev_pin >= 1 && dev_pin <= GPIO_EXTERNAL_TOP_PIN ); + + pin = &gpio_pin[dev_pin - 1]; + + /* If the pin already has an enabled interrupt removes it first, + * as well as its handler. */ + if ( pin->enabled_interrupt != NONE ) { + sc = gpio_disable_interrupt(dev_pin); + + if ( sc != RTEMS_SUCCESSFUL ) { + return RTEMS_UNSATISFIED; + } + } + + pin->h_args.pin_number = dev_pin; + pin->h_args.handler = handler; + + pin->h_args.last_isr_tick = rtems_clock_get_ticks_since_boot(); + + /* Installs the generic_handler, which will call the user handler received + * a parameter. */ + sc = rtems_interrupt_handler_install(BCM2835_IRQ_ID_GPIO_0, + NULL, + RTEMS_INTERRUPT_SHARED, + (rtems_interrupt_handler) generic_handler, + &(pin->h_args)); + + if ( sc != RTEMS_SUCCESSFUL ) { + return RTEMS_UNSATISFIED; + } + + switch ( interrupt ) { + case FALLING_EDGE: + /* Enables asynchronous falling edge detection. */ + BCM2835_REG(BCM2835_GPIO_GPAFEN0) |= (1 << dev_pin); + break; + + case RISING_EDGE: + /* Enables asynchronous rising edge detection. */ + BCM2835_REG(BCM2835_GPIO_GPAREN0) |= (1 << dev_pin); + break; + + case BOTH_EDGES: + /* Enables asynchronous falling edge detection. */ + BCM2835_REG(BCM2835_GPIO_GPAFEN0) |= (1 << dev_pin); + + /* Enables asynchronous rising edge detection. */ + BCM2835_REG(BCM2835_GPIO_GPAREN0) |= (1 << dev_pin); + break; + + case LOW_LEVEL: + /* Enables pin low level detection. */ + BCM2835_REG(BCM2835_GPIO_GPLEN0) |= (1 << dev_pin); + break; + + case HIGH_LEVEL: + /* Enables pin high level detection. */ + BCM2835_REG(BCM2835_GPIO_GPHEN0) |= (1 << dev_pin); + break; + + case BOTH_LEVELS: + /* Enables pin low level detection. */ + BCM2835_REG(BCM2835_GPIO_GPLEN0) |= (1 << dev_pin); + + /* Enables pin high level detection. */ + BCM2835_REG(BCM2835_GPIO_GPHEN0) |= (1 << dev_pin); + break; + + case NONE: + return RTEMS_SUCCESSFUL; + } + + pin->enabled_interrupt = interrupt; + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Stops interrupts from being generated from a given GPIO pin + * and removes the corresponding handler. + * + * @param[in] dev_pin Raspberry Pi GPIO pin label number (not its position + * on the header). + * + * @retval RTEMS_SUCCESSFUL Interrupt successfully disabled for this pin. + * @retval RTEMS_UNSATISFIED Could not remove the current interrupt handler or + * could not recognise the current active interrupt + * on this pin. + */ +rtems_status_code gpio_disable_interrupt(int dev_pin) +{ + rtems_status_code sc; + rpi_gpio_pin *pin; + + assert( dev_pin >= 1 && dev_pin <= GPIO_EXTERNAL_TOP_PIN ); + + pin = &gpio_pin[dev_pin - 1]; + + switch ( pin->enabled_interrupt ) { + case FALLING_EDGE: + /* Disables asynchronous falling edge detection. */ + BCM2835_REG(BCM2835_GPIO_GPAFEN0) &= ~(1 << dev_pin); + break; + + case RISING_EDGE: + /* Disables asynchronous rising edge detection. */ + BCM2835_REG(BCM2835_GPIO_GPAREN0) &= ~(1 << dev_pin); + break; + + case BOTH_EDGES: + /* Disables asynchronous falling edge detection. */ + BCM2835_REG(BCM2835_GPIO_GPAFEN0) &= ~(1 << dev_pin); + + /* Disables asynchronous rising edge detection. */ + BCM2835_REG(BCM2835_GPIO_GPAREN0) &= ~(1 << dev_pin); + break; + + case LOW_LEVEL: + /* Disables pin low level detection. */ + BCM2835_REG(BCM2835_GPIO_GPLEN0) &= ~(1 << dev_pin); + break; + + case HIGH_LEVEL: + /* Disables pin high level detection. */ + BCM2835_REG(BCM2835_GPIO_GPHEN0) &= ~(1 << dev_pin); + break; + + case BOTH_LEVELS: + /* Disables pin low level detection. */ + BCM2835_REG(BCM2835_GPIO_GPLEN0) &= ~(1 << dev_pin); + + /* Disables pin high level detection. */ + BCM2835_REG(BCM2835_GPIO_GPHEN0) &= ~(1 << dev_pin); + break; + + case NONE: + return RTEMS_SUCCESSFUL; + } + + /* Removes the handler. */ + sc = rtems_interrupt_handler_remove(BCM2835_IRQ_ID_GPIO_0, + (rtems_interrupt_handler) generic_handler, + &(pin->h_args)); + + if ( sc != RTEMS_SUCCESSFUL ) { + return RTEMS_UNSATISFIED; + } + + pin->enabled_interrupt = NONE; + + return RTEMS_SUCCESSFUL; +} 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..13c33ad --- /dev/null +++ b/c/src/lib/libbsp/arm/raspberrypi/i2c/i2c.c @@ -0,0 +1,480 @@ +/** + * @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 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. + */ + +/* + * TODO: + * - Clock stretching (currently using default values) + * - 10-bit addressing + * - Falling/Rising edge delays (currently using default values) + */ + +#include <bsp.h> +#include <bsp/raspberrypi.h> +#include <bsp/gpio.h> +#include <bsp/irq.h> +#include <bsp/i2c.h> +#include <assert.h> + +/** + * @brief 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. + * + * @param[in] clock_hz The desired clock frequency for the I2C bus operation. + * @param[out] clock_divider Pointer to a variable where the calculated + * clock divider will be stored. + * + * @retval RTEMS_SUCCESSFUL Successfully calculated a valid clock divider. + * @retval RTEMS_INVALID_NUMBER The resulting clock divider is invalid, due to + * an invalid BSC_CORE_CLOCK_HZ + * or clock_hz value. + */ +static rtems_status_code +bcm2835_i2c_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 = BSC_CORE_CLK_HZ / clock_hz; + + 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_hz ) { + ++divider; + + clock_rate = BSC_CORE_CLK_HZ / divider; + } + + *clock_divider = divider; + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Set the I2C bus clock divider. + * + * @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 @see bcm2835_i2c_calculate_clock_divider(). + */ +static rtems_status_code bcm2835_i2c_set_tfr_mode( +rtems_libi2c_bus_t *bushdl, +const rtems_libi2c_tfr_mode_t *tfr_mode +) +{ + rtems_status_code sc; + uint16_t clock_divider; + + /* Calculate the most appropriate clock divider. */ + sc = bcm2835_i2c_calculate_clock_divider(tfr_mode->baudrate, &clock_divider); + + if ( sc != RTEMS_SUCCESSFUL ) { + return sc; + } + + /* Set clock divider. */ + BCM2835_REG(BCM2835_I2C_DIV) = clock_divider; + + return sc; +} + +/** + * @brief Reads/writes to/from the I2C 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 bcm2835_i2c_read_write( +rtems_libi2c_bus_t * bushdl, +unsigned char *rd_buf, +const unsigned char *wr_buf, +int buffer_size +) +{ + bcm2835_i2c_softc_t *softc_ptr = &(((bcm2835_i2c_desc_t *)(bushdl))->softc); + + uint32_t bytes_sent = buffer_size; + + /* Since there is a maximum of 0xFFFF packets per transfer + * (size of the DLEN register), count how many transfers will be + * needed and adjust each transfer size accordingly. */ + int transfer_count = (buffer_size + 0xFFFE) / 0xFFFF; + uint16_t dlen_buffer_size; + + do { + if ( transfer_count > 1 ) { + dlen_buffer_size = 0xFFFF; + } + else { + dlen_buffer_size = (buffer_size & 0xFFFF); + } + + /* Set the DLEN register, which specifies how many data packets + * will be transferred. */ + BCM2835_REG(BCM2835_I2C_DLEN) = dlen_buffer_size; + + /* Clear the acknowledgment and clock stretching error status. */ + BCM2835_REG(BCM2835_I2C_S) |= (3 << 8); + + /* While there is data to transfer. */ + while ( dlen_buffer_size >= 1 ) { + /* If writing. */ + if ( rd_buf == NULL ) { + + /* If transfer is not active, send start bit. */ + if( (BCM2835_REG(BCM2835_I2C_S) & (1 << 0)) == 0 ) + BCM2835_REG(BCM2835_I2C_C) |= (1 << 7); + + /* 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); + + if ( + rtems_semaphore_obtain(softc_ptr->irq_sema_id, RTEMS_WAIT, 50) != + RTEMS_SUCCESSFUL + ) { + return -1; + } + } + + /* If using the bus in polling mode. */ + else { + /* Poll TXW bit until there is space available to write. */ + while ( (BCM2835_REG(BCM2835_I2C_S) & (1 << 2)) == 0 ) { + ; + } + } + + /* Write data to the TX FIFO. */ + BCM2835_REG(BCM2835_I2C_FIFO) = (*(uint8_t *)wr_buf); + + ++wr_buf; + + /* Check for acknowledgment or clock stretching errors. */ + if ( + (BCM2835_REG(BCM2835_I2C_S) & (1 << 8)) || + (BCM2835_REG(BCM2835_I2C_S) & (1 << 9)) + ) { + return -1; + } + } + /* If reading. */ + else { + /* Send start bit. Before any read a libi2c_send_addr call should be + * made signaling a read operation. */ + BCM2835_REG(BCM2835_I2C_C) |= (1 << 7); + + /* Check for an acknowledgment error. */ + if ( (BCM2835_REG(BCM2835_I2C_S) & (1 << 8)) != 0 ) { + return -1; + } + + /* Poll RXD bit until there is data on the RX FIFO to read. */ + while ( (BCM2835_REG(BCM2835_I2C_S) & (1 << 5)) == 0 ) { + ; + } + + /* Read data from the RX FIFO. */ + (*(uint8_t *)rd_buf) = BCM2835_REG(BCM2835_I2C_FIFO) & 0xFF; + + ++rd_buf; + + /* Check for acknowledgment or clock stretching errors. */ + if ( + (BCM2835_REG(BCM2835_I2C_S) & (1 << 8)) || + (BCM2835_REG(BCM2835_I2C_S) & (1 << 9)) + ) { + return -1; + } + } + + --dlen_buffer_size; + --transfer_count; + --buffer_size; + } + } while ( transfer_count > 0 ); + + /* 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_semaphore_obtain(softc_ptr->irq_sema_id, RTEMS_WAIT, 50) != + RTEMS_SUCCESSFUL + ) { + return -1; + } + } + /* If using the bus in polling mode. */ + else { + /* Poll DONE bit until data has been sent. */ + while ( (BCM2835_REG(BCM2835_I2C_S) & (1 << 1)) == 0 ) { + ; + } + } + + bytes_sent -= buffer_size; + + return bytes_sent; +} + +/** + * @brief 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 releases the irq semaphore, which will allow + * the transfer process to continue (by writing to the TX FIFO); + * + * - When the transfer is done on 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 releases the irq semaphore, marking the end of the + * transfer. + * + * @param[in] arg Void pointer to the bus data structure. + */ +static void i2c_handler(void* arg) +{ + bcm2835_i2c_softc_t *softc_ptr = (bcm2835_i2c_softc_t *) 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); + + /* Release the irq semaphore. */ + rtems_semaphore_release(softc_ptr->irq_sema_id); + } +} + +/** + * @brief Low level function to initialize the I2C 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_semaphore_create() and + * @see rtems_interrupt_handler_install(). + */ +rtems_status_code bcm2835_i2c_init(rtems_libi2c_bus_t * bushdl) +{ + bcm2835_i2c_softc_t *softc_ptr = &(((bcm2835_i2c_desc_t *)(bushdl))->softc); + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if ( softc_ptr->initialized == 1 ) { + return sc; + } + + softc_ptr->initialized = 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 ) { + sc = rtems_semaphore_create(rtems_build_name('i','2','c','s'), + 0, + RTEMS_FIFO | RTEMS_SIMPLE_BINARY_SEMAPHORE, + 0, + &softc_ptr->irq_sema_id + ); + + sc = rtems_interrupt_handler_install(BCM2835_IRQ_ID_I2C, + NULL, + RTEMS_INTERRUPT_UNIQUE, + (rtems_interrupt_handler) i2c_handler, + softc_ptr + ); + } + + return sc; +} + +/** + * @brief Low level function that would send a start condition over the I2C bus. + * Because of the way the BSC controller implements the I2C protocol, the + * start sequence is sent whenever appropriate in bcm2835_i2c_read_write. + * Instead this function clears the bus FIFOS before each new data + * transfer. + * This function is used by the libi2c API. + * + * @param[in] bushdl Pointer to the libi2c API bus driver data structure. + * + * @retval RTEMS_SUCCESSFUL + */ +rtems_status_code bcm2835_i2c_send_start(rtems_libi2c_bus_t * bushdl) +{ + /* Clear FIFOs. */ + BCM2835_REG(BCM2835_I2C_C) |= (3 << 4); + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Low level function that would send a stop condition over the I2C bus, + * however the BSC controller send this condition automatically when the + * DLEN (data length - the number of bytes to be transferred) register + * value reaches 0. + * For that reason, 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 + */ +rtems_status_code bcm2835_i2c_stop(rtems_libi2c_bus_t * bushdl) +{ + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Low level function which addresses a I2C device. + * This function is used by the libi2c API. + * + * @param[in] bushdl Pointer to the libi2c API bus driver data structure. + * @param[in] addr Address of a connected I2C device + * @param[in] rw Defines the nature of the transfer which will take place with + * the addressed device - 0 to write and 1 to read. + * + * @retval RTEMS_SUCCESSFUL The device has been successfully addressed. + */ +rtems_status_code +bcm2835_i2c_send_addr(rtems_libi2c_bus_t * bushdl, uint32_t addr, int rw) +{ + /* Address slave device. */ + BCM2835_REG(BCM2835_I2C_A) = addr; + + /* Set read/write bit. + * If writing. */ + if ( rw == 0 ) { + BCM2835_REG(BCM2835_I2C_C) &= ~(1 << 0); + } + /* If reading. */ + else { + BCM2835_REG(BCM2835_I2C_C) |= (1 << 0); + } + + return RTEMS_SUCCESSFUL; +} + +/** + * @brief Low level function that reads a number of bytes from the I2C 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 bcm2835_i2c_read_write(). + */ +int bcm2835_i2c_read_bytes( +rtems_libi2c_bus_t * bushdl, +unsigned char *bytes, +int nbytes +) +{ + return bcm2835_i2c_read_write(bushdl, bytes, NULL, nbytes); +} + +/** + * @brief Low level function that writes a number of bytes from a buffer + * to the I2C 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 through the bus. + * @param[in] nbytes Number of bytes to be written from the bytes buffer + to the bus. + * + * @retval @see bcm2835_i2c_read_write(). + */ +int bcm2835_i2c_write_bytes( +rtems_libi2c_bus_t * bushdl, +unsigned char *bytes, +int nbytes +) +{ + return bcm2835_i2c_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, namely the bus clock divider. + * 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 bcm2835_i2c_set_tfr_mode(). + */ +int bcm2835_i2c_ioctl(rtems_libi2c_bus_t * bushdl, int cmd, void *arg) +{ + switch ( cmd ) { + case RTEMS_LIBI2C_IOCTL_SET_TFRMODE: + + return bcm2835_i2c_set_tfr_mode(bushdl, + (const rtems_libi2c_tfr_mode_t *)arg + ); + + default: + return -1; + } + + return 0; +} diff --git a/c/src/lib/libbsp/arm/raspberrypi/i2c/i2c_init.c b/c/src/lib/libbsp/arm/raspberrypi/i2c/i2c_init.c new file mode 100644 index 0000000..3a68407 --- /dev/null +++ b/c/src/lib/libbsp/arm/raspberrypi/i2c/i2c_init.c @@ -0,0 +1,84 @@ +/** + * @file i2c_init.c + * + * @ingroup raspberrypi_i2c + * + * @brief Raspberry Pi I2C bus initialization. + */ + +/* + * Copyright (c) 2014 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. + */ + +#include <bsp/raspberrypi.h> +#include <bsp/gpio.h> +#include <bsp/i2c.h> + +#include <libchip/mcp23008.h> + +static rtems_libi2c_bus_ops_t bcm2835_i2c_ops = { + init: bcm2835_i2c_init, + send_start: bcm2835_i2c_send_start, + send_stop: bcm2835_i2c_stop, + send_addr: bcm2835_i2c_send_addr, + read_bytes: bcm2835_i2c_read_bytes, + write_bytes: bcm2835_i2c_write_bytes, + ioctl: bcm2835_i2c_ioctl +}; + +static bcm2835_i2c_desc_t bcm2835_i2c_bus_desc = { + { + ops: &bcm2835_i2c_ops, + size: sizeof(bcm2835_i2c_bus_desc) + }, + { + initialized: 0 + } +}; + +/* Register drivers here for all the devices + * which require access to the I2C bus. + * + * The libi2c function "rtems_libi2c_register_drv" must be used to + * register each device driver, using the received i2c bus number. + * + * This function returns 0 on success. */ +int BSP_i2c_register_drivers(int i2c_bus_number) +{ + int rv = 0; + + rv = rtems_libi2c_register_drv("mcp23008", &i2c_mcp23008_drv_t, i2c_bus_number, MCP23008_ADDR); + + return rv; +} + +int BSP_i2c_init(void) +{ + int rv; + + /* Initialize the libi2c API. */ + rtems_libi2c_initialize (); + + /* Enable the I2C interface on the Raspberry Pi P1 GPIO header. */ + gpio_initialize (); + + if ( gpio_select_i2c_p1_rev2() < 0 ) { + return RTEMS_RESOURCE_IN_USE; + } + + /* Register the I2C bus. */ + rv = rtems_libi2c_register_bus("/dev/i2c", &(bcm2835_i2c_bus_desc.bus_desc)); + + if ( rv < 0 ) { + return -rv; + } + + /* Register SPI device drivers. */ + rv = BSP_i2c_register_drivers(rv); + + return 0; +} diff --git a/c/src/lib/libbsp/arm/raspberrypi/i2c/spi.c b/c/src/lib/libbsp/arm/raspberrypi/i2c/spi.c new file mode 100644 index 0000000..96b6db0 --- /dev/null +++ b/c/src/lib/libbsp/arm/raspberrypi/i2c/spi.c @@ -0,0 +1,608 @@ +/** + * @file spi.c + * + * @ingroup raspberrypi_i2c + * + * @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 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/irq.h> +#include <bsp/i2c.h> +#include <assert.h> + +/** + * @brief 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. + * + * @param[in] clock_hz The desired clock frequency for the SPI bus operation. + * @param[out] clock_divider Pointer to a variable where the calculated + * clock divider will be stored. + * + * @retval RTEMS_SUCCESSFUL Successfully calculated a valid clock divider. + * @retval RTEMS_INVALID_NUMBER The resulting clock divider is invalid, due to + * an invalid GPU_CORE_CLOCK_RATE + * or clock_hz value. + */ +static rtems_status_code +bcm2835_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 bcm2835_spi_calculate_clock_divider() + */ +static rtems_status_code bcm2835_spi_set_tfr_mode( +rtems_libi2c_bus_t *bushdl, +const rtems_libi2c_tfr_mode_t *tfr_mode +) +{ + bcm2835_spi_softc_t *softc_ptr = &(((bcm2835_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 = bcm2835_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 bcm2835_spi_read_write( +rtems_libi2c_bus_t * bushdl, +unsigned char *rd_buf, +const unsigned char *wr_buf, +int buffer_size +) +{ + bcm2835_spi_softc_t *softc_ptr = &(((bcm2835_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_semaphore_obtain(softc_ptr->irq_sema_id, RTEMS_WAIT, 50) != + RTEMS_SUCCESSFUL + ) { + 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. */ + while ( (BCM2835_REG(BCM2835_SPI_CS) & (1 << 18)) == 0 ) { + ; + } + } + + /* 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 ( softc_ptr->bidirectional == 1 ) { + /* 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_semaphore_obtain(softc_ptr->irq_sema_id, RTEMS_WAIT, 50) != + RTEMS_SUCCESSFUL + ) { + return -1; + } + } + /* If using the bus in polling mode. */ + else { + /* Poll the Done bit until the data transfer is complete. */ + while ( (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. */ + while ( (BCM2835_REG(BCM2835_SPI_CS) & (1 << 17)) == 0 ) { + ; + } + } + + /* 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 ( softc_ptr->bidirectional == 1 ) { + /* 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_semaphore_obtain(softc_ptr->irq_sema_id, RTEMS_WAIT, 50) != + RTEMS_SUCCESSFUL + ) { + return -1; + } + } + /* If using the bus in polling mode. */ + else { + /* Poll the Done bit until the data transfer is complete. */ + while ( (BCM2835_REG(BCM2835_SPI_CS) & (1 << 16)) == 0 ) { + ; + } + } + + 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 releases a irq semaphore which will allow the + * following transfer to proceed. + * + * @param[in] arg Void pointer to the bus data structure. + */ +static void spi_handler(void* arg) +{ + bcm2835_spi_softc_t *softc_ptr = (bcm2835_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 releasing the irq semaphore. */ + 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); + + /* Release the irq semaphore. */ + rtems_semaphore_release(softc_ptr->irq_sema_id); + } +} + +/** + * @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_semaphore_create() and + * @see rtems_interrupt_handler_install(). + */ +rtems_status_code bcm2835_spi_init(rtems_libi2c_bus_t * bushdl) +{ + bcm2835_spi_softc_t *softc_ptr = &(((bcm2835_spi_desc_t *)(bushdl))->softc); + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if ( softc_ptr->initialized == 1 ) { + return sc; + } + + softc_ptr->initialized = 1; + + /* TODO: This should be set on the device driver itself and configured + * during the bus transfer mode setup or another ioctl request. */ + softc_ptr->bidirectional = 0; + + /* If using the SPI bus in interrupt-driven mode. */ + if ( SPI_IO_MODE == 1 ) { + sc = rtems_semaphore_create(rtems_build_name('s','p','i','s'), + 0, + RTEMS_FIFO | RTEMS_SIMPLE_BINARY_SEMAPHORE, + 0, + &softc_ptr->irq_sema_id + ); + + if ( sc != RTEMS_SUCCESSFUL ) { + return sc; + } + + sc = rtems_interrupt_handler_install(BCM2835_IRQ_ID_SPI, + NULL, + RTEMS_INTERRUPT_UNIQUE, + (rtems_interrupt_handler) spi_handler, + softc_ptr + ); + } + + 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 + */ +rtems_status_code bcm2835_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. + */ +rtems_status_code bcm2835_spi_stop(rtems_libi2c_bus_t * bushdl) +{ + bcm2835_spi_softc_t *softc_ptr = &(((bcm2835_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. + */ +rtems_status_code +bcm2835_spi_send_addr(rtems_libi2c_bus_t * bushdl, uint32_t addr, int rw) +{ + bcm2835_spi_softc_t *softc_ptr = &(((bcm2835_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 bcm2835_spi_read_write(). + */ +int bcm2835_spi_read_bytes( +rtems_libi2c_bus_t * bushdl, +unsigned char *bytes, +int nbytes +) +{ + return bcm2835_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 bcm2835_spi_read_write(). + */ +int bcm2835_spi_write_bytes( +rtems_libi2c_bus_t * bushdl, +unsigned char *bytes, +int nbytes +) +{ + return bcm2835_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 bcm2835_spi_set_tfr_mode(). + */ +int bcm2835_spi_ioctl(rtems_libi2c_bus_t * bushdl, int cmd, void *arg) +{ + switch ( cmd ) { + case RTEMS_LIBI2C_IOCTL_SET_TFRMODE: + + return bcm2835_spi_set_tfr_mode(bushdl, + (const rtems_libi2c_tfr_mode_t *)arg + ); + + default: + return -1; + } + + return 0; +} diff --git a/c/src/lib/libbsp/arm/raspberrypi/i2c/spi_init.c b/c/src/lib/libbsp/arm/raspberrypi/i2c/spi_init.c new file mode 100644 index 0000000..1e1a6cc --- /dev/null +++ b/c/src/lib/libbsp/arm/raspberrypi/i2c/spi_init.c @@ -0,0 +1,85 @@ +/** + * @file spi_init.c + * + * @ingroup raspberrypi_i2c + * + * @brief Raspberry Pi SPI bus initialization. + */ + +/* + * Copyright (c) 2014 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. + */ + +#include <bsp/raspberrypi.h> +#include <bsp/gpio.h> +#include <bsp/i2c.h> + +static rtems_libi2c_bus_ops_t bcm2835_spi_ops = { + init: bcm2835_spi_init, + send_start: bcm2835_spi_send_start, + send_stop: bcm2835_spi_stop, + send_addr: bcm2835_spi_send_addr, + read_bytes: bcm2835_spi_read_bytes, + write_bytes: bcm2835_spi_write_bytes, + ioctl: bcm2835_spi_ioctl +}; + +static bcm2835_spi_desc_t bcm2835_spi_bus_desc = { + { + ops: &bcm2835_spi_ops, + size: sizeof(bcm2835_spi_bus_desc) + }, + { + initialized: 0 + } +}; + +/* Register drivers here for all the devices + * which require access to the SPI bus. + * + * The libi2c function "rtems_libi2c_register_drv" must be used to + * register each device driver, using the received spi bus number. + * + * This function returns 0 on success. */ +int BSP_spi_register_drivers(int spi_bus_number) +{ + int rv = 0; + + return rv; +} + +int BSP_spi_init(void) +{ + int rv; + + /* Initialize the libi2c API. */ + rtems_libi2c_initialize (); + + /* Enable the SPI interface on the Raspberry Pi P1 GPIO header. */ + gpio_initialize (); + + if ( gpio_select_spi_p1() < 0 ) { + return RTEMS_RESOURCE_IN_USE; + } + + /* Clear SPI control register and clear SPI FIFOs. */ + BCM2835_REG(BCM2835_SPI_CS) = 0x0000030; + + /* Register the SPI bus. */ + rv = rtems_libi2c_register_bus("/dev/spi", &(bcm2835_spi_bus_desc.bus_desc)); + + if ( rv < 0 ) { + return rv; + } + + /* Register SPI device drivers. */ + rv = BSP_spi_register_drivers(rv); + + return rv; +} + + diff --git a/c/src/lib/libbsp/arm/raspberrypi/include/gpio.h b/c/src/lib/libbsp/arm/raspberrypi/include/gpio.h new file mode 100644 index 0000000..d8c8b08 --- /dev/null +++ b/c/src/lib/libbsp/arm/raspberrypi/include/gpio.h @@ -0,0 +1,210 @@ +/** + * @file gpio.h + * + * @ingroup raspberrypi_gpio + * + * @brief Raspberry Pi specific GPIO definitions. + */ + +/* + * Copyright (c) 2014 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_GPIO_H +#define LIBBSP_ARM_RASPBERRYPI_GPIO_H + +#include <rtems.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * @brief Number of total GPIOS on the Raspberry Pi, + * including inaccessible ones. + */ +#define GPIO_PIN_COUNT 54 + +/** + * @brief Highest GPIO index directly accessible on the Raspberry Pi board. + */ +#define GPIO_EXTERNAL_TOP_PIN 32 + +/** + * @brief The set of possible configurations for a GPIO pull-up resistor. + * + * Enumerated type to define the possible pull-up resistor configuratons + * for an input pin. + */ +typedef enum +{ + PULL_UP=1, + PULL_DOWN, + NO_PULL_RESISTOR +} rpi_gpio_input_mode; + +/** + * @brief The set of possible functions a pin can have. + * + * Enumerated type to define a pin function. + */ +typedef enum +{ + DIGITAL_INPUT=0, + DIGITAL_OUTPUT, + ALT_FUNC_5, + ALT_FUNC_4, + ALT_FUNC_0, + ALT_FUNC_1, + ALT_FUNC_2, + ALT_FUNC_3, + + NOT_USED +} rpi_pin; + +/** + * @brief The set of possible interrupts an input pin can generate. + * + * Enumerated type to define an input pin interrupt. + */ +typedef enum +{ + FALLING_EDGE, + RISING_EDGE, + BOTH_EDGES, + LOW_LEVEL, + HIGH_LEVEL, + BOTH_LEVELS, + NONE +} gpio_interrupt; + +/** + * @brief Object containing relevant information to a interrupt handler. + * + * Encapsulates relevant data for a GPIO interrupt handler. + */ +typedef struct +{ + int pin_number; + + void (*handler) (void); + + int debouncing_tick_count; + + rtems_interval last_isr_tick; + +} handler_arguments; + +/** + * @brief Object containing information on a GPIO pin. + * + * Encapsulates relevant data about a GPIO pin. + */ +typedef struct +{ + /* The pin type */ + rpi_pin pin_type; + + /* Interrupt handler arguments*/ + handler_arguments h_args; + + gpio_interrupt enabled_interrupt; + + /* GPIO input pin mode. */ + rpi_gpio_input_mode input_mode; + +} rpi_gpio_pin; + +/** @} */ + +/** + * @name gpio Usage + * + * @{ + */ + +/** + * @brief Initializes the GPIO API. + */ +extern void gpio_initialize(void); + +/** + * @brief Turns on the given pin. + */ +extern rtems_status_code gpio_set(int pin); + +/** + * @brief Turns off the given pin. + */ +extern rtems_status_code gpio_clear(int pin); + +/** + * @brief Returns the current value of a GPIO pin. + */ +extern int gpio_get_value(int pin); + +/** + * @brief Selects a GPIO pin for a specific function. + */ +extern rtems_status_code gpio_select_pin(int pin, rpi_pin type); + +/** + * @brief Setups a JTAG pin configuration. + */ +extern rtems_status_code gpio_select_jtag(void); + +/** + * @brief Setups the SPI interface on the RPI P1 GPIO header. + */ +extern rtems_status_code gpio_select_spi_p1(void); + +/** + * @brief Setups the I2C interface on the main (P1) GPIO pin header (rev2). + */ +extern rtems_status_code gpio_select_i2c_p1_rev2(void); + +/** + * @brief Configures a input GPIO pin pull-up resistor. + */ +extern rtems_status_code gpio_input_mode(int pin, rpi_gpio_input_mode mode); + +/** + * @brief Configures several input GPIO pins to the same pull-up resistor setup. + */ +extern rtems_status_code +gpio_setup_input_mode(int *pin, int pin_count, rpi_gpio_input_mode mode); + +/** + * @brief Discards any configuration made on this pin. + */ +extern rtems_status_code gpio_disable_pin(int dev_pin); + +/** + * @brief Debouces a switch by requiring a number of clock ticks to + * pass between interruts. + */ +extern rtems_status_code gpio_debounce_switch(int pin, int ticks); + +/** + * @brief Enables interrupts on the given GPIO pin. + */ +extern rtems_status_code gpio_enable_interrupt( +int dev_pin, +gpio_interrupt interrupt, +void (*handler) (void) +); + +/** + * @brief Disables any interrupt enabled on the given GPIO pin. + */ +extern rtems_status_code gpio_disable_interrupt(int dev_pin); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LIBBSP_ARM_RASPBERRYPI_GPIO_H */ 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..3224048 --- /dev/null +++ b/c/src/lib/libbsp/arm/raspberrypi/include/i2c.h @@ -0,0 +1,183 @@ +/** + * @file i2c.h + * + * @ingroup raspberrypi_i2c + * + * @brief Raspberry Pi specific I2C and SPI definitions. + */ + +/* + * Copyright (c) 2014 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 <rtems/libi2c.h> + +/** + * @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 data structures. + * + * @{ + */ + +/** + * @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; + + /* If set to 0 uses 3-wire SPI, with 2 separate data lines (MOSI and MISO), + * if set to 1 uses 2-wire SPI, where the MOSI data line doubles as the + * slave out (SO) and slave in (SI) data lines. */ + int bidirectional; + uint32_t current_slave_addr; + rtems_id irq_sema_id; + int irq_write; +} bcm2835_spi_softc_t; + +typedef struct { + rtems_libi2c_bus_t bus_desc; + bcm2835_spi_softc_t softc; +} bcm2835_spi_desc_t; + +/** @} */ + +/** + * @name SPI directives. + * + * @{ + */ + +rtems_status_code bcm2835_spi_init(rtems_libi2c_bus_t * bushdl); + +rtems_status_code bcm2835_spi_send_start(rtems_libi2c_bus_t * bushdl); + +rtems_status_code bcm2835_spi_stop(rtems_libi2c_bus_t * bushdl); + +rtems_status_code +bcm2835_spi_send_addr(rtems_libi2c_bus_t * bushdl, uint32_t addr, int rw); + +int bcm2835_spi_read_bytes( +rtems_libi2c_bus_t * bushdl, +unsigned char *bytes, +int nbytes +); + +int bcm2835_spi_write_bytes( +rtems_libi2c_bus_t * bushdl, +unsigned char *bytes, +int nbytes); + +int bcm2835_spi_ioctl(rtems_libi2c_bus_t * bushdl, int cmd, void *arg); + +int BSP_spi_register_drivers(int spi_bus_number); + +int BSP_spi_init(void); + +/** @} */ + +/** + * @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 + +/** @} */ + +/** + * @name I2C data structures. + * + * @{ + */ + +typedef struct { + int initialized; + rtems_id irq_sema_id; +} bcm2835_i2c_softc_t; + +typedef struct { + rtems_libi2c_bus_t bus_desc; + bcm2835_i2c_softc_t softc; +} bcm2835_i2c_desc_t; + +/** @} */ + +/** + * @name I2C directives. + * + * @{ + */ + +rtems_status_code bcm2835_i2c_init(rtems_libi2c_bus_t * bushdl); + +rtems_status_code bcm2835_i2c_send_start(rtems_libi2c_bus_t * bushdl); + +rtems_status_code bcm2835_i2c_stop(rtems_libi2c_bus_t * bushdl); + +rtems_status_code +bcm2835_i2c_send_addr(rtems_libi2c_bus_t * bushdl, uint32_t addr, int rw); + +int bcm2835_i2c_read_bytes( +rtems_libi2c_bus_t * bushdl, +unsigned char *bytes, +int nbytes +); + +int bcm2835_i2c_write_bytes( +rtems_libi2c_bus_t * bushdl, +unsigned char *bytes, +int nbytes +); + +int bcm2835_i2c_ioctl(rtems_libi2c_bus_t * bushdl, int cmd, void *arg); + +int BSP_i2c_register_drivers(int i2c_bus_number); + +int BSP_i2c_init(void); + +/** @} */ + +#endif /* LIBBSP_ARM_RASPBERRYPI_I2C_H */ diff --git a/c/src/lib/libbsp/arm/raspberrypi/include/irq.h b/c/src/lib/libbsp/arm/raspberrypi/include/irq.h index 8436c2d..fb5f90e 100644 --- a/c/src/lib/libbsp/arm/raspberrypi/include/irq.h +++ b/c/src/lib/libbsp/arm/raspberrypi/include/irq.h @@ -1,5 +1,5 @@ /** - * @file + * @file irq.h * * @ingroup raspberrypi_interrupt * @@ -7,7 +7,8 @@ */ /** - * Copyright (c) 2013 Alan Cudmore + * Copyright (c) 2014 Andre Marques <andre.lousa.marques at gmail.com> + * Copyright (c) 2013 Alan Cudmore * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at @@ -62,6 +63,7 @@ #define BSP_IRQ_COUNT (BCM2835_INTC_TOTAL_IRQ) +rtems_status_code bsp_interrupt_vector_enable(rtems_vector_number vector); void raspberrypi_set_exception_handler(Arm_symbolic_exception_name exception, void (*handler)(void)); diff --git a/c/src/lib/libbsp/arm/raspberrypi/include/raspberrypi.h b/c/src/lib/libbsp/arm/raspberrypi/include/raspberrypi.h index 4cc7eec..2e902c6 100644 --- a/c/src/lib/libbsp/arm/raspberrypi/include/raspberrypi.h +++ b/c/src/lib/libbsp/arm/raspberrypi/include/raspberrypi.h @@ -1,6 +1,5 @@ - /** - * @file + * @file raspberrypi.h * * @ingroup raspberrypi_reg * @@ -8,7 +7,8 @@ */ /* - * Copyright (c) 2013 Alan Cudmore. + * Copyright (c) 2014 Andre Marques <andre.lousa.marques at gmail.com> + * Copyright (c) 2013 Alan Cudmore. * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at @@ -79,8 +79,16 @@ #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_GPPUDCLK0 (BCM2835_GPIO_REGS_BASE+0x98) /** @} */ @@ -107,14 +115,12 @@ /** @} */ - /** * @name UART 0 (PL011) Registers * * @{ */ - #define BCM2835_UART0_BASE (0x20201000) #define BCM2835_UART0_DR (BCM2835_UART0_BASE+0x00) @@ -145,9 +151,68 @@ #define BCM2835_UART0_ICR_RX 0x10 #define BCM2835_UART0_ICR_TX 0x20 +/** @} */ + +/** + * @name I2C (BSC) Registers + * + * @{ + */ + +#define BCM2835_I2C_BASE (0x20804000) + +#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) /** @} */ +/** + * @name SPI Registers + * + * @{ + */ + +#define BCM2835_SPI_BASE (0x20204000) + +#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) + +/** @} */ + +/** + * @name I2C/SPI slave BSC Registers + * + * @{ + */ + +#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) + +/** @} */ /** * @name IRQ Registers @@ -170,7 +235,6 @@ /** @} */ - /** * @name GPU Timer Registers * @@ -194,7 +258,6 @@ /** @} */ - /** @} */ #endif /* LIBBSP_ARM_RASPBERRYPI_RASPBERRYPI_H */ diff --git a/c/src/lib/libbsp/arm/raspberrypi/irq/irq.c b/c/src/lib/libbsp/arm/raspberrypi/irq/irq.c index 4132ef9..c2c7d89 100644 --- a/c/src/lib/libbsp/arm/raspberrypi/irq/irq.c +++ b/c/src/lib/libbsp/arm/raspberrypi/irq/irq.c @@ -1,5 +1,5 @@ /** - * @file + * @file irq.c * * @ingroup raspberrypi_interrupt * @@ -7,6 +7,8 @@ */ /* + * Copyright (c) 2014 Andre Marques <andre.lousa.marques at gmail.com> + * * Copyright (c) 2009 * embedded brains GmbH * Obere Lagerstr. 30 @@ -52,22 +54,47 @@ void bsp_interrupt_dispatch(void) rtems_vector_number vector = 255; /* ARM timer */ - if (BCM2835_REG(BCM2835_IRQ_BASIC) && 0x1) + if ( BCM2835_REG(BCM2835_IRQ_BASIC) & 0x1 ) { vector = BCM2835_IRQ_ID_TIMER_0; - } /* UART 0 */ - else if ( BCM2835_REG(BCM2835_IRQ_BASIC) && BCM2835_BIT(19)) + else if ( BCM2835_REG(BCM2835_IRQ_BASIC) & BCM2835_BIT(19) ) { vector = BCM2835_IRQ_ID_UART; } + /* GPIO 0*/ + else if ( BCM2835_REG(BCM2835_IRQ_PENDING2) & BCM2835_BIT(17) ) + { + vector = BCM2835_IRQ_ID_GPIO_0; + } + else if ( BCM2835_REG(BCM2835_IRQ_PENDING2) & BCM2835_BIT(18) ) + { + vector = BCM2835_IRQ_ID_GPIO_1; + } + else if ( BCM2835_REG(BCM2835_IRQ_PENDING2) & BCM2835_BIT(19) ) + { + vector = BCM2835_IRQ_ID_GPIO_2; + } + else if ( BCM2835_REG(BCM2835_IRQ_PENDING2) & BCM2835_BIT(20) ) + { + vector = BCM2835_IRQ_ID_GPIO_3; + } + /* I2C */ + else if ( BCM2835_REG(BCM2835_IRQ_PENDING2) & BCM2835_BIT(21) ) + { + vector = BCM2835_IRQ_ID_I2C; + } + /* SPI */ + else if ( BCM2835_REG(BCM2835_IRQ_PENDING2) & BCM2835_BIT(22) ) + { + vector = BCM2835_IRQ_ID_SPI; + } if ( vector < 255 ) { bsp_interrupt_handler_dispatch(vector); } - } rtems_status_code bsp_interrupt_vector_enable(rtems_vector_number vector) @@ -76,7 +103,7 @@ rtems_status_code bsp_interrupt_vector_enable(rtems_vector_number vector) rtems_interrupt_disable(level); - /* ARM Timer */ + /* ARM Timer */ if ( vector == BCM2835_IRQ_ID_TIMER_0 ) { BCM2835_REG(BCM2835_IRQ_ENABLE_BASIC) = 0x1; @@ -85,8 +112,38 @@ rtems_status_code bsp_interrupt_vector_enable(rtems_vector_number vector) else if ( vector == BCM2835_IRQ_ID_UART ) { BCM2835_REG(BCM2835_IRQ_ENABLE2) = BCM2835_BIT(25); - } + /* GPIO 0 */ + else if ( vector == BCM2835_IRQ_ID_GPIO_0 ) + { + BCM2835_REG(BCM2835_IRQ_ENABLE2) = BCM2835_BIT(17); + } + /* GPIO 1 */ + else if ( vector == BCM2835_IRQ_ID_GPIO_1 ) + { + BCM2835_REG(BCM2835_IRQ_ENABLE2) = BCM2835_BIT(18); + } + /* GPIO 2 */ + else if ( vector == BCM2835_IRQ_ID_GPIO_2 ) + { + BCM2835_REG(BCM2835_IRQ_ENABLE2) = BCM2835_BIT(19); + } + /* GPIO 3 */ + else if ( vector == BCM2835_IRQ_ID_GPIO_3 ) + { + BCM2835_REG(BCM2835_IRQ_ENABLE2) = BCM2835_BIT(20); + } + /* I2C */ + else if ( vector == BCM2835_IRQ_ID_I2C ) + { + BCM2835_REG(BCM2835_IRQ_ENABLE2) = BCM2835_BIT(21); + } + /* SPI */ + else if ( vector == BCM2835_IRQ_ID_SPI ) + { + BCM2835_REG(BCM2835_IRQ_ENABLE2) = BCM2835_BIT(22); + } + rtems_interrupt_enable(level); return RTEMS_SUCCESSFUL; @@ -106,12 +163,42 @@ rtems_status_code bsp_interrupt_vector_disable(rtems_vector_number vector) { BCM2835_REG(BCM2835_IRQ_DISABLE2) = BCM2835_BIT(25); } + /* GPIO 0 */ + else if ( vector == BCM2835_IRQ_ID_GPIO_0 ) + { + BCM2835_REG(BCM2835_IRQ_DISABLE2) = BCM2835_BIT(17); + } + /* GPIO 1 */ + else if ( vector == BCM2835_IRQ_ID_GPIO_1 ) + { + BCM2835_REG(BCM2835_IRQ_DISABLE2) = BCM2835_BIT(18); + } + /* GPIO 2 */ + else if ( vector == BCM2835_IRQ_ID_GPIO_2 ) + { + BCM2835_REG(BCM2835_IRQ_DISABLE2) = BCM2835_BIT(19); + } + /* GPIO 3 */ + else if ( vector == BCM2835_IRQ_ID_GPIO_3 ) + { + BCM2835_REG(BCM2835_IRQ_DISABLE2) = BCM2835_BIT(20); + } + /* I2C */ + else if ( vector == BCM2835_IRQ_ID_I2C ) + { + BCM2835_REG(BCM2835_IRQ_DISABLE2) = BCM2835_BIT(21); + } + /* SPI */ + else if ( vector == BCM2835_IRQ_ID_SPI ) + { + BCM2835_REG(BCM2835_IRQ_DISABLE2) = BCM2835_BIT(22); + } + rtems_interrupt_enable(level); return RTEMS_SUCCESSFUL; } - void bsp_interrupt_handler_default(rtems_vector_number vector) { printk("spurious interrupt: %u\n", vector); diff --git a/c/src/lib/libbsp/arm/raspberrypi/preinstall.am b/c/src/lib/libbsp/arm/raspberrypi/preinstall.am index 70259e2..f9a87e0 100644 --- a/c/src/lib/libbsp/arm/raspberrypi/preinstall.am +++ b/c/src/lib/libbsp/arm/raspberrypi/preinstall.am @@ -130,6 +130,14 @@ $(PROJECT_INCLUDE)/bsp/raspberrypi.h: include/raspberrypi.h $(PROJECT_INCLUDE)/b $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/bsp/raspberrypi.h PREINSTALL_FILES += $(PROJECT_INCLUDE)/bsp/raspberrypi.h +$(PROJECT_INCLUDE)/bsp/gpio.h: include/gpio.h $(PROJECT_INCLUDE)/bsp/$(dirstamp) + $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/bsp/gpio.h +PREINSTALL_FILES += $(PROJECT_INCLUDE)/bsp/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)/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/startup/bspstart.c b/c/src/lib/libbsp/arm/raspberrypi/startup/bspstart.c index c5786bf..5ca6612 100644 --- a/c/src/lib/libbsp/arm/raspberrypi/startup/bspstart.c +++ b/c/src/lib/libbsp/arm/raspberrypi/startup/bspstart.c @@ -1,5 +1,5 @@ /** - * @file + * @file bspstart.c * * @ingroup arm_start * @@ -7,7 +7,8 @@ */ /* - * Copyright (c) 2013 by Alan Cudmore + * Copyright (c) 2014 Andre Marques <andre.lousa.marques at gmail.com> + * Copyright (c) 2013 by Alan Cudmore * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at @@ -22,8 +23,18 @@ #include <bsp/linker-symbols.h> #include <bsp/stackalloc.h> #include <bsp/raspberrypi.h> +#include <bsp/i2c.h> + +void bsp_predriver_hook(void) +{ + if ( BSP_ENABLE_SPI == 1 ) + BSP_spi_init(); + + if ( BSP_ENABLE_I2C == 1 ) + BSP_i2c_init(); +} void bsp_start(void) { - bsp_interrupt_initialize(); + bsp_interrupt_initialize(); } -- 2.0.4 _______________________________________________ devel mailing list devel@rtems.org http://lists.rtems.org/mailman/listinfo/devel