diff --git a/module/dw_apb_i2c/include/mod_dw_apb_i2c.h b/module/dw_apb_i2c/include/mod_dw_apb_i2c.h new file mode 100644 index 0000000000000000000000000000000000000000..a340f73d983cbebeb93991234cf86a890500163e --- /dev/null +++ b/module/dw_apb_i2c/include/mod_dw_apb_i2c.h @@ -0,0 +1,64 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2019, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * DesignWare DW_apb_i2c I2C controller + */ + +#ifndef MOD_DW_APB_I2C_H +#define MOD_DW_APB_I2C_H + +#include +#include +#include + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupModuleI2CController I2C Controller + * + * \brief Driver for I2C device. + * @{ + */ + +/*! + * \brief Element configuration + */ +struct mod_dw_apb_i2c_dev_config { + /*! + * \brief Identifier of the timer + * + * \details The timer is used to wait on the I2C bus status. + */ + fwk_id_t timer_id; + /*! Interrupt number of the I2C device */ + unsigned int i2c_irq; + /*! Base address of the I2C device registers */ + uintptr_t reg; +}; + +/*! API indices */ +enum mod_dw_apb_i2c_api_idx { + MOD_DW_APB_I2C_API_IDX_DRIVER, + MOD_DW_APB_I2C_API_IDX_COUNT, +}; + +/*! Identifier for the I2C driver API */ +static const fwk_id_t mod_dw_apb_i2c_api_id_driver = FWK_ID_API_INIT( + FWK_MODULE_IDX_DW_APB_I2C, MOD_DW_APB_I2C_API_IDX_DRIVER); + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_DW_APB_I2C_H */ diff --git a/module/dw_apb_i2c/src/Makefile b/module/dw_apb_i2c/src/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f731f73a3a9f50d3e38c827a9c17b8eab3273701 --- /dev/null +++ b/module/dw_apb_i2c/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2015-2019, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := "DW APB I2C" +BS_LIB_SOURCES = mod_dw_apb_i2c.c + +include $(BS_DIR)/lib.mk diff --git a/module/dw_apb_i2c/src/dw_apb_i2c.h b/module/dw_apb_i2c/src/dw_apb_i2c.h new file mode 100644 index 0000000000000000000000000000000000000000..6e50ed5a39a4718ddd79557b831375391c3bd2ef --- /dev/null +++ b/module/dw_apb_i2c/src/dw_apb_i2c.h @@ -0,0 +1,70 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2019, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Definitions and utility functions for the I2C controller module. + */ + +#ifndef DW_APB_I2C_H +#define DW_APB_I2C_H + +#include +#include + +#define I2C_TRANSMIT_BUFFER_LENGTH 16 +#define I2C_RECEIVE_BUFFER_LENGTH 16 +#define I2C_TIMEOUT_US 250 + +/* + * I2C controller register definitions + */ +struct dw_apb_i2c_reg { + uint8_t RESERVED0[0x04 - 0x00]; + FWK_RW uint32_t IC_TAR; + uint8_t RESERVED1[0x10 - 0x08]; + FWK_RW uint32_t IC_DATA_CMD; + uint8_t RESERVED2[0x2C - 0x14]; + FWK_R uint32_t IC_INTR_STAT; + FWK_RW uint32_t IC_INTR_MASK; + uint8_t RESERVED3[0x54 - 0x34]; + FWK_R uint32_t IC_CLR_TX_ABRT; + uint8_t RESERVED4[0x60 - 0x58]; + FWK_R uint32_t IC_CLR_STOP_DET; + uint8_t RESERVED5[0x6C - 0x64]; + FWK_RW uint32_t IC_ENABLE; + FWK_R uint32_t IC_STATUS; + uint8_t RESERVED6[0x9C - 0x74]; + FWK_R uint32_t IC_ENABLE_STATUS; + uint8_t RESERVED7[0x100 - 0xA0]; +}; + +#define IC_TAR_ADDRESS UINT32_C(0x000003FF) + +#define IC_ENABLE_STATUS_MASK UINT32_C(0x00000001) +#define IC_ENABLE_STATUS_DISABLED 0x0 +#define IC_ENABLE_STATUS_ENABLED 0x1 + +#define IC_STATUS_MST_ACTIVITY_MASK UINT32_C(0x00000020) +#define IC_STATUS_TFNF_MASK UINT32_C(0x00000002) + +#define IC_DATA_CMD_CMD_MASK UINT32_C(0x00000100) +#define IC_DATA_CMD_DATA_MASK UINT32_C(0x000000FF) + +/* + * Command modes for IC_DATA_CMD + * Note: Bit [8] will be cleared when a new byte is written into the IC_DATA_CMD + * register, setting write mode automatically. + */ +#define IC_DATA_CMD_READ 0x100 + +/* IRQ Masks */ +#define IC_INTR_TX_ABRT_POS 6 +#define IC_INTR_TX_ABRT_MASK (UINT32_C(1) << IC_INTR_TX_ABRT_POS) + +#define IC_INTR_STOP_DET_POS 9 +#define IC_INTR_STOP_DET_MASK (UINT32_C(1) << IC_INTR_STOP_DET_POS) + +#endif /* DW_APB_I2C_H */ diff --git a/module/dw_apb_i2c/src/mod_dw_apb_i2c.c b/module/dw_apb_i2c/src/mod_dw_apb_i2c.c new file mode 100644 index 0000000000000000000000000000000000000000..948a35a4d1436b22295873bd74f3868a25e14cfc --- /dev/null +++ b/module/dw_apb_i2c/src/mod_dw_apb_i2c.c @@ -0,0 +1,324 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2019, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dw_apb_i2c_ctx { + const struct mod_dw_apb_i2c_dev_config *config; + const struct mod_i2c_driver_response_api *i2c_api; + const struct mod_timer_api *timer_api; + fwk_id_t i2c_id; + struct dw_apb_i2c_reg *i2c_reg; + bool read_on_going; + uint8_t byte_count; + uint8_t *data; +}; + +static struct dw_apb_i2c_ctx *ctx_table; + +/* + * Static helpers + */ +static bool is_i2c_disabled(void *param) +{ + struct dw_apb_i2c_reg *i2c_reg = (struct dw_apb_i2c_reg *)param; + + return ((i2c_reg->IC_ENABLE_STATUS & IC_ENABLE_STATUS_MASK) == + IC_ENABLE_STATUS_DISABLED); +} + +static int disable_i2c(struct dw_apb_i2c_ctx *ctx) +{ + int status; + fwk_id_t timer_id; + const struct mod_timer_api *timer_api; + struct dw_apb_i2c_reg *i2c_reg; + + timer_api = ctx->timer_api; + timer_id = ctx->config->timer_id; + i2c_reg = ctx->i2c_reg; + + /* Check whether the device is already disabled */ + if (is_i2c_disabled(i2c_reg)) + return FWK_SUCCESS; + + /* The bus should be idle */ + if ((ctx->i2c_reg->IC_STATUS & IC_STATUS_MST_ACTIVITY_MASK) != 0) + return FWK_E_DEVICE; + + /* Disable the I2C device */ + ctx->i2c_reg->IC_ENABLE = IC_ENABLE_STATUS_DISABLED; + + /* Wait until the device is disabled */ + status = timer_api->wait(timer_id, I2C_TIMEOUT_US, is_i2c_disabled, + i2c_reg); + if (status != FWK_SUCCESS) + return FWK_E_TIMEOUT; + + return FWK_SUCCESS; +} + +static int enable_i2c(struct dw_apb_i2c_ctx *ctx, uint8_t slave_address) +{ + int status; + struct dw_apb_i2c_reg *i2c_reg; + + i2c_reg = ctx->i2c_reg; + + /* Disable the I2C device to configure it */ + status = disable_i2c(ctx); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + /* Program the slave address */ + i2c_reg->IC_TAR = (slave_address & IC_TAR_ADDRESS); + + /* Enable STOP detected interrupt and TX aborted interrupt */ + i2c_reg->IC_INTR_MASK = (IC_INTR_STOP_DET_MASK | IC_INTR_TX_ABRT_MASK); + + /* Enable the I2C device */ + i2c_reg->IC_ENABLE = IC_ENABLE_STATUS_ENABLED; + + return FWK_SUCCESS; +} + +/* + * An IRQ is triggered if the transaction has been completed successfully or + * if the transaction has been aborted. + */ +static void i2c_isr(uintptr_t data) +{ + unsigned int i; + int i2c_status = FWK_E_DEVICE; + struct dw_apb_i2c_reg *i2c_reg; + struct dw_apb_i2c_ctx *ctx = (struct dw_apb_i2c_ctx *)data; + + i2c_reg = ctx->i2c_reg; + + /* The transaction has completed successfully */ + if (i2c_reg->IC_INTR_STAT & IC_INTR_STOP_DET_MASK) { + i2c_reg->IC_CLR_STOP_DET; + i2c_status = FWK_SUCCESS; + if (ctx->read_on_going) { + ctx->read_on_going = false; + /* Read the data from the device buffer */ + for (i = 0; i < ctx->byte_count; i++) + ctx->data[i] = + (uint8_t)(i2c_reg->IC_DATA_CMD & IC_DATA_CMD_DATA_MASK); + } + } + + /* The transaction has been aborted */ + if (i2c_reg->IC_INTR_STAT & IC_INTR_TX_ABRT_MASK) + i2c_reg->IC_CLR_TX_ABRT; + + ctx->i2c_api->transaction_completed(ctx->i2c_id, i2c_status); +} + +/* + * Driver API + */ +static int transmit_as_master(fwk_id_t dev_id, + struct mod_i2c_request *transmit_request) +{ + int status; + unsigned int sent_bytes; + struct dw_apb_i2c_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + if (transmit_request->transmit_byte_count > I2C_TRANSMIT_BUFFER_LENGTH) + return FWK_E_SUPPORT; + + if (transmit_request->slave_address == 0) + return FWK_E_PARAM; + + ctx = ctx_table + fwk_id_get_element_idx(dev_id); + + status = enable_i2c(ctx, transmit_request->slave_address); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + /* The program of the I2C controller cannot be interrupted. */ + fwk_interrupt_global_disable(); + + for (sent_bytes = 0; sent_bytes < transmit_request->transmit_byte_count; + sent_bytes++) + ctx->i2c_reg->IC_DATA_CMD = transmit_request->transmit_data[sent_bytes]; + + fwk_interrupt_global_enable(); + + return FWK_SUCCESS; +} + +static int receive_as_master(fwk_id_t dev_id, + struct mod_i2c_request *receive_request) +{ + int status; + unsigned int i; + struct dw_apb_i2c_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + if (receive_request->receive_byte_count > I2C_RECEIVE_BUFFER_LENGTH) + return FWK_E_SUPPORT; + + if (receive_request->slave_address == 0) + return FWK_E_PARAM; + + ctx = ctx_table + fwk_id_get_element_idx(dev_id); + + ctx->byte_count = receive_request->receive_byte_count; + ctx->data = receive_request->receive_data; + ctx->read_on_going = true; + + status = enable_i2c(ctx, receive_request->slave_address); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + /* The program of the I2C controller cannot be interrupted. */ + fwk_interrupt_global_disable(); + + /* Program the I2C controller with the expected reply length in bytes. */ + for (i = 0; i < receive_request->receive_byte_count; i++) + ctx->i2c_reg->IC_DATA_CMD = IC_DATA_CMD_READ; + + fwk_interrupt_global_enable(); + + return FWK_SUCCESS; +} + +static const struct mod_i2c_driver_api driver_api = { + .transmit_as_master = transmit_as_master, + .receive_as_master = receive_as_master +}; + +/* + * Framework handlers + */ +static int dw_apb_i2c_init(fwk_id_t module_id, + unsigned int element_count, + const void *data) +{ + ctx_table = fwk_mm_calloc(element_count, sizeof(*ctx_table)); + + if (ctx_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int dw_apb_i2c_element_init(fwk_id_t element_id, + unsigned int sub_element_count, + const void *data) +{ + struct mod_dw_apb_i2c_dev_config *config = + (struct mod_dw_apb_i2c_dev_config *)data; + + if (config->reg == 0) + return FWK_E_DATA; + + ctx_table[fwk_id_get_element_idx(element_id)].config = config; + ctx_table[fwk_id_get_element_idx(element_id)].i2c_reg = + (struct dw_apb_i2c_reg *)config->reg; + + return FWK_SUCCESS; +} + +static int dw_apb_i2c_bind(fwk_id_t id, unsigned int round) +{ + int status; + struct dw_apb_i2c_ctx *ctx; + const struct mod_dw_apb_i2c_dev_config *config; + + if (!fwk_module_is_valid_element_id(id) || (round == 0)) + return FWK_SUCCESS; + + ctx = ctx_table + fwk_id_get_element_idx(id); + config = ctx->config; + + status = fwk_module_bind(config->timer_id, MOD_TIMER_API_ID_TIMER, + &ctx->timer_api); + if (status != FWK_SUCCESS) + return status; + + return fwk_module_bind(ctx->i2c_id, mod_i2c_api_id_driver_response, + &ctx->i2c_api); +} + +static int dw_apb_i2c_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_id, + const void **api) +{ + struct dw_apb_i2c_ctx *ctx; + + if (!fwk_module_is_valid_element_id(target_id)) + return FWK_E_PARAM; + + ctx = ctx_table + fwk_id_get_element_idx(target_id); + + if (!fwk_id_is_equal(api_id, mod_dw_apb_i2c_api_id_driver)) + return FWK_E_PARAM; + + ctx->i2c_id = source_id; + + *api = &driver_api; + + return FWK_SUCCESS; +} + +static int dw_apb_i2c_start(fwk_id_t id) +{ + int status; + struct dw_apb_i2c_ctx *ctx; + unsigned int i2c_irq; + + /* Nothing to do for the module */ + if (!fwk_module_is_valid_element_id(id)) + return FWK_SUCCESS; + + ctx = ctx_table + fwk_id_get_element_idx(id); + i2c_irq = ctx->config->i2c_irq; + + status = fwk_interrupt_set_isr_param(i2c_irq, i2c_isr, (uintptr_t)ctx); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + status = fwk_interrupt_clear_pending(i2c_irq); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + status = fwk_interrupt_enable(i2c_irq); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + return FWK_SUCCESS; +} + +const struct fwk_module module_dw_apb_i2c = { + .name = "DW APB I2C", + .api_count = MOD_DW_APB_I2C_API_IDX_COUNT, + .type = FWK_MODULE_TYPE_DRIVER, + .init = dw_apb_i2c_init, + .element_init = dw_apb_i2c_element_init, + .bind = dw_apb_i2c_bind, + .start = dw_apb_i2c_start, + .process_bind_request = dw_apb_i2c_process_bind_request, +}; diff --git a/module/i2c/doc/module_i2c_architecture.md b/module/i2c/doc/module_i2c_architecture.md new file mode 100644 index 0000000000000000000000000000000000000000..cca5ebd39491add5f3ac6d1a33ae2ae8e5c9c184 --- /dev/null +++ b/module/i2c/doc/module_i2c_architecture.md @@ -0,0 +1,82 @@ +\ingroup GroupI2C +Module I2C Architecture +======================= + +# Overview {#module_i2c_architecture_overview} + +This module implements a Hardware Abstraction Layer (HAL) API for I2C +transactions. + +# Architecture {#module_i2c_architecture_architecture} + +The I2C module provides an interface for modules to transmit data through an I2C +bus, to receive data from an I2C bus and to perform a transmission followed by a +reception on the I2C bus. + +The I2C module defines a driver interface on which it relies to transfer/receive +data to/from the bus. + +A response event notifies the caller of the transaction completion. + +# Restriction {#module_i2c_architecture_restriction} + +The following features are unsupported. Support may be added in the future. + +- Acting like a slave. +- Concurrent accesses over the bus. +- 10-bit slave addressing. + +# Flow {#module_i2c_architecture_flow} + +The following schematic describes the transaction flow for an I2C master +transmission. The flow for a reception is similar. + + Client I2C I2C Driver I2C ISR (Driver) + | | | | + | transmit_ | | | + +-+ as_master | | | + | +-------------->+-+ | | + | | | +- - + | | + | +<--------------+-+ |process_ | | + +-+ | |event E1 | | + | | | | | + | +-+<- -+ | | + | | | transmit_ | | + | | | as_master | | + | | +------------>+-+ | + | | +<------------+-+ | + | +-+ | | + | | | transaction +-+ + | | | _completed | | + | +-+<-------------+--------------+ | + | +- -+ | | | | + | process_ | +-+--------------+------------->+ | + | event E2 | | | | | + | +- >+-+ | +-+ + | | | | | + +-+<- - - - - - - +-+ | | + +-+ process_ | | | + | event R1 | | | + + E1 : Request event + E2 : Request completed event + R1 : Response to the request event E1 + ---> : Function call/return + - -> : Asynchronous call via the event/notification interface + +The client calls *transmit_as_master* API of the I2C module. +This function creates and sends the targeted I2C device request event which +defines the targeted slave on the bus and the data to be transmitted. When +processing the request event, the I2C module initiates the transfer by +programming the I2C controller through the *transmit_as_master* API of the I2C +driver. + +An interrupt is asserted when the I2C transaction either completes or encounters +an error. The I2C ISR calls the *transaction_completed* API of the I2C HAL +module which sends a response event to indicate that transaction completed to +the client. + +In the case of *transmit_then_receive_as_master*, the I2C HAL does not send a +event at the end of the transmission. Instead it starts the reception by calling +*receive_as_master* driver function. The event is then sent when the reception +has completed. diff --git a/module/i2c/include/mod_i2c.h b/module/i2c/include/mod_i2c.h new file mode 100644 index 0000000000000000000000000000000000000000..24016aeb69a44410a4a5e5cf2b49eb70aa2ca52d --- /dev/null +++ b/module/i2c/include/mod_i2c.h @@ -0,0 +1,292 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2019, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * I2C HAL + */ + +#ifndef MOD_I2C_H +#define MOD_I2C_H + +#include +#include +#include +#include +#include +#include +#include + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupI2C I2C HAL + * + * \details Support for transmitting and receiving data through I2C Bus + * peripherals + * @{ + */ + +/*! + * \brief Configuration data for an I2C device. + */ +struct mod_i2c_dev_config { + /*! + * Identifier of the module or element providing the driver for the I2C + * device. + */ + fwk_id_t driver_id; + + /*! Identifier of the driver API. */ + fwk_id_t api_id; +}; + +/*! + * \brief Parameters of the event. + */ +struct mod_i2c_event_param { + /*! Status of the I2C transaction. */ + int status; +}; + +/*! + * \brief I2C transaction request parameters. + */ +struct mod_i2c_request { + /*! + * \brief Number of data bytes to transmit. + */ + uint8_t transmit_byte_count; + + /*! + * \brief Number of data bytes to receive. + */ + uint8_t receive_byte_count; + + /*! + * \brief Address of the slave on the I2C bus. + */ + uint8_t slave_address; + + /*! + * \brief Pointer to the data to transmit. + */ + uint8_t *transmit_data; + + /*! + * \brief Pointer to the received data. + */ + uint8_t *receive_data; +}; + +static_assert(sizeof(struct mod_i2c_request) <= FWK_EVENT_PARAMETERS_SIZE, + "An I2C request should fit in the params field of an event\n"); + +/*! + * \brief I2C driver interface. + * + * \details The interface the I2C HAL module relies on to perform actions on an + * I2C device. + */ +struct mod_i2c_driver_api { + /*! + * \brief Request transmission of data as Master to a selected slave. + * + * \details When the function returns the transmission may not be completed. + * The driver can assume the integrity of the data during the + * transmission. When the transmission operation has finished, + * the driver shall report it through the I2C HAL module driver + * response API. + * + * \param dev_id Identifier of the I2C device + * \param transmit_request Request information for the I2C transmission + * + * \retval FWK_SUCCESS The request was submitted. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \return One of the standard framework error codes. + */ + int (*transmit_as_master)( + fwk_id_t dev_id, struct mod_i2c_request *transmit_request); + + /*! + * \brief Request the reception of data as Master from a selected slave. + * + * \details When the function returns the reception may not be completed. + * The driver can assume the integrity of the data pointer during the + * reception. When the reception operation has finished, the driver + * shall report it through the I2C HAL module driver response API. + * + * \param dev_id Identifier of the I2C device + * \param receive_request Request information for the I2C reception + * + * \retval FWK_SUCCESS The request was submitted. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \return One of the standard framework error codes. + */ + int (*receive_as_master)( + fwk_id_t dev_id, struct mod_i2c_request *receive_request); +}; + +/*! + * \brief I2C HAL module interface. + */ +struct mod_i2c_api { + /*! + * \brief Request transmission of data as Master to a selected slave. + * + * \details When the function returns the transmission is not completed, + * not even started. The data buffer must stay allocated and its + * content must not be modified until the transmission is completed or + * aborted. When the transmission operation has finished a response + * event is sent to the client. + * + * \param dev_id Identifier of the I2C device + * \param slave_address Address of the slave on the I2C bus + * \param data Pointer to the data bytes to transmit to the slave + * \param byte_count Number of data bytes to transmit + * + * \retval FWK_SUCCESS The request was submitted. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \retval FWK_E_BUSY An I2C transaction is already on-going. + * \retval FWK_E_DEVICE The transmission is aborted due to a device error. + * \return One of the standard framework error codes. + */ + int (*transmit_as_master)(fwk_id_t dev_id, uint8_t slave_address, + uint8_t *data, uint8_t byte_count); + + /*! + * \brief Request reception of data as Master from a selected slave. + * + * \details When the function returns the reception is not completed, + * not even started. The data buffer must stay allocated and its + * content must not be modified until the reception is completed or + * aborted. When the reception operation has finished a response event + * is sent to the client. + * + * \param dev_id Identifier of the I2C device + * \param slave_address Address of the slave on the I2C bus + * \param data Pointer to the buffer to receive data from the slave + * \param byte_count Number of data bytes to receive + * + * \retval FWK_SUCCESS The request was submitted. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \retval FWK_E_BUSY An I2C transaction is already on-going. + * \retval FWK_E_DEVICE The reception is aborted due to a device error. + * \return One of the standard framework error codes. + */ + int (*receive_as_master)(fwk_id_t dev_id, uint8_t slave_address, + uint8_t *data, uint8_t byte_count); + + /*! + * \brief Request the transmission followed by the reception of data as + * Master to/from a selected slave. + * + * \details When the function returns the transaction is not completed, not + * even started. The data buffers must stay allocated and their content + * must not be modified until the transaction is completed or aborted. + * When the transaction has finished a response event is sent to the + * client. + * + * \param dev_id Identifier of the I2C device + * \param slave_address Address of the slave on the I2C bus + * \param transmit_data Pointer to the buffer to transmit the data in the + * first phase. + * \param receive_data Pointer to the buffer to receive the data in the + * second phase. + * \param transmit_byte_count Number of data bytes to transmit in the first + * phase + * \param receive_byte_count Number of data bytes to receive in the second + * phase + * + * \retval FWK_SUCCESS The request was submitted. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \retval FWK_E_BUSY An I2C transaction is already on-going. + * \retval FWK_E_DEVICE The reception is aborted due to a device error. + * \return One of the standard framework error codes. + */ + int (*transmit_then_receive_as_master)(fwk_id_t dev_id, + uint8_t slave_address, uint8_t *transmit_data, uint8_t *receive_data, + uint8_t transmit_byte_count, uint8_t receive_byte_count); +}; + +/*! + * \brief I2C HAL module driver response API. + * + * \details The interface the I2C HAL module exposes to its module drivers to + * report transmission or reception completion following the occurrence of + * interrupts. + */ +struct mod_i2c_driver_response_api { + /*! + * \brief Function called back after the completion or abortion of an I2C + * transaction request. + * + * \param dev_id Identifier of the I2C device + * \param i2c_status I2C transaction status + */ + void (*transaction_completed)(fwk_id_t dev_id, int i2c_status); +}; + +/*! + * \defgroup GroupI2CIds Identifiers + * \{ + */ + +/*! + * \brief API indices. + */ +enum mod_i2c_api_idx { + MOD_I2C_API_IDX_I2C, + MOD_I2C_API_IDX_DRIVER_RESPONSE, + MOD_I2C_API_IDX_COUNT, +}; + +/*! I2C API identifier */ +static const fwk_id_t mod_i2c_api_id_i2c = + FWK_ID_API_INIT(FWK_MODULE_IDX_I2C, MOD_I2C_API_IDX_I2C); + +/*! Driver response API identifier */ +static const fwk_id_t mod_i2c_api_id_driver_response = + FWK_ID_API_INIT(FWK_MODULE_IDX_I2C, MOD_I2C_API_IDX_DRIVER_RESPONSE); + +/*! + * \brief Event indices + */ +enum mod_i2c_event_idx { + MOD_I2C_EVENT_IDX_REQUEST, + MOD_I2C_EVENT_IDX_REQUEST_COMPLETED, + MOD_I2C_EVENT_IDX_RESTART, + MOD_I2C_EVENT_IDX_COUNT, +}; + +/*! Request event identifier */ +static const fwk_id_t mod_i2c_event_id_request = FWK_ID_EVENT_INIT( + FWK_MODULE_IDX_I2C, MOD_I2C_EVENT_IDX_REQUEST); + +/*! Request completed event identifier */ +static const fwk_id_t mod_i2c_event_id_request_completed = FWK_ID_EVENT_INIT( + FWK_MODULE_IDX_I2C, MOD_I2C_EVENT_IDX_REQUEST_COMPLETED); + +/*! Restart event identifier */ +static const fwk_id_t mod_i2c_event_id_restart = FWK_ID_EVENT_INIT( + FWK_MODULE_IDX_I2C, MOD_I2C_EVENT_IDX_RESTART); + + +/*! + * \} + */ + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_I2C_H */ diff --git a/module/i2c/src/Makefile b/module/i2c/src/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..786859c93e25217f3fd8415141741c99b30fe57c --- /dev/null +++ b/module/i2c/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2015-2019, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := "I2C" +BS_LIB_SOURCES = mod_i2c.c + +include $(BS_DIR)/lib.mk diff --git a/module/i2c/src/mod_i2c.c b/module/i2c/src/mod_i2c.c new file mode 100644 index 0000000000000000000000000000000000000000..34e67a2d3300ca796f376ff33d8a7497104edcb8 --- /dev/null +++ b/module/i2c/src/mod_i2c.c @@ -0,0 +1,317 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2019, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct mod_i2c_dev_ctx { + const struct mod_i2c_dev_config *config; + const struct mod_i2c_driver_api *driver_api; + struct mod_i2c_request request; + bool busy; + uint32_t cookie; +}; + +static struct mod_i2c_dev_ctx *ctx_table; + +/* + * Static helpers + */ +static int get_ctx(fwk_id_t id, struct mod_i2c_dev_ctx **ctx) +{ + int status; + + status = fwk_module_check_call(id); + if (status != FWK_SUCCESS) + return FWK_E_PARAM; + + *ctx = ctx_table + fwk_id_get_element_idx(id); + + return FWK_SUCCESS; +} + +static int create_i2c_request(fwk_id_t dev_id, uint8_t slave_address, + uint8_t *transmit_data, uint8_t *receive_data, uint8_t transmit_byte_count, + uint8_t receive_byte_count) +{ + int status; + struct fwk_event event; + struct mod_i2c_dev_ctx *ctx; + struct mod_i2c_request *event_param = + (struct mod_i2c_request *)event.params; + + fwk_assert(fwk_module_is_valid_element_id(dev_id)); + + status = get_ctx(dev_id, &ctx); + if (status != FWK_SUCCESS) + return status; + + /* The slave address should be on 7 bits */ + if (!fwk_expect(slave_address < 0x80)) + return FWK_E_PARAM; + + /* Check whether an I2C request is already on-going */ + if (ctx->busy) + return FWK_E_BUSY; + + ctx->busy = true; + + /* Create the request */ + event = (struct fwk_event) { + .id = mod_i2c_event_id_request, + .target_id = dev_id, + .response_requested = true, + }; + + *event_param = (struct mod_i2c_request) { + .slave_address = slave_address, + .transmit_data = transmit_data, + .receive_data = receive_data, + .transmit_byte_count = transmit_byte_count, + .receive_byte_count = receive_byte_count, + }; + + return fwk_thread_put_event(&event); +} + +/* + * I2C API + */ +static int transmit_as_master(fwk_id_t dev_id, + uint8_t slave_address, + uint8_t *data, + uint8_t byte_count) +{ + if (!fwk_expect(byte_count != 0)) + return FWK_E_PARAM; + + if (!fwk_expect(data != NULL)) + return FWK_E_PARAM; + + return create_i2c_request(dev_id, slave_address, data, NULL, byte_count, 0); +} + +static int receive_as_master(fwk_id_t dev_id, + uint8_t slave_address, + uint8_t *data, + uint8_t byte_count) +{ + if (!fwk_expect(byte_count != 0)) + return FWK_E_PARAM; + + if (!fwk_expect(data != NULL)) + return FWK_E_PARAM; + + return create_i2c_request(dev_id, slave_address, NULL, data, 0, byte_count); +} + +static int transmit_then_receive_as_master(fwk_id_t dev_id, + uint8_t slave_address, + uint8_t *transmit_data, + uint8_t *receive_data, + uint8_t transmit_byte_count, + uint8_t receive_byte_count) +{ + if (!fwk_expect((transmit_byte_count != 0) && (receive_byte_count != 0))) + return FWK_E_PARAM; + + if (!fwk_expect((transmit_data != NULL) && (receive_data != NULL))) + return FWK_E_PARAM; + + return create_i2c_request(dev_id, slave_address, transmit_data, + receive_data, transmit_byte_count, receive_byte_count); +} + +static struct mod_i2c_api i2c_api = { + .transmit_as_master = transmit_as_master, + .receive_as_master = receive_as_master, + .transmit_then_receive_as_master = transmit_then_receive_as_master, +}; + +/* + * Driver response API + */ +static void transaction_completed(fwk_id_t dev_id, int i2c_status) +{ + int status; + struct fwk_event event; + struct mod_i2c_event_param* param = + (struct mod_i2c_event_param *)event.params; + + event = (struct fwk_event) { + .target_id = dev_id, + .source_id = dev_id, + .id = mod_i2c_event_id_request_completed, + }; + + param->status = i2c_status; + + status = fwk_thread_put_event(&event); + fwk_assert(status == FWK_SUCCESS); +} + +static struct mod_i2c_driver_response_api driver_response_api = { + .transaction_completed = transaction_completed, +}; + +/* + * Framework handlers + */ +static int mod_i2c_init(fwk_id_t module_id, + unsigned int element_count, + const void *unused) +{ + ctx_table = fwk_mm_calloc(element_count, sizeof(ctx_table[0])); + + if (ctx_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int mod_i2c_dev_init(fwk_id_t element_id, + unsigned int unused, + const void *data) +{ + struct mod_i2c_dev_ctx *ctx; + + ctx = ctx_table + fwk_id_get_element_idx(element_id); + ctx->config = (struct mod_i2c_dev_config *)data; + + return FWK_SUCCESS; +} + +static int mod_i2c_bind(fwk_id_t id, unsigned int round) +{ + int status; + struct mod_i2c_dev_ctx *ctx; + + /* + * Only bind in first round of calls + * Nothing to do for module + */ + if ((round > 0) || fwk_id_is_type(id, FWK_ID_TYPE_MODULE)) + return FWK_SUCCESS; + + ctx = ctx_table + fwk_id_get_element_idx(id); + + /* Bind to driver */ + status = fwk_module_bind(ctx->config->driver_id, + ctx->config->api_id, + &ctx->driver_api); + if (status != FWK_SUCCESS) + return status; + + if ((ctx->driver_api->transmit_as_master == NULL) || + (ctx->driver_api->receive_as_master == NULL)) + return FWK_E_DATA; + + return FWK_SUCCESS; +} + +static int mod_i2c_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_id, + const void **api) +{ + struct mod_i2c_dev_ctx *ctx; + + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_ELEMENT)) + return FWK_E_PARAM; + + ctx = ctx_table + fwk_id_get_element_idx(target_id); + + if (fwk_id_is_equal(source_id, ctx->config->driver_id)) { + if (fwk_id_is_equal(api_id, mod_i2c_api_id_driver_response)) + *api = &driver_response_api; + else + return FWK_E_PARAM; + } else + *api = &i2c_api; + + return FWK_SUCCESS; +} + +static int mod_i2c_process_event(const struct fwk_event *event, + struct fwk_event *resp_event) +{ + int status; + struct fwk_event resp; + struct mod_i2c_dev_ctx *ctx; + struct mod_i2c_event_param *param = + (struct mod_i2c_event_param *)resp.params; + struct mod_i2c_event_param *event_param = + (struct mod_i2c_event_param *)event->params; + struct mod_i2c_request *request = (struct mod_i2c_request *)event->params; + + fwk_assert(fwk_module_is_valid_element_id(event->target_id)); + + status = get_ctx(event->target_id, &ctx); + if (status != FWK_SUCCESS) + return status; + + if (fwk_id_is_equal(event->id, mod_i2c_event_id_request)) { + ctx->request = *request; + + if (ctx->request.transmit_byte_count != 0) { + status = ctx->driver_api->transmit_as_master(ctx->config->driver_id, + &ctx->request); + } else { + status = ctx->driver_api->receive_as_master(ctx->config->driver_id, + &ctx->request); + } + ctx->cookie = event->cookie; + resp_event->is_delayed_response = true; + + } else if (fwk_id_is_equal(event->id, mod_i2c_event_id_request_completed)) { + + if ((ctx->request.transmit_byte_count != 0) && + (ctx->request.receive_byte_count != 0) && + (event_param->status == FWK_SUCCESS)) { + /* A receive operation needs to be performed */ + ctx->request.transmit_byte_count = 0; + status = ctx->driver_api->receive_as_master(ctx->config->driver_id, + &ctx->request); + } else { + /* The transaction is completed or aborted. */ + ctx->busy = false; + status = fwk_thread_get_delayed_response(event->target_id, + ctx->cookie, &resp); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + param->status = event_param->status; + status = fwk_thread_put_event(&resp); + } + + } else + return FWK_E_PARAM; + + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + return FWK_SUCCESS; +} + +const struct fwk_module module_i2c = { + .name = "I2C", + .type = FWK_MODULE_TYPE_HAL, + .api_count = MOD_I2C_API_IDX_COUNT, + .event_count = MOD_I2C_EVENT_IDX_COUNT, + .init = mod_i2c_init, + .element_init = mod_i2c_dev_init, + .bind = mod_i2c_bind, + .process_bind_request = mod_i2c_process_bind_request, + .process_event = mod_i2c_process_event, +}; diff --git a/product/juno/scp_ramfw/config_i2c.c b/product/juno/scp_ramfw/config_i2c.c new file mode 100644 index 0000000000000000000000000000000000000000..232432ad4ce25e1620fdd7549c0a62c3a31c9902 --- /dev/null +++ b/product/juno/scp_ramfw/config_i2c.c @@ -0,0 +1,58 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2019, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct fwk_element dw_apb_i2c_element_table[] = { + [0] = { + .name = "DW APB I2C", + .data = &(struct mod_dw_apb_i2c_dev_config) { + .timer_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_TIMER, 0), + .i2c_irq = I2C_IRQ, + .reg = (uintptr_t)I2C_BASE, + } + }, + [1] = {0}, +}; + +static const struct fwk_element *dw_apb_i2c_get_element_table( + fwk_id_t module_id) +{ + return dw_apb_i2c_element_table; +} + +struct fwk_module_config config_dw_apb_i2c = { + .get_element_table = dw_apb_i2c_get_element_table, +}; + +static const struct fwk_element i2c_element_table[] = { + [0] = { + .name = "I2C", + .data = &(struct mod_i2c_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_DW_APB_I2C, 0), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_DW_APB_I2C, + MOD_DW_APB_I2C_API_IDX_DRIVER), + }, + }, + [1] = {0}, +}; + +static const struct fwk_element *i2c_get_element_table(fwk_id_t module_id) +{ + return i2c_element_table; +} + +struct fwk_module_config config_i2c = { + .get_element_table = i2c_get_element_table, +}; diff --git a/product/juno/scp_ramfw/firmware.mk b/product/juno/scp_ramfw/firmware.mk index 9e96cf8f8c3809518a991ce7d6126ffbbfb12d23..6be39094e621aed6c40e4e2bd42730f6581419e0 100644 --- a/product/juno/scp_ramfw/firmware.mk +++ b/product/juno/scp_ramfw/firmware.mk @@ -32,7 +32,9 @@ BS_FIRMWARE_MODULES := \ scmi \ scmi_power_domain \ scmi_system_power \ - sds + sds \ + i2c \ + dw_apb_i2c BS_FIRMWARE_SOURCES := \ rtx_config.c \ @@ -51,6 +53,7 @@ BS_FIRMWARE_SOURCES := \ config_mhu.c \ config_smt.c \ config_scmi.c \ - config_scmi_system_power.c + config_scmi_system_power.c \ + config_i2c.c include $(BS_DIR)/firmware.mk