| /* |
| * 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: |
| case QDF_BUS_TYPE_USB: |
| status = bmi_get_target_info_message_based(targ_info, ol_ctx); |
| break; |
| #ifdef HIF_SDIO |
| case QDF_BUS_TYPE_SDIO: |
| status = hif_reg_based_get_target_info(scn, targ_info); |
| break; |
| #endif |
| default: |
| status = QDF_STATUS_E_FAILURE; |
| break; |
| } |
| return status; |
| } |
| |
| QDF_STATUS bmi_download_firmware(struct ol_context *ol_ctx) |
| { |
| struct hif_opaque_softc *scn; |
| |
| if (!ol_ctx) { |
| if (NO_BMI) { |
| /* ol_ctx is not allocated in NO_BMI case */ |
| return QDF_STATUS_SUCCESS; |
| } else { |
| BMI_ERR("ol_ctx is NULL"); |
| bmi_assert(0); |
| return QDF_STATUS_NOT_INITIALIZED; |
| } |
| } |
| |
| scn = ol_ctx->scn; |
| |
| if (!scn) { |
| BMI_ERR("Invalid scn context"); |
| bmi_assert(0); |
| return QDF_STATUS_NOT_INITIALIZED; |
| } |
| |
| if (!hif_needs_bmi(scn)) |
| return QDF_STATUS_SUCCESS; |
| |
| 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); |
| } |