aboutsummaryrefslogtreecommitdiff
path: root/drivers/bluetooth/btmrvl_sdio.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/bluetooth/btmrvl_sdio.c')
-rw-r--r--drivers/bluetooth/btmrvl_sdio.c359
1 files changed, 274 insertions, 85 deletions
diff --git a/drivers/bluetooth/btmrvl_sdio.c b/drivers/bluetooth/btmrvl_sdio.c
index 27b74b0d547..9dedca516ff 100644
--- a/drivers/bluetooth/btmrvl_sdio.c
+++ b/drivers/bluetooth/btmrvl_sdio.c
@@ -64,6 +64,7 @@ static const struct btmrvl_sdio_card_reg btmrvl_reg_8688 = {
.io_port_0 = 0x00,
.io_port_1 = 0x01,
.io_port_2 = 0x02,
+ .int_read_to_clear = false,
};
static const struct btmrvl_sdio_card_reg btmrvl_reg_87xx = {
.cfg = 0x00,
@@ -80,11 +81,32 @@ static const struct btmrvl_sdio_card_reg btmrvl_reg_87xx = {
.io_port_0 = 0x78,
.io_port_1 = 0x79,
.io_port_2 = 0x7a,
+ .int_read_to_clear = false,
+};
+
+static const struct btmrvl_sdio_card_reg btmrvl_reg_88xx = {
+ .cfg = 0x00,
+ .host_int_mask = 0x02,
+ .host_intstatus = 0x03,
+ .card_status = 0x50,
+ .sq_read_base_addr_a0 = 0x60,
+ .sq_read_base_addr_a1 = 0x61,
+ .card_revision = 0xbc,
+ .card_fw_status0 = 0xc0,
+ .card_fw_status1 = 0xc1,
+ .card_rx_len = 0xc2,
+ .card_rx_unit = 0xc3,
+ .io_port_0 = 0xd8,
+ .io_port_1 = 0xd9,
+ .io_port_2 = 0xda,
+ .int_read_to_clear = true,
+ .host_int_rsr = 0x01,
+ .card_misc_cfg = 0xcc,
};
static const struct btmrvl_sdio_device btmrvl_sdio_sd8688 = {
- .helper = "sd8688_helper.bin",
- .firmware = "sd8688.bin",
+ .helper = "mrvl/sd8688_helper.bin",
+ .firmware = "mrvl/sd8688.bin",
.reg = &btmrvl_reg_8688,
.sd_blksz_fw_dl = 64,
};
@@ -103,6 +125,13 @@ static const struct btmrvl_sdio_device btmrvl_sdio_sd8797 = {
.sd_blksz_fw_dl = 256,
};
+static const struct btmrvl_sdio_device btmrvl_sdio_sd8897 = {
+ .helper = NULL,
+ .firmware = "mrvl/sd8897_uapsta.bin",
+ .reg = &btmrvl_reg_88xx,
+ .sd_blksz_fw_dl = 256,
+};
+
static const struct sdio_device_id btmrvl_sdio_ids[] = {
/* Marvell SD8688 Bluetooth device */
{ SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x9105),
@@ -110,9 +139,15 @@ static const struct sdio_device_id btmrvl_sdio_ids[] = {
/* Marvell SD8787 Bluetooth device */
{ SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x911A),
.driver_data = (unsigned long) &btmrvl_sdio_sd8787 },
+ /* Marvell SD8787 Bluetooth AMP device */
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x911B),
+ .driver_data = (unsigned long) &btmrvl_sdio_sd8787 },
/* Marvell SD8797 Bluetooth device */
{ SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x912A),
.driver_data = (unsigned long) &btmrvl_sdio_sd8797 },
+ /* Marvell SD8897 Bluetooth device */
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x912E),
+ .driver_data = (unsigned long) &btmrvl_sdio_sd8897 },
{ } /* Terminating entry */
};
@@ -225,24 +260,24 @@ failed:
static int btmrvl_sdio_verify_fw_download(struct btmrvl_sdio_card *card,
int pollnum)
{
- int ret = -ETIMEDOUT;
u16 firmwarestat;
- unsigned int tries;
+ int tries, ret;
/* Wait for firmware to become ready */
for (tries = 0; tries < pollnum; tries++) {
- if (btmrvl_sdio_read_fw_status(card, &firmwarestat) < 0)
+ sdio_claim_host(card->func);
+ ret = btmrvl_sdio_read_fw_status(card, &firmwarestat);
+ sdio_release_host(card->func);
+ if (ret < 0)
continue;
- if (firmwarestat == FIRMWARE_READY) {
- ret = 0;
- break;
- } else {
- msleep(10);
- }
+ if (firmwarestat == FIRMWARE_READY)
+ return 0;
+
+ msleep(10);
}
- return ret;
+ return -ETIMEDOUT;
}
static int btmrvl_sdio_download_helper(struct btmrvl_sdio_card *card)
@@ -339,9 +374,7 @@ static int btmrvl_sdio_download_helper(struct btmrvl_sdio_card *card)
done:
kfree(tmphlprbuf);
- if (fw_helper)
- release_firmware(fw_helper);
-
+ release_firmware(fw_helper);
return ret;
}
@@ -458,7 +491,7 @@ static int btmrvl_sdio_download_fw_w_helper(struct btmrvl_sdio_card *card)
if (firmwarelen - offset < txlen)
txlen = firmwarelen - offset;
- tx_blocks = (txlen + blksz_dl - 1) / blksz_dl;
+ tx_blocks = DIV_ROUND_UP(txlen, blksz_dl);
memcpy(fwbuf, &firmware[offset], txlen);
}
@@ -484,17 +517,14 @@ static int btmrvl_sdio_download_fw_w_helper(struct btmrvl_sdio_card *card)
done:
kfree(tmpfwbuf);
-
- if (fw_firmware)
- release_firmware(fw_firmware);
-
+ release_firmware(fw_firmware);
return ret;
}
static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv)
{
u16 buf_len = 0;
- int ret, buf_block_len, blksz;
+ int ret, num_blocks, blksz;
struct sk_buff *skb = NULL;
u32 type;
u8 *payload = NULL;
@@ -516,20 +546,20 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv)
}
blksz = SDIO_BLOCK_SIZE;
- buf_block_len = (buf_len + blksz - 1) / blksz;
+ num_blocks = DIV_ROUND_UP(buf_len, blksz);
if (buf_len <= SDIO_HEADER_LEN
- || (buf_block_len * blksz) > ALLOC_BUF_SIZE) {
+ || (num_blocks * blksz) > ALLOC_BUF_SIZE) {
BT_ERR("invalid packet length: %d", buf_len);
ret = -EINVAL;
goto exit;
}
/* Allocate buffer */
- skb = bt_skb_alloc(buf_block_len * blksz + BTSDIO_DMA_ALIGN,
- GFP_ATOMIC);
+ skb = bt_skb_alloc(num_blocks * blksz + BTSDIO_DMA_ALIGN, GFP_ATOMIC);
if (skb == NULL) {
BT_ERR("No free skb");
+ ret = -ENOMEM;
goto exit;
}
@@ -543,7 +573,7 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv)
payload = skb->data;
ret = sdio_readsb(card->func, payload, card->ioport,
- buf_block_len * blksz);
+ num_blocks * blksz);
if (ret < 0) {
BT_ERR("readsb failed: %d", ret);
ret = -EIO;
@@ -555,7 +585,16 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv)
*/
buf_len = payload[0];
- buf_len |= (u16) payload[1] << 8;
+ buf_len |= payload[1] << 8;
+ buf_len |= payload[2] << 16;
+
+ if (buf_len > blksz * num_blocks) {
+ BT_ERR("Skip incorrect packet: hdrlen %d buffer %d",
+ buf_len, blksz * num_blocks);
+ ret = -EIO;
+ goto exit;
+ }
+
type = payload[3];
switch (type) {
@@ -563,33 +602,33 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv)
case HCI_SCODATA_PKT:
case HCI_EVENT_PKT:
bt_cb(skb)->pkt_type = type;
- skb->dev = (void *)hdev;
skb_put(skb, buf_len);
skb_pull(skb, SDIO_HEADER_LEN);
- if (type == HCI_EVENT_PKT)
- btmrvl_check_evtpkt(priv, skb);
+ if (type == HCI_EVENT_PKT) {
+ if (btmrvl_check_evtpkt(priv, skb))
+ hci_recv_frame(hdev, skb);
+ } else {
+ hci_recv_frame(hdev, skb);
+ }
- hci_recv_frame(skb);
hdev->stat.byte_rx += buf_len;
break;
case MRVL_VENDOR_PKT:
bt_cb(skb)->pkt_type = HCI_VENDOR_PKT;
- skb->dev = (void *)hdev;
skb_put(skb, buf_len);
skb_pull(skb, SDIO_HEADER_LEN);
if (btmrvl_process_event(priv, skb))
- hci_recv_frame(skb);
+ hci_recv_frame(hdev, skb);
hdev->stat.byte_rx += buf_len;
break;
default:
BT_ERR("Unknown packet type:%d", type);
- print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, payload,
- blksz * buf_block_len);
+ BT_ERR("hex: %*ph", blksz * num_blocks, payload);
kfree_skb(skb);
skb = NULL;
@@ -599,8 +638,7 @@ static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv)
exit:
if (ret) {
hdev->stat.err_rx++;
- if (skb)
- kfree_skb(skb);
+ kfree_skb(skb);
}
return ret;
@@ -634,46 +672,78 @@ static int btmrvl_sdio_process_int_status(struct btmrvl_private *priv)
return 0;
}
-static void btmrvl_sdio_interrupt(struct sdio_func *func)
+static int btmrvl_sdio_read_to_clear(struct btmrvl_sdio_card *card, u8 *ireg)
{
- struct btmrvl_private *priv;
- struct btmrvl_sdio_card *card;
- ulong flags;
- u8 ireg = 0;
+ struct btmrvl_adapter *adapter = card->priv->adapter;
int ret;
- card = sdio_get_drvdata(func);
- if (!card || !card->priv) {
- BT_ERR("sbi_interrupt(%p) card or priv is "
- "NULL, card=%p\n", func, card);
- return;
+ ret = sdio_readsb(card->func, adapter->hw_regs, 0, SDIO_BLOCK_SIZE);
+ if (ret) {
+ BT_ERR("sdio_readsb: read int hw_regs failed: %d", ret);
+ return ret;
}
- priv = card->priv;
+ *ireg = adapter->hw_regs[card->reg->host_intstatus];
+ BT_DBG("hw_regs[%#x]=%#x", card->reg->host_intstatus, *ireg);
+
+ return 0;
+}
+
+static int btmrvl_sdio_write_to_clear(struct btmrvl_sdio_card *card, u8 *ireg)
+{
+ int ret;
- ireg = sdio_readb(card->func, card->reg->host_intstatus, &ret);
+ *ireg = sdio_readb(card->func, card->reg->host_intstatus, &ret);
if (ret) {
- BT_ERR("sdio_readb: read int status register failed");
- return;
+ BT_ERR("sdio_readb: read int status failed: %d", ret);
+ return ret;
}
- if (ireg != 0) {
+ if (*ireg) {
/*
* DN_LD_HOST_INT_STATUS and/or UP_LD_HOST_INT_STATUS
* Clear the interrupt status register and re-enable the
* interrupt.
*/
- BT_DBG("ireg = 0x%x", ireg);
+ BT_DBG("int_status = 0x%x", *ireg);
- sdio_writeb(card->func, ~(ireg) & (DN_LD_HOST_INT_STATUS |
- UP_LD_HOST_INT_STATUS),
- card->reg->host_intstatus, &ret);
+ sdio_writeb(card->func, ~(*ireg) & (DN_LD_HOST_INT_STATUS |
+ UP_LD_HOST_INT_STATUS),
+ card->reg->host_intstatus, &ret);
if (ret) {
- BT_ERR("sdio_writeb: clear int status register failed");
- return;
+ BT_ERR("sdio_writeb: clear int status failed: %d", ret);
+ return ret;
}
}
+ return 0;
+}
+
+static void btmrvl_sdio_interrupt(struct sdio_func *func)
+{
+ struct btmrvl_private *priv;
+ struct btmrvl_sdio_card *card;
+ ulong flags;
+ u8 ireg = 0;
+ int ret;
+
+ card = sdio_get_drvdata(func);
+ if (!card || !card->priv) {
+ BT_ERR("sbi_interrupt(%p) card or priv is "
+ "NULL, card=%p\n", func, card);
+ return;
+ }
+
+ priv = card->priv;
+
+ if (card->reg->int_read_to_clear)
+ ret = btmrvl_sdio_read_to_clear(card, &ireg);
+ else
+ ret = btmrvl_sdio_write_to_clear(card, &ireg);
+
+ if (ret)
+ return;
+
spin_lock_irqsave(&priv->driver_lock, flags);
sdio_ireg |= ireg;
spin_unlock_irqrestore(&priv->driver_lock, flags);
@@ -744,6 +814,30 @@ static int btmrvl_sdio_register_dev(struct btmrvl_sdio_card *card)
BT_DBG("SDIO FUNC%d IO port: 0x%x", func->num, card->ioport);
+ if (card->reg->int_read_to_clear) {
+ reg = sdio_readb(func, card->reg->host_int_rsr, &ret);
+ if (ret < 0) {
+ ret = -EIO;
+ goto release_irq;
+ }
+ sdio_writeb(func, reg | 0x3f, card->reg->host_int_rsr, &ret);
+ if (ret < 0) {
+ ret = -EIO;
+ goto release_irq;
+ }
+
+ reg = sdio_readb(func, card->reg->card_misc_cfg, &ret);
+ if (ret < 0) {
+ ret = -EIO;
+ goto release_irq;
+ }
+ sdio_writeb(func, reg | 0x10, card->reg->card_misc_cfg, &ret);
+ if (ret < 0) {
+ ret = -EIO;
+ goto release_irq;
+ }
+ }
+
sdio_set_drvdata(func, card);
sdio_release_host(func);
@@ -838,7 +932,7 @@ static int btmrvl_sdio_host_to_card(struct btmrvl_private *priv,
}
blksz = SDIO_BLOCK_SIZE;
- buf_block_len = (nb + blksz - 1) / blksz;
+ buf_block_len = DIV_ROUND_UP(nb, blksz);
sdio_claim_host(card->func);
@@ -849,8 +943,7 @@ static int btmrvl_sdio_host_to_card(struct btmrvl_private *priv,
if (ret < 0) {
i++;
BT_ERR("i=%d writesb failed: %d", i, ret);
- print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
- payload, nb);
+ BT_ERR("hex: %*ph", nb, payload);
ret = -EIO;
if (i > MAX_WRITE_IOMEM_RETRY)
goto exit;
@@ -868,7 +961,7 @@ exit:
static int btmrvl_sdio_download_fw(struct btmrvl_sdio_card *card)
{
- int ret = 0;
+ int ret;
u8 fws0;
int pollnum = MAX_POLL_TRIES;
@@ -876,13 +969,14 @@ static int btmrvl_sdio_download_fw(struct btmrvl_sdio_card *card)
BT_ERR("card or function is NULL!");
return -EINVAL;
}
- sdio_claim_host(card->func);
if (!btmrvl_sdio_verify_fw_download(card, 1)) {
BT_DBG("Firmware already downloaded!");
- goto done;
+ return 0;
}
+ sdio_claim_host(card->func);
+
/* Check if other function driver is downloading the firmware */
fws0 = sdio_readb(card->func, card->reg->card_fw_status0, &ret);
if (ret) {
@@ -912,15 +1006,21 @@ static int btmrvl_sdio_download_fw(struct btmrvl_sdio_card *card)
}
}
+ sdio_release_host(card->func);
+
+ /*
+ * winner or not, with this test the FW synchronizes when the
+ * module can continue its initialization
+ */
if (btmrvl_sdio_verify_fw_download(card, pollnum)) {
BT_ERR("FW failed to be active in time!");
- ret = -ETIMEDOUT;
- goto done;
+ return -ETIMEDOUT;
}
+ return 0;
+
done:
sdio_release_host(card->func);
-
return ret;
}
@@ -955,11 +1055,9 @@ static int btmrvl_sdio_probe(struct sdio_func *func,
BT_INFO("vendor=0x%x, device=0x%x, class=%d, fn=%d",
id->vendor, id->device, id->class, func->num);
- card = kzalloc(sizeof(*card), GFP_KERNEL);
- if (!card) {
- ret = -ENOMEM;
- goto done;
- }
+ card = devm_kzalloc(&func->dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
card->func = func;
@@ -973,8 +1071,7 @@ static int btmrvl_sdio_probe(struct sdio_func *func,
if (btmrvl_sdio_register_dev(card) < 0) {
BT_ERR("Failed to register BT device!");
- ret = -ENODEV;
- goto free_card;
+ return -ENODEV;
}
/* Disable the interrupts on the card */
@@ -986,8 +1083,6 @@ static int btmrvl_sdio_probe(struct sdio_func *func,
goto unreg_dev;
}
- msleep(100);
-
btmrvl_sdio_enable_host_int(card);
priv = btmrvl_add_card(card);
@@ -1010,18 +1105,12 @@ static int btmrvl_sdio_probe(struct sdio_func *func,
goto disable_host_int;
}
- priv->btmrvl_dev.psmode = 1;
- btmrvl_enable_ps(priv);
-
return 0;
disable_host_int:
btmrvl_sdio_disable_host_int(card);
unreg_dev:
btmrvl_sdio_unregister_dev(card);
-free_card:
- kfree(card);
-done:
return ret;
}
@@ -1043,16 +1132,115 @@ static void btmrvl_sdio_remove(struct sdio_func *func)
BT_DBG("unregester dev");
btmrvl_sdio_unregister_dev(card);
btmrvl_remove_card(card->priv);
- kfree(card);
}
}
}
+static int btmrvl_sdio_suspend(struct device *dev)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ struct btmrvl_sdio_card *card;
+ struct btmrvl_private *priv;
+ mmc_pm_flag_t pm_flags;
+ struct hci_dev *hcidev;
+
+ if (func) {
+ pm_flags = sdio_get_host_pm_caps(func);
+ BT_DBG("%s: suspend: PM flags = 0x%x", sdio_func_id(func),
+ pm_flags);
+ if (!(pm_flags & MMC_PM_KEEP_POWER)) {
+ BT_ERR("%s: cannot remain alive while suspended",
+ sdio_func_id(func));
+ return -ENOSYS;
+ }
+ card = sdio_get_drvdata(func);
+ if (!card || !card->priv) {
+ BT_ERR("card or priv structure is not valid");
+ return 0;
+ }
+ } else {
+ BT_ERR("sdio_func is not specified");
+ return 0;
+ }
+
+ priv = card->priv;
+
+ if (priv->adapter->hs_state != HS_ACTIVATED) {
+ if (btmrvl_enable_hs(priv)) {
+ BT_ERR("HS not actived, suspend failed!");
+ return -EBUSY;
+ }
+ }
+ hcidev = priv->btmrvl_dev.hcidev;
+ BT_DBG("%s: SDIO suspend", hcidev->name);
+ hci_suspend_dev(hcidev);
+ skb_queue_purge(&priv->adapter->tx_queue);
+
+ priv->adapter->is_suspended = true;
+
+ /* We will keep the power when hs enabled successfully */
+ if (priv->adapter->hs_state == HS_ACTIVATED) {
+ BT_DBG("suspend with MMC_PM_KEEP_POWER");
+ return sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+ } else {
+ BT_DBG("suspend without MMC_PM_KEEP_POWER");
+ return 0;
+ }
+}
+
+static int btmrvl_sdio_resume(struct device *dev)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ struct btmrvl_sdio_card *card;
+ struct btmrvl_private *priv;
+ mmc_pm_flag_t pm_flags;
+ struct hci_dev *hcidev;
+
+ if (func) {
+ pm_flags = sdio_get_host_pm_caps(func);
+ BT_DBG("%s: resume: PM flags = 0x%x", sdio_func_id(func),
+ pm_flags);
+ card = sdio_get_drvdata(func);
+ if (!card || !card->priv) {
+ BT_ERR("card or priv structure is not valid");
+ return 0;
+ }
+ } else {
+ BT_ERR("sdio_func is not specified");
+ return 0;
+ }
+ priv = card->priv;
+
+ if (!priv->adapter->is_suspended) {
+ BT_DBG("device already resumed");
+ return 0;
+ }
+
+ priv->adapter->is_suspended = false;
+ hcidev = priv->btmrvl_dev.hcidev;
+ BT_DBG("%s: SDIO resume", hcidev->name);
+ hci_resume_dev(hcidev);
+ priv->hw_wakeup_firmware(priv);
+ priv->adapter->hs_state = HS_DEACTIVATED;
+ BT_DBG("%s: HS DEACTIVATED in resume!", hcidev->name);
+
+ return 0;
+}
+
+static const struct dev_pm_ops btmrvl_sdio_pm_ops = {
+ .suspend = btmrvl_sdio_suspend,
+ .resume = btmrvl_sdio_resume,
+};
+
static struct sdio_driver bt_mrvl_sdio = {
.name = "btmrvl_sdio",
.id_table = btmrvl_sdio_ids,
.probe = btmrvl_sdio_probe,
.remove = btmrvl_sdio_remove,
+ .drv = {
+ .owner = THIS_MODULE,
+ .pm = &btmrvl_sdio_pm_ops,
+ }
};
static int __init btmrvl_sdio_init_module(void)
@@ -1083,7 +1271,8 @@ MODULE_AUTHOR("Marvell International Ltd.");
MODULE_DESCRIPTION("Marvell BT-over-SDIO driver ver " VERSION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL v2");
-MODULE_FIRMWARE("sd8688_helper.bin");
-MODULE_FIRMWARE("sd8688.bin");
+MODULE_FIRMWARE("mrvl/sd8688_helper.bin");
+MODULE_FIRMWARE("mrvl/sd8688.bin");
MODULE_FIRMWARE("mrvl/sd8787_uapsta.bin");
MODULE_FIRMWARE("mrvl/sd8797_uapsta.bin");
+MODULE_FIRMWARE("mrvl/sd8897_uapsta.bin");