When firmware crashes it's possible to create a coredump for later analysis, add support to collect the register and memory info from SDIO devices. The coredump configuration is different between QCA6174 PCI and QCA6174 SDIO, so add specific registers and memory regions for the latter. QCA6174 SDIO has two methods to dump the firmware: fastdump and slowdump. Fastdump is not supported in olded versions of firmware, and for these ath10k will automatically select slowdump. If firmware supports fastdump, ath10k will automatically select it. QCA6174 SDIO firmware version WLAN.RMH.4.4.1-00017-QCARMSWPZ-2 is the first version supporting fastdump. For slowdump, ath10k_sdio_hif_diag_read() can not be used as the diag window has a limit value, it is 4 bytes and the dump's buffer length is larger than it, it will trigger error. So this patch adds ath10k_sdio_read_mem() to read 4 bytes for each time. Example output of a firmware crash: ath10k_sdio mmc1:0001:1: simulating soft firmware crash ath10k_sdio mmc1:0001:1: firmware crashed! (guid 413d98b1-84c0-4298-b605-2b10ec0c54a5) ath10k_sdio mmc1:0001:1: qca6174 hw3.2 sdio target 0x05030000 chip_id 0x00000000 sub 0000:0000 ath10k_sdio mmc1:0001:1: kconfig debug 1 debugfs 1 tracing 1 dfs 0 testmode 1 ath10k_sdio mmc1:0001:1: firmware ver WLAN.RMH4.4.1-00126-QCARMSWP-1 api 6 features wowlan,ignore-otp,raw-mode crc32 b84317cf ath10k_sdio mmc1:0001:1: board_file api 2 bmi_id 0:4 crc32 6364cfcc ath10k_sdio mmc1:0001:1: htt-ver 3.69 wmi-op 4 htt-op 3 cal otp max-sta 32 raw 0 hwcrypto 1 ath10k_sdio mmc1:0001:1: firmware register dump: ath10k_sdio mmc1:0001:1: [00]: 0x05030000 0x000015B3 0x0099908D 0x00955B31 ath10k_sdio mmc1:0001:1: [04]: 0x0099908D 0x00060730 0x00000018 0x004641A0 ath10k_sdio mmc1:0001:1: [08]: 0x0041FAA4 0x0041FA9C 0x00999070 0x00404490 ath10k_sdio mmc1:0001:1: [12]: 0x00000009 0xFFFFFFFF 0x00952CD0 0x00952CE6 ath10k_sdio mmc1:0001:1: [16]: 0x00952CC4 0x00910712 0x00000000 0x00000000 ath10k_sdio mmc1:0001:1: [20]: 0x4099908D 0x0040E9E8 0x00000001 0x00423AC0 ath10k_sdio mmc1:0001:1: [24]: 0x809F3189 0x0040EA48 0x00426240 0xC099908D ath10k_sdio mmc1:0001:1: [28]: 0x809143A7 0x0040EA68 0x0041FAA4 0x00423A80 ath10k_sdio mmc1:0001:1: [32]: 0x809F1193 0x0040EA88 0x00411770 0x004117E0 ath10k_sdio mmc1:0001:1: [36]: 0x809F0EEE 0x0040EAA8 0x00000000 0x00000000 ath10k_sdio mmc1:0001:1: [40]: 0x80911210 0x0040EAC8 0x00000008 0x00404130 ath10k_sdio mmc1:0001:1: [44]: 0x80911154 0x0040EB28 0x00400000 0x00000000 ath10k_sdio mmc1:0001:1: [48]: 0x8091122D 0x0040EB48 0x00000000 0x00400600 ath10k_sdio mmc1:0001:1: [52]: 0x40910024 0x0040EB78 0x0040AB98 0x0040AB98 ath10k_sdio mmc1:0001:1: [56]: 0x00000000 0x0040EB98 0x009BB001 0x00040020 Tested-on: QCA6174 SDIO WLAN.RMH.4.4.1-00018-QCARMSWP-1 Signed-off-by: Wen Gong <wgong@codeaurora.org> Signed-off-by: Kalle Valo <kvalo@codeaurora.org> Link: https://lore.kernel.org/r/1569310030-834-3-git-send-email-wgong@codeaurora.org
519 lines
12 KiB
C
519 lines
12 KiB
C
// SPDX-License-Identifier: ISC
|
|
/*
|
|
* Copyright (c) 2005-2011 Atheros Communications Inc.
|
|
* Copyright (c) 2011-2014,2016-2017 Qualcomm Atheros, Inc.
|
|
*/
|
|
|
|
#include "bmi.h"
|
|
#include "hif.h"
|
|
#include "debug.h"
|
|
#include "htc.h"
|
|
#include "hw.h"
|
|
|
|
void ath10k_bmi_start(struct ath10k *ar)
|
|
{
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi start\n");
|
|
|
|
ar->bmi.done_sent = false;
|
|
}
|
|
EXPORT_SYMBOL(ath10k_bmi_start);
|
|
|
|
int ath10k_bmi_done(struct ath10k *ar)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.done);
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi done\n");
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi skipped\n");
|
|
return 0;
|
|
}
|
|
|
|
ar->bmi.done_sent = true;
|
|
cmd.id = __cpu_to_le32(BMI_DONE);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, NULL, NULL);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to write to the device: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath10k_bmi_get_target_info(struct ath10k *ar,
|
|
struct bmi_target_info *target_info)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
union bmi_resp resp;
|
|
u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.get_target_info);
|
|
u32 resplen = sizeof(resp.get_target_info);
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi get target info\n");
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "BMI Get Target Info Command disallowed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
cmd.id = __cpu_to_le32(BMI_GET_TARGET_INFO);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, &resp, &resplen);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to get target info from device\n");
|
|
return ret;
|
|
}
|
|
|
|
if (resplen < sizeof(resp.get_target_info)) {
|
|
ath10k_warn(ar, "invalid get_target_info response length (%d)\n",
|
|
resplen);
|
|
return -EIO;
|
|
}
|
|
|
|
target_info->version = __le32_to_cpu(resp.get_target_info.version);
|
|
target_info->type = __le32_to_cpu(resp.get_target_info.type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define TARGET_VERSION_SENTINAL 0xffffffffu
|
|
|
|
int ath10k_bmi_get_target_info_sdio(struct ath10k *ar,
|
|
struct bmi_target_info *target_info)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
union bmi_resp resp;
|
|
u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.get_target_info);
|
|
u32 resplen, ver_len;
|
|
__le32 tmp;
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi get target info SDIO\n");
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "BMI Get Target Info Command disallowed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
cmd.id = __cpu_to_le32(BMI_GET_TARGET_INFO);
|
|
|
|
/* Step 1: Read 4 bytes of the target info and check if it is
|
|
* the special sentinal version word or the first word in the
|
|
* version response.
|
|
*/
|
|
resplen = sizeof(u32);
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, &tmp, &resplen);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to read from device\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Some SDIO boards have a special sentinal byte before the real
|
|
* version response.
|
|
*/
|
|
if (__le32_to_cpu(tmp) == TARGET_VERSION_SENTINAL) {
|
|
/* Step 1b: Read the version length */
|
|
resplen = sizeof(u32);
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, NULL, 0, &tmp,
|
|
&resplen);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to read from device\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ver_len = __le32_to_cpu(tmp);
|
|
|
|
/* Step 2: Check the target info length */
|
|
if (ver_len != sizeof(resp.get_target_info)) {
|
|
ath10k_warn(ar, "Unexpected target info len: %u. Expected: %zu\n",
|
|
ver_len, sizeof(resp.get_target_info));
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Step 3: Read the rest of the version response */
|
|
resplen = sizeof(resp.get_target_info) - sizeof(u32);
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, NULL, 0,
|
|
&resp.get_target_info.version,
|
|
&resplen);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to read from device\n");
|
|
return ret;
|
|
}
|
|
|
|
target_info->version = __le32_to_cpu(resp.get_target_info.version);
|
|
target_info->type = __le32_to_cpu(resp.get_target_info.type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath10k_bmi_read_memory(struct ath10k *ar,
|
|
u32 address, void *buffer, u32 length)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
union bmi_resp resp;
|
|
u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.read_mem);
|
|
u32 rxlen;
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi read address 0x%x length %d\n",
|
|
address, length);
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "command disallowed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
while (length) {
|
|
rxlen = min_t(u32, length, BMI_MAX_DATA_SIZE);
|
|
|
|
cmd.id = __cpu_to_le32(BMI_READ_MEMORY);
|
|
cmd.read_mem.addr = __cpu_to_le32(address);
|
|
cmd.read_mem.len = __cpu_to_le32(rxlen);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen,
|
|
&resp, &rxlen);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to read from the device (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
memcpy(buffer, resp.read_mem.payload, rxlen);
|
|
address += rxlen;
|
|
buffer += rxlen;
|
|
length -= rxlen;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(ath10k_bmi_read_memory);
|
|
|
|
int ath10k_bmi_write_soc_reg(struct ath10k *ar, u32 address, u32 reg_val)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.write_soc_reg);
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI,
|
|
"bmi write soc register 0x%08x val 0x%08x\n",
|
|
address, reg_val);
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "bmi write soc register command in progress\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
cmd.id = __cpu_to_le32(BMI_WRITE_SOC_REGISTER);
|
|
cmd.write_soc_reg.addr = __cpu_to_le32(address);
|
|
cmd.write_soc_reg.value = __cpu_to_le32(reg_val);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, NULL, NULL);
|
|
if (ret) {
|
|
ath10k_warn(ar, "Unable to write soc register to device: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath10k_bmi_read_soc_reg(struct ath10k *ar, u32 address, u32 *reg_val)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
union bmi_resp resp;
|
|
u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.read_soc_reg);
|
|
u32 resplen = sizeof(resp.read_soc_reg);
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi read soc register 0x%08x\n",
|
|
address);
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "bmi read soc register command in progress\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
cmd.id = __cpu_to_le32(BMI_READ_SOC_REGISTER);
|
|
cmd.read_soc_reg.addr = __cpu_to_le32(address);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, &resp, &resplen);
|
|
if (ret) {
|
|
ath10k_warn(ar, "Unable to read soc register from device: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
*reg_val = __le32_to_cpu(resp.read_soc_reg.value);
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi read soc register value 0x%08x\n",
|
|
*reg_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath10k_bmi_write_memory(struct ath10k *ar,
|
|
u32 address, const void *buffer, u32 length)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
u32 hdrlen = sizeof(cmd.id) + sizeof(cmd.write_mem);
|
|
u32 txlen;
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi write address 0x%x length %d\n",
|
|
address, length);
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "command disallowed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
while (length) {
|
|
txlen = min(length, BMI_MAX_DATA_SIZE - hdrlen);
|
|
|
|
/* copy before roundup to avoid reading beyond buffer*/
|
|
memcpy(cmd.write_mem.payload, buffer, txlen);
|
|
txlen = roundup(txlen, 4);
|
|
|
|
cmd.id = __cpu_to_le32(BMI_WRITE_MEMORY);
|
|
cmd.write_mem.addr = __cpu_to_le32(address);
|
|
cmd.write_mem.len = __cpu_to_le32(txlen);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, hdrlen + txlen,
|
|
NULL, NULL);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to write to the device (%d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
/* fixup roundup() so `length` zeroes out for last chunk */
|
|
txlen = min(txlen, length);
|
|
|
|
address += txlen;
|
|
buffer += txlen;
|
|
length -= txlen;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath10k_bmi_execute(struct ath10k *ar, u32 address, u32 param, u32 *result)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
union bmi_resp resp;
|
|
u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.execute);
|
|
u32 resplen = sizeof(resp.execute);
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi execute address 0x%x param 0x%x\n",
|
|
address, param);
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "command disallowed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
cmd.id = __cpu_to_le32(BMI_EXECUTE);
|
|
cmd.execute.addr = __cpu_to_le32(address);
|
|
cmd.execute.param = __cpu_to_le32(param);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, &resp, &resplen);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to read from the device\n");
|
|
return ret;
|
|
}
|
|
|
|
if (resplen < sizeof(resp.execute)) {
|
|
ath10k_warn(ar, "invalid execute response length (%d)\n",
|
|
resplen);
|
|
return -EIO;
|
|
}
|
|
|
|
*result = __le32_to_cpu(resp.execute.result);
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi execute result 0x%x\n", *result);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ath10k_bmi_lz_data_large(struct ath10k *ar, const void *buffer, u32 length)
|
|
{
|
|
struct bmi_cmd *cmd;
|
|
u32 hdrlen = sizeof(cmd->id) + sizeof(cmd->lz_data);
|
|
u32 txlen;
|
|
int ret;
|
|
size_t buf_len;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "large bmi lz data buffer 0x%pK length %d\n",
|
|
buffer, length);
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "command disallowed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
buf_len = sizeof(*cmd) + BMI_MAX_LARGE_DATA_SIZE - BMI_MAX_DATA_SIZE;
|
|
cmd = kzalloc(buf_len, GFP_KERNEL);
|
|
if (!cmd)
|
|
return -ENOMEM;
|
|
|
|
while (length) {
|
|
txlen = min(length, BMI_MAX_LARGE_DATA_SIZE - hdrlen);
|
|
|
|
WARN_ON_ONCE(txlen & 3);
|
|
|
|
cmd->id = __cpu_to_le32(BMI_LZ_DATA);
|
|
cmd->lz_data.len = __cpu_to_le32(txlen);
|
|
memcpy(cmd->lz_data.payload, buffer, txlen);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, cmd, hdrlen + txlen,
|
|
NULL, NULL);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to write to the device\n");
|
|
kfree(cmd);
|
|
return ret;
|
|
}
|
|
|
|
buffer += txlen;
|
|
length -= txlen;
|
|
}
|
|
|
|
kfree(cmd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath10k_bmi_lz_data(struct ath10k *ar, const void *buffer, u32 length)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
u32 hdrlen = sizeof(cmd.id) + sizeof(cmd.lz_data);
|
|
u32 txlen;
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi lz data buffer 0x%pK length %d\n",
|
|
buffer, length);
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "command disallowed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
while (length) {
|
|
txlen = min(length, BMI_MAX_DATA_SIZE - hdrlen);
|
|
|
|
WARN_ON_ONCE(txlen & 3);
|
|
|
|
cmd.id = __cpu_to_le32(BMI_LZ_DATA);
|
|
cmd.lz_data.len = __cpu_to_le32(txlen);
|
|
memcpy(cmd.lz_data.payload, buffer, txlen);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, hdrlen + txlen,
|
|
NULL, NULL);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to write to the device\n");
|
|
return ret;
|
|
}
|
|
|
|
buffer += txlen;
|
|
length -= txlen;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath10k_bmi_lz_stream_start(struct ath10k *ar, u32 address)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.lz_start);
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi lz stream start address 0x%x\n",
|
|
address);
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "command disallowed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
cmd.id = __cpu_to_le32(BMI_LZ_STREAM_START);
|
|
cmd.lz_start.addr = __cpu_to_le32(address);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, NULL, NULL);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to Start LZ Stream to the device\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath10k_bmi_fast_download(struct ath10k *ar,
|
|
u32 address, const void *buffer, u32 length)
|
|
{
|
|
u8 trailer[4] = {};
|
|
u32 head_len = rounddown(length, 4);
|
|
u32 trailer_len = length - head_len;
|
|
int ret;
|
|
|
|
ath10k_dbg(ar, ATH10K_DBG_BMI,
|
|
"bmi fast download address 0x%x buffer 0x%pK length %d\n",
|
|
address, buffer, length);
|
|
|
|
ret = ath10k_bmi_lz_stream_start(ar, address);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* copy the last word into a zero padded buffer */
|
|
if (trailer_len > 0)
|
|
memcpy(trailer, buffer + head_len, trailer_len);
|
|
|
|
if (ar->hw_params.bmi_large_size_download)
|
|
ret = ath10k_bmi_lz_data_large(ar, buffer, head_len);
|
|
else
|
|
ret = ath10k_bmi_lz_data(ar, buffer, head_len);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (trailer_len > 0)
|
|
ret = ath10k_bmi_lz_data(ar, trailer, 4);
|
|
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
/*
|
|
* Close compressed stream and open a new (fake) one.
|
|
* This serves mainly to flush Target caches.
|
|
*/
|
|
ret = ath10k_bmi_lz_stream_start(ar, 0x00);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ath10k_bmi_set_start(struct ath10k *ar, u32 address)
|
|
{
|
|
struct bmi_cmd cmd;
|
|
u32 cmdlen = sizeof(cmd.id) + sizeof(cmd.set_app_start);
|
|
int ret;
|
|
|
|
if (ar->bmi.done_sent) {
|
|
ath10k_warn(ar, "bmi set start command disallowed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
cmd.id = __cpu_to_le32(BMI_SET_APP_START);
|
|
cmd.set_app_start.addr = __cpu_to_le32(address);
|
|
|
|
ret = ath10k_hif_exchange_bmi_msg(ar, &cmd, cmdlen, NULL, NULL);
|
|
if (ret) {
|
|
ath10k_warn(ar, "unable to set start to the device:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|