/* * Arm SCP/MCP Software * Copyright (c) 2019-2022, Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include <dw_apb_i2c.h> #include <mod_dw_apb_i2c.h> #include <mod_i2c.h> #include <mod_timer.h> #include <fwk_id.h> #include <fwk_interrupt.h> #include <fwk_mm.h> #include <fwk_module.h> #include <fwk_status.h> #include <stdbool.h> #include <stddef.h> 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 target_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 target address */ i2c_reg->IC_TAR = (target_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_controller( fwk_id_t dev_id, struct mod_i2c_request *transmit_request) { int status; unsigned int sent_bytes, flags; struct dw_apb_i2c_ctx *ctx; if (transmit_request->transmit_byte_count > I2C_TRANSMIT_BUFFER_LENGTH) { return FWK_E_SUPPORT; } if (transmit_request->target_address == 0) { return FWK_E_PARAM; } ctx = ctx_table + fwk_id_get_element_idx(dev_id); status = enable_i2c(ctx, transmit_request->target_address); if (status != FWK_SUCCESS) { return FWK_E_DEVICE; } /* The program of the I2C controller cannot be interrupted. */ flags = 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(flags); /* * The data has been pushed to the I2C FIFO for transmission to the * target device. An interrupt will signal the completion of the * transfer. The i2c_isr() interrupt handler will be invoked to notify * the caller. */ return FWK_PENDING; } static int receive_as_controller( fwk_id_t dev_id, struct mod_i2c_request *receive_request) { int status; unsigned int i, flags; struct dw_apb_i2c_ctx *ctx; if (receive_request->receive_byte_count > I2C_RECEIVE_BUFFER_LENGTH) { return FWK_E_SUPPORT; } if (receive_request->target_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->target_address); if (status != FWK_SUCCESS) { return FWK_E_DEVICE; } /* The program of the I2C controller cannot be interrupted. */ flags = 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(flags); /* * The command has been sent to the I2C for requesting data from * the target device. An interrupt will signal the completion of the * transfer. The i2c_isr() interrupt handler will be invoked to notify * the caller. */ return FWK_PENDING; } static const struct mod_i2c_driver_api driver_api = { .transmit_as_controller = transmit_as_controller, .receive_as_controller = receive_as_controller }; /* * 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)); 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 = { .api_count = (unsigned int)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, };