/* * Copyright (c) 2014-2016 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * * * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all * copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /* * This file was originally distributed by Qualcomm Atheros, Inc. * under proprietary terms before Copyright ownership was assigned * to the Linux Foundation. */ #include "i_bmi.h" #include "cds_api.h" /* APIs visible to the driver */ QDF_STATUS bmi_init(struct ol_context *ol_ctx) { struct bmi_info *info = GET_BMI_CONTEXT(ol_ctx); struct hif_opaque_softc *scn = ol_ctx->scn; qdf_device_t qdf_dev = ol_ctx->qdf_dev; if (!scn) { BMI_ERR("Invalid scn Context"); bmi_assert(0); return QDF_STATUS_NOT_INITIALIZED; } if (!qdf_dev->dev) { BMI_ERR("%s: Invalid Device Pointer", __func__); return QDF_STATUS_NOT_INITIALIZED; } info->bmi_done = false; if (!info->bmi_cmd_buff) { info->bmi_cmd_buff = qdf_mem_alloc_consistent(qdf_dev, qdf_dev->dev, MAX_BMI_CMDBUF_SZ, &info->bmi_cmd_da); if (!info->bmi_cmd_buff) { BMI_ERR("No Memory for BMI Command"); return QDF_STATUS_E_NOMEM; } } if (!info->bmi_rsp_buff) { info->bmi_rsp_buff = qdf_mem_alloc_consistent(qdf_dev, qdf_dev->dev, MAX_BMI_CMDBUF_SZ, &info->bmi_rsp_da); if (!info->bmi_rsp_buff) { BMI_ERR("No Memory for BMI Response"); goto end; } } return QDF_STATUS_SUCCESS; end: qdf_mem_free_consistent(qdf_dev, qdf_dev->dev, MAX_BMI_CMDBUF_SZ, info->bmi_cmd_buff, info->bmi_cmd_da, 0); info->bmi_cmd_buff = NULL; return QDF_STATUS_E_NOMEM; } void bmi_cleanup(struct ol_context *ol_ctx) { struct bmi_info *info = GET_BMI_CONTEXT(ol_ctx); qdf_device_t qdf_dev; if (!info || !ol_ctx) { BMI_WARN("%s: no bmi to cleanup", __func__); return; } qdf_dev = ol_ctx->qdf_dev; if (!qdf_dev || !qdf_dev->dev) { BMI_ERR("%s: Invalid Device Pointer", __func__); return; } if (info->bmi_cmd_buff) { qdf_mem_free_consistent(qdf_dev, qdf_dev->dev, MAX_BMI_CMDBUF_SZ, info->bmi_cmd_buff, info->bmi_cmd_da, 0); info->bmi_cmd_buff = NULL; info->bmi_cmd_da = 0; } if (info->bmi_rsp_buff) { qdf_mem_free_consistent(qdf_dev, qdf_dev->dev, MAX_BMI_CMDBUF_SZ, info->bmi_rsp_buff, info->bmi_rsp_da, 0); info->bmi_rsp_buff = NULL; info->bmi_rsp_da = 0; } } /** * bmi_done() - finish the bmi opperation * @ol_ctx: the bmi context * * does some sanity checking. * exchanges one last message with firmware. * frees some buffers. * * Return: QDF_STATUS_SUCCESS if bmi isn't needed. * QDF_STATUS_SUCCESS if bmi finishes. * otherwise returns failure. */ QDF_STATUS bmi_done(struct ol_context *ol_ctx) { QDF_STATUS status = QDF_STATUS_SUCCESS; if (NO_BMI) return QDF_STATUS_SUCCESS; if (!ol_ctx) { BMI_ERR("%s: null context", __func__); return QDF_STATUS_E_NOMEM; } hif_claim_device(ol_ctx->scn); if (!hif_needs_bmi(ol_ctx->scn)) return QDF_STATUS_SUCCESS; status = bmi_done_local(ol_ctx); if (status != QDF_STATUS_SUCCESS) BMI_ERR("BMI_DONE Failed status:%d", status); return status; } void bmi_target_ready(struct hif_opaque_softc *scn, void *cfg_ctx) { ol_target_ready(scn, cfg_ctx); } static QDF_STATUS bmi_get_target_info_message_based(struct bmi_target_info *targ_info, struct ol_context *ol_ctx) { int status = 0; struct hif_opaque_softc *scn = ol_ctx->scn; struct bmi_info *info = GET_BMI_CONTEXT(ol_ctx); uint8_t *bmi_cmd_buff = info->bmi_cmd_buff; uint8_t *bmi_rsp_buff = info->bmi_rsp_buff; uint32_t cid, length; qdf_dma_addr_t cmd = info->bmi_cmd_da; qdf_dma_addr_t rsp = info->bmi_rsp_da; if (!bmi_cmd_buff || !bmi_rsp_buff) { BMI_ERR("%s:BMI CMD/RSP Buffer is NULL", __func__); return QDF_STATUS_NOT_INITIALIZED; } cid = BMI_GET_TARGET_INFO; qdf_mem_copy(bmi_cmd_buff, &cid, sizeof(cid)); length = sizeof(struct bmi_target_info); status = hif_exchange_bmi_msg(scn, cmd, rsp, bmi_cmd_buff, sizeof(cid), (uint8_t *)bmi_rsp_buff, &length, BMI_EXCHANGE_TIMEOUT_MS); if (status) { BMI_ERR("Failed to target info: status:%d", status); return QDF_STATUS_E_FAILURE; } qdf_mem_copy(targ_info, bmi_rsp_buff, length); return QDF_STATUS_SUCCESS; } QDF_STATUS bmi_get_target_info(struct bmi_target_info *targ_info, struct ol_context *ol_ctx) { struct hif_opaque_softc *scn = ol_ctx->scn; struct bmi_info *info = GET_BMI_CONTEXT(ol_ctx); QDF_STATUS status; if (info->bmi_done) { BMI_ERR("BMI Phase is Already Done"); return QDF_STATUS_E_PERM; } switch (hif_get_bus_type(scn)) { case QDF_BUS_TYPE_PCI: case QDF_BUS_TYPE_SNOC: status = bmi_get_target_info_message_based(targ_info, ol_ctx); break; case QDF_BUS_TYPE_SDIO: status = hif_reg_based_get_target_info(scn, targ_info); break; default: status = QDF_STATUS_E_FAILURE; break; } return status; } QDF_STATUS bmi_download_firmware(struct ol_context *ol_ctx) { struct hif_opaque_softc *scn = ol_ctx->scn; if (NO_BMI || !hif_needs_bmi(scn)) return QDF_STATUS_SUCCESS; if (!scn) { BMI_ERR("Invalid scn context"); bmi_assert(0); return QDF_STATUS_NOT_INITIALIZED; } return bmi_firmware_download(ol_ctx); } QDF_STATUS bmi_read_soc_register(uint32_t address, uint32_t *param, struct ol_context *ol_ctx) { struct hif_opaque_softc *scn = ol_ctx->scn; uint32_t cid; int status; uint32_t offset, param_len; struct bmi_info *info = GET_BMI_CONTEXT(ol_ctx); uint8_t *bmi_cmd_buff = info->bmi_cmd_buff; uint8_t *bmi_rsp_buff = info->bmi_rsp_buff; qdf_dma_addr_t cmd = info->bmi_cmd_da; qdf_dma_addr_t rsp = info->bmi_rsp_da; bmi_assert(BMI_COMMAND_FITS(sizeof(cid) + sizeof(address))); qdf_mem_set(bmi_cmd_buff, 0, sizeof(cid) + sizeof(address)); qdf_mem_set(bmi_rsp_buff, 0, sizeof(cid) + sizeof(address)); if (info->bmi_done) { BMI_DBG("Command disallowed"); return QDF_STATUS_E_PERM; } BMI_DBG("BMI Read SOC Register:device: 0x%p, address: 0x%x", scn, address); cid = BMI_READ_SOC_REGISTER; offset = 0; qdf_mem_copy(&(bmi_cmd_buff[offset]), &cid, sizeof(cid)); offset += sizeof(cid); qdf_mem_copy(&(bmi_cmd_buff[offset]), &address, sizeof(address)); offset += sizeof(address); param_len = sizeof(*param); status = hif_exchange_bmi_msg(scn, cmd, rsp, bmi_cmd_buff, offset, bmi_rsp_buff, ¶m_len, BMI_EXCHANGE_TIMEOUT_MS); if (status) { BMI_DBG("Unable to read from the device; status:%d", status); return QDF_STATUS_E_FAILURE; } qdf_mem_copy(param, bmi_rsp_buff, sizeof(*param)); BMI_DBG("BMI Read SOC Register: Exit value: %d", *param); return QDF_STATUS_SUCCESS; } QDF_STATUS bmi_write_soc_register(uint32_t address, uint32_t param, struct ol_context *ol_ctx) { struct hif_opaque_softc *scn = ol_ctx->scn; uint32_t cid; int status; uint32_t offset; struct bmi_info *info = GET_BMI_CONTEXT(ol_ctx); uint8_t *bmi_cmd_buff = info->bmi_cmd_buff; uint32_t size = sizeof(cid) + sizeof(address) + sizeof(param); qdf_dma_addr_t cmd = info->bmi_cmd_da; qdf_dma_addr_t rsp = info->bmi_rsp_da; bmi_assert(BMI_COMMAND_FITS(size)); qdf_mem_set(bmi_cmd_buff, 0, size); if (info->bmi_done) { BMI_DBG("Command disallowed"); return QDF_STATUS_E_FAILURE; } BMI_DBG("SOC Register Write:device:0x%p, addr:0x%x, param:%d", scn, address, param); cid = BMI_WRITE_SOC_REGISTER; offset = 0; qdf_mem_copy(&(bmi_cmd_buff[offset]), &cid, sizeof(cid)); offset += sizeof(cid); qdf_mem_copy(&(bmi_cmd_buff[offset]), &address, sizeof(address)); offset += sizeof(address); qdf_mem_copy(&(bmi_cmd_buff[offset]), ¶m, sizeof(param)); offset += sizeof(param); status = hif_exchange_bmi_msg(scn, cmd, rsp, bmi_cmd_buff, offset, NULL, NULL, 0); if (status) { BMI_ERR("Unable to write to the device: status:%d", status); return QDF_STATUS_E_FAILURE; } BMI_DBG("BMI Read SOC Register: Exit"); return QDF_STATUS_SUCCESS; } QDF_STATUS bmilz_data(uint8_t *buffer, uint32_t length, struct ol_context *ol_ctx) { uint32_t cid; int status; uint32_t offset; uint32_t remaining, txlen; const uint32_t header = sizeof(cid) + sizeof(length); struct hif_opaque_softc *scn = ol_ctx->scn; struct bmi_info *info = GET_BMI_CONTEXT(ol_ctx); uint8_t *bmi_cmd_buff = info->bmi_cmd_buff; qdf_dma_addr_t cmd = info->bmi_cmd_da; qdf_dma_addr_t rsp = info->bmi_rsp_da; bmi_assert(BMI_COMMAND_FITS(BMI_DATASZ_MAX + header)); qdf_mem_set(bmi_cmd_buff, 0, BMI_DATASZ_MAX + header); if (info->bmi_done) { BMI_ERR("Command disallowed"); return QDF_STATUS_E_PERM; } BMI_DBG("BMI Send LZ Data: device: 0x%p, length: %d", scn, length); cid = BMI_LZ_DATA; remaining = length; while (remaining) { txlen = (remaining < (BMI_DATASZ_MAX - header)) ? remaining : (BMI_DATASZ_MAX - header); offset = 0; qdf_mem_copy(&(bmi_cmd_buff[offset]), &cid, sizeof(cid)); offset += sizeof(cid); qdf_mem_copy(&(bmi_cmd_buff[offset]), &txlen, sizeof(txlen)); offset += sizeof(txlen); qdf_mem_copy(&(bmi_cmd_buff[offset]), &buffer[length - remaining], txlen); offset += txlen; status = hif_exchange_bmi_msg(scn, cmd, rsp, bmi_cmd_buff, offset, NULL, NULL, 0); if (status) { BMI_ERR("Failed to write to the device: status:%d", status); return QDF_STATUS_E_FAILURE; } remaining -= txlen; } BMI_DBG("BMI LZ Data: Exit"); return QDF_STATUS_SUCCESS; } QDF_STATUS bmi_sign_stream_start(uint32_t address, uint8_t *buffer, uint32_t length, struct ol_context *ol_ctx) { uint32_t cid; int status; uint32_t offset; const uint32_t header = sizeof(cid) + sizeof(address) + sizeof(length); uint8_t aligned_buf[BMI_DATASZ_MAX + 4]; uint8_t *src; struct hif_opaque_softc *scn = ol_ctx->scn; struct bmi_info *info = GET_BMI_CONTEXT(ol_ctx); uint8_t *bmi_cmd_buff = info->bmi_cmd_buff; uint32_t remaining, txlen; qdf_dma_addr_t cmd = info->bmi_cmd_da; qdf_dma_addr_t rsp = info->bmi_rsp_da; bmi_assert(BMI_COMMAND_FITS(BMI_DATASZ_MAX + header)); qdf_mem_set(bmi_cmd_buff, 0, BMI_DATASZ_MAX + header); if (info->bmi_done) { BMI_ERR("Command disallowed"); return QDF_STATUS_E_PERM; } BMI_ERR("Sign Stream start:device:0x%p, addr:0x%x, length:%d", scn, address, length); cid = BMI_SIGN_STREAM_START; remaining = length; while (remaining) { src = &buffer[length - remaining]; if (remaining < (BMI_DATASZ_MAX - header)) { if (remaining & 0x3) { remaining = remaining + (4 - (remaining & 0x3)); memcpy(aligned_buf, src, remaining); src = aligned_buf; } txlen = remaining; } else { txlen = (BMI_DATASZ_MAX - header); } offset = 0; qdf_mem_copy(&(bmi_cmd_buff[offset]), &cid, sizeof(cid)); offset += sizeof(cid); qdf_mem_copy(&(bmi_cmd_buff[offset]), &address, sizeof(address)); offset += sizeof(offset); qdf_mem_copy(&(bmi_cmd_buff[offset]), &txlen, sizeof(txlen)); offset += sizeof(txlen); qdf_mem_copy(&(bmi_cmd_buff[offset]), src, txlen); offset += txlen; status = hif_exchange_bmi_msg(scn, cmd, rsp, bmi_cmd_buff, offset, NULL, NULL, BMI_EXCHANGE_TIMEOUT_MS); if (status) { BMI_ERR("Unable to write to the device: status:%d", status); return QDF_STATUS_E_FAILURE; } remaining -= txlen; } BMI_DBG("BMI SIGN Stream Start: Exit"); return QDF_STATUS_SUCCESS; } QDF_STATUS bmilz_stream_start(uint32_t address, struct ol_context *ol_ctx) { uint32_t cid; int status; uint32_t offset; struct hif_opaque_softc *scn = ol_ctx->scn; struct bmi_info *info = GET_BMI_CONTEXT(ol_ctx); uint8_t *bmi_cmd_buff = info->bmi_cmd_buff; qdf_dma_addr_t cmd = info->bmi_cmd_da; qdf_dma_addr_t rsp = info->bmi_rsp_da; bmi_assert(BMI_COMMAND_FITS(sizeof(cid) + sizeof(address))); qdf_mem_set(bmi_cmd_buff, 0, sizeof(cid) + sizeof(address)); if (info->bmi_done) { BMI_DBG("Command disallowed"); return QDF_STATUS_E_PERM; } BMI_DBG("BMI LZ Stream Start: (device: 0x%p, address: 0x%x)", scn, address); cid = BMI_LZ_STREAM_START; offset = 0; qdf_mem_copy(&(bmi_cmd_buff[offset]), &cid, sizeof(cid)); offset += sizeof(cid); qdf_mem_copy(&(bmi_cmd_buff[offset]), &address, sizeof(address)); offset += sizeof(address); status = hif_exchange_bmi_msg(scn, cmd, rsp, bmi_cmd_buff, offset, NULL, NULL, 0); if (status) { BMI_ERR("Unable to Start LZ Stream to the device status:%d", status); return QDF_STATUS_E_FAILURE; } BMI_DBG("BMI LZ Stream: Exit"); return QDF_STATUS_SUCCESS; } QDF_STATUS bmi_fast_download(uint32_t address, uint8_t *buffer, uint32_t length, struct ol_context *ol_ctx) { QDF_STATUS status = QDF_STATUS_E_FAILURE; uint32_t last_word = 0; uint32_t last_word_offset = length & ~0x3; uint32_t unaligned_bytes = length & 0x3; status = bmilz_stream_start(address, ol_ctx); if (status != QDF_STATUS_SUCCESS) goto end; /* copy the last word into a zero padded buffer */ if (unaligned_bytes) qdf_mem_copy(&last_word, &buffer[last_word_offset], unaligned_bytes); status = bmilz_data(buffer, last_word_offset, ol_ctx); if (status != QDF_STATUS_SUCCESS) goto end; if (unaligned_bytes) status = bmilz_data((uint8_t *) &last_word, 4, ol_ctx); if (status != QDF_STATUS_SUCCESS) /* * Close compressed stream and open a new (fake) one. * This serves mainly to flush Target caches. */ status = bmilz_stream_start(0x00, ol_ctx); end: return status; } /** * ol_cds_init() - API to initialize global CDS OL Context * @qdf_dev: QDF Device * @hif_ctx: HIF Context * * Return: Success/Failure */ QDF_STATUS ol_cds_init(qdf_device_t qdf_dev, void *hif_ctx) { struct ol_context *ol_info; QDF_STATUS status = QDF_STATUS_SUCCESS; if (NO_BMI) return QDF_STATUS_SUCCESS; /* no BMI for Q6 bring up */ status = cds_alloc_context(cds_get_global_context(), QDF_MODULE_ID_BMI, (void **)&ol_info, sizeof(*ol_info)); if (status != QDF_STATUS_SUCCESS) { BMI_ERR("%s: CDS Allocation failed for ol_bmi context", __func__); return status; } ol_info->qdf_dev = qdf_dev; ol_info->scn = hif_ctx; ol_info->tgt_def.targetdef = hif_get_targetdef(hif_ctx); qdf_create_work(qdf_dev, &ol_info->ramdump_work, ramdump_work_handler, ol_info); qdf_create_work(qdf_dev, &ol_info->fw_indication_work, fw_indication_work_handler, ol_info); return status; } /** * ol_cds_free() - API to free the global CDS OL Context * * Return: void */ void ol_cds_free(void) { struct ol_context *ol_info = cds_get_context(QDF_MODULE_ID_BMI); if (NO_BMI) return; cds_free_context(cds_get_global_context(), QDF_MODULE_ID_BMI, ol_info); }