1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * |
4 | * Broadcom Blutonium firmware driver |
5 | * |
6 | * Copyright (C) 2003 Maxim Krasnyansky <maxk@qualcomm.com> |
7 | * Copyright (C) 2003 Marcel Holtmann <marcel@holtmann.org> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | |
12 | #include <linux/kernel.h> |
13 | #include <linux/init.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/types.h> |
16 | #include <linux/errno.h> |
17 | |
18 | #include <linux/device.h> |
19 | #include <linux/firmware.h> |
20 | |
21 | #include <linux/usb.h> |
22 | |
23 | #include <net/bluetooth/bluetooth.h> |
24 | |
25 | #define VERSION "1.2" |
26 | |
27 | static const struct usb_device_id bcm203x_table[] = { |
28 | /* Broadcom Blutonium (BCM2033) */ |
29 | { USB_DEVICE(0x0a5c, 0x2033) }, |
30 | |
31 | { } /* Terminating entry */ |
32 | }; |
33 | |
34 | MODULE_DEVICE_TABLE(usb, bcm203x_table); |
35 | |
36 | #define BCM203X_ERROR 0 |
37 | #define BCM203X_RESET 1 |
38 | #define BCM203X_LOAD_MINIDRV 2 |
39 | #define BCM203X_SELECT_MEMORY 3 |
40 | #define BCM203X_CHECK_MEMORY 4 |
41 | #define BCM203X_LOAD_FIRMWARE 5 |
42 | #define BCM203X_CHECK_FIRMWARE 6 |
43 | |
44 | #define BCM203X_IN_EP 0x81 |
45 | #define BCM203X_OUT_EP 0x02 |
46 | |
47 | struct bcm203x_data { |
48 | struct usb_device *udev; |
49 | |
50 | unsigned long state; |
51 | |
52 | struct work_struct work; |
53 | atomic_t shutdown; |
54 | |
55 | struct urb *urb; |
56 | unsigned char *buffer; |
57 | |
58 | unsigned char *fw_data; |
59 | unsigned int fw_size; |
60 | unsigned int fw_sent; |
61 | }; |
62 | |
63 | static void bcm203x_complete(struct urb *urb) |
64 | { |
65 | struct bcm203x_data *data = urb->context; |
66 | struct usb_device *udev = urb->dev; |
67 | int len; |
68 | |
69 | BT_DBG("udev %p urb %p" , udev, urb); |
70 | |
71 | if (urb->status) { |
72 | BT_ERR("URB failed with status %d" , urb->status); |
73 | data->state = BCM203X_ERROR; |
74 | return; |
75 | } |
76 | |
77 | switch (data->state) { |
78 | case BCM203X_LOAD_MINIDRV: |
79 | memcpy(data->buffer, "#" , 1); |
80 | |
81 | usb_fill_bulk_urb(urb, dev: udev, usb_sndbulkpipe(udev, BCM203X_OUT_EP), |
82 | transfer_buffer: data->buffer, buffer_length: 1, complete_fn: bcm203x_complete, context: data); |
83 | |
84 | data->state = BCM203X_SELECT_MEMORY; |
85 | |
86 | /* use workqueue to have a small delay */ |
87 | schedule_work(work: &data->work); |
88 | break; |
89 | |
90 | case BCM203X_SELECT_MEMORY: |
91 | usb_fill_int_urb(urb, dev: udev, usb_rcvintpipe(udev, BCM203X_IN_EP), |
92 | transfer_buffer: data->buffer, buffer_length: 32, complete_fn: bcm203x_complete, context: data, interval: 1); |
93 | |
94 | data->state = BCM203X_CHECK_MEMORY; |
95 | |
96 | if (usb_submit_urb(urb: data->urb, GFP_ATOMIC) < 0) |
97 | BT_ERR("Can't submit URB" ); |
98 | break; |
99 | |
100 | case BCM203X_CHECK_MEMORY: |
101 | if (data->buffer[0] != '#') { |
102 | BT_ERR("Memory select failed" ); |
103 | data->state = BCM203X_ERROR; |
104 | break; |
105 | } |
106 | |
107 | data->state = BCM203X_LOAD_FIRMWARE; |
108 | fallthrough; |
109 | case BCM203X_LOAD_FIRMWARE: |
110 | if (data->fw_sent == data->fw_size) { |
111 | usb_fill_int_urb(urb, dev: udev, usb_rcvintpipe(udev, BCM203X_IN_EP), |
112 | transfer_buffer: data->buffer, buffer_length: 32, complete_fn: bcm203x_complete, context: data, interval: 1); |
113 | |
114 | data->state = BCM203X_CHECK_FIRMWARE; |
115 | } else { |
116 | len = min_t(uint, data->fw_size - data->fw_sent, 4096); |
117 | |
118 | usb_fill_bulk_urb(urb, dev: udev, usb_sndbulkpipe(udev, BCM203X_OUT_EP), |
119 | transfer_buffer: data->fw_data + data->fw_sent, buffer_length: len, complete_fn: bcm203x_complete, context: data); |
120 | |
121 | data->fw_sent += len; |
122 | } |
123 | |
124 | if (usb_submit_urb(urb: data->urb, GFP_ATOMIC) < 0) |
125 | BT_ERR("Can't submit URB" ); |
126 | break; |
127 | |
128 | case BCM203X_CHECK_FIRMWARE: |
129 | if (data->buffer[0] != '.') { |
130 | BT_ERR("Firmware loading failed" ); |
131 | data->state = BCM203X_ERROR; |
132 | break; |
133 | } |
134 | |
135 | data->state = BCM203X_RESET; |
136 | break; |
137 | } |
138 | } |
139 | |
140 | static void bcm203x_work(struct work_struct *work) |
141 | { |
142 | struct bcm203x_data *data = |
143 | container_of(work, struct bcm203x_data, work); |
144 | |
145 | if (atomic_read(v: &data->shutdown)) |
146 | return; |
147 | |
148 | if (usb_submit_urb(urb: data->urb, GFP_KERNEL) < 0) |
149 | BT_ERR("Can't submit URB" ); |
150 | } |
151 | |
152 | static int bcm203x_probe(struct usb_interface *intf, const struct usb_device_id *id) |
153 | { |
154 | const struct firmware *firmware; |
155 | struct usb_device *udev = interface_to_usbdev(intf); |
156 | struct bcm203x_data *data; |
157 | int size; |
158 | |
159 | BT_DBG("intf %p id %p" , intf, id); |
160 | |
161 | if (intf->cur_altsetting->desc.bInterfaceNumber != 0) |
162 | return -ENODEV; |
163 | |
164 | data = devm_kzalloc(dev: &intf->dev, size: sizeof(*data), GFP_KERNEL); |
165 | if (!data) |
166 | return -ENOMEM; |
167 | |
168 | data->udev = udev; |
169 | data->state = BCM203X_LOAD_MINIDRV; |
170 | |
171 | data->urb = usb_alloc_urb(iso_packets: 0, GFP_KERNEL); |
172 | if (!data->urb) |
173 | return -ENOMEM; |
174 | |
175 | if (request_firmware(fw: &firmware, name: "BCM2033-MD.hex" , device: &udev->dev) < 0) { |
176 | BT_ERR("Mini driver request failed" ); |
177 | usb_free_urb(urb: data->urb); |
178 | return -EIO; |
179 | } |
180 | |
181 | BT_DBG("minidrv data %p size %zu" , firmware->data, firmware->size); |
182 | |
183 | size = max_t(uint, firmware->size, 4096); |
184 | |
185 | data->buffer = kmalloc(size, GFP_KERNEL); |
186 | if (!data->buffer) { |
187 | BT_ERR("Can't allocate memory for mini driver" ); |
188 | release_firmware(fw: firmware); |
189 | usb_free_urb(urb: data->urb); |
190 | return -ENOMEM; |
191 | } |
192 | |
193 | memcpy(data->buffer, firmware->data, firmware->size); |
194 | |
195 | usb_fill_bulk_urb(urb: data->urb, dev: udev, usb_sndbulkpipe(udev, BCM203X_OUT_EP), |
196 | transfer_buffer: data->buffer, buffer_length: firmware->size, complete_fn: bcm203x_complete, context: data); |
197 | |
198 | release_firmware(fw: firmware); |
199 | |
200 | if (request_firmware(fw: &firmware, name: "BCM2033-FW.bin" , device: &udev->dev) < 0) { |
201 | BT_ERR("Firmware request failed" ); |
202 | usb_free_urb(urb: data->urb); |
203 | kfree(objp: data->buffer); |
204 | return -EIO; |
205 | } |
206 | |
207 | BT_DBG("firmware data %p size %zu" , firmware->data, firmware->size); |
208 | |
209 | data->fw_data = kmemdup(p: firmware->data, size: firmware->size, GFP_KERNEL); |
210 | if (!data->fw_data) { |
211 | BT_ERR("Can't allocate memory for firmware image" ); |
212 | release_firmware(fw: firmware); |
213 | usb_free_urb(urb: data->urb); |
214 | kfree(objp: data->buffer); |
215 | return -ENOMEM; |
216 | } |
217 | |
218 | data->fw_size = firmware->size; |
219 | data->fw_sent = 0; |
220 | |
221 | release_firmware(fw: firmware); |
222 | |
223 | INIT_WORK(&data->work, bcm203x_work); |
224 | |
225 | usb_set_intfdata(intf, data); |
226 | |
227 | /* use workqueue to have a small delay */ |
228 | schedule_work(work: &data->work); |
229 | |
230 | return 0; |
231 | } |
232 | |
233 | static void bcm203x_disconnect(struct usb_interface *intf) |
234 | { |
235 | struct bcm203x_data *data = usb_get_intfdata(intf); |
236 | |
237 | BT_DBG("intf %p" , intf); |
238 | |
239 | atomic_inc(v: &data->shutdown); |
240 | cancel_work_sync(work: &data->work); |
241 | |
242 | usb_kill_urb(urb: data->urb); |
243 | |
244 | usb_set_intfdata(intf, NULL); |
245 | |
246 | usb_free_urb(urb: data->urb); |
247 | kfree(objp: data->fw_data); |
248 | kfree(objp: data->buffer); |
249 | } |
250 | |
251 | static struct usb_driver bcm203x_driver = { |
252 | .name = "bcm203x" , |
253 | .probe = bcm203x_probe, |
254 | .disconnect = bcm203x_disconnect, |
255 | .id_table = bcm203x_table, |
256 | .disable_hub_initiated_lpm = 1, |
257 | }; |
258 | |
259 | module_usb_driver(bcm203x_driver); |
260 | |
261 | MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>" ); |
262 | MODULE_DESCRIPTION("Broadcom Blutonium firmware driver ver " VERSION); |
263 | MODULE_VERSION(VERSION); |
264 | MODULE_LICENSE("GPL" ); |
265 | MODULE_FIRMWARE("BCM2033-MD.hex" ); |
266 | MODULE_FIRMWARE("BCM2033-FW.bin" ); |
267 | |