From 713bbf468a8a07ea92f8bdfe44f276086c5707bc Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Wed, 24 Oct 2018 10:52:15 +0200 Subject: [PATCH 1/4] I2C: HAL module definition and documentation Change-Id: I3fc622e1e6347eb440754467ab14957045669b65 Signed-off-by: Ronald Cron --- module/i2c/doc/module_i2c_architecture.md | 82 ++++++ module/i2c/include/mod_i2c.h | 292 ++++++++++++++++++++++ 2 files changed, 374 insertions(+) create mode 100644 module/i2c/doc/module_i2c_architecture.md create mode 100644 module/i2c/include/mod_i2c.h diff --git a/module/i2c/doc/module_i2c_architecture.md b/module/i2c/doc/module_i2c_architecture.md new file mode 100644 index 000000000..cca5ebd39 --- /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 000000000..24016aeb6 --- /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 */ -- GitLab From a04a4a34d21540526dd3e88570f6c93160001df6 Mon Sep 17 00:00:00 2001 From: Elieva Pignat Date: Fri, 18 Jan 2019 11:33:07 +0000 Subject: [PATCH 2/4] I2C: HAL module implementation The I2C HAL module provides an API for modules to simplify the creation of I2C requests to an I2C controller. The I2C HAL module supports transmission, reception and transmission followed by a reception for 7-bit addressed slave. Concurrent requests are not supported. Change-Id: I125dba56efa70150af0a7c97a6f1a060576981c9 Signed-off-by: Elieva Pignat --- module/i2c/src/Makefile | 11 ++ module/i2c/src/mod_i2c.c | 317 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 module/i2c/src/Makefile create mode 100644 module/i2c/src/mod_i2c.c diff --git a/module/i2c/src/Makefile b/module/i2c/src/Makefile new file mode 100644 index 000000000..786859c93 --- /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 000000000..34e67a2d3 --- /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, +}; -- GitLab From 448594c677f4763c0223c212221714104666dcbd Mon Sep 17 00:00:00 2001 From: Elieva Pignat Date: Mon, 28 Jan 2019 16:49:43 +0000 Subject: [PATCH 3/4] dw_apb_i2c: add support This patch provides a driver for the DesignWare I2C controller of Synopsis. Change-Id: I8eb61c82b6b038e97665590ae1fcdede37623c29 Signed-off-by: Elieva Pignat --- module/dw_apb_i2c/include/mod_dw_apb_i2c.h | 64 ++++ module/dw_apb_i2c/src/Makefile | 11 + module/dw_apb_i2c/src/dw_apb_i2c.h | 70 +++++ module/dw_apb_i2c/src/mod_dw_apb_i2c.c | 324 +++++++++++++++++++++ 4 files changed, 469 insertions(+) create mode 100644 module/dw_apb_i2c/include/mod_dw_apb_i2c.h create mode 100644 module/dw_apb_i2c/src/Makefile create mode 100644 module/dw_apb_i2c/src/dw_apb_i2c.h create mode 100644 module/dw_apb_i2c/src/mod_dw_apb_i2c.c 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 000000000..a340f73d9 --- /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 000000000..f731f73a3 --- /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 000000000..6e50ed5a3 --- /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 000000000..948a35a4d --- /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, +}; -- GitLab From 0fc6e100f41372ffa38362d4ad98fc2edcb4ea5d Mon Sep 17 00:00:00 2001 From: Elieva Pignat Date: Wed, 13 Feb 2019 14:07:20 +0000 Subject: [PATCH 4/4] juno: add i2c support This patch adds the I2C HAL module and the I2C controller configuration for juno and adds them to the build. Change-Id: I031c52d5303b19d223736015eceb656ea9b789db Signed-off-by: Elieva Pignat --- product/juno/scp_ramfw/config_i2c.c | 58 +++++++++++++++++++++++++++++ product/juno/scp_ramfw/firmware.mk | 7 +++- 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 product/juno/scp_ramfw/config_i2c.c diff --git a/product/juno/scp_ramfw/config_i2c.c b/product/juno/scp_ramfw/config_i2c.c new file mode 100644 index 000000000..232432ad4 --- /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 9e96cf8f8..6be39094e 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 -- GitLab