| /* |
| * Copyright (c) 2013-2017 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 "athdefs.h" |
| #include "a_types.h" |
| #include "a_osapi.h" |
| #define ATH_MODULE_NAME hif |
| #include "a_debug.h" |
| #define ATH_DEBUG_BMI ATH_DEBUG_MAKE_MODULE_MASK(0) |
| #include "hif.h" |
| #include "bmi.h" |
| #include "htc_api.h" |
| #include "if_sdio.h" |
| #include "regtable_sdio.h" |
| |
| #define BMI_COMMUNICATION_TIMEOUT 100000 |
| |
| static bool pending_events_func_check; |
| static uint32_t command_credits; |
| static uint32_t *p_bmi_cmd_credits = &command_credits; |
| /* BMI Access routines */ |
| |
| /** |
| * hif_bmi_buffer_send - call to send bmi buffer |
| * @device: hif context |
| * @buffer: buffer |
| * @length: length |
| * |
| * Return: QDF_STATUS_SUCCESS for success. |
| */ |
| static QDF_STATUS |
| hif_bmi_buffer_send(struct hif_sdio_dev *device, char *buffer, uint32_t length) |
| { |
| QDF_STATUS status; |
| uint32_t timeout; |
| uint32_t address; |
| uint32_t mbox_address[HTC_MAILBOX_NUM_MAX]; |
| |
| hif_configure_device(device, HIF_DEVICE_GET_MBOX_ADDR, |
| &mbox_address[0], sizeof(mbox_address)); |
| |
| *p_bmi_cmd_credits = 0; |
| timeout = BMI_COMMUNICATION_TIMEOUT; |
| |
| while (timeout-- && !(*p_bmi_cmd_credits)) { |
| /* Read the counter register to get the command credits */ |
| address = |
| COUNT_DEC_ADDRESS + (HTC_MAILBOX_NUM_MAX + ENDPOINT1) * 4; |
| /* hit the credit counter with a 4-byte access, the first |
| * byte read will hit the counter and cause |
| * a decrement, while the remaining 3 bytes has no effect. |
| * The rationale behind this is to make all HIF accesses |
| * 4-byte aligned |
| */ |
| status = |
| hif_read_write(device, address, |
| (uint8_t *) p_bmi_cmd_credits, 4, |
| HIF_RD_SYNC_BYTE_INC, NULL); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to decrement the credit count register\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| /* the counter is only 8=bits, ignore anything in the |
| * upper 3 bytes |
| */ |
| (*p_bmi_cmd_credits) &= 0xFF; |
| } |
| |
| if (*p_bmi_cmd_credits) { |
| address = mbox_address[ENDPOINT1]; |
| status = hif_read_write(device, address, buffer, length, |
| HIF_WR_SYNC_BYTE_INC, NULL); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to send the BMI data to the device\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| } else { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:BMI Communication timeout - hif_bmi_buffer_send\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| return status; |
| } |
| |
| #if defined(SDIO_3_0) |
| |
| static QDF_STATUS |
| hif_bmi_read_write(struct hif_sdio_dev *device, |
| char *buffer, uint32_t length) |
| { |
| QDF_STATUS status; |
| |
| status = hif_read_write(device, HOST_INT_STATUS_ADDRESS, |
| buffer, length, |
| HIF_RD_SYNC_BYTE_INC, NULL); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to read int status reg\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| *buffer = (HOST_INT_STATUS_MBOX_DATA_GET(*buffer) & (1 << ENDPOINT1)); |
| return status; |
| } |
| #else |
| |
| static QDF_STATUS |
| hif_bmi_read_write(struct hif_sdio_dev *device, |
| char *buffer, uint32_t length) |
| { |
| QDF_STATUS status; |
| |
| status = hif_read_write(device, RX_LOOKAHEAD_VALID_ADDRESS, |
| buffer, length, |
| HIF_RD_SYNC_BYTE_INC, NULL); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to read rx lookahead reg\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| *buffer &= (1 << ENDPOINT1); |
| return status; |
| } |
| #endif |
| |
| /** |
| * hif_bmi_buffer_receive - call when bmi buffer is received |
| * @device: hif context |
| * @buffer: buffer |
| * @length: length |
| * @want_timeout: timeout is needed or not |
| * |
| * Return: QDF_STATUS_SUCCESS for success. |
| */ |
| static QDF_STATUS |
| hif_bmi_buffer_receive(struct hif_sdio_dev *device, |
| char *buffer, uint32_t length, bool want_timeout) |
| { |
| QDF_STATUS status; |
| uint32_t address; |
| uint32_t mbox_address[HTC_MAILBOX_NUM_MAX]; |
| struct _HIF_PENDING_EVENTS_INFO hif_pending_events; |
| |
| static HIF_PENDING_EVENTS_FUNC get_pending_events_func; |
| |
| if (!pending_events_func_check) { |
| /* see if the HIF layer implements an alternative |
| * function to get pending events |
| * do this only once! |
| */ |
| hif_configure_device(device, |
| HIF_DEVICE_GET_PENDING_EVENTS_FUNC, |
| &get_pending_events_func, |
| sizeof(get_pending_events_func)); |
| pending_events_func_check = true; |
| } |
| |
| hif_configure_device(device, HIF_DEVICE_GET_MBOX_ADDR, |
| &mbox_address[0], sizeof(mbox_address)); |
| |
| /* |
| * During normal bootup, small reads may be required. |
| * Rather than issue an HIF Read and then wait as the Target |
| * adds successive bytes to the FIFO, we wait here until |
| * we know that response data is available. |
| * |
| * This allows us to cleanly timeout on an unexpected |
| * Target failure rather than risk problems at the HIF level. In |
| * particular, this avoids SDIO timeouts and possibly garbage |
| * data on some host controllers. And on an interconnect |
| * such as Compact Flash (as well as some SDIO masters) which |
| * does not provide any indication on data timeout, it avoids |
| * a potential hang or garbage response. |
| * |
| * Synchronization is more difficult for reads larger than the |
| * size of the MBOX FIFO (128B), because the Target is unable |
| * to push the 129th byte of data until AFTER the Host posts an |
| * HIF Read and removes some FIFO data. So for large reads the |
| * Host proceeds to post an HIF Read BEFORE all the data is |
| * actually available to read. Fortunately, large BMI reads do |
| * not occur in practice -- they're supported for debug/development. |
| * |
| * So Host/Target BMI synchronization is divided into these cases: |
| * CASE 1: length < 4 |
| * Should not happen |
| * |
| * CASE 2: 4 <= length <= 128 |
| * Wait for first 4 bytes to be in FIFO |
| * If CONSERVATIVE_BMI_READ is enabled, also wait for |
| * a BMI command credit, which indicates that the ENTIRE |
| * response is available in the the FIFO |
| * |
| * CASE 3: length > 128 |
| * Wait for the first 4 bytes to be in FIFO |
| * |
| * For most uses, a small timeout should be sufficient and we will |
| * usually see a response quickly; but there may be some unusual |
| * (debug) cases of BMI_EXECUTE where we want an larger timeout. |
| * For now, we use an unbounded busy loop while waiting for |
| * BMI_EXECUTE. |
| * |
| * If BMI_EXECUTE ever needs to support longer-latency execution, |
| * especially in production, this code needs to be enhanced to sleep |
| * and yield. Also note that BMI_COMMUNICATION_TIMEOUT is currently |
| * a function of Host processor speed. |
| */ |
| if (length >= 4) { /* NB: Currently, always true */ |
| /* |
| * NB: word_available is declared static for esoteric reasons |
| * having to do with protection on some OSes. |
| */ |
| static uint32_t word_available; |
| uint32_t timeout; |
| |
| word_available = 0; |
| timeout = BMI_COMMUNICATION_TIMEOUT; |
| while ((!want_timeout || timeout--) && !word_available) { |
| |
| if (get_pending_events_func != NULL) { |
| status = get_pending_events_func(device, |
| &hif_pending_events, |
| NULL); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Failed to get pending events\n", |
| __func__)); |
| break; |
| } |
| |
| if (hif_pending_events.available_recv_bytes >= |
| sizeof(uint32_t)) { |
| word_available = 1; |
| } |
| continue; |
| } |
| status = hif_bmi_read_write(device, |
| (uint8_t *) &word_available, |
| sizeof(word_available)); |
| if (status != QDF_STATUS_SUCCESS) |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| if (!word_available) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:BMI Communication timeout FIFO empty\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| } |
| |
| address = mbox_address[ENDPOINT1]; |
| status = hif_read_write(device, address, buffer, length, |
| HIF_RD_SYNC_BYTE_INC, NULL); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to read the BMI data from the device\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * hif_reg_based_get_target_info - to retrieve target info |
| * @hif_ctx: hif context |
| * @targ_info: bmi target info |
| * |
| * Return: QDF_STATUS_SUCCESS for success. |
| */ |
| QDF_STATUS hif_reg_based_get_target_info(struct hif_opaque_softc *hif_ctx, |
| struct bmi_target_info *targ_info) { |
| QDF_STATUS status; |
| uint32_t cid; |
| struct hif_sdio_softc *scn = HIF_GET_SDIO_SOFTC(hif_ctx); |
| struct hif_sdio_dev *device = scn->hif_handle; |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_BMI, |
| ("BMI Get Target Info: Enter (device: 0x%pK)\n", |
| device)); |
| cid = BMI_GET_TARGET_INFO; |
| status = hif_bmi_buffer_send(device, (char *) &cid, sizeof(cid)); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to write to the device\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| status = hif_bmi_buffer_receive(device, |
| (char *) &targ_info->target_ver, |
| sizeof(targ_info->target_ver), true); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to read Target Version from the device\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| if (targ_info->target_ver == TARGET_VERSION_SENTINAL) { |
| /* Determine how many bytes are in the Target's targ_info */ |
| status = hif_bmi_buffer_receive(device, |
| (char *) &targ_info-> |
| target_info_byte_count, |
| sizeof(targ_info-> |
| target_info_byte_count), |
| true); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to read target Info\n", |
| __func__)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| /* |
| * The Target's targ_info doesn't match the Host's targ_info. |
| * We need to do some backwards compatibility work to make this |
| * OK. |
| */ |
| QDF_ASSERT(targ_info->target_info_byte_count == |
| sizeof(*targ_info)); |
| /* Read the remainder of the targ_info */ |
| status = hif_bmi_buffer_receive(device, |
| ((char *) targ_info) + |
| sizeof(targ_info-> |
| target_info_byte_count), |
| sizeof(*targ_info) - |
| sizeof(targ_info-> |
| target_info_byte_count), |
| true); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to read Target Info (%d bytes)\n", |
| __func__, targ_info->target_info_byte_count)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| } else { |
| /* |
| * Target must be an AR6001 whose firmware does not |
| * support BMI_GET_TARGET_INFO. Construct the data |
| * that it would have sent. |
| */ |
| targ_info->target_info_byte_count = sizeof(*targ_info); |
| targ_info->target_type = TARGET_TYPE_AR6001; |
| } |
| |
| AR_DEBUG_PRINTF(ATH_DEBUG_BMI, |
| ("BMI Get Target Info: Exit (ver: 0x%x type: 0x%x)\n", |
| targ_info->target_ver, |
| targ_info->target_type)); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * hif_exchange_bmi_msg - API to handle HIF-specific BMI message exchanges |
| * @hif_ctx: hif context |
| * @bmi_cmd_da: bmi cmd |
| * @bmi_rsp_da: bmi rsp |
| * @send_message: send message |
| * @length: length |
| * @response_message: response message |
| * @response_length: response length |
| * @timeout_ms: timeout in ms |
| * |
| * This API is synchronous |
| * and only allowed to be called from a context that can block (sleep) |
| * |
| * Return: QDF_STATUS_SUCCESS for success. |
| */ |
| QDF_STATUS hif_exchange_bmi_msg(struct hif_opaque_softc *hif_ctx, |
| qdf_dma_addr_t bmi_cmd_da, |
| qdf_dma_addr_t bmi_rsp_da, |
| uint8_t *send_message, |
| uint32_t length, |
| uint8_t *response_message, |
| uint32_t *response_length, |
| uint32_t timeout_ms) { |
| struct hif_sdio_softc *scn = HIF_GET_SDIO_SOFTC(hif_ctx); |
| struct hif_sdio_dev *device = scn->hif_handle; |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| |
| if (device == NULL) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Null device argument\n", |
| __func__)); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| status = hif_bmi_buffer_send(device, send_message, length); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to Send Message to device\n", |
| __func__)); |
| return status; |
| } |
| |
| if (response_message != NULL) { |
| status = hif_bmi_buffer_receive(device, response_message, |
| *response_length, |
| timeout_ms ? true : false); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s:Unable to read response\n", |
| __func__)); |
| return status; |
| } |
| } |
| |
| return status; |
| } |
| |
| #ifdef BRINGUP_DEBUG |
| #define SDIO_SCRATCH_1_ADDRESS 0x864 |
| /*Functions used for debugging*/ |
| /** |
| * hif_bmi_write_scratch_register - API to write scratch register |
| * @device: hif context |
| * @buffer: buffer |
| * |
| * Return: QDF_STATUS_SUCCESS for success. |
| */ |
| QDF_STATUS hif_bmi_write_scratch_register(struct hif_sdio_dev *device, |
| uint32_t buffer) { |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| |
| status = hif_read_write(device, SDIO_SCRATCH_1_ADDRESS, |
| (uint8_t *) &buffer, 4, |
| HIF_WR_SYNC_BYTE_INC, NULL); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s: Unable to write to 0x%x\n", |
| __func__, SDIO_SCRATCH_1_ADDRESS)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s: wrote 0x%x to 0x%x\n", __func__, |
| buffer, SDIO_SCRATCH_1_ADDRESS)); |
| |
| return status; |
| } |
| |
| /** |
| * hif_bmi_read_scratch_register - API to read from scratch register |
| * @device: hif context |
| * |
| * Return: QDF_STATUS_SUCCESS for success. |
| */ |
| QDF_STATUS hif_bmi_read_scratch_register(struct hif_sdio_dev *device) |
| { |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| uint32_t buffer = 0; |
| |
| status = hif_read_write(device, SDIO_SCRATCH_1_ADDRESS, |
| (uint8_t *) &buffer, 4, |
| HIF_RD_SYNC_BYTE_INC, NULL); |
| if (status != QDF_STATUS_SUCCESS) { |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, |
| ("%s: Unable to read from 0x%x\n", |
| __func__, SDIO_SCRATCH_1_ADDRESS)); |
| return QDF_STATUS_E_FAILURE; |
| } |
| AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s: read 0x%x from 0x%x\n", __func__, |
| buffer, SDIO_SCRATCH_1_ADDRESS)); |
| |
| return status; |
| } |
| #endif |