mbed library sources. Supersedes mbed-src.
Dependents: Nucleo_Hello_Encoder BLE_iBeaconScan AM1805_DEMO DISCO-F429ZI_ExportTemplate1 ... more
Diff: targets/TARGET_Cypress/TARGET_PSOC6/i2c_api.c
- Revision:
- 188:bcfe06ba3d64
- Child:
- 189:f392fc9709a3
diff -r 0387e8f68319 -r bcfe06ba3d64 targets/TARGET_Cypress/TARGET_PSOC6/i2c_api.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/targets/TARGET_Cypress/TARGET_PSOC6/i2c_api.c Thu Nov 08 11:46:34 2018 +0000 @@ -0,0 +1,548 @@ +/* + * mbed Microcontroller Library + * Copyright (c) 2017-2018 Future Electronics + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cmsis.h" +#include "mbed_assert.h" +#include "mbed_error.h" +#include "PeripheralPins.h" +#include "pinmap.h" +#include "i2c_api.h" +#include "psoc6_utils.h" + +#include "drivers/peripheral/sysclk/cy_sysclk.h" +#include "drivers/peripheral/gpio/cy_gpio.h" +#include "drivers/peripheral/scb/cy_scb_i2c.h" +#include "drivers/peripheral/sysint/cy_sysint.h" + +#define I2C_DEFAULT_SPEED 100000 +#define NUM_I2C_PORTS 8 +#define I2C_DEFAULT_IRQ_PRIORITY 3 +#define I2C_NUM_DIVIDERS 3 +#define MIN_I2C_CLOCK_FREQUENCY CY_SCB_I2C_SLAVE_STD_CLK_MIN +// Default timeout in milliseconds. +#define I2C_DEFAULT_TIMEOUT 1000 + +#define PENDING_NONE 0 +#define PENDING_RX 1 +#define PENDING_TX 2 +#define PENDING_TX_RX 3 + +typedef enum { + I2C_DIVIDER_LOW = 0, + I2C_DIVIDER_MID, + I2C_DIVIDER_HIGH, + I2C_INVALID_DIVIDER = 0xff +} I2cDividerType; + + +typedef struct { + uint32_t div_num; + cy_en_divider_types_t div_type; + uint32_t clk_frequency; +} I2cDividerInfo; + + +static const cy_stc_scb_i2c_config_t default_i2c_config = { + .i2cMode = CY_SCB_I2C_MASTER, + .useRxFifo = true, + .useTxFifo = true, + .slaveAddress = 0, + .slaveAddressMask = 0, + .acceptAddrInFifo = false, + .ackGeneralAddr = false, + .enableWakeFromSleep = false +}; + + +static I2cDividerInfo i2c_dividers[I2C_NUM_DIVIDERS] = { + { I2C_INVALID_DIVIDER, 0, CY_SCB_I2C_SLAVE_STD_CLK_MIN }, // Low divider uses lowest possible frequency. + { I2C_INVALID_DIVIDER, 0, CY_SCB_I2C_SLAVE_FST_CLK_MIN }, + { I2C_INVALID_DIVIDER, 0, CY_SCB_I2C_SLAVE_FSTP_CLK_MIN } +}; + +typedef struct i2c_s i2c_obj_t; + +#if DEVICE_I2C_ASYNCH +#define OBJ_P(in) (&(in->i2c)) +#else +#define OBJ_P(in) (in) +#endif + + +#if DEVICE_I2C_ASYNCH + +static IRQn_Type i2c_irq_allocate_channel(i2c_obj_t *obj) +{ +#if defined (TARGET_MCU_PSOC6_M0) + obj->cm0p_irq_src = scb_0_interrupt_IRQn + obj->i2c_id; + return cy_m0_nvic_allocate_channel(CY_SERIAL_IRQN_ID + obj->i2c_id); +#else + return (IRQn_Type)(ioss_interrupts_gpio_0_IRQn + obj->i2c_id); +#endif // M0 +} + +static void i2c_irq_release_channel(IRQn_Type channel, uint32_t i2c_id) +{ +#if defined (TARGET_MCU_PSOC6_M0) + cy_m0_nvic_release_channel(channel, CY_SERIAL_IRQN_ID + i2c_id); +#endif //M0 +} + +static int i2c_irq_setup_channel(i2c_obj_t *obj) +{ + cy_stc_sysint_t irq_config; + + if (obj->irqn == unconnected_IRQn) { + IRQn_Type irqn = i2c_irq_allocate_channel(obj); + if (irqn < 0) { + return (-1); + } + // Configure NVIC + irq_config.intrPriority = I2C_DEFAULT_IRQ_PRIORITY; + irq_config.intrSrc = irqn; +#if defined (TARGET_MCU_PSOC6_M0) + irq_config.cm0pSrc = obj->cm0p_irq_src; +#endif + if (Cy_SysInt_Init(&irq_config, (cy_israddress)(obj->handler)) != CY_SYSINT_SUCCESS) { + return(-1); + } + + obj->irqn = irqn; + NVIC_EnableIRQ(irqn); + } + return 0; +} + +#endif // DEVICE_I2C_ASYNCH + +static int allocate_divider(I2cDividerType divider) +{ + I2cDividerInfo *p_div = &i2c_dividers[divider]; + + if (p_div->div_num == CY_INVALID_DIVIDER) { + p_div->div_num = cy_clk_allocate_divider(CY_SYSCLK_DIV_8_BIT); + if (p_div->div_num != CY_INVALID_DIVIDER) { + p_div->div_type = CY_SYSCLK_DIV_8_BIT; + } else { + p_div->div_num = cy_clk_allocate_divider(CY_SYSCLK_DIV_16_BIT); + if (p_div->div_num != CY_INVALID_DIVIDER) { + p_div->div_type = CY_SYSCLK_DIV_16_BIT; + } + } + } + + if (p_div->div_num != CY_INVALID_DIVIDER) { + // Set up proper frequency; + uint32_t div_value = CY_CLK_PERICLK_FREQ_HZ / p_div->clk_frequency; + p_div->clk_frequency = CY_CLK_PERICLK_FREQ_HZ / div_value; + if (Cy_SysClk_PeriphSetDivider(p_div->div_type, p_div->div_num, div_value) == CY_SYSCLK_SUCCESS) { + Cy_SysClk_PeriphEnableDivider(p_div->div_type, p_div->div_num); + } else { + p_div->div_num = CY_INVALID_DIVIDER; + } + } + + return (p_div->div_num == CY_INVALID_DIVIDER)? -1 : 0; +} + +/* + * Select one of the 3 dividers used depending on the required frequency. + */ +static I2cDividerType select_divider(uint32_t frequency) +{ + if (frequency <= (MIN_I2C_CLOCK_FREQUENCY / CY_SCB_I2C_DUTY_CYCLE_MAX)) { + // Required speed lower than min supported. + return I2C_INVALID_DIVIDER; + } else if (frequency <= CY_SCB_I2C_STD_DATA_RATE) { + return I2C_DIVIDER_LOW; + } else if (frequency <= CY_SCB_I2C_FST_DATA_RATE) { + return I2C_DIVIDER_MID; + } else if (frequency <= CY_SCB_I2C_FSTP_DATA_RATE) { + return I2C_DIVIDER_HIGH; + } else { + // Required speed too high; + return I2C_INVALID_DIVIDER; + } +} + +/* + * Initializes i2c clock for the required speed + */ +static cy_en_sysclk_status_t i2c_init_clock(i2c_obj_t *obj, uint32_t speed) +{ + I2cDividerInfo *p_div = NULL; + cy_en_sysclk_status_t status = CY_SYSCLK_INVALID_STATE; + I2cDividerType divider = select_divider(speed); + + if (divider == I2C_INVALID_DIVIDER) { + error("i2c: required speed/frequency is out of valid range."); + return CY_SYSCLK_BAD_PARAM; + } + + if (allocate_divider(divider) < 0) { + error("i2c: cannot allocate clock divider."); + return CY_SYSCLK_INVALID_STATE; + } + + obj->divider = divider; + p_div = &i2c_dividers[divider]; + + status = Cy_SysClk_PeriphAssignDivider(obj->clock, p_div->div_type, p_div->div_num); + if (status != CY_SYSCLK_SUCCESS) { + error("i2c: cannot assign clock divider."); + return status; + } + + /* Set desired speed/frequency */ + obj->actual_speed = Cy_SCB_I2C_SetDataRate(obj->base, speed, p_div->clk_frequency); + return (obj->actual_speed != 0)? CY_SYSCLK_SUCCESS : CY_SYSCLK_BAD_PARAM; +} + +/* + * Initializes i/o pins for i2c sda/scl. + */ +static void i2c_init_pins(i2c_obj_t *obj) +{ + int sda_function = pinmap_function(obj->pin_sda, PinMap_I2C_SDA); + int scl_function = pinmap_function(obj->pin_scl, PinMap_I2C_SCL); + pin_function(obj->pin_sda, sda_function); + pin_function(obj->pin_scl, scl_function); +} + + +/* + * Initializes and enables I2C/SCB. + */ +static void i2c_init_peripheral(i2c_obj_t *obj) +{ + cy_stc_scb_i2c_config_t i2c_config = default_i2c_config; + I2cDividerInfo *p_div = &i2c_dividers[obj->divider]; + + Cy_SCB_I2C_Init(obj->base, &i2c_config, &obj->context); + Cy_SCB_I2C_SetDataRate(obj->base,obj->actual_speed, p_div->clk_frequency); + Cy_SCB_I2C_Enable(obj->base); +} + +/* + * Coverts PDL status into Mbed status. + */ +static int i2c_convert_status(cy_en_scb_i2c_status_t status) +{ + switch (status) { + case CY_SCB_I2C_MASTER_NOT_READY: + case CY_SCB_I2C_MASTER_MANUAL_ARB_LOST: + case CY_SCB_I2C_MASTER_MANUAL_BUS_ERR: + case CY_SCB_I2C_MASTER_MANUAL_ABORT_START: + return I2C_ERROR_BUS_BUSY; + + case CY_SCB_I2C_MASTER_MANUAL_TIMEOUT: + case CY_SCB_I2C_MASTER_MANUAL_ADDR_NAK: + case CY_SCB_I2C_MASTER_MANUAL_NAK: + return I2C_ERROR_NO_SLAVE; + + case CY_SCB_I2C_SUCCESS: + case CY_SCB_I2C_BAD_PARAM: + default: + return 0; + } +} + +/* + * Callback function to handle into and out of deep sleep state transitions. + */ +#if DEVICE_SLEEP && DEVICE_LOWPOWERTIMER +static cy_en_syspm_status_t i2c_pm_callback(cy_stc_syspm_callback_params_t *callback_params) +{ + cy_stc_syspm_callback_params_t params = *callback_params; + i2c_obj_t *obj = (i2c_obj_t *)params.context; + params.context = &obj->context; + + return Cy_SCB_I2C_DeepSleepCallback(¶ms); +} +#endif // DEVICE_SLEEP && DEVICE_LOWPOWERTIMER + + +void i2c_init(i2c_t *obj_in, PinName sda, PinName scl) +{ + i2c_obj_t *obj = OBJ_P(obj_in); + uint32_t i2c = pinmap_peripheral(sda, PinMap_I2C_SDA); + i2c = pinmap_merge(i2c, pinmap_peripheral(scl, PinMap_I2C_SCL)); + if (i2c != (uint32_t)NC) { + if (cy_reserve_io_pin(sda) || cy_reserve_io_pin(scl)) { + error("I2C pin reservation conflict."); + } + obj->base = (CySCB_Type*)i2c; + obj->i2c_id = ((I2CName)i2c - I2C_0) / (I2C_1 - I2C_0); + obj->pin_sda = sda; + obj->pin_scl = scl; + obj->clock = CY_PIN_CLOCK(pinmap_function(scl, PinMap_I2C_SCL)); + obj->divider = I2C_INVALID_DIVIDER; + obj->mode = CY_SCB_I2C_MASTER; + obj->timeout = I2C_DEFAULT_TIMEOUT; +#if DEVICE_I2C_ASYNCH + obj->pending = PENDING_NONE; + obj->events = 0; +#endif // DEVICE_I2C_ASYNCH + i2c_init_clock(obj, I2C_DEFAULT_SPEED); + i2c_init_pins(obj); + i2c_init_peripheral(obj); +#if DEVICE_SLEEP && DEVICE_LOWPOWERTIMER + obj->pm_callback_handler.callback = i2c_pm_callback; + obj->pm_callback_handler.type = CY_SYSPM_DEEPSLEEP; + obj->pm_callback_handler.skipMode = 0; + obj->pm_callback_handler.callbackParams = &obj->pm_callback_params; + obj->pm_callback_params.base = obj->base; + obj->pm_callback_params.context = obj; + if (!Cy_SysPm_RegisterCallback(&obj->pm_callback_handler)) { + error("PM callback registration failed!"); + } +#endif // DEVICE_SLEEP && DEVICE_LOWPOWERTIMER + } else { + error("I2C pinout mismatch. Requested pins Rx and Tx can't be used for the same I2C communication."); + } +} + +void i2c_frequency(i2c_t *obj_in, int hz) +{ + i2c_obj_t *obj = OBJ_P(obj_in); + Cy_SCB_I2C_Disable(obj->base, &obj->context); + i2c_init_clock(obj, hz); + Cy_SCB_I2C_Enable(obj->base); +} + +int i2c_start(i2c_t *obj_in) +{ + // Unsupported, start condition is sent automatically. + return 0; +} + +int i2c_stop(i2c_t *obj_in) +{ + // Unsupported, stop condition is sent automatically. + return 0; +} + +int i2c_read(i2c_t *obj_in, int address, char *data, int length, int stop) +{ + cy_en_scb_i2c_status_t status = CY_SCB_I2C_SUCCESS; + i2c_obj_t *obj = OBJ_P(obj_in); + cy_en_scb_i2c_command_t ack = CY_SCB_I2C_ACK; + int byte_count = 0; + address >>= 1; + + // Start transaction, send address. + if (obj->context.state == CY_SCB_I2C_IDLE) { + status = Cy_SCB_I2C_MasterSendStart(obj->base, address, CY_SCB_I2C_READ_XFER, obj->timeout, &obj->context); + } + if (status == CY_SCB_I2C_SUCCESS) { + while (length > 0) { + if (length == 1) { + ack = CY_SCB_I2C_NAK; + } + status = Cy_SCB_I2C_MasterReadByte(obj->base, ack, (uint8_t *)data, obj->timeout, &obj->context); + if (status != CY_SCB_I2C_SUCCESS) { + break; + } + ++byte_count; + --length; + ++data; + } + // SCB in I2C mode is very time sensitive. In practice we have to request STOP after + // each block, otherwise it may break the transmission. + Cy_SCB_I2C_MasterSendStop(obj->base, obj->timeout, &obj->context); + } + + if (status != CY_SCB_I2C_SUCCESS) { + Cy_SCB_I2C_MasterSendStop(obj->base, obj->timeout, &obj->context); + byte_count = i2c_convert_status(status); + } + + return byte_count; +} + +int i2c_write(i2c_t *obj_in, int address, const char *data, int length, int stop) +{ + cy_en_scb_i2c_status_t status = CY_SCB_I2C_SUCCESS; + i2c_obj_t *obj = OBJ_P(obj_in); + int byte_count = 0; + address >>= 1; + + // Start transaction, send address. + if (obj->context.state == CY_SCB_I2C_IDLE) { + status = Cy_SCB_I2C_MasterSendStart(obj->base, address, CY_SCB_I2C_WRITE_XFER, obj->timeout, &obj->context); + } + if (status == CY_SCB_I2C_SUCCESS) { + while (length > 0) { + status = Cy_SCB_I2C_MasterWriteByte(obj->base, *data, obj->timeout, &obj->context); + if (status != CY_SCB_I2C_SUCCESS) { + break;; + } + ++byte_count; + --length; + ++data; + } + // SCB in I2C mode is very time sensitive. In practice we have to request STOP after + // each block, otherwise it may break the transmission. + Cy_SCB_I2C_MasterSendStop(obj->base, obj->timeout, &obj->context); + } + + if (status != CY_SCB_I2C_SUCCESS) { + Cy_SCB_I2C_MasterSendStop(obj->base, obj->timeout, &obj->context); + byte_count = i2c_convert_status(status); + } + + return byte_count; +} + +void i2c_reset(i2c_t *obj_in) +{ + i2c_stop(obj_in); +} + +int i2c_byte_read(i2c_t *obj_in, int last) +{ + i2c_obj_t *obj = OBJ_P(obj_in); + uint8_t tmp_byte = 0; + cy_en_scb_i2c_command_t ack = last? CY_SCB_I2C_NAK : CY_SCB_I2C_ACK; + cy_en_scb_i2c_status_t status = Cy_SCB_I2C_MasterReadByte(obj->base, ack, &tmp_byte, obj->timeout, &obj->context); + + if (status == CY_SCB_I2C_SUCCESS) { + return tmp_byte; + } else { + return 0; + } +} + +int i2c_byte_write(i2c_t *obj_in, int data) +{ + i2c_obj_t *obj = OBJ_P(obj_in); + cy_en_scb_i2c_status_t status = Cy_SCB_I2C_MasterWriteByte(obj->base, (uint8_t)data, obj->timeout, &obj->context); + switch (status) { + case CY_SCB_I2C_MASTER_MANUAL_TIMEOUT: + return 2; + case CY_SCB_I2C_MASTER_MANUAL_ADDR_NAK: + case CY_SCB_I2C_MASTER_MANUAL_NAK: + return 0; + case CY_SCB_I2C_SUCCESS: + return 1; + default: + // Error has occurred. + return (-1); + } +} + +#if DEVICE_I2C_ASYNCH + +void i2c_transfer_asynch(i2c_t *obj_in, + const void *tx, + size_t tx_length, + void *rx, size_t rx_length, + uint32_t address, + uint32_t stop, + uint32_t handler, + uint32_t event, + DMAUsage hint) +{ + i2c_obj_t *obj = OBJ_P(obj_in); + + (void)hint; // At the moment we do not support DMA transfers, so this parameter gets ignored. + + if (obj->pending != PENDING_NONE) { + return; + } + + obj->rx_config.slaveAddress = address >> 1; + obj->tx_config.slaveAddress = address >> 1; + obj->events = event; + obj->handler = handler; + if (i2c_irq_setup_channel(obj) < 0) { + return; + } + + obj->rx_config.buffer = rx; + obj->rx_config.bufferSize = rx_length; + obj->rx_config.xferPending = !stop; + + obj->tx_config.buffer = (void*)tx; + obj->tx_config.bufferSize = tx_length; + obj->tx_config.xferPending = rx_length || !stop; + + if (tx_length) { + // Write first, then read, or write only. + if (rx_length > 0) { + obj->pending = PENDING_TX_RX; + } else { + obj->pending = PENDING_TX; + } + Cy_SCB_I2C_MasterWrite(obj->base, &obj->tx_config, &obj->context); + } else if (rx_length) { + // Read transaction; + obj->pending = PENDING_RX; + Cy_SCB_I2C_MasterRead(obj->base, &obj->rx_config, &obj->context); + } +} + +uint32_t i2c_irq_handler_asynch(i2c_t *obj_in) +{ + i2c_obj_t *obj = OBJ_P(obj_in); + uint32_t event = 0; + // Process actual interrupt. + Cy_SCB_I2C_Interrupt(obj->base, &obj->context); + if (obj->context.state == CY_SCB_I2C_MASTER_CMPLT) { + if (obj->context.masterStatus & CY_SCB_I2C_MASTER_ERR) { + if (obj->context.masterStatus & CY_SCB_I2C_MASTER_ADDR_NAK) { + event = I2C_EVENT_ERROR_NO_SLAVE; + } else if (obj->context.masterStatus & CY_SCB_I2C_MASTER_DATA_NAK) { + event = I2C_EVENT_TRANSFER_EARLY_NACK; + } else { + event = I2C_EVENT_ERROR; + } + } else { + // Check if a read phase is pending after write. + if (obj->pending == PENDING_TX_RX) { + obj->pending = PENDING_RX; + Cy_SCB_I2C_MasterRead(obj->base, &obj->rx_config, &obj->context); + } else { + event = I2C_EVENT_TRANSFER_COMPLETE; + } + } + } + if (event) { + obj->pending = PENDING_NONE; + } + return event & obj->events; +} + +uint8_t i2c_active(i2c_t *obj_in) +{ + i2c_obj_t *obj = OBJ_P(obj_in); + return (obj->pending != PENDING_NONE); +} + +void i2c_abort_asynch(i2c_t *obj_in) +{ + i2c_obj_t *obj = OBJ_P(obj_in); + if (obj->pending != PENDING_NONE) { + if (obj->pending == PENDING_RX) { + Cy_SCB_I2C_MasterAbortRead(obj->base, &obj->context); + } else { + Cy_SCB_I2C_MasterAbortWrite(obj->base, &obj->context); + } + } +} + +#endif // DEVICE_ASYNCH