]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/bluetooth/btqcomsmd.c
Merge branch 'tracking-qcomlt-config-fragments' into integration-linux-qcomlt
[karo-tx-linux.git] / drivers / bluetooth / btqcomsmd.c
1 /*
2  * Copyright (c) 2015, Sony Mobile Communications Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 and
6  * only version 2 as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13
14 #include <linux/module.h>
15 #include <linux/slab.h>
16 #include <linux/soc/qcom/smd.h>
17 #include <net/bluetooth/bluetooth.h>
18 #include <net/bluetooth/hci_core.h>
19 #include <net/bluetooth/hci.h>
20
21 #define EDL_NVM_ACCESS_SET_REQ_CMD      0x01
22 #define EDL_NVM_ACCESS_OPCODE           0xfc0b
23
24 struct btqcomsmd {
25         struct qcom_smd_channel *acl_channel;
26         struct qcom_smd_channel *cmd_channel;
27 };
28
29 static int btqcomsmd_recv(struct hci_dev *hdev,
30                           unsigned type,
31                           const void *data,
32                           size_t count)
33 {
34         struct sk_buff *skb;
35         void *buf;
36
37         /* Use GFP_ATOMIC as we're in IRQ context */
38         skb = bt_skb_alloc(count, GFP_ATOMIC);
39         if (!skb)
40                 return -ENOMEM;
41
42         bt_cb(skb)->pkt_type = type;
43
44         /* Use io accessor as data might be ioremapped */
45         buf = skb_put(skb, count);
46         memcpy_fromio(buf, data, count);
47
48         return hci_recv_frame(hdev, skb);
49 }
50
51 static int btqcomsmd_acl_callback(struct qcom_smd_device *qsdev,
52                                   const void *data,
53                                   size_t count)
54 {
55         struct hci_dev *hdev = dev_get_drvdata(&qsdev->dev);
56
57         return btqcomsmd_recv(hdev, HCI_ACLDATA_PKT, data, count);
58 }
59
60 static int btqcomsmd_cmd_callback(struct qcom_smd_device *qsdev,
61                                   const void *data,
62                                   size_t count)
63 {
64         struct hci_dev *hdev = dev_get_drvdata(&qsdev->dev);
65
66         return btqcomsmd_recv(hdev, HCI_EVENT_PKT, data, count);
67 }
68
69 static int btqcomsmd_send(struct hci_dev *hdev, struct sk_buff *skb)
70 {
71         struct btqcomsmd *btq = hci_get_drvdata(hdev);
72         int ret;
73
74         switch (bt_cb(skb)->pkt_type) {
75         case HCI_ACLDATA_PKT:
76         case HCI_SCODATA_PKT:
77                 ret = qcom_smd_send(btq->acl_channel, skb->data, skb->len);
78                 break;
79         case HCI_COMMAND_PKT:
80                 ret = qcom_smd_send(btq->cmd_channel, skb->data, skb->len);
81                 break;
82         default:
83                 ret = -ENODEV;
84                 break;
85         }
86
87         return ret;
88 }
89
90 static int btqcomsmd_open(struct hci_dev *hdev)
91 {
92         set_bit(HCI_RUNNING, &hdev->flags);
93         return 0;
94 }
95
96 static int btqcomsmd_close(struct hci_dev *hdev)
97 {
98         clear_bit(HCI_RUNNING, &hdev->flags);
99         return 0;
100 }
101
102 static int btqcomsmd_set_bdaddr(struct hci_dev *hdev,
103                               const bdaddr_t *bdaddr)
104 {
105         struct sk_buff *skb;
106         u8 cmd[9];
107         int err;
108
109         cmd[0] = EDL_NVM_ACCESS_SET_REQ_CMD;
110         cmd[1] = 0x02;                  /* TAG ID */
111         cmd[2] = sizeof(bdaddr_t);      /* size */
112         memcpy(cmd + 3, bdaddr, sizeof(bdaddr_t));
113         skb = __hci_cmd_sync_ev(hdev,
114                                 EDL_NVM_ACCESS_OPCODE,
115                                 sizeof(cmd), cmd,
116                                 HCI_VENDOR_PKT, HCI_INIT_TIMEOUT);
117         if (IS_ERR(skb)) {
118                 err = PTR_ERR(skb);
119                 BT_ERR("%s: Change address command failed (%d)",
120                        hdev->name, err);
121                 return err;
122         }
123
124         kfree_skb(skb);
125
126         return 0;
127 }
128
129 static int btqcomsmd_probe(struct qcom_smd_device *sdev)
130 {
131         struct qcom_smd_channel *acl;
132         struct btqcomsmd *btq;
133         struct hci_dev *hdev;
134         int ret;
135
136         acl = qcom_smd_open_channel(sdev,
137                                     "APPS_RIVA_BT_ACL",
138                                     btqcomsmd_acl_callback);
139         if (IS_ERR(acl))
140                 return PTR_ERR(acl);
141
142         btq = devm_kzalloc(&sdev->dev, sizeof(*btq), GFP_KERNEL);
143         if (!btq)
144                 return -ENOMEM;
145
146         btq->acl_channel = acl;
147         btq->cmd_channel = sdev->channel;
148
149         hdev = hci_alloc_dev();
150         if (!hdev)
151                 return -ENOMEM;
152
153         hdev->bus = HCI_SMD;
154         hdev->open = btqcomsmd_open;
155         hdev->close = btqcomsmd_close;
156         hdev->send = btqcomsmd_send;
157         hdev->set_bdaddr = btqcomsmd_set_bdaddr;
158
159         ret = hci_register_dev(hdev);
160         if (ret < 0) {
161                 hci_free_dev(hdev);
162                 return ret;
163         }
164
165         hci_set_drvdata(hdev, btq);
166         dev_set_drvdata(&sdev->dev, hdev);
167
168         return 0;
169 }
170
171 static void btqcomsmd_remove(struct qcom_smd_device *sdev)
172 {
173         struct hci_dev *hdev = dev_get_drvdata(&sdev->dev);;
174
175         hci_unregister_dev(hdev);
176         hci_free_dev(hdev);
177 }
178
179 static const struct qcom_smd_id btqcomsmd_match[] = {
180         { .name = "APPS_RIVA_BT_CMD" },
181         {}
182 };
183
184 static struct qcom_smd_driver btqcomsmd_cmd_driver = {
185         .probe = btqcomsmd_probe,
186         .remove = btqcomsmd_remove,
187         .callback = btqcomsmd_cmd_callback,
188         .smd_match_table = btqcomsmd_match,
189         .driver  = {
190                 .name  = "btqcomsmd",
191                 .owner = THIS_MODULE,
192         },
193 };
194
195 module_qcom_smd_driver(btqcomsmd_cmd_driver);
196
197 MODULE_DESCRIPTION("Qualcomm SMD HCI driver");
198 MODULE_LICENSE("GPL v2");