// SPDX-License-Identifier: GPL-2.0-only
/*
* Bluetooth supports for Qualcomm Atheros chips
*
* Copyright (c) 2015 The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/vmalloc.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include "btqca.h"
int qca_read_soc_version(struct hci_dev *hdev, struct qca_btsoc_version *ver,
enum qca_btsoc_type soc_type)
{
struct sk_buff *skb;
struct edl_event_hdr *edl;
char cmd;
int err = 0;
u8 event_type = HCI_EV_VENDOR;
u8 rlen = sizeof(*edl) + sizeof(*ver);
u8 rtype = EDL_APP_VER_RES_EVT;
bt_dev_dbg(hdev, "QCA Version Request");
/* Unlike other SoC's sending version command response as payload to
* VSE event. WCN3991 sends version command response as a payload to
* command complete event.
*/
if (soc_type >= QCA_WCN3991) {
event_type = 0;
rlen += 1;
rtype = EDL_PATCH_VER_REQ_CMD;
}
cmd = EDL_PATCH_VER_REQ_CMD;
skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, EDL_PATCH_CMD_LEN,
&cmd, event_type, HCI_INIT_TIMEOUT);
if (IS_ERR(skb)) {
err = PTR_ERR(skb);
bt_dev_err(hdev, "Reading QCA version information failed (%d)",
err);
return err;
}
if (skb->len != rlen) {
bt_dev_err(hdev, "QCA Version size mismatch len %d", skb->len);
err = -EILSEQ;
goto out;
}
edl = (struct edl_event_hdr *)(skb->data);
if (edl->cresp != EDL_CMD_REQ_RES_EVT ||
edl->rtype != rtype) {
bt_dev_err(hdev, "QCA Wrong packet received %d %d", edl->cresp,
edl->rtype);
err = -EIO;
goto out;
}
if (soc_type >= QCA_WCN3991)
memcpy(ver, edl->data + 1, sizeof(*ver));
else
memcpy(ver, &edl->data, sizeof(*ver));
bt_dev_info(hdev, "QCA Product ID :0x%08x",
le32_to_cpu(ver->product_id));
bt_dev_info(hdev, "QCA SOC Version :0x%08x",
le32_to_cpu(ver->soc_id));
bt_dev_info(hdev, "QCA ROM Version :0x%08x",
le16_to_cpu(ver->rom_ver));
bt_dev_info(hdev, "QCA Patch Version:0x%08x",
le16_to_cpu(ver->patch_ver));
if (ver->soc_id == 0 || ver->rom_ver == 0)
err = -EILSEQ;
out:
kfree_skb(skb);
if (err)
bt_dev_err(hdev, "QCA Failed to get version (%d)", err);
return err;
}
EXPORT_SYMBOL_GPL(qca_read_soc_version);
static int qca_read_fw_build_info(struct hci_dev *hdev)
{
struct sk_buff *skb;
struct edl_event_hdr *edl;
char *build_label;
char cmd;
int build_lbl_len, err = 0;
bt_dev_dbg(hdev, "QCA read fw build info");
cmd = EDL_GET_BUILD_INFO_CMD;
skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, EDL_PATCH_CMD_LEN,
&cmd, 0, HCI_INIT_TIMEOUT);
if (IS_ERR(skb)) {
err = PTR_ERR(skb);
bt_dev_err(hdev, "Reading QCA fw build info failed (%d)",
err);
return err;
}
if (skb->len < sizeof(*edl)) {
err = -EILSEQ;
goto out;
}
edl = (struct edl_event_hdr *)(skb->data);
if (edl->cresp != EDL_CMD_REQ_RES_EVT ||
edl->rtype != EDL_GET_BUILD_INFO_CMD) {
bt_dev_err(hdev, "QCA Wrong packet received %d %d", edl->cresp,
edl->rtype);
err = -EIO;
goto out;
}
if (skb->len < sizeof(*edl) + 1) {
err = -EILSEQ;
goto out;
}
build_lbl_len = edl->data[0];
if (skb->len < sizeof(*edl) + 1 + build_lbl_len) {
err = -EILSEQ;
goto out;
}
build_label = kstrndup(&edl->data[1], build_lbl_len, GFP_KERNEL);
if (!build_label) {
err = -ENOMEM;
goto out;
}
hci_set_fw_info(hdev, "%s", build_label);
kfree(build_label);
out:
kfree_skb(skb);
return err;
}
static int qca_send_patch_config_cmd(struct hci_dev *hdev)
{
const u8 cmd[] = { EDL_PATCH_CONFIG_CMD, 0x01, 0, 0, 0 };
struct sk_buff *skb;
struct edl_event_hdr *edl;
int err;
bt_dev_dbg(hdev, "QCA Patch config");
skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, sizeof(cmd),
cmd, 0, HCI_INIT_TIMEOUT);
if (IS_ERR(skb)) {
err = PTR_ERR(skb);
bt_dev_err(hdev, "Sending QCA Patch config failed (%d)", err);
return err;
}
if (skb->len != 2) {
bt_dev_err(hdev, "QCA Patch config cmd size mismatch len %d", skb->len);
err = -EILSEQ;
goto out;
}
edl = (struct edl_event_hdr *)(skb