diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2018-04-23 09:45:28 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2018-04-23 15:18:43 +0200 |
commit | a2dad96ab736f66ed54421cad53caf31f250e181 (patch) | |
tree | 1935320b5b52276ed2e52741cdae71637f209f77 /bsps/arm | |
parent | bsps/arm: Remove unused stm32f* files (diff) | |
download | rtems-a2dad96ab736f66ed54421cad53caf31f250e181.tar.bz2 |
bsps: Move I2C drivers to bsps
This patch is a part of the BSP source reorganization.
Update #3285.
Diffstat (limited to 'bsps/arm')
-rw-r--r-- | bsps/arm/altera-cyclone-v/i2c/i2cdrv-config.c | 24 | ||||
-rw-r--r-- | bsps/arm/altera-cyclone-v/i2c/i2cdrv-config.h | 37 | ||||
-rw-r--r-- | bsps/arm/altera-cyclone-v/i2c/i2cdrv.c | 215 | ||||
-rw-r--r-- | bsps/arm/atsam/i2c/atsam_i2c_bus.c | 398 | ||||
-rw-r--r-- | bsps/arm/atsam/i2c/atsam_i2c_init.c | 64 | ||||
-rw-r--r-- | bsps/arm/beagle/i2c/bbb-i2c.c | 466 | ||||
-rw-r--r-- | bsps/arm/imx/i2c/imx-i2c.c | 442 | ||||
-rw-r--r-- | bsps/arm/lpc24xx/i2c/i2c-config.c | 91 | ||||
-rw-r--r-- | bsps/arm/lpc24xx/i2c/i2c.c | 324 | ||||
-rw-r--r-- | bsps/arm/lpc32xx/i2c/i2c.c | 251 | ||||
-rw-r--r-- | bsps/arm/raspberrypi/i2c/i2c.c | 422 | ||||
-rw-r--r-- | bsps/arm/stm32f4/i2c/i2c-config.c | 37 | ||||
-rw-r--r-- | bsps/arm/stm32f4/i2c/i2c.c | 329 | ||||
-rw-r--r-- | bsps/arm/xilinx-zynq/i2c/cadence-i2c.c | 469 |
14 files changed, 3569 insertions, 0 deletions
diff --git a/bsps/arm/altera-cyclone-v/i2c/i2cdrv-config.c b/bsps/arm/altera-cyclone-v/i2c/i2cdrv-config.c new file mode 100644 index 0000000000..3c29b61e50 --- /dev/null +++ b/bsps/arm/altera-cyclone-v/i2c/i2cdrv-config.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * <info@embedded-brains.de> + * + * 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.h> +#include "i2cdrv-config.h" + +const i2cdrv_configuration i2cdrv_config[CYCLONE_V_NO_I2C] = { + { + .controller = ALT_I2C_I2C0, + .device_name = "/dev/i2c0", + .speed = CYCLONE_V_I2C0_SPEED, + } +}; diff --git a/bsps/arm/altera-cyclone-v/i2c/i2cdrv-config.h b/bsps/arm/altera-cyclone-v/i2c/i2cdrv-config.h new file mode 100644 index 0000000000..650974751e --- /dev/null +++ b/bsps/arm/altera-cyclone-v/i2c/i2cdrv-config.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2014 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * <info@embedded-brains.de> + * + * 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 XXX_H +#define XXX_H + +#include <rtems.h> +#include <bsp/alt_i2c.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct { + ALT_I2C_CTLR_t controller; + char *device_name; + uint32_t speed; +} i2cdrv_configuration; + +extern const i2cdrv_configuration i2cdrv_config[]; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* XXX_H */ diff --git a/bsps/arm/altera-cyclone-v/i2c/i2cdrv.c b/bsps/arm/altera-cyclone-v/i2c/i2cdrv.c new file mode 100644 index 0000000000..3ea23551d8 --- /dev/null +++ b/bsps/arm/altera-cyclone-v/i2c/i2cdrv.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2014 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * <info@embedded-brains.de> + * + * 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.h> +#include <bsp/i2cdrv.h> +#include <assert.h> +#include <rtems/libio.h> +#include "i2cdrv-config.h" + +typedef struct { + ALT_I2C_DEV_t i2c_dev; + rtems_id mutex; +} i2cdrv_entry; + +i2cdrv_entry i2cdrv_table[CYCLONE_V_NO_I2C]; + +static ALT_I2C_DEV_t *get_device(i2cdrv_entry *e) +{ + return &e->i2c_dev; +} + +static rtems_status_code init_i2c_module( + i2cdrv_entry *e, + const i2cdrv_configuration *cfg +) +{ + ALT_STATUS_CODE asc = ALT_E_SUCCESS; + ALT_I2C_CTLR_t controller = cfg->controller; + ALT_I2C_DEV_t *dev = get_device(e); + ALT_I2C_MASTER_CONFIG_t i2c_cfg = { + .addr_mode = ALT_I2C_ADDR_MODE_7_BIT, + .restart_enable = false, + }; + + asc = alt_i2c_init(controller, dev); + if ( asc != ALT_E_SUCCESS ) { + return RTEMS_IO_ERROR; + } + asc = alt_i2c_op_mode_set(dev, ALT_I2C_MODE_MASTER); + if ( asc != ALT_E_SUCCESS ) { + return RTEMS_IO_ERROR; + } + asc = alt_i2c_master_config_speed_set(dev, &i2c_cfg, cfg->speed); + if ( asc != ALT_E_SUCCESS ) { + return RTEMS_IO_ERROR; + } + asc = alt_i2c_master_config_set(dev, &i2c_cfg); + if ( asc != ALT_E_SUCCESS ) { + return RTEMS_IO_ERROR; + } + asc = alt_i2c_enable(dev); + if ( asc != ALT_E_SUCCESS ) { + return RTEMS_IO_ERROR; + } + + return RTEMS_SUCCESSFUL; +} + +rtems_device_driver i2cdrv_initialize( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + for ( size_t i = 0; i < CYCLONE_V_NO_I2C; ++i ) { + i2cdrv_entry *e = &i2cdrv_table[i]; + const i2cdrv_configuration *cfg = &i2cdrv_config[i]; + + sc = rtems_io_register_name(cfg->device_name, major, i); + assert(sc == RTEMS_SUCCESSFUL); + + sc = rtems_semaphore_create( + rtems_build_name ('I', '2', 'C', '0' + i), + 0, + RTEMS_BINARY_SEMAPHORE | RTEMS_PRIORITY | RTEMS_INHERIT_PRIORITY, + 0, + &e->mutex + ); + assert(sc == RTEMS_SUCCESSFUL); + + sc = init_i2c_module(e, cfg); + if ( sc != RTEMS_SUCCESSFUL ) { + /* I2C is not usable at this point. Releasing the mutex would allow the + * usage which could lead to undefined behaviour. */ + return sc; + } + + sc = rtems_semaphore_release(e->mutex); + assert(sc == RTEMS_SUCCESSFUL); + } + + return sc; +} + +rtems_device_driver i2cdrv_open( + rtems_device_major_number major, + rtems_device_major_number minor, + void *arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + i2cdrv_entry *e = &i2cdrv_table[minor]; + + sc = rtems_semaphore_obtain(e->mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + return sc; +} + +rtems_device_driver i2cdrv_close( + rtems_device_major_number major, + rtems_device_major_number minor, + void *arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + i2cdrv_entry *e = &i2cdrv_table[minor]; + + sc = rtems_semaphore_release(e->mutex); + return sc; +} + +rtems_device_driver i2cdrv_read( + rtems_device_major_number major, + rtems_device_major_number minor, + void *arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + i2cdrv_entry *e = &i2cdrv_table[minor]; + rtems_libio_rw_args_t *rw = arg; + ALT_I2C_DEV_t *dev = get_device(e); + ALT_STATUS_CODE asc = ALT_E_SUCCESS; + + asc = alt_i2c_master_receive(dev, rw->buffer, rw->count, true, true); + if ( asc == ALT_E_SUCCESS ) { + rw->bytes_moved = rw->count; + } else { + sc = RTEMS_IO_ERROR; + } + + return sc; +} + +rtems_device_driver i2cdrv_write( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + i2cdrv_entry *e = &i2cdrv_table[minor]; + rtems_libio_rw_args_t *rw = arg; + ALT_I2C_DEV_t *dev = get_device(e); + ALT_STATUS_CODE asc = ALT_E_SUCCESS; + + asc = alt_i2c_master_transmit(dev, rw->buffer, rw->count, true, true); + if ( asc == ALT_E_SUCCESS ) { + rw->bytes_moved = rw->count; + } else { + sc = RTEMS_IO_ERROR; + } + + return sc; +} + +static rtems_status_code ioctl_set_slave_address( + i2cdrv_entry *e, + rtems_libio_ioctl_args_t *args +) +{ + ALT_I2C_DEV_t *dev = get_device(e); + ALT_STATUS_CODE asc = ALT_E_SUCCESS; + uint32_t address = (uint32_t) args->buffer; + + asc = alt_i2c_master_target_set(dev, address); + if ( asc != ALT_E_SUCCESS ) { + return RTEMS_IO_ERROR; + } + + return RTEMS_SUCCESSFUL; +} + +rtems_device_driver i2cdrv_ioctl( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + i2cdrv_entry *e = &i2cdrv_table[minor]; + rtems_libio_ioctl_args_t *args = arg; + + switch (args->command) { + case I2C_IOC_SET_SLAVE_ADDRESS: + sc = ioctl_set_slave_address(e, args); + break; + default: + sc = RTEMS_INVALID_NUMBER; + break; + } + + return sc; +} diff --git a/bsps/arm/atsam/i2c/atsam_i2c_bus.c b/bsps/arm/atsam/i2c/atsam_i2c_bus.c new file mode 100644 index 0000000000..bace7e656e --- /dev/null +++ b/bsps/arm/atsam/i2c/atsam_i2c_bus.c @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2016 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * <info@embedded-brains.de> + * + * 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/atsam-clock-config.h> +#include <bsp/atsam-i2c.h> + +#include <rtems/irq-extension.h> + +#define ATSAMV_I2C_IRQ_ERROR \ + (TWIHS_IDR_ARBLST \ + | TWIHS_IDR_TOUT \ + | TWIHS_IDR_OVRE \ + | TWIHS_IDR_UNRE \ + | TWIHS_IDR_NACK) + +#define TEN_BIT_MASK 0xFC00 +#define SEVEN_BIT_MASK 0xFF80 +#define TEN_BIT_START_ADDR_MASK 0x78 +#define LAST_TWO_BITS_MASK 0x03 +#define LAST_BYTE_MASK 0x00FF + +static void +atsam_i2c_disable_interrupts(Twihs *regs) +{ + regs->TWIHS_IDR = 0xFFFFFFFF; +} + +static void +atsam_i2c_set_transfer_status(transfer_desc *trans_desc, transfer_state state) +{ + trans_desc->trans_state = state; +} + +static void +atsam_i2c_continue_read(Twihs *regs, transfer_desc *trans_desc) +{ + trans_desc->data[trans_desc->already_transferred] = TWI_ReadByte(regs); + trans_desc->already_transferred++; + + /* check for transfer finish */ + if (trans_desc->already_transferred == trans_desc->data_size) { + if (trans_desc->stop_request){ + TWI_DisableIt(regs, TWIHS_IDR_RXRDY); + TWI_EnableIt(regs, TWIHS_IER_TXCOMP); + atsam_i2c_set_transfer_status(trans_desc, TX_RX_STOP_SENT); + } else { + atsam_i2c_set_transfer_status(trans_desc, RX_CONT_MESSAGE_NEEDED); + } + } + /* Last byte? */ + else if ((trans_desc->already_transferred == (trans_desc->data_size - 1)) + && (trans_desc->stop_request)){ + TWI_Stop(regs); + } +} + +static bool +atsam_i2c_is_state(transfer_desc *trans_desc, transfer_state state) +{ + return (trans_desc->trans_state == state); +} + +static void +atsam_i2c_continue_write(Twihs *regs, transfer_desc *trans_desc) +{ + /* Transfer finished ? */ + if (trans_desc->already_transferred == trans_desc->data_size) { + TWI_DisableIt(regs, TWIHS_IDR_TXRDY); + if (trans_desc->stop_request){ + TWI_EnableIt(regs, TWIHS_IER_TXCOMP); + TWI_SendSTOPCondition(regs); + atsam_i2c_set_transfer_status(trans_desc, TX_RX_STOP_SENT); + } else { + atsam_i2c_set_transfer_status(trans_desc, TX_CONT_MESSAGE_NEEDED); + } + } + /* Bytes remaining */ + else { + TWI_WriteByte(regs, + trans_desc->data[trans_desc->already_transferred]); + trans_desc->already_transferred++; + } +} + +static void +atsam_i2c_finish_write_transfer(Twihs *regs, transfer_desc *trans_desc) +{ + TWI_ReadByte(regs); + TWI_DisableIt(regs, TWIHS_IDR_TXCOMP); + trans_desc->status = 0; +} + +static void +atsam_i2c_next_packet(atsam_i2c_bus *bus) +{ + i2c_msg *msg; + + ++bus->msgs; + --bus->msg_todo; + + msg = &bus->msgs[0]; + + bus->current_msg_todo = msg->len; + bus->current_msg_byte = msg->buf; +} + +static void +atsam_i2c_set_td(atsam_i2c_bus *bus, uint32_t already_transferred, + bool stop_needed) +{ + transfer_desc *trans_desc = &bus->trans_desc; + + trans_desc->status = ASYNC_STATUS_PENDING; + trans_desc->data = bus->current_msg_byte; + trans_desc->data_size = bus->current_msg_todo; + trans_desc->already_transferred = already_transferred; + trans_desc->stop_request = stop_needed; +} + +static bool +atsam_i2c_set_address_size(const i2c_msg *msg) +{ + bool rv = ((msg->flags & I2C_M_TEN) == 0) ? false : true; + return rv; +} + +static void +atsam_i2c_set_address_regs(Twihs *regs, uint16_t address, bool ten_bit_addr, + bool read_transfer) +{ + /* No internal addresses used */ + uint32_t mmr_temp = 0; + uint32_t iadr_temp = 0; + + assert(regs != NULL); + if (ten_bit_addr){ + uint8_t addr_temp = TEN_BIT_START_ADDR_MASK; + assert(address & TEN_BIT_MASK); + mmr_temp = (1u << 8) | ((addr_temp & LAST_TWO_BITS_MASK) << 16); + iadr_temp = (addr_temp & LAST_BYTE_MASK); + } else { + assert((address & SEVEN_BIT_MASK) == 0); + mmr_temp = (address << 16); + } + + if (read_transfer){ + mmr_temp |= TWIHS_MMR_MREAD; + } + + /* Set slave and number of internal address bytes */ + regs->TWIHS_MMR = 0; + regs->TWIHS_MMR = mmr_temp; + + /* Set internal address bytes */ + regs->TWIHS_IADR = 0; + regs->TWIHS_IADR = iadr_temp; +} + +static void +atsam_i2c_setup_read_transfer(Twihs *regs, bool ctrl, uint16_t slave_addr, + bool send_stop) +{ + TWI_EnableIt(regs, TWIHS_IER_RXRDY); + atsam_i2c_set_address_regs(regs, slave_addr, ctrl, true); + if (send_stop){ + regs->TWIHS_CR = TWIHS_CR_START | TWIHS_CR_STOP; + } else { + regs->TWIHS_CR = TWIHS_CR_START; + } +} + +static void +atsam_i2c_setup_write_transfer(atsam_i2c_bus *bus, Twihs *regs, bool ctrl, + uint16_t slave_addr) +{ + atsam_i2c_set_address_regs(regs, slave_addr, ctrl, false); + TWI_WriteByte(regs, *bus->current_msg_byte); + TWI_EnableIt(regs, TWIHS_IER_TXRDY); +} + +static void +atsam_i2c_setup_transfer(atsam_i2c_bus *bus, Twihs *regs) +{ + const i2c_msg *msgs = bus->msgs; + bool send_stop = false; + uint32_t msg_todo = bus->msg_todo; + uint16_t slave_addr; + bool ten_bit_addr = false; + uint32_t already_transferred; + bool stop_needed = true; + + ten_bit_addr = atsam_i2c_set_address_size(msgs); + + if ((msg_todo > 1) && ((msgs[1].flags & I2C_M_NOSTART) != 0)){ + stop_needed = false; + } + + bus->read = (msgs->flags & I2C_M_RD) != 0; + slave_addr = msgs->addr; + already_transferred = (bus->read == true) ? 0 : 1; + atsam_i2c_set_td(bus, already_transferred, stop_needed); + + transfer_desc *trans_desc = &bus->trans_desc; + + if (bus->read){ + if (bus->current_msg_todo == 1){ + send_stop = true; + } + atsam_i2c_set_transfer_status(trans_desc, RX_SEND_DATA); + atsam_i2c_setup_read_transfer(regs, ten_bit_addr, + slave_addr, send_stop); + } else { + atsam_i2c_set_transfer_status(trans_desc, TX_SEND_DATA); + atsam_i2c_setup_write_transfer(bus, regs, ten_bit_addr, + slave_addr); + } +} + +static void +atsam_i2c_interrupt(void *arg) +{ + atsam_i2c_bus *bus = arg; + uint32_t irqstatus; + bool done = false; + transfer_desc *trans_desc; + + Twihs *regs = bus->regs; + + /* read interrupts */ + irqstatus = regs->TWIHS_SR; + + if((irqstatus & (TWIHS_SR_ARBLST | TWIHS_SR_NACK)) != 0) { + done = true; + } + + trans_desc = &bus->trans_desc; + + if (((irqstatus & TWIHS_SR_RXRDY) != 0) && + (atsam_i2c_is_state(trans_desc, RX_SEND_DATA))){ + /* carry on read transfer */ + atsam_i2c_continue_read(regs, trans_desc); + } else if (((irqstatus & TWIHS_SR_TXCOMP) != 0) && + (atsam_i2c_is_state(trans_desc, TX_RX_STOP_SENT))){ + atsam_i2c_finish_write_transfer(regs, trans_desc); + done = true; + } else if (((irqstatus & TWIHS_SR_TXRDY) != 0) && + (atsam_i2c_is_state(trans_desc, TX_SEND_DATA))){ + atsam_i2c_continue_write(regs, trans_desc); + if (trans_desc->trans_state == TX_CONT_MESSAGE_NEEDED){ + done = true; + } + } + + if(done){ + uint32_t err = irqstatus & ATSAMV_I2C_IRQ_ERROR; + + atsam_i2c_next_packet(bus); + if (bus->msg_todo == 0 || err != 0) { + rtems_status_code sc; + + atsam_i2c_disable_interrupts(regs); + sc = rtems_event_transient_send(bus->task_id); + assert(sc == RTEMS_SUCCESSFUL); + } else { + atsam_i2c_setup_transfer(bus, regs); + } + } +} + +static int +atsam_i2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t msg_count) +{ + rtems_status_code sc; + atsam_i2c_bus *bus = (atsam_i2c_bus *)base; + Twihs *regs; + uint32_t i; + + rtems_task_wake_after(1); + + if (msg_count < 1){ + return 1; + } + + for (i = 0; i < msg_count; ++i) { + if ((msgs[i].flags & I2C_M_RECV_LEN) != 0) { + return -EINVAL; + } + } + + bus->msgs = &msgs[0]; + bus->msg_todo = msg_count; + bus->current_msg_todo = msgs[0].len; + bus->current_msg_byte = msgs[0].buf; + bus->task_id = rtems_task_self(); + + regs = bus->regs; + + atsam_i2c_setup_transfer(bus, regs); + + regs->TWIHS_IER = ATSAMV_I2C_IRQ_ERROR; + + sc = rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout); + if (sc != RTEMS_SUCCESSFUL){ + rtems_event_transient_clear(); + return -ETIMEDOUT; + } + + return 0; +} + +static int +atsam_i2c_set_clock(i2c_bus *base, unsigned long clock) +{ + atsam_i2c_bus *bus = (atsam_i2c_bus *)base; + Twihs *regs = bus->regs; + TWI_ConfigureMaster(regs, clock, BOARD_MCK); + return 0; +} + +static void +atsam_i2c_destroy(i2c_bus *base) +{ + atsam_i2c_bus *bus = (atsam_i2c_bus *)base; + rtems_status_code sc; + + sc = rtems_interrupt_handler_remove(bus->irq, atsam_i2c_interrupt, bus); + assert(sc == RTEMS_SUCCESSFUL); + + i2c_bus_destroy_and_free(&bus->base); +} + +static void +atsam_i2c_init(atsam_i2c_bus *bus, uint32_t input_clock, Twihs *board_base, + uint32_t board_id, const Pin *pins) +{ + + /* Initialize the TWI */ + PIO_Configure(pins, TWI_AMOUNT_PINS); + + /* Enable the TWI clock */ + ENABLE_PERIPHERAL(board_id); + + TWI_ConfigureMaster(board_base, input_clock, BOARD_MCK); +} + +int +i2c_bus_register_atsam( + const char *bus_path, + Twihs *register_base, + rtems_vector_number irq, + const Pin pins[TWI_AMOUNT_PINS] +) +{ + atsam_i2c_bus *bus; + rtems_status_code sc; + uint32_t board_id = (uint32_t) irq; + + bus = (atsam_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus)); + if (bus == NULL){ + return -1; + } + + bus->regs = register_base; + bus->irq = irq; + + atsam_i2c_init(bus, I2C_BUS_CLOCK_DEFAULT, bus->regs, + board_id, pins); + + sc = rtems_interrupt_handler_install( + irq, + "Atsamv_I2C", + RTEMS_INTERRUPT_UNIQUE, + atsam_i2c_interrupt, + bus + ); + if(sc != RTEMS_SUCCESSFUL){ + (*bus->base.destroy)(&bus->base); + + rtems_set_errno_and_return_minus_one(EIO); + } + + bus->base.transfer = atsam_i2c_transfer; + bus->base.set_clock = atsam_i2c_set_clock; + bus->base.destroy = atsam_i2c_destroy; + + return i2c_bus_register(&bus->base, bus_path); +} diff --git a/bsps/arm/atsam/i2c/atsam_i2c_init.c b/bsps/arm/atsam/i2c/atsam_i2c_init.c new file mode 100644 index 0000000000..1c31fbe899 --- /dev/null +++ b/bsps/arm/atsam/i2c/atsam_i2c_init.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * <info@embedded-brains.de> + * + * 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/atsam-i2c.h> +#include <bsp/i2c.h> + +/** TWI0 data pin */ +#define PIN_TWI_TWD0 {PIO_PA3A_TWD0, PIOA, ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT} +/** TWI0 clock pin */ +#define PIN_TWI_TWCK0 {PIO_PA4A_TWCK0, PIOA, ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT} + +/** TWI1 data pin */ +#define PIN_TWI_TWD1 {PIO_PB4A_TWD1, PIOB, ID_PIOB, PIO_PERIPH_A, PIO_DEFAULT} +/** TWI1 clock pin */ +#define PIN_TWI_TWCK1 {PIO_PB5A_TWCK1, PIOB, ID_PIOB, PIO_PERIPH_A, PIO_DEFAULT} + +/** TWI2 data pin */ +#define PIN_TWI_TWD2 {PIO_PD27C_TWD2, PIOD, ID_PIOD, PIO_PERIPH_C, PIO_DEFAULT} +/** TWI2 clock pin */ +#define PIN_TWI_TWCK2 {PIO_PD28C_TWCK2, PIOD, ID_PIOD, PIO_PERIPH_C, PIO_DEFAULT} + +int atsam_register_i2c_0(void) +{ + static const Pin pins[] = {PIN_TWI_TWD0, PIN_TWI_TWCK0}; + + return i2c_bus_register_atsam( + ATSAM_I2C_0_BUS_PATH, + TWIHS0, + ID_TWIHS0, + pins); +} + +int atsam_register_i2c_1(void) +{ + static const Pin pins[] = {PIN_TWI_TWD1, PIN_TWI_TWCK1}; + + return i2c_bus_register_atsam( + ATSAM_I2C_1_BUS_PATH, + TWIHS1, + ID_TWIHS1, + pins); +} + +int atsam_register_i2c_2(void) +{ + static const Pin pins[] = {PIN_TWI_TWD2, PIN_TWI_TWCK2}; + + return i2c_bus_register_atsam( + ATSAM_I2C_2_BUS_PATH, + TWIHS2, + ID_TWIHS2, + pins); +} diff --git a/bsps/arm/beagle/i2c/bbb-i2c.c b/bsps/arm/beagle/i2c/bbb-i2c.c new file mode 100644 index 0000000000..3a8637d457 --- /dev/null +++ b/bsps/arm/beagle/i2c/bbb-i2c.c @@ -0,0 +1,466 @@ +/** + * @file + * + * @ingroup arm_beagle + * + * @brief BeagleBoard I2C bus initialization and API Support. + */ + +/* + * Copyright (c) 2016 Punit Vara <punitvara@gmail.com> + * Copyright (c) 2017 Sichen Zhao <zsc19940506@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. + */ + +/* + * Modified on Punit Vara<punitvara@gmail.com> works, currently + * the i2c file is working on the Beaglebone Black board(AM335x) + */ + +#include <stdio.h> +#include <bsp/i2c.h> +#include <libcpu/am335x.h> +#include <rtems/irq-extension.h> +#include <rtems/counter.h> +#include <bsp/bbb-gpio.h> +#include <rtems/score/assert.h> + +static void am335x_i2c0_pinmux( bbb_i2c_bus *bus ) +{ + REG( bus->regs + AM335X_CONF_I2C0_SDA ) = + ( BBB_RXACTIVE | BBB_SLEWCTRL | BBB_PU_EN ); + + REG( bus->regs + AM335X_CONF_I2C0_SCL ) = + ( BBB_RXACTIVE | BBB_SLEWCTRL | BBB_PU_EN ); +} + +static void I2C0ModuleClkConfig( void ) +{ + /* Writing to MODULEMODE field of AM335X_CM_WKUP_I2C0_CLKCTRL register. */ + REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_I2C0_CLKCTRL ) |= + AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE_ENABLE; + + /* Waiting for MODULEMODE field to reflect the written value. */ + while ( AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE_ENABLE != + ( REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_I2C0_CLKCTRL ) & + AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE ) ) ; + + /* + * Waiting for IDLEST field in AM335X_CM_WKUP_CONTROL_CLKCTRL + * register to attain desired value. + */ + while ( ( AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST_FUNC << + AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST_SHIFT ) != + ( REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_CONTROL_CLKCTRL ) & + AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST ) ) ; + + /* + * Waiting for CLKACTIVITY_I2C0_GFCLK field in AM335X_CM_WKUP_CLKSTCTRL + * register to attain desired value. + */ + while ( AM335X_CM_WKUP_CLKSTCTRL_CLKACTIVITY_I2C0_GFCLK != + ( REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_CLKSTCTRL ) & + AM335X_CM_WKUP_CLKSTCTRL_CLKACTIVITY_I2C0_GFCLK ) ) ; + + /* + * Waiting for IDLEST field in AM335X_CM_WKUP_I2C0_CLKCTRL register to attain + * desired value. + */ + while ( ( AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST_FUNC << + AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST_SHIFT ) != + ( REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_I2C0_CLKCTRL ) & + AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST ) ) ; +} + +static void am335x_i2c_reset( bbb_i2c_bus *bus ) +{ + volatile bbb_i2c_regs *regs = bus->regs; + int timeout = I2C_TIMEOUT; + + if ( REG( ®s->BBB_I2C_CON ) & BBB_I2C_CON_EN ) { + REG( ®s->BBB_I2C_CON ) = BBB_I2C_CON_CLR; + udelay( 50000 ); + } + + REG( ®s->BBB_I2C_SYSC ) = BBB_I2C_SYSC_SRST; /* for ES2 after soft reset */ + udelay( 1000 ); + REG( ®s->BBB_I2C_CON ) = BBB_I2C_CON_EN; + + while ( !( REG( ®s->BBB_I2C_SYSS ) & BBB_I2C_SYSS_RDONE ) && timeout-- ) { + if ( timeout <= 0 ) { + puts( "ERROR: Timeout in soft-reset\n" ); + + return; + } + + udelay( 1000 ); + } +} +/* + * Possible values for msg->flag + * - @ref I2C_M_TEN, + * - @ref I2C_M_RD, + * - @ref I2C_M_STOP, + * - @ref I2C_M_NOSTART, + * - @ref I2C_M_REV_DIR_ADDR, + * - @ref I2C_M_IGNORE_NAK, + * - @ref I2C_M_NO_RD_ACK, and + * - @ref I2C_M_RECV_LEN. + */ + +static void am335x_i2c_set_address_size( + const i2c_msg *msgs, + volatile bbb_i2c_regs *regs +) +{ + /* + * Can be configured multiple modes here. + * Need to think about own address modes + */ + if ( ( msgs->flags & I2C_M_TEN ) == 0 ) { + /* 7-bit mode slave address mode */ + REG( ®s->BBB_I2C_CON ) = AM335X_I2C_CFG_7BIT_SLAVE_ADDR; + } else { + /* 10-bit slave address mode */ + REG( ®s->BBB_I2C_CON ) = AM335X_I2C_CFG_10BIT_SLAVE_ADDR; + } +} + +static void am335x_i2c_next_byte( bbb_i2c_bus *bus ) +{ + i2c_msg *msg; + + ++bus->msgs; + --bus->msg_todo; + msg = &bus->msgs[ 0 ]; + bus->current_msg_todo = msg->len; + bus->current_msg_byte = msg->buf; +} + +static void am335x_i2c_masterint_enable( + volatile bbb_i2c_regs *regs, + unsigned int flag +) +{ + REG( ®s->BBB_I2C_IRQENABLE_SET ) |= flag; +} + +static void am335x_i2c_masterint_disable( + volatile bbb_i2c_regs *regs, + unsigned int flag +) +{ + REG( ®s->BBB_I2C_IRQENABLE_CLR ) = flag; +} + +static void am335x_int_clear( + volatile bbb_i2c_regs *regs, + unsigned int flag +) +{ + REG( ®s->BBB_I2C_IRQSTATUS ) = flag; +} + +static void am335x_clean_interrupts( volatile bbb_i2c_regs *regs ) +{ + am335x_i2c_masterint_enable( regs, BBB_I2C_ALL_FLAGS ); + am335x_int_clear( regs, BBB_I2C_ALL_FLAGS ); + am335x_i2c_masterint_disable( regs, BBB_I2C_ALL_FLAGS ); +} + +static void am335x_i2c_setup_read_transfer( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs, + const i2c_msg *msgs, + bool send_stop +) +{ + REG( ®s->BBB_I2C_CNT ) = bus->current_msg_todo; + + REG( ®s->BBB_I2C_CON ) = AM335X_I2C_CFG_MST_RX | AM335X_I2C_CON_I2C_EN; + + if ( send_stop ) { + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_START | AM335X_I2C_CON_STOP; + } else { + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_START; + } + + am335x_i2c_masterint_enable( regs, AM335X_I2C_INT_RECV_READY | + AM335X_I2C_IRQSTATUS_ARDY ); +} + +static void am335x_i2c_continue_read_transfer( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs +) +{ + bus->current_msg_byte[ bus->already_transferred ] = + REG( ®s->BBB_I2C_DATA ); + + bus->already_transferred++; + + REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_INT_RECV_READY; + + if ( bus->already_transferred == bus->current_msg_todo - 1 ) { + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_STOP; + } +} + +static void am335x_i2c_continue_write( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs +) +{ + if ( bus->already_transferred == bus->msg_todo ) { + REG( ®s->BBB_I2C_DATA ) = + bus->current_msg_byte[ bus->already_transferred ]; + REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_XRDY; + am335x_i2c_masterint_disable( regs, AM335X_I2C_IRQSTATUS_XRDY ); + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_STOP; + } else { + writeb( bus->current_msg_byte[ bus->already_transferred ], + ®s->BBB_I2C_DATA ); + REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_XRDY; + bus->already_transferred++; + } +} + +static void am335x_i2c_setup_write_transfer( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs, + const i2c_msg *msgs +) +{ + volatile unsigned int no_bytes; + + REG( ®s->BBB_I2C_CNT ) = bus->current_msg_todo; + no_bytes = REG( ®s->BBB_I2C_CNT ); + (void) no_bytes; /* indicate we know that no_bytes is not referenced again */ + REG( ®s->BBB_I2C_SA ) = msgs->addr; + REG( ®s->BBB_I2C_CON ) = AM335X_I2C_CFG_MST_TX | AM335X_I2C_CON_I2C_EN; + am335x_clean_interrupts( regs ); + am335x_i2c_masterint_enable( regs, AM335X_I2C_IRQSTATUS_XRDY ); + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_START | AM335X_I2C_CON_STOP; +} + +static void am335x_i2c_setup_transfer( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs +) +{ + const i2c_msg *msgs = bus->msgs; + uint32_t msg_todo = bus->msg_todo; + bool send_stop = false; + uint32_t i; + + bus->current_todo = msgs[ 0 ].len; + + for ( i = 1; i < msg_todo && ( msgs[ i ].flags & I2C_M_NOSTART ) != 0; + ++i ) { + bus->current_todo += msgs[ i ].len; + } + + regs = bus->regs; + REG( &bus->regs->BBB_I2C_BUF ) |= AM335X_I2C_BUF_TXFIFO_CLR; + REG( &bus->regs->BBB_I2C_BUF ) |= AM335X_I2C_BUF_RXFIFO_CLR; + am335x_i2c_set_address_size( msgs, regs ); + bus->read = ( msgs->flags & I2C_M_RD ) != 0; + bus->already_transferred = ( bus->read == true ) ? 0 : 1; + + if ( bus->read ) { + if ( bus->current_msg_todo == 1 ) { + send_stop = true; + } + + am335x_i2c_setup_read_transfer( bus, regs, msgs, send_stop ); + } else { + am335x_i2c_setup_write_transfer( bus, regs, msgs ); + } +} + +static void am335x_i2c_interrupt( void *arg ) +{ + bbb_i2c_bus *bus = arg; + volatile bbb_i2c_regs *regs = bus->regs; + /* Get status of enabled interrupts */ + uint32_t irqstatus = REG( ®s->BBB_I2C_IRQSTATUS ); + bool done = false; + + /* + * Clear all enabled interrupt except receive ready + * and transmit ready interrupt in status register + */ + REG( ®s->BBB_I2C_IRQSTATUS ) = + ( irqstatus & ~( AM335X_I2C_IRQSTATUS_RRDY | + AM335X_I2C_IRQSTATUS_XRDY ) ); + + if ( irqstatus & AM335X_I2C_INT_RECV_READY ) { + am335x_i2c_continue_read_transfer( bus, regs ); + } + + if ( irqstatus & AM335X_I2C_IRQSTATUS_XRDY ) { + am335x_i2c_continue_write( bus, regs ); + } + + if ( irqstatus & AM335X_I2C_IRQSTATUS_NACK ) { + done = true; + am335x_i2c_masterint_disable( regs, AM335X_I2C_IRQSTATUS_NACK ); + } + + if ( irqstatus & AM335X_I2C_IRQSTATUS_ARDY ) { + done = true; + REG( ®s->BBB_I2C_IRQSTATUS ) = BBB_I2C_STAT_ARDY; + } + + if ( irqstatus & AM335X_I2C_IRQSTATUS_BF ) { + REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_BF; + } + + if ( done ) { + uint32_t err = irqstatus & BBB_I2C_IRQ_ERROR; + am335x_i2c_next_byte( bus ); + + if ( bus->msg_todo == 0 ) { + rtems_status_code sc; + am335x_i2c_masterint_disable( regs, ( AM335X_I2C_IRQSTATUS_RRDY | + AM335X_I2C_IRQSTATUS_XRDY | + AM335X_I2C_IRQSTATUS_BF ) ); + REG( ®s->BBB_I2C_IRQSTATUS ) = err; + + sc = rtems_event_transient_send( bus->task_id ); + _Assert( sc == RTEMS_SUCCESSFUL ); + (void) sc; + } else { + am335x_i2c_setup_transfer( bus, regs ); + } + } +} + +static int am335x_i2c_transfer( + i2c_bus *base, + i2c_msg *msgs, + uint32_t msg_count +) +{ + rtems_status_code sc; + bbb_i2c_bus *bus = (bbb_i2c_bus *) base; + volatile bbb_i2c_regs *regs; + uint32_t i; + + rtems_task_wake_after( 1 ); + + if ( msg_count < 1 ) { + return 1; + } + + for ( i = 0; i < msg_count; ++i ) { + if ( ( msgs[ i ].flags & I2C_M_RECV_LEN ) != 0 ) { + return -EINVAL; + } + } + + bus->msgs = &msgs[ 0 ]; + bus->msg_todo = msg_count; + bus->current_msg_todo = msgs[ 0 ].len; + bus->current_msg_byte = msgs[ 0 ].buf; + bus->task_id = rtems_task_self(); + regs = bus->regs; + am335x_i2c_setup_transfer( bus, regs ); + REG( ®s->BBB_I2C_IRQENABLE_SET ) = BBB_I2C_IRQ_USED; + + sc = rtems_event_transient_receive( RTEMS_WAIT, bus->base.timeout ); + + if ( sc != RTEMS_SUCCESSFUL ) { + am335x_i2c_reset( bus ); + rtems_event_transient_clear(); + + return -ETIMEDOUT; + } + + return 0; +} + +static int am335x_i2c_set_clock( + i2c_bus *base, + unsigned long clock +) +{ + bbb_i2c_bus *bus = (bbb_i2c_bus *) base; + uint32_t prescaler, divider; + + prescaler = ( BBB_I2C_SYSCLK / BBB_I2C_INTERNAL_CLK ) - 1; + REG( &bus->regs->BBB_I2C_PSC ) = prescaler; + divider = BBB_I2C_INTERNAL_CLK / ( 2 * clock ); + REG( &bus->regs->BBB_I2C_SCLL ) = ( divider - 7 ); + REG( &bus->regs->BBB_I2C_SCLH ) = ( divider - 5 ); + + return 0; +} + +static void am335x_i2c_destroy( i2c_bus *base ) +{ + bbb_i2c_bus *bus = (bbb_i2c_bus *) base; + rtems_status_code sc; + + sc = rtems_interrupt_handler_remove( bus->irq, am335x_i2c_interrupt, bus ); + _Assert( sc == RTEMS_SUCCESSFUL ); + (void) sc; + i2c_bus_destroy_and_free( &bus->base ); +} + +int am335x_i2c_bus_register( + const char *bus_path, + uintptr_t register_base, + uint32_t input_clock, + rtems_vector_number irq +) +{ + bbb_i2c_bus *bus; + rtems_status_code sc; + int err; + + /* Check bus number is >0 & <MAX */ + bus = (bbb_i2c_bus *) i2c_bus_alloc_and_init( sizeof( *bus ) ); + + if ( bus == NULL ) { + return -1; + } + + bus->regs = (volatile bbb_i2c_regs *) register_base; + + I2C0ModuleClkConfig(); + am335x_i2c0_pinmux( bus ); + am335x_i2c_reset( bus ); + bus->input_clock = input_clock; + err = am335x_i2c_set_clock( &bus->base, I2C_BUS_CLOCK_DEFAULT ); + + if ( err != 0 ) { + ( *bus->base.destroy )( &bus->base ); + rtems_set_errno_and_return_minus_one( -err ); + } + + bus->irq = irq; + REG( &bus->regs->BBB_I2C_IRQSTATUS ) = BBB_I2C_ALL_IRQ_FLAGS; + + sc = rtems_interrupt_handler_install( + irq, + "BBB_I2C", + RTEMS_INTERRUPT_UNIQUE, + (rtems_interrupt_handler) am335x_i2c_interrupt, + bus + ); + + if ( sc != RTEMS_SUCCESSFUL ) { + ( *bus->base.destroy )( &bus->base ); + rtems_set_errno_and_return_minus_one( EIO ); + } + + bus->base.transfer = am335x_i2c_transfer; + bus->base.set_clock = am335x_i2c_set_clock; + bus->base.destroy = am335x_i2c_destroy; + + return i2c_bus_register( &bus->base, bus_path ); +} diff --git a/bsps/arm/imx/i2c/imx-i2c.c b/bsps/arm/imx/i2c/imx-i2c.c new file mode 100644 index 0000000000..7633c7a487 --- /dev/null +++ b/bsps/arm/imx/i2c/imx-i2c.c @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2017 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * <info@embedded-brains.de> + * + * 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.h> +#include <bsp/fdt.h> +#include <libfdt.h> +#include <arm/freescale/imx/imx_ccmvar.h> +#include <arm/freescale/imx/imx_i2creg.h> +#include <dev/i2c/i2c.h> +#include <rtems/irq-extension.h> + +#define IMX_I2C_TRANSMIT (IMX_I2C_I2CR_IEN | IMX_I2C_I2CR_IIEN \ + | IMX_I2C_I2CR_MSTA | IMX_I2C_I2CR_MTX) + +#define IMX_I2C_RECEIVE (IMX_I2C_I2CR_IEN | IMX_I2C_I2CR_IIEN \ + | IMX_I2C_I2CR_MSTA) + +typedef struct { + i2c_bus base; + volatile imx_i2c *regs; + uint32_t msg_todo; + const i2c_msg *msg; + bool read; + bool start; + uint16_t restart; + uint32_t chunk_total; + uint32_t chunk_done; + uint16_t buf_todo; + uint8_t *buf; + rtems_id task_id; + int eno; + rtems_vector_number irq; +} imx_i2c_bus; + +typedef struct { + uint16_t divisor; + uint8_t ifdr; +} imx_i2c_clock_divisor; + +static const imx_i2c_clock_divisor imx_i2c_clock_divisor_table[] = { + { 0, 0x20 }, { 22, 0x20 }, { 24, 0x21 }, { 26, 0x22 }, + { 28, 0x23 }, { 30, 0x00 }, { 32, 0x24 }, { 36, 0x25 }, + { 40, 0x26 }, { 42, 0x03 }, { 44, 0x27 }, { 48, 0x28 }, + { 52, 0x05 }, { 56, 0x29 }, { 60, 0x06 }, { 64, 0x2a }, + { 72, 0x2b }, { 80, 0x2c }, { 88, 0x09 }, { 96, 0x2d }, + { 104, 0x0a }, { 112, 0x2e }, { 128, 0x2f }, { 144, 0x0c }, + { 160, 0x30 }, { 192, 0x31 }, { 224, 0x32 }, { 240, 0x0f }, + { 256, 0x33 }, { 288, 0x10 }, { 320, 0x34 }, { 384, 0x35 }, + { 448, 0x36 }, { 480, 0x13 }, { 512, 0x37 }, { 576, 0x14 }, + { 640, 0x38 }, { 768, 0x39 }, { 896, 0x3a }, { 960, 0x17 }, + { 1024, 0x3b }, { 1152, 0x18 }, { 1280, 0x3c }, { 1536, 0x3d }, + { 1792, 0x3e }, { 1920, 0x1b }, { 2048, 0x3f }, { 2304, 0x1c }, + { 2560, 0x1d }, { 3072, 0x1e }, { 3840, 0x1f }, { 0xffff, 0x1f } +}; + +static void imx_i2c_stop(volatile imx_i2c *regs) +{ + regs->i2cr = IMX_I2C_I2CR_IEN; + regs->i2sr = 0; + regs->i2sr; +} + +static void imx_i2c_trigger_receive(imx_i2c_bus *bus, volatile imx_i2c *regs) +{ + uint16_t i2cr; + + i2cr = IMX_I2C_RECEIVE; + + if (bus->chunk_total == 1) { + i2cr |= IMX_I2C_I2CR_TXAK; + } + + regs->i2cr = i2cr; + regs->i2dr; +} + +static void imx_i2c_done(imx_i2c_bus *bus, int eno) +{ + /* + * Generates a stop in case of transmit, otherwise, only disables interrupts + * (IMX_I2C_I2CR_MSTA is already cleared). + */ + imx_i2c_stop(bus->regs); + + bus->eno = eno; + rtems_event_transient_send(bus->task_id); +} + +static const i2c_msg *imx_i2c_msg_inc(imx_i2c_bus *bus) +{ + const i2c_msg *next; + + next = bus->msg + 1; + bus->msg = next; + --bus->msg_todo; + return next; +} + +static void imx_i2c_msg_inc_and_set_buf(imx_i2c_bus *bus) +{ + const i2c_msg *next; + + next = imx_i2c_msg_inc(bus); + bus->buf_todo = next->len; + bus->buf = next->buf; +} + +static void imx_i2c_buf_inc(imx_i2c_bus *bus) +{ + ++bus->buf; + --bus->buf_todo; + ++bus->chunk_done; +} + +static void imx_i2c_buf_push(imx_i2c_bus *bus, uint8_t c) +{ + while (true) { + if (bus->buf_todo > 0) { + bus->buf[0] = c; + imx_i2c_buf_inc(bus); + break; + } + + imx_i2c_msg_inc_and_set_buf(bus); + } +} + +static uint8_t imx_i2c_buf_pop(imx_i2c_bus *bus) +{ + while (true) { + if (bus->buf_todo > 0) { + uint8_t c; + + c = bus->buf[0]; + imx_i2c_buf_inc(bus); + return c; + } + + imx_i2c_msg_inc_and_set_buf(bus); + } +} + +RTEMS_STATIC_ASSERT(I2C_M_RD == 1, imx_i2c_read_flag); + +static void imx_i2c_setup_chunk(imx_i2c_bus *bus, volatile imx_i2c *regs) +{ + while (true) { + const i2c_msg *msg; + int flags; + int can_continue; + uint32_t i; + + if (bus->msg_todo == 0) { + imx_i2c_done(bus, 0); + break; + } + + msg = bus->msg; + flags = msg->flags; + + bus->read = (flags & I2C_M_RD) != 0; + bus->start = (flags & I2C_M_NOSTART) == 0; + bus->chunk_total = msg->len; + bus->chunk_done = 0; + bus->buf_todo = msg->len; + bus->buf = msg->buf; + + can_continue = (flags & I2C_M_RD) | I2C_M_NOSTART; + + for (i = 1; i < bus->msg_todo; ++i) { + if ((msg[i].flags & (I2C_M_RD | I2C_M_NOSTART)) != can_continue) { + break; + } + + bus->chunk_total += msg[i].len; + } + + if (bus->start) { + regs->i2cr = IMX_I2C_TRANSMIT | bus->restart; + regs->i2dr = (uint8_t) ((msg->addr << 1) | (flags & I2C_M_RD)); + bus->restart = IMX_I2C_I2CR_RSTA; + break; + } else if (bus->chunk_total > 0) { + if (bus->read) { + imx_i2c_trigger_receive(bus, regs); + } else { + regs->i2cr = IMX_I2C_TRANSMIT; + regs->i2dr = imx_i2c_buf_pop(bus); + } + + break; + } else { + ++bus->msg; + --bus->msg_todo; + } + } +} + +static void imx_i2c_transfer_complete( + imx_i2c_bus *bus, + volatile imx_i2c *regs, + uint16_t i2sr +) +{ + if (bus->start) { + bus->start = false; + + if ((i2sr & IMX_I2C_I2SR_RXAK) != 0) { + imx_i2c_done(bus, EIO); + return; + } + + if (bus->read) { + imx_i2c_trigger_receive(bus, regs); + return; + } + } + + if (bus->chunk_done < bus->chunk_total) { + if (bus->read) { + if (bus->chunk_done + 2 == bus->chunk_total) { + /* Receive second last byte with NACK */ + regs->i2cr = IMX_I2C_RECEIVE | IMX_I2C_I2CR_TXAK; + } else if (bus->chunk_done + 1 == bus->chunk_total) { + /* Receive last byte with STOP */ + bus->restart = 0; + regs->i2cr = (IMX_I2C_RECEIVE | IMX_I2C_I2CR_TXAK) + & ~IMX_I2C_I2CR_MSTA; + } + + imx_i2c_buf_push(bus, (uint8_t) regs->i2dr); + + if (bus->chunk_done == bus->chunk_total) { + imx_i2c_msg_inc(bus); + imx_i2c_setup_chunk(bus, regs); + } + } else { + if (bus->chunk_done > 0 && (i2sr & IMX_I2C_I2SR_RXAK) != 0) { + imx_i2c_done(bus, EIO); + return; + } + + regs->i2dr = imx_i2c_buf_pop(bus); + } + } else { + imx_i2c_msg_inc(bus); + imx_i2c_setup_chunk(bus, regs); + } +} + +static void imx_i2c_interrupt(void *arg) +{ + imx_i2c_bus *bus; + volatile imx_i2c *regs; + uint16_t i2sr; + + bus = arg; + regs = bus->regs; + + i2sr = regs->i2sr; + regs->i2sr = 0; + + if ((i2sr & (IMX_I2C_I2SR_IAL | IMX_I2C_I2SR_ICF)) == IMX_I2C_I2SR_ICF) { + imx_i2c_transfer_complete(bus, regs, i2sr); + } else { + imx_i2c_done(bus, EIO); + } +} + +static int imx_i2c_wait_for_not_busy(volatile imx_i2c *regs) +{ + rtems_interval timeout; + bool before; + + if ((regs->i2sr & IMX_I2C_I2SR_IBB) == 0) { + return 0; + } + + timeout = rtems_clock_tick_later(10); + + do { + before = rtems_clock_tick_before(timeout); + + if ((regs->i2sr & IMX_I2C_I2SR_IBB) == 0) { + return 0; + } + } while (before); + + return ETIMEDOUT; +} + +static int imx_i2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t n) +{ + imx_i2c_bus *bus; + int supported_flags; + uint32_t i; + volatile imx_i2c *regs; + int eno; + rtems_status_code sc; + + supported_flags = I2C_M_RD; + + for (i = 0; i < n; ++i) { + if ((msgs[i].flags & ~supported_flags) != 0) { + return -EINVAL; + } + + supported_flags |= I2C_M_NOSTART; + } + + bus = (imx_i2c_bus *) base; + regs = bus->regs; + + eno = imx_i2c_wait_for_not_busy(regs); + if (eno != 0) { + return -eno; + } + + bus->msg_todo = n; + bus->msg = &msgs[0]; + bus->restart = 0; + bus->task_id = rtems_task_self(); + bus->eno = 0; + + regs->i2sr = 0; + imx_i2c_setup_chunk(bus, regs); + + sc = rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout); + if (sc != RTEMS_SUCCESSFUL) { + imx_i2c_stop(bus->regs); + rtems_event_transient_clear(); + return -ETIMEDOUT; + } + + return -bus->eno; +} + +static int imx_i2c_set_clock(i2c_bus *base, unsigned long clock) +{ + imx_i2c_bus *bus; + uint32_t ipg_clock; + uint16_t div; + size_t i; + const imx_i2c_clock_divisor *clock_divisor; + + bus = (imx_i2c_bus *) base; + ipg_clock = imx_ccm_ipg_hz(); + div = (uint16_t) ((ipg_clock + clock - 1) / clock); + + for (i = 0; i < RTEMS_ARRAY_SIZE(imx_i2c_clock_divisor_table); ++i) { + clock_divisor = &imx_i2c_clock_divisor_table[i]; + + if (clock_divisor->divisor >= div) { + break; + } + } + + bus->regs->ifdr = clock_divisor->ifdr; + return 0; +} + +static void imx_i2c_destroy(i2c_bus *base) +{ + imx_i2c_bus *bus; + + bus = (imx_i2c_bus *) base; + rtems_interrupt_handler_remove(bus->irq, imx_i2c_interrupt, bus); + i2c_bus_destroy_and_free(&bus->base); +} + +static int imx_i2c_init(imx_i2c_bus *bus) +{ + rtems_status_code sc; + + imx_i2c_set_clock(&bus->base, I2C_BUS_CLOCK_DEFAULT); + bus->regs->i2cr = IMX_I2C_I2CR_IEN; + + sc = rtems_interrupt_handler_install( + bus->irq, + "I2C", + RTEMS_INTERRUPT_UNIQUE, + imx_i2c_interrupt, + bus + ); + if (sc != RTEMS_SUCCESSFUL) { + return EAGAIN; + } + + return 0; +} + +int i2c_bus_register_imx(const char *bus_path, const char *alias_or_path) +{ + const void *fdt; + const char *path; + int node; + imx_i2c_bus *bus; + int eno; + + fdt = bsp_fdt_get(); + path = fdt_get_alias(fdt, alias_or_path); + + if (path == NULL) { + path = alias_or_path; + } + + node = fdt_path_offset(fdt, path); + if (node < 0) { + rtems_set_errno_and_return_minus_one(ENXIO); + } + + bus = (imx_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus)); + if (bus == NULL){ + return -1; + } + + bus->regs = imx_get_reg_of_node(fdt, node); + bus->irq = imx_get_irq_of_node(fdt, node, 0); + + eno = imx_i2c_init(bus); + if (eno != 0) { + (*bus->base.destroy)(&bus->base); + rtems_set_errno_and_return_minus_one(eno); + } + + bus->base.transfer = imx_i2c_transfer; + bus->base.set_clock = imx_i2c_set_clock; + bus->base.destroy = imx_i2c_destroy; + + return i2c_bus_register(&bus->base, bus_path); +} diff --git a/bsps/arm/lpc24xx/i2c/i2c-config.c b/bsps/arm/lpc24xx/i2c/i2c-config.c new file mode 100644 index 0000000000..5ba16ae874 --- /dev/null +++ b/bsps/arm/lpc24xx/i2c/i2c-config.c @@ -0,0 +1,91 @@ +/** + * @file + * + * @ingroup lpc24xx_libi2c + * + * @brief LibI2C bus driver for the I2C modules. + */ + +/* + * Copyright (c) 2009-2011 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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 <bspopts.h> +#include <bsp/i2c.h> +#include <bsp/irq.h> + +#ifdef LPC24XX_CONFIG_I2C_0 + static const lpc24xx_pin_range lpc24xx_i2c_pins_0 [] = { + LPC24XX_PIN_I2C_0_SDA, + LPC24XX_PIN_I2C_0_SCL, + LPC24XX_PIN_TERMINAL + }; + + static lpc24xx_i2c_bus_entry lpc24xx_i2c_entry_0 = { + .bus = { + .ops = &lpc24xx_i2c_ops, + .size = sizeof(lpc24xx_i2c_bus_entry) + }, + .regs = (volatile lpc24xx_i2c *) I2C0_BASE_ADDR, + .index = 0, + .pins = &lpc24xx_i2c_pins_0 [0], + .vector = LPC24XX_IRQ_I2C_0 + }; + + rtems_libi2c_bus_t * const lpc24xx_i2c_0 = + &lpc24xx_i2c_entry_0.bus; +#endif + +#ifdef LPC24XX_CONFIG_I2C_1 + static const lpc24xx_pin_range lpc24xx_i2c_pins_1 [] = { + LPC24XX_PIN_I2C_1_SDA_P0_19, + LPC24XX_PIN_I2C_1_SCL_P0_20, + LPC24XX_PIN_TERMINAL + }; + + static lpc24xx_i2c_bus_entry lpc24xx_i2c_entry_1 = { + .bus = { + .ops = &lpc24xx_i2c_ops, + .size = sizeof(lpc24xx_i2c_bus_entry) + }, + .regs = (volatile lpc24xx_i2c *) I2C1_BASE_ADDR, + .index = 1, + .pins = &lpc24xx_i2c_pins_1 [0], + .vector = LPC24XX_IRQ_I2C_1 + }; + + rtems_libi2c_bus_t * const lpc24xx_i2c_1 = + &lpc24xx_i2c_entry_1.bus; +#endif + +#ifdef LPC24XX_CONFIG_I2C_2 + static const lpc24xx_pin_range lpc24xx_i2c_pins_2 [] = { + LPC24XX_PIN_I2C_2_SDA_P0_10, + LPC24XX_PIN_I2C_2_SCL_P0_11, + LPC24XX_PIN_TERMINAL + }; + + static lpc24xx_i2c_bus_entry lpc24xx_i2c_entry_2 = { + .bus = { + .ops = &lpc24xx_i2c_ops, + .size = sizeof(lpc24xx_i2c_bus_entry) + }, + .regs = (volatile lpc24xx_i2c *) I2C2_BASE_ADDR, + .index = 2, + .pins = &lpc24xx_i2c_pins_2 [0], + .vector = LPC24XX_IRQ_I2C_2 + }; + + rtems_libi2c_bus_t * const lpc24xx_i2c_2 = + &lpc24xx_i2c_entry_2.bus; +#endif diff --git a/bsps/arm/lpc24xx/i2c/i2c.c b/bsps/arm/lpc24xx/i2c/i2c.c new file mode 100644 index 0000000000..ef638e69b9 --- /dev/null +++ b/bsps/arm/lpc24xx/i2c/i2c.c @@ -0,0 +1,324 @@ +/** + * @file + * + * @ingroup lpc24xx_libi2c + * + * @brief LibI2C bus driver for the I2C modules. + */ + +/* + * Copyright (c) 2009-2011 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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.h> +#include <bsp/i2c.h> +#include <bsp/irq.h> +#include <bsp/irq-generic.h> +#include <bsp/system-clocks.h> + +#define RTEMS_STATUS_CHECKS_USE_PRINTK + +#include <rtems/status-checks.h> + +static void lpc24xx_i2c_handler(void *arg) +{ + lpc24xx_i2c_bus_entry *e = arg; + volatile lpc24xx_i2c *regs = e->regs; + unsigned state = regs->stat; + uint8_t *data = e->data; + uint8_t *end = e->end; + bool notify = true; + + switch (state) { + case 0x28U: + /* Data has been transmitted successfully */ + if (data != end) { + regs->dat = *data; + ++data; + regs->conset = LPC24XX_I2C_AA; + regs->conclr = LPC24XX_I2C_SI; + notify = false; + e->data = data; + } + break; + case 0x50U: + /* Data has been received */ + if (data != end) { + *data = (uint8_t) regs->dat; + ++data; + if (data != end) { + if (data + 1 != end) { + regs->conset = LPC24XX_I2C_AA; + } else { + regs->conclr = LPC24XX_I2C_AA; + } + regs->conclr = LPC24XX_I2C_SI; + notify = false; + e->data = data; + } else { + /* This is an error and should never happen */ + } + } + break; + case 0x58U: + /* Last data has been received */ + if (data != end) { + *data = (uint8_t) regs->dat; + } + break; + default: + /* Do nothing */ + break; + } + + /* Notify task if necessary */ + if (notify) { + bsp_interrupt_vector_disable(e->vector); + + rtems_semaphore_release(e->state_update); + } +} + +static rtems_status_code lpc24xx_i2c_wait(lpc24xx_i2c_bus_entry *e) +{ + bsp_interrupt_vector_enable(e->vector); + + return rtems_semaphore_obtain(e->state_update, RTEMS_WAIT, RTEMS_NO_TIMEOUT); +} + +static rtems_status_code lpc24xx_i2c_init(rtems_libi2c_bus_t *bus) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + lpc24xx_i2c_bus_entry *e = (lpc24xx_i2c_bus_entry *) bus; + volatile lpc24xx_i2c *regs = e->regs; + unsigned cycles = LPC24XX_CCLK / (8U * 100000U * 2U); + + /* Create semaphore */ + sc = rtems_semaphore_create ( + rtems_build_name ('I', '2', 'C', '0' + e->index), + 0, + RTEMS_SIMPLE_BINARY_SEMAPHORE, + 0, + &e->state_update + ); + RTEMS_CHECK_SC(sc, "create status update semaphore"); + + /* Enable module power */ + sc = lpc24xx_module_enable(LPC24XX_MODULE_I2C_0 + e->index, LPC24XX_MODULE_CCLK_8); + RTEMS_CHECK_SC(sc, "enable module"); + + /* Pin configuration */ + sc = lpc24xx_pin_config(e->pins, LPC24XX_PIN_SET_FUNCTION); + RTEMS_CHECK_SC(sc, "pin configuration"); + + /* Clock high and low duty cycles */ + regs->sclh = cycles; + regs->scll = cycles; + + /* Disable module */ + regs->conclr = LPC24XX_I2C_EN; + + /* Install interrupt handler and disable this vector */ + sc = rtems_interrupt_handler_install( + e->vector, + "I2C", + RTEMS_INTERRUPT_UNIQUE, + lpc24xx_i2c_handler, + e + ); + RTEMS_CHECK_SC(sc, "install interrupt handler"); + bsp_interrupt_vector_disable(e->vector); + + /* Enable module in master mode */ + regs->conset = LPC24XX_I2C_EN; + + /* Set self address */ + regs->adr = 0; + + return RTEMS_SUCCESSFUL; +} + +static rtems_status_code lpc24xx_i2c_send_start(rtems_libi2c_bus_t *bus) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + lpc24xx_i2c_bus_entry *e = (lpc24xx_i2c_bus_entry *) bus; + volatile lpc24xx_i2c *regs = e->regs; + + /* Start */ + regs->conclr = LPC24XX_I2C_STA | LPC24XX_I2C_AA | LPC24XX_I2C_SI; + regs->conset = LPC24XX_I2C_STA; + + /* Wait */ + sc = lpc24xx_i2c_wait(e); + RTEMS_CHECK_SC(sc, "wait for state update"); + + return RTEMS_SUCCESSFUL; +} + +static rtems_status_code lpc24xx_i2c_send_stop(rtems_libi2c_bus_t *bus) +{ + lpc24xx_i2c_bus_entry *e = (lpc24xx_i2c_bus_entry *) bus; + volatile lpc24xx_i2c *regs = e->regs; + + /* Stop */ + regs->conset = LPC24XX_I2C_STO | LPC24XX_I2C_AA; + regs->conclr = LPC24XX_I2C_STA | LPC24XX_I2C_SI; + + return RTEMS_SUCCESSFUL; +} + +static rtems_status_code lpc24xx_i2c_send_addr( + rtems_libi2c_bus_t *bus, + uint32_t addr, + int rw +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + lpc24xx_i2c_bus_entry *e = (lpc24xx_i2c_bus_entry *) bus; + volatile lpc24xx_i2c *regs = e->regs; + unsigned state = regs->stat; + + /* Check state */ + if (state != 0x8U && state != 0x10U) { + return -RTEMS_IO_ERROR; + } + + /* Send address */ + regs->dat = (uint8_t) ((addr << 1U) | ((rw != 0) ? 1U : 0U)); + regs->conset = LPC24XX_I2C_AA; + regs->conclr = LPC24XX_I2C_STA | LPC24XX_I2C_SI; + + /* Wait */ + sc = lpc24xx_i2c_wait(e); + RTEMS_CHECK_SC_RV(sc, "wait for state update"); + + /* Check state */ + state = regs->stat; + if (state != 0x18U && state != 0x40U) { + return -RTEMS_IO_ERROR; + } + + return RTEMS_SUCCESSFUL; +} + +static int lpc24xx_i2c_read(rtems_libi2c_bus_t *bus, unsigned char *in, int n) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + lpc24xx_i2c_bus_entry *e = (lpc24xx_i2c_bus_entry *) bus; + volatile lpc24xx_i2c *regs = e->regs; + unsigned state = regs->stat; + uint8_t *data = in; + uint8_t *end = in + n; + + if (n <= 0) { + return n; + } else if (state != 0x40U) { + return -RTEMS_IO_ERROR; + } + + /* Setup receive buffer */ + e->data = data; + e->end = end; + + /* Ready to receive data */ + if (data + 1 != end) { + regs->conset = LPC24XX_I2C_AA; + } else { + regs->conclr = LPC24XX_I2C_AA; + } + regs->conclr = LPC24XX_I2C_SI; + + /* Wait */ + sc = lpc24xx_i2c_wait(e); + RTEMS_CHECK_SC_RV(sc, "wait for state update"); + + /* Check state */ + state = regs->stat; + if (state != 0x58U) { + return -RTEMS_IO_ERROR; + } + + return n; +} + +static int lpc24xx_i2c_write( + rtems_libi2c_bus_t *bus, + unsigned char *out, + int n +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + lpc24xx_i2c_bus_entry *e = (lpc24xx_i2c_bus_entry *) bus; + volatile lpc24xx_i2c *regs = e->regs; + unsigned state = 0; + + if (n <= 0) { + return n; + } + + /* Setup transmit buffer */ + e->data = out + 1; + e->end = out + n; + + /* Transmit first byte */ + regs->dat = *out; + regs->conset = LPC24XX_I2C_AA; + regs->conclr = LPC24XX_I2C_SI; + + /* Wait */ + sc = lpc24xx_i2c_wait(e); + RTEMS_CHECK_SC_RV(sc, "wait for state update"); + + /* Check state */ + state = regs->stat; + if (state != 0x28U) { + return -RTEMS_IO_ERROR; + } + + return n; +} + +static int lpc24xx_i2c_set_transfer_mode( + rtems_libi2c_bus_t *bus, + const rtems_libi2c_tfr_mode_t *mode +) +{ + return -RTEMS_NOT_IMPLEMENTED; +} + +static int lpc24xx_i2c_ioctl(rtems_libi2c_bus_t *bus, int cmd, void *arg) +{ + int rv = -1; + const rtems_libi2c_tfr_mode_t *tm = (const rtems_libi2c_tfr_mode_t *) arg; + + switch (cmd) { + case RTEMS_LIBI2C_IOCTL_SET_TFRMODE: + rv = lpc24xx_i2c_set_transfer_mode(bus, tm); + break; + default: + rv = -RTEMS_NOT_DEFINED; + break; + } + + return rv; +} + +const rtems_libi2c_bus_ops_t lpc24xx_i2c_ops = { + .init = lpc24xx_i2c_init, + .send_start = lpc24xx_i2c_send_start, + .send_stop = lpc24xx_i2c_send_stop, + .send_addr = lpc24xx_i2c_send_addr, + .read_bytes = lpc24xx_i2c_read, + .write_bytes = lpc24xx_i2c_write, + .ioctl = lpc24xx_i2c_ioctl +}; diff --git a/bsps/arm/lpc32xx/i2c/i2c.c b/bsps/arm/lpc32xx/i2c/i2c.c new file mode 100644 index 0000000000..508cd6458a --- /dev/null +++ b/bsps/arm/lpc32xx/i2c/i2c.c @@ -0,0 +1,251 @@ +/** + * @file + * + * @ingroup lpc32xx_i2c + * + * @brief I2C support implementation. + */ + +/* + * Copyright (c) 2010-2011 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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 <rtems.h> + +#include <bsp.h> +#include <bsp/i2c.h> + +void lpc32xx_i2c_reset(volatile lpc32xx_i2c *i2c) +{ + i2c->ctrl = I2C_CTRL_RESET; +} + +rtems_status_code lpc32xx_i2c_init( + volatile lpc32xx_i2c *i2c, + unsigned clock_in_hz +) +{ + uint32_t i2cclk = 0; + + if (i2c == &lpc32xx.i2c_1) { + i2cclk |= I2CCLK_1_EN | I2CCLK_1_HIGH_DRIVE; + } else if (i2c == &lpc32xx.i2c_2) { + i2cclk |= I2CCLK_2_EN | I2CCLK_2_HIGH_DRIVE; + } else { + return RTEMS_INVALID_ID; + } + + LPC32XX_I2CCLK_CTRL |= i2cclk; + + lpc32xx_i2c_reset(i2c); + + return lpc32xx_i2c_clock(i2c, clock_in_hz); +} + +rtems_status_code lpc32xx_i2c_clock( + volatile lpc32xx_i2c *i2c, + unsigned clock_in_hz +) +{ + uint32_t clk_div = lpc32xx_hclk() / clock_in_hz; + uint32_t clk_lo = 0; + uint32_t clk_hi = 0; + + switch (clock_in_hz) { + case 100000: + clk_lo = clk_div / 2; + break; + case 400000: + clk_lo = (64 * clk_div) / 100; + break; + default: + return RTEMS_INVALID_CLOCK; + } + + clk_hi = clk_div - clk_lo; + + i2c->clk_lo = clk_lo; + i2c->clk_hi = clk_hi; + + return RTEMS_SUCCESSFUL; +} + +static rtems_status_code wait_for_transaction_done(volatile lpc32xx_i2c *i2c) +{ + uint32_t stat = 0; + + do { + stat = i2c->stat; + } while ((stat & I2C_STAT_TDI) == 0); + + if ((stat & I2C_STAT_TFE) != 0) { + i2c->stat = I2C_STAT_TDI; + + return RTEMS_SUCCESSFUL; + } else { + lpc32xx_i2c_reset(i2c); + + return RTEMS_IO_ERROR; + } +} + +static rtems_status_code tx(volatile lpc32xx_i2c *i2c, uint32_t data) +{ + uint32_t stat = 0; + + do { + stat = i2c->stat; + } while ((stat & (I2C_STAT_TFE | I2C_STAT_TDI)) == 0); + + if ((stat & I2C_STAT_TDI) == 0) { + i2c->rx_or_tx = data; + + return RTEMS_SUCCESSFUL; + } else { + lpc32xx_i2c_reset(i2c); + + return RTEMS_IO_ERROR; + } +} + +rtems_status_code lpc32xx_i2c_write_start( + volatile lpc32xx_i2c *i2c, + unsigned addr +) +{ + return tx(i2c, I2C_TX_ADDR(addr) | I2C_TX_START); +} + +rtems_status_code lpc32xx_i2c_read_start( + volatile lpc32xx_i2c *i2c, + unsigned addr +) +{ + return tx(i2c, I2C_TX_ADDR(addr) | I2C_TX_START | I2C_TX_READ); +} + +rtems_status_code lpc32xx_i2c_write_with_optional_stop( + volatile lpc32xx_i2c *i2c, + const uint8_t *out, + size_t n, + bool stop +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + size_t i = 0; + + for (i = 0; i < n - 1 && sc == RTEMS_SUCCESSFUL; ++i) { + sc = tx(i2c, out [i]); + } + + if (sc == RTEMS_SUCCESSFUL) { + uint32_t stop_flag = stop ? I2C_TX_STOP : 0; + + sc = tx(i2c, out [n - 1] | stop_flag); + } + + if (stop && sc == RTEMS_SUCCESSFUL) { + sc = wait_for_transaction_done(i2c); + } + + return sc; +} + +static bool can_tx_for_rx(volatile lpc32xx_i2c *i2c) +{ + return (i2c->stat & (I2C_STAT_TFF | I2C_STAT_RFF)) == 0; +} + +static bool can_rx(volatile lpc32xx_i2c *i2c) +{ + return (i2c->stat & I2C_STAT_RFE) == 0; +} + +rtems_status_code lpc32xx_i2c_read_with_optional_stop( + volatile lpc32xx_i2c *i2c, + uint8_t *in, + size_t n, + bool stop +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + size_t last = n - 1; + size_t rx = 0; + size_t tx = 0; + + if (!stop) { + return RTEMS_NOT_IMPLEMENTED; + } + + while (rx <= last) { + while (tx < last && can_tx_for_rx(i2c)) { + i2c->rx_or_tx = 0; + ++tx; + } + + if (tx == last && can_tx_for_rx(i2c)) { + uint32_t stop_flag = stop ? I2C_TX_STOP : 0; + + i2c->rx_or_tx = stop_flag; + ++tx; + } + + while (rx <= last && can_rx(i2c)) { + in [rx] = (uint8_t) i2c->rx_or_tx; + ++rx; + } + } + + if (stop) { + sc = wait_for_transaction_done(i2c); + } + + return sc; +} + +rtems_status_code lpc32xx_i2c_write_and_read( + volatile lpc32xx_i2c *i2c, + unsigned addr, + const uint8_t *out, + size_t out_size, + uint8_t *in, + size_t in_size +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if (out_size > 0) { + bool stop = in_size == 0; + + sc = lpc32xx_i2c_write_start(i2c, addr); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + sc = lpc32xx_i2c_write_with_optional_stop(i2c, out, out_size, stop); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + } + + if (in_size > 0) { + sc = lpc32xx_i2c_read_start(i2c, addr); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + lpc32xx_i2c_read_with_optional_stop(i2c, in, in_size, true); + } + + return RTEMS_SUCCESSFUL; +} diff --git a/bsps/arm/raspberrypi/i2c/i2c.c b/bsps/arm/raspberrypi/i2c/i2c.c new file mode 100644 index 0000000000..d3912671cd --- /dev/null +++ b/bsps/arm/raspberrypi/i2c/i2c.c @@ -0,0 +1,422 @@ +/** + * @file i2c.c + * + * @ingroup raspberrypi_i2c + * + * @brief Support for the I2C bus on the Raspberry Pi GPIO P1 header (model A/B) + * and GPIO J8 header on model B+. + */ + +/* + * Copyright (c) 2014-2015 Andre Marques <andre.lousa.marques at gmail.com> + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +/* + * STATUS: + * - 10-bit slave addressing untested + */ + +#include <bsp.h> +#include <bsp/raspberrypi.h> +#include <bsp/gpio.h> +#include <bsp/rpi-gpio.h> +#include <bsp/irq.h> +#include <bsp/i2c.h> +#include <assert.h> + +#define TRANSFER_COUNT(buffer_size) (buffer_size + 0xFFFE) / 0xFFFF + +#define ADJUST_TRANSFER_SIZE(transfer_count, remaining_bytes) \ + transfer_count > 1 ? 0xFFFF : (remaining_bytes & 0xFFFF) + +#define I2C_POLLING(condition) \ + while ( condition ) { \ + ; \ + } + +/** + * @brief Object containing relevant information about an I2C bus. + * + * Encapsulates relevant data for a I2C bus transfer. + */ +typedef struct +{ + i2c_bus base; + uint32_t input_clock; + rtems_id task_id; + + /* Remaining bytes to read/write on the current bus transfer. */ + uint32_t remaining_bytes; + /* Each transfer has a limit of 0xFFFF bytes, hence larger transfers + * have to be divided. Each transfer implies a stop condition, signaled + * automatically by the BSC controller. */ + uint32_t remaining_transfers; + + uint8_t *current_buffer; + uint32_t current_buffer_size; + + bool read_transfer; +} rpi_i2c_bus; + +static int rpi_i2c_bus_transfer(rpi_i2c_bus *bus) +{ + while ( bus->remaining_bytes >= 1 ) { + /* If reading. */ + if ( bus->read_transfer ) { + /* Poll RXD bit until there is data on the RX FIFO to read. */ + I2C_POLLING((BCM2835_REG(BCM2835_I2C_S) & (1 << 5)) == 0); + + /* Read data from the RX FIFO. */ + (*(uint8_t *) bus->current_buffer) = BCM2835_REG(BCM2835_I2C_FIFO) & 0xFF; + + ++bus->current_buffer; + + /* Check for acknowledgment or clock stretching errors. */ + if ( + (BCM2835_REG(BCM2835_I2C_S) & (1 << 8)) || + (BCM2835_REG(BCM2835_I2C_S) & (1 << 9)) + ) { + return -EIO; + } + } + /* If writing. */ + else { + /* If using the I2C bus in interrupt-driven mode. */ +#if I2C_IO_MODE == 1 + /* Generate interrupts on the TXW bit condition. */ + BCM2835_REG(BCM2835_I2C_C) |= (1 << 9); + + /* Sleep until the TX FIFO has free space for a new write. */ + bus->task_id = rtems_task_self(); + 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; + int 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/bsps/arm/stm32f4/i2c/i2c-config.c b/bsps/arm/stm32f4/i2c/i2c-config.c new file mode 100644 index 0000000000..eac16bafc0 --- /dev/null +++ b/bsps/arm/stm32f4/i2c/i2c-config.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Christian Mauderer. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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 <bspopts.h> +#include <bsp/i2c.h> +#include <bsp/irq.h> + +#ifdef STM32F4_ENABLE_I2C1 + static stm32f4_i2c_bus_entry stm32f4_i2c1_entry = { + .regs = STM32F4_I2C1, + .index = 0, + .vector = STM32F4_IRQ_I2C1_EV, + }; + + stm32f4_i2c_bus_entry *const stm32f4_i2c1 = &stm32f4_i2c1_entry; +#endif + +#ifdef STM32F4_ENABLE_I2C2 + static stm32f4_i2c_bus_entry stm32f4_i2c2_entry = { + .regs = STM32F4_I2C2, + .index = 1, + .vector = STM32F4_IRQ_I2C2_EV, + }; + + stm32f4_i2c_bus_entry *const stm32f4_i2c2 = &stm32f4_i2c2_entry; +#endif diff --git a/bsps/arm/stm32f4/i2c/i2c.c b/bsps/arm/stm32f4/i2c/i2c.c new file mode 100644 index 0000000000..1d0a409455 --- /dev/null +++ b/bsps/arm/stm32f4/i2c/i2c.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2013 Christian Mauderer. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +/* The I2C-module can not run with libi2c. The reason for this is, that libi2c + * needs a possibility to generate a stop condition separately. This controller + * wants to generate the condition automatically when sending or receiving data. + */ + +#include <bsp.h> +#include <bsp/i2c.h> +#include <bsp/rcc.h> +#include <bsp/irq.h> +#include <bsp/irq-generic.h> +#include <assert.h> + +#define RTEMS_STATUS_CHECKS_USE_PRINTK + +#include <rtems/status-checks.h> + +#define STM32F4_I2C_INITIAL_BITRATE 100000 + +#define I2C_RW_BIT 0x1 + +stm32f4_rcc_index i2c_rcc_index [] = { + STM32F4_RCC_I2C1, + STM32F4_RCC_I2C2, +}; + +static stm32f4_rcc_index i2c_get_rcc_index(stm32f4_i2c_bus_entry *e) +{ + return i2c_rcc_index [e->index]; +} + +static uint32_t i2c_get_pclk(stm32f4_i2c_bus_entry *e) +{ + return STM32F4_PCLK1; +} + +rtems_status_code stm32f4_i2c_set_bitrate( + stm32f4_i2c_bus_entry *e, + uint32_t br +) +{ + volatile stm32f4_i2c *regs = e->regs; + uint32_t ccr; + uint32_t trise; + uint32_t pclk = i2c_get_pclk(e); + + /* Make sure, that the module is disabled */ + if((regs->cr1 & STM32F4_I2C_CR1_PE) != 0) + { + return RTEMS_RESOURCE_IN_USE; + } + + /* Configure clock control register and rise time register */ + ccr = regs->ccr; + trise = regs->trise; + + if(br <= 100000) + { + uint32_t ccr_val = pclk / (2 * br); + /* according to datasheet, the rise time for standard mode is 1us -> 1MHz */ + uint32_t trise_val = pclk / 1000000 + 1; + trise = STM32F4_I2C_TRISE_SET(trise, trise_val); + + if(ccr_val > STM32F4_I2C_CCR_CCR_MAX) + { + return RTEMS_INVALID_NUMBER; + } + + /* standard mode */ + ccr &= ~STM32F4_I2C_CCR_FS; + ccr = STM32F4_I2C_CCR_CCR_SET(ccr, ccr_val); + } + else + { + /* FIXME: Implement speeds 100kHz < f <= 400kHz (fast mode) */ + return RTEMS_NOT_IMPLEMENTED; + } + + regs->ccr = ccr; + regs->trise = trise; + + return RTEMS_SUCCESSFUL; +} + +static void stm32f4_i2c_handler(void *arg) +{ + /* This handler implements the suggested read method from stm32f103xx + * reference manual if the handler is not the one with the highest priority */ + stm32f4_i2c_bus_entry *e = arg; + volatile stm32f4_i2c *regs = e->regs; + uint32_t sr1 = regs->sr1; + uint8_t *data = e->data; + uint8_t *last = e->last; + bool read = e->read; + bool wake_task = false; + uint32_t cr1; + + if(sr1 & STM32F4_I2C_SR1_SB) { + /* Start condition sent. */ + regs->dr = e->addr_with_rw; + } + + if(read) { + size_t len = e->len; + + if(len == 1) { + /* special case for one single byte */ + if(sr1 & STM32F4_I2C_SR1_ADDR) { + cr1 = regs->cr1; + cr1 &= ~STM32F4_I2C_CR1_ACK; + regs->cr1 = cr1; + + /* Read sr2 to clear flag */ + regs->sr2; + + cr1 = regs->cr1; + cr1 |= STM32F4_I2C_CR1_STOP; + regs->cr1 = cr1; + } else if(sr1 & STM32F4_I2C_SR1_RxNE) { + *data = regs->dr; + wake_task = true; + } + } else if (len == 2) { + /* special case for two bytes */ + if(sr1 & STM32F4_I2C_SR1_ADDR) { + /* Read sr2 to clear flag */ + regs->sr2; + + cr1 = regs->cr1; + cr1 &= ~STM32F4_I2C_CR1_ACK; + regs->cr1 = cr1; + } else if(sr1 & STM32F4_I2C_SR1_BTF) { + cr1 = regs->cr1; + cr1 |= STM32F4_I2C_CR1_STOP; + regs->cr1 = cr1; + + *data = regs->dr; + ++data; + *data = regs->dr; + wake_task = true; + } + } else { + /* more than two bytes */ + if(sr1 & STM32F4_I2C_SR1_ADDR) { + /* Read sr2 to clear flag */ + regs->sr2; + } else if(sr1 & STM32F4_I2C_SR1_BTF && data == last - 2) { + cr1 = regs->cr1; + cr1 &= ~STM32F4_I2C_CR1_ACK; + regs->cr1 = cr1; + + *data = regs->dr; + ++data; + + cr1 = regs->cr1; + cr1 |= STM32F4_I2C_CR1_STOP; + regs->cr1 = cr1; + + *data = regs->dr; + ++data; + } else if((sr1 & STM32F4_I2C_SR1_RxNE) && (data != last - 2)) { + *data = regs->dr; + + if(data == last) { + wake_task = true; + } else { + ++data; + } + } + } + } else /* write */ { + if(sr1 & STM32F4_I2C_SR1_ADDR) { + /* Address sent */ + regs->sr2; + } + + if((sr1 & (STM32F4_I2C_SR1_ADDR | STM32F4_I2C_SR1_TxE)) && (data <= last)) { + regs->dr = *data; + ++data; + } else if(sr1 & STM32F4_I2C_SR1_BTF) { + uint32_t cr1 = regs->cr1; + cr1 |= STM32F4_I2C_CR1_STOP; + regs->cr1 = cr1; + wake_task = true; + } + } + + e->data = data; + + if(wake_task) { + bsp_interrupt_vector_disable(e->vector); + rtems_event_transient_send(e->task_id); + } +} + +static rtems_status_code i2c_wait_done(stm32f4_i2c_bus_entry *e) +{ + bsp_interrupt_vector_enable(e->vector); + e->task_id = rtems_task_self(); + return rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT); +} + +rtems_status_code stm32f4_i2c_init(stm32f4_i2c_bus_entry *e) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + volatile stm32f4_i2c *regs = e->regs; + stm32f4_rcc_index rcc_index = i2c_get_rcc_index(e); + uint32_t pclk = i2c_get_pclk(e); + uint32_t cr1 = 0; + uint32_t cr2 = 0; + + assert(pclk >= 2000000); + + /* Create mutex */ + sc = rtems_semaphore_create ( + rtems_build_name ('I', '2', 'C', '1' + e->index), + 0, + RTEMS_BINARY_SEMAPHORE | RTEMS_PRIORITY | RTEMS_INHERIT_PRIORITY, + 0, + &e->mutex + ); + RTEMS_CHECK_SC(sc, "create mutex"); + + /* Install interrupt handler and disable this vector */ + sc = rtems_interrupt_handler_install( + e->vector, + "I2C", + RTEMS_INTERRUPT_UNIQUE, + stm32f4_i2c_handler, + e + ); + RTEMS_CHECK_SC(sc, "install interrupt handler"); + bsp_interrupt_vector_disable(e->vector); + + /* Enable module clock */ + stm32f4_rcc_set_clock(rcc_index, true); + + /* Setup initial bit rate */ + sc = stm32f4_i2c_set_bitrate(e, STM32F4_I2C_INITIAL_BITRATE); + RTEMS_CHECK_SC(sc, "set bitrate"); + + /* Set config registers */ + cr2 = regs->cr2; + cr2 = STM32F4_I2C_CR2_FREQ_SET(cr2, pclk / 1000000); + cr2 |= STM32F4_I2C_CR2_ITEVTEN; + cr2 |= STM32F4_I2C_CR2_ITBUFEN; + regs->cr2 = cr2; + + cr1 = regs->cr1; + cr1 |= STM32F4_I2C_CR1_PE; + regs->cr1 = cr1; + + return RTEMS_SUCCESSFUL; +} + +rtems_status_code stm32f4_i2c_process_message( + stm32f4_i2c_bus_entry *e, + stm32f4_i2c_message *msg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_status_code sc_return = RTEMS_SUCCESSFUL; + volatile stm32f4_i2c *regs = e->regs; + uint16_t max_7_bit_address = (1 << 7) - 1; + uint32_t cr1 = regs->cr1; + + if(msg->addr > max_7_bit_address) { + return RTEMS_NOT_IMPLEMENTED; + } + + if(msg->len == 0) { + return RTEMS_INVALID_SIZE; + } + + sc = rtems_semaphore_obtain(e->mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + RTEMS_CHECK_SC(sc, "obtaining mutex"); + + e->data = msg->buf; + e->last = msg->buf + msg->len - 1; + e->len = msg->len; + + e->addr_with_rw = msg->addr << 1; + if(msg->read) { + e->addr_with_rw |= I2C_RW_BIT; + } + e->read = msg->read; + + /* Check if no stop is active. */ + if(cr1 & STM32F4_I2C_CR1_STOP) { + return RTEMS_IO_ERROR; + } + + /* Start */ + cr1 = regs->cr1; + if(e->len == 2) { + cr1 |= STM32F4_I2C_CR1_POS; + } else { + cr1 &= ~STM32F4_I2C_CR1_POS; + } + cr1 |= STM32F4_I2C_CR1_ACK; + cr1 |= STM32F4_I2C_CR1_START; + regs->cr1 = cr1; + + /* Wait for end of message */ + sc = i2c_wait_done(e); + + if(sc != RTEMS_SUCCESSFUL) { + sc_return = sc; + } + + sc = rtems_semaphore_release(e->mutex); + RTEMS_CHECK_SC(sc, "releasing mutex"); + + return sc_return; +} + diff --git a/bsps/arm/xilinx-zynq/i2c/cadence-i2c.c b/bsps/arm/xilinx-zynq/i2c/cadence-i2c.c new file mode 100644 index 0000000000..76211da496 --- /dev/null +++ b/bsps/arm/xilinx-zynq/i2c/cadence-i2c.c @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2014 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * <info@embedded-brains.de> + * + * 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/cadence-i2c.h> +#include <bsp/cadence-i2c-regs.h> + +#include <rtems/irq-extension.h> +#include <rtems/score/assert.h> + +#include <dev/i2c/i2c.h> + +#define CADENCE_I2C_DIV_A_MAX 4 + +#define CADENCE_I2C_DIV_B_MAX 64 + +#define CADENCE_I2C_FIFO_DEPTH 16 + +#define CADENCE_I2C_DATA_IRQ_DEPTH (CADENCE_I2C_FIFO_DEPTH - 2) + +#define CADENCE_I2C_TRANSFER_SIZE_MAX 255 + +#define CADENCE_I2C_TRANSFER_SIZE_ONCE_MAX (18 * CADENCE_I2C_DATA_IRQ_DEPTH) + +#define CADENCE_I2C_IRQ_ERROR \ + (CADENCE_I2C_IXR_ARB_LOST \ + | CADENCE_I2C_IXR_RX_UNF \ + | CADENCE_I2C_IXR_TX_OVR \ + | CADENCE_I2C_IXR_RX_OVR \ + | CADENCE_I2C_IXR_NACK) + +#define CADENCE_I2C_IRQ_USED \ + (CADENCE_I2C_IRQ_ERROR \ + | CADENCE_I2C_IXR_DATA \ + | CADENCE_I2C_IXR_COMP) + +typedef struct { + i2c_bus base; + volatile cadence_i2c *regs; + i2c_msg *msgs; + uint32_t msg_todo; + uint32_t current_msg_todo; + uint8_t *current_msg_byte; + uint32_t current_todo; + uint32_t irqstatus; + bool read; + bool hold; + rtems_id task_id; + uint32_t input_clock; + rtems_vector_number irq; +} cadence_i2c_bus; + +static void cadence_i2c_disable_interrupts(volatile cadence_i2c *regs) +{ + regs->irqdisable = 0xffff; +} + +static void cadence_i2c_clear_irq_status(volatile cadence_i2c *regs) +{ + regs->irqstatus = regs->irqstatus; +} + +static void cadence_i2c_reset(cadence_i2c_bus *bus) +{ + volatile cadence_i2c *regs = bus->regs; + uint32_t val; + + cadence_i2c_disable_interrupts(regs); + + val = regs->control; + val &= ~CADENCE_I2C_CONTROL_HOLD; + val |= CADENCE_I2C_CONTROL_ACKEN + | CADENCE_I2C_CONTROL_MS + | CADENCE_I2C_CONTROL_CLR_FIFO; + regs->control = val; + + regs->transfer_size = 0; + regs->status = regs->status; + + cadence_i2c_clear_irq_status(regs); +} + +static uint32_t cadence_i2c_set_address_size( + const i2c_msg *msg, + uint32_t control +) +{ + if ((msg->flags & I2C_M_TEN) == 0) { + control |= CADENCE_I2C_CONTROL_NEA; + } else { + control &= ~CADENCE_I2C_CONTROL_NEA; + } + + return control; +} + +static void cadence_i2c_setup_read_transfer( + cadence_i2c_bus *bus, + volatile cadence_i2c *regs, + uint32_t control +) +{ + control |= CADENCE_I2C_CONTROL_RW; + regs->control = control; + + if (bus->current_todo <= CADENCE_I2C_TRANSFER_SIZE_MAX) { + regs->transfer_size = bus->current_todo; + } else { + regs->transfer_size = CADENCE_I2C_TRANSFER_SIZE_ONCE_MAX; + } +} + +static void cadence_i2c_next_byte(cadence_i2c_bus *bus) +{ + --bus->current_msg_todo; + ++bus->current_msg_byte; + + if (bus->current_msg_todo == 0) { + i2c_msg *msg; + + ++bus->msgs; + --bus->msg_todo; + + msg = &bus->msgs[0]; + + bus->current_msg_todo = msg->len; + bus->current_msg_byte = msg->buf; + } +} + +static void cadence_i2c_write_to_fifo( + cadence_i2c_bus *bus, + volatile cadence_i2c *regs +) +{ + uint32_t space_available; + uint32_t todo_now; + uint32_t i; + + space_available = CADENCE_I2C_FIFO_DEPTH - regs->transfer_size; + + if (bus->current_todo > space_available) { + todo_now = space_available; + } else { + todo_now = bus->current_todo; + } + + bus->current_todo -= todo_now; + + for (i = 0; i < todo_now; ++i) { + regs->data = *bus->current_msg_byte; + + cadence_i2c_next_byte(bus); + } +} + +static void cadence_i2c_setup_write_transfer( + cadence_i2c_bus *bus, + volatile cadence_i2c *regs, + uint32_t control +) +{ + control &= ~CADENCE_I2C_CONTROL_RW; + regs->control = control; + + cadence_i2c_write_to_fifo(bus, regs); +} + +static void cadence_i2c_setup_transfer( + cadence_i2c_bus *bus, + volatile cadence_i2c *regs +) +{ + const i2c_msg *msgs = bus->msgs; + uint32_t msg_todo = bus->msg_todo; + uint32_t i; + uint32_t control; + + bus->current_todo = msgs[0].len; + for (i = 1; i < msg_todo && (msgs[i].flags & I2C_M_NOSTART) != 0; ++i) { + bus->current_todo += msgs[i].len; + } + + regs = bus->regs; + + control = regs->control; + control |= CADENCE_I2C_CONTROL_CLR_FIFO; + + bus->hold = i < msg_todo; + + if (bus->hold || bus->current_todo > CADENCE_I2C_FIFO_DEPTH) { + control |= CADENCE_I2C_CONTROL_HOLD; + } else { + control &= ~CADENCE_I2C_CONTROL_HOLD; + } + + control = cadence_i2c_set_address_size(msgs, control); + + bus->read = (msgs->flags & I2C_M_RD) != 0; + if (bus->read) { + cadence_i2c_setup_read_transfer(bus, regs, control); + } else { + cadence_i2c_setup_write_transfer(bus, regs, control); + } + + cadence_i2c_clear_irq_status(regs); + + regs->address = CADENCE_I2C_ADDRESS(msgs->addr); +} + +static void cadence_i2c_continue_read_transfer( + cadence_i2c_bus *bus, + volatile cadence_i2c *regs +) +{ + uint32_t i; + + bus->current_todo -= CADENCE_I2C_DATA_IRQ_DEPTH; + + /* + * This works since CADENCE_I2C_TRANSFER_SIZE_ONCE_MAX is an integral + * multiple of CADENCE_I2C_DATA_IRQ_DEPTH. + * + * FIXME: Tests with a 1024 byte EEPROM show that this doesn't work. Needs + * further investigations with an I2C analyser or an oscilloscope. + */ + if (regs->transfer_size == 0) { + if (bus->current_todo <= CADENCE_I2C_TRANSFER_SIZE_MAX) { + regs->transfer_size = bus->current_todo; + } else { + regs->transfer_size = CADENCE_I2C_TRANSFER_SIZE_ONCE_MAX; + } + } + + for (i = 0; i < CADENCE_I2C_DATA_IRQ_DEPTH; ++i) { + *bus->current_msg_byte = (uint8_t) regs->data; + + cadence_i2c_next_byte(bus); + } + + if (!bus->hold && bus->current_todo <= CADENCE_I2C_FIFO_DEPTH) { + regs->control &= ~CADENCE_I2C_CONTROL_HOLD; + } +} + +static void cadence_i2c_interrupt(void *arg) +{ + cadence_i2c_bus *bus = arg; + volatile cadence_i2c *regs = bus->regs; + uint32_t irqstatus = regs->irqstatus; + bool done = false; + + /* Clear interrupts */ + regs->irqstatus = irqstatus; + + if ((irqstatus & (CADENCE_I2C_IXR_ARB_LOST | CADENCE_I2C_IXR_NACK)) != 0) { + done = true; + } + + if ( + (irqstatus & CADENCE_I2C_IXR_DATA) != 0 + && bus->read + && bus->current_todo >= CADENCE_I2C_DATA_IRQ_DEPTH + ) { + cadence_i2c_continue_read_transfer(bus, regs); + } + + if ((irqstatus & CADENCE_I2C_IXR_COMP) != 0) { + if (bus->read) { + uint32_t todo_now = bus->current_todo; + uint32_t i; + + for (i = 0; i < todo_now; ++i) { + *bus->current_msg_byte = (uint8_t) regs->data; + + cadence_i2c_next_byte(bus); + } + + bus->current_todo = 0; + + done = true; + } else { + if (bus->current_todo > 0) { + cadence_i2c_write_to_fifo(bus, regs); + } else { + done = true; + } + + if (!bus->hold && bus->current_todo == 0) { + regs->control &= ~CADENCE_I2C_CONTROL_HOLD; + } + } + } + + if (done) { + uint32_t err = irqstatus & CADENCE_I2C_IRQ_ERROR; + + if (bus->msg_todo == 0 || err != 0) { + rtems_status_code sc; + + cadence_i2c_disable_interrupts(regs); + + bus->irqstatus = err; + + sc = rtems_event_transient_send(bus->task_id); + _Assert(sc == RTEMS_SUCCESSFUL); + (void) sc; + } else { + cadence_i2c_setup_transfer(bus, regs); + } + } +} + +static int cadence_i2c_transfer( + i2c_bus *base, + i2c_msg *msgs, + uint32_t msg_count +) +{ + cadence_i2c_bus *bus = (cadence_i2c_bus *) base; + volatile cadence_i2c *regs; + rtems_status_code sc; + uint32_t i; + + _Assert(msg_count > 0); + + for (i = 0; i < msg_count; ++i) { + /* FIXME: Not sure if we can support this. */ + if ((msgs[i].flags & I2C_M_RECV_LEN) != 0) { + return -EINVAL; + } + } + + bus->msgs = &msgs[0]; + bus->msg_todo = msg_count; + bus->current_msg_todo = msgs[0].len; + bus->current_msg_byte = msgs[0].buf; + bus->task_id = rtems_task_self(); + + regs = bus->regs; + cadence_i2c_setup_transfer(bus, regs); + regs->irqenable = CADENCE_I2C_IRQ_USED; + + sc = rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout); + if (sc != RTEMS_SUCCESSFUL) { + cadence_i2c_reset(bus); + rtems_event_transient_clear(); + + return -ETIMEDOUT; + } + + return bus->irqstatus == 0 ? 0 : -EIO; +} + +static int cadence_i2c_set_clock(i2c_bus *base, unsigned long clock) +{ + cadence_i2c_bus *bus = (cadence_i2c_bus *) base; + volatile cadence_i2c *regs = bus->regs; + uint32_t error = 0xffffffff; + uint32_t best_div_a = CADENCE_I2C_DIV_A_MAX - 1; + uint32_t best_div_b = CADENCE_I2C_DIV_B_MAX - 1; + uint32_t div = bus->input_clock / (22 * clock); + uint32_t div_a; + uint32_t control; + + if (div <= 0 || div > (CADENCE_I2C_DIV_A_MAX * CADENCE_I2C_DIV_B_MAX)) { + return -EIO; + } + + for (div_a = 0; div_a < CADENCE_I2C_DIV_A_MAX; ++div_a) { + uint32_t a = 22 * clock * (div_a + 1); + uint32_t b = (bus->input_clock + a - 1) / a; + + if (b > 0 && b <= CADENCE_I2C_DIV_B_MAX) { + uint32_t actual_clock = bus->input_clock / (22 * (div_a + 1) * b); + uint32_t e = clock < actual_clock ? + actual_clock - clock : clock - actual_clock; + + /* + * Favour greater div_a values according to UG585, Zynq-7000 AP SoC + * Technical Reference Manual, Table 20-1: Calculated Values for Standard + * and High Speed SCL Clock Values". + */ + if (e <= error && actual_clock <= clock) { + error = e; + best_div_a = div_a; + best_div_b = b - 1; + } + } + } + + control = regs->control; + control = CADENCE_I2C_CONTROL_DIV_A_SET(control, best_div_a); + control = CADENCE_I2C_CONTROL_DIV_B_SET(control, best_div_b); + regs->control = control; + + return 0; +} + +static void cadence_i2c_destroy(i2c_bus *base) +{ + cadence_i2c_bus *bus = (cadence_i2c_bus *) base; + rtems_status_code sc; + + sc = rtems_interrupt_handler_remove(bus->irq, cadence_i2c_interrupt, bus); + _Assert(sc == RTEMS_SUCCESSFUL); + (void) sc; + + i2c_bus_destroy_and_free(&bus->base); +} + +int i2c_bus_register_cadence( + const char *bus_path, + uintptr_t register_base, + uint32_t input_clock, + rtems_vector_number irq +) +{ + cadence_i2c_bus *bus; + rtems_status_code sc; + int err; + + bus = (cadence_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus)); + if (bus == NULL) { + return -1; + } + + bus->regs = (volatile cadence_i2c *) register_base; + bus->input_clock = input_clock; + bus->irq = irq; + + cadence_i2c_reset(bus); + + err = cadence_i2c_set_clock(&bus->base, I2C_BUS_CLOCK_DEFAULT); + if (err != 0) { + (*bus->base.destroy)(&bus->base); + + rtems_set_errno_and_return_minus_one(-err); + } + + sc = rtems_interrupt_handler_install( + irq, + "Cadence I2C", + RTEMS_INTERRUPT_UNIQUE, + cadence_i2c_interrupt, + bus + ); + if (sc != RTEMS_SUCCESSFUL) { + (*bus->base.destroy)(&bus->base); + + rtems_set_errno_and_return_minus_one(EIO); + } + + bus->base.transfer = cadence_i2c_transfer; + bus->base.set_clock = cadence_i2c_set_clock; + bus->base.destroy = cadence_i2c_destroy; + + return i2c_bus_register(&bus->base, bus_path); +} |