Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4.16 229 lines 5.4 kB view raw
1/* 2 * Copyright (c) 2016, Linaro Ltd. 3 * Copyright (c) 2015, Sony Mobile Communications Inc. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 2 and 7 * only version 2 as published by the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 */ 14 15#include <linux/module.h> 16#include <linux/slab.h> 17#include <linux/rpmsg.h> 18#include <linux/of.h> 19 20#include <linux/soc/qcom/wcnss_ctrl.h> 21#include <linux/platform_device.h> 22 23#include <net/bluetooth/bluetooth.h> 24#include <net/bluetooth/hci_core.h> 25 26#include "btqca.h" 27 28struct btqcomsmd { 29 struct hci_dev *hdev; 30 31 bdaddr_t bdaddr; 32 struct rpmsg_endpoint *acl_channel; 33 struct rpmsg_endpoint *cmd_channel; 34}; 35 36static int btqcomsmd_recv(struct hci_dev *hdev, unsigned int type, 37 const void *data, size_t count) 38{ 39 struct sk_buff *skb; 40 41 /* Use GFP_ATOMIC as we're in IRQ context */ 42 skb = bt_skb_alloc(count, GFP_ATOMIC); 43 if (!skb) { 44 hdev->stat.err_rx++; 45 return -ENOMEM; 46 } 47 48 hci_skb_pkt_type(skb) = type; 49 skb_put_data(skb, data, count); 50 51 return hci_recv_frame(hdev, skb); 52} 53 54static int btqcomsmd_acl_callback(struct rpmsg_device *rpdev, void *data, 55 int count, void *priv, u32 addr) 56{ 57 struct btqcomsmd *btq = priv; 58 59 btq->hdev->stat.byte_rx += count; 60 return btqcomsmd_recv(btq->hdev, HCI_ACLDATA_PKT, data, count); 61} 62 63static int btqcomsmd_cmd_callback(struct rpmsg_device *rpdev, void *data, 64 int count, void *priv, u32 addr) 65{ 66 struct btqcomsmd *btq = priv; 67 68 return btqcomsmd_recv(btq->hdev, HCI_EVENT_PKT, data, count); 69} 70 71static int btqcomsmd_send(struct hci_dev *hdev, struct sk_buff *skb) 72{ 73 struct btqcomsmd *btq = hci_get_drvdata(hdev); 74 int ret; 75 76 switch (hci_skb_pkt_type(skb)) { 77 case HCI_ACLDATA_PKT: 78 ret = rpmsg_send(btq->acl_channel, skb->data, skb->len); 79 hdev->stat.acl_tx++; 80 hdev->stat.byte_tx += skb->len; 81 break; 82 case HCI_COMMAND_PKT: 83 ret = rpmsg_send(btq->cmd_channel, skb->data, skb->len); 84 hdev->stat.cmd_tx++; 85 break; 86 default: 87 ret = -EILSEQ; 88 break; 89 } 90 91 if (!ret) 92 kfree_skb(skb); 93 94 return ret; 95} 96 97static int btqcomsmd_open(struct hci_dev *hdev) 98{ 99 return 0; 100} 101 102static int btqcomsmd_close(struct hci_dev *hdev) 103{ 104 return 0; 105} 106 107static int btqcomsmd_setup(struct hci_dev *hdev) 108{ 109 struct btqcomsmd *btq = hci_get_drvdata(hdev); 110 struct sk_buff *skb; 111 int err; 112 113 skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT); 114 if (IS_ERR(skb)) 115 return PTR_ERR(skb); 116 kfree_skb(skb); 117 118 /* Devices do not have persistent storage for BD address. If no 119 * BD address has been retrieved during probe, mark the device 120 * as having an invalid BD address. 121 */ 122 if (!bacmp(&btq->bdaddr, BDADDR_ANY)) { 123 set_bit(HCI_QUIRK_INVALID_BDADDR, &hdev->quirks); 124 return 0; 125 } 126 127 /* When setting a configured BD address fails, mark the device 128 * as having an invalid BD address. 129 */ 130 err = qca_set_bdaddr_rome(hdev, &btq->bdaddr); 131 if (err) { 132 set_bit(HCI_QUIRK_INVALID_BDADDR, &hdev->quirks); 133 return 0; 134 } 135 136 return 0; 137} 138 139static int btqcomsmd_probe(struct platform_device *pdev) 140{ 141 struct btqcomsmd *btq; 142 struct hci_dev *hdev; 143 void *wcnss; 144 int ret; 145 146 btq = devm_kzalloc(&pdev->dev, sizeof(*btq), GFP_KERNEL); 147 if (!btq) 148 return -ENOMEM; 149 150 wcnss = dev_get_drvdata(pdev->dev.parent); 151 152 btq->acl_channel = qcom_wcnss_open_channel(wcnss, "APPS_RIVA_BT_ACL", 153 btqcomsmd_acl_callback, btq); 154 if (IS_ERR(btq->acl_channel)) 155 return PTR_ERR(btq->acl_channel); 156 157 btq->cmd_channel = qcom_wcnss_open_channel(wcnss, "APPS_RIVA_BT_CMD", 158 btqcomsmd_cmd_callback, btq); 159 if (IS_ERR(btq->cmd_channel)) 160 return PTR_ERR(btq->cmd_channel); 161 162 /* The local-bd-address property is usually injected by the 163 * bootloader which has access to the allocated BD address. 164 */ 165 if (!of_property_read_u8_array(pdev->dev.of_node, "local-bd-address", 166 (u8 *)&btq->bdaddr, sizeof(bdaddr_t))) { 167 dev_info(&pdev->dev, "BD address %pMR retrieved from device-tree", 168 &btq->bdaddr); 169 } 170 171 hdev = hci_alloc_dev(); 172 if (!hdev) 173 return -ENOMEM; 174 175 hci_set_drvdata(hdev, btq); 176 btq->hdev = hdev; 177 SET_HCIDEV_DEV(hdev, &pdev->dev); 178 179 hdev->bus = HCI_SMD; 180 hdev->open = btqcomsmd_open; 181 hdev->close = btqcomsmd_close; 182 hdev->send = btqcomsmd_send; 183 hdev->setup = btqcomsmd_setup; 184 hdev->set_bdaddr = qca_set_bdaddr_rome; 185 186 ret = hci_register_dev(hdev); 187 if (ret < 0) { 188 hci_free_dev(hdev); 189 return ret; 190 } 191 192 platform_set_drvdata(pdev, btq); 193 194 return 0; 195} 196 197static int btqcomsmd_remove(struct platform_device *pdev) 198{ 199 struct btqcomsmd *btq = platform_get_drvdata(pdev); 200 201 hci_unregister_dev(btq->hdev); 202 hci_free_dev(btq->hdev); 203 204 rpmsg_destroy_ept(btq->cmd_channel); 205 rpmsg_destroy_ept(btq->acl_channel); 206 207 return 0; 208} 209 210static const struct of_device_id btqcomsmd_of_match[] = { 211 { .compatible = "qcom,wcnss-bt", }, 212 { }, 213}; 214MODULE_DEVICE_TABLE(of, btqcomsmd_of_match); 215 216static struct platform_driver btqcomsmd_driver = { 217 .probe = btqcomsmd_probe, 218 .remove = btqcomsmd_remove, 219 .driver = { 220 .name = "btqcomsmd", 221 .of_match_table = btqcomsmd_of_match, 222 }, 223}; 224 225module_platform_driver(btqcomsmd_driver); 226 227MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 228MODULE_DESCRIPTION("Qualcomm SMD HCI driver"); 229MODULE_LICENSE("GPL v2");