18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * 38c2ecf20Sopenharmony_ci * Driver for the 3Com Bluetooth PCMCIA card 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2001-2002 Marcel Holtmann <marcel@holtmann.org> 68c2ecf20Sopenharmony_ci * Jose Orlando Pereira <jop@di.uminho.pt> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 108c2ecf20Sopenharmony_ci * it under the terms of the GNU General Public License version 2 as 118c2ecf20Sopenharmony_ci * published by the Free Software Foundation; 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * Software distributed under the License is distributed on an "AS 148c2ecf20Sopenharmony_ci * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or 158c2ecf20Sopenharmony_ci * implied. See the License for the specific language governing 168c2ecf20Sopenharmony_ci * rights and limitations under the License. 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * The initial developer of the original code is David A. Hinds 198c2ecf20Sopenharmony_ci * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds 208c2ecf20Sopenharmony_ci * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci */ 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include <linux/module.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include <linux/kernel.h> 278c2ecf20Sopenharmony_ci#include <linux/init.h> 288c2ecf20Sopenharmony_ci#include <linux/slab.h> 298c2ecf20Sopenharmony_ci#include <linux/types.h> 308c2ecf20Sopenharmony_ci#include <linux/delay.h> 318c2ecf20Sopenharmony_ci#include <linux/errno.h> 328c2ecf20Sopenharmony_ci#include <linux/ptrace.h> 338c2ecf20Sopenharmony_ci#include <linux/ioport.h> 348c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 358c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#include <linux/skbuff.h> 388c2ecf20Sopenharmony_ci#include <linux/string.h> 398c2ecf20Sopenharmony_ci#include <linux/serial.h> 408c2ecf20Sopenharmony_ci#include <linux/serial_reg.h> 418c2ecf20Sopenharmony_ci#include <linux/bitops.h> 428c2ecf20Sopenharmony_ci#include <asm/io.h> 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#include <linux/device.h> 458c2ecf20Sopenharmony_ci#include <linux/firmware.h> 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci#include <pcmcia/cistpl.h> 488c2ecf20Sopenharmony_ci#include <pcmcia/ciscode.h> 498c2ecf20Sopenharmony_ci#include <pcmcia/ds.h> 508c2ecf20Sopenharmony_ci#include <pcmcia/cisreg.h> 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci#include <net/bluetooth/bluetooth.h> 538c2ecf20Sopenharmony_ci#include <net/bluetooth/hci_core.h> 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci/* ======================== Module parameters ======================== */ 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ciMODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); 618c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Bluetooth driver for the 3Com Bluetooth PCMCIA card"); 628c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 638c2ecf20Sopenharmony_ciMODULE_FIRMWARE("BT3CPCC.bin"); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci/* ======================== Local structures ======================== */ 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistruct bt3c_info { 718c2ecf20Sopenharmony_ci struct pcmcia_device *p_dev; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci struct hci_dev *hdev; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci spinlock_t lock; /* For serializing operations */ 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci struct sk_buff_head txq; 788c2ecf20Sopenharmony_ci unsigned long tx_state; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci unsigned long rx_state; 818c2ecf20Sopenharmony_ci unsigned long rx_count; 828c2ecf20Sopenharmony_ci struct sk_buff *rx_skb; 838c2ecf20Sopenharmony_ci}; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int bt3c_config(struct pcmcia_device *link); 878c2ecf20Sopenharmony_cistatic void bt3c_release(struct pcmcia_device *link); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic void bt3c_detach(struct pcmcia_device *p_dev); 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci/* Transmit states */ 938c2ecf20Sopenharmony_ci#define XMIT_SENDING 1 948c2ecf20Sopenharmony_ci#define XMIT_WAKEUP 2 958c2ecf20Sopenharmony_ci#define XMIT_WAITING 8 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci/* Receiver states */ 988c2ecf20Sopenharmony_ci#define RECV_WAIT_PACKET_TYPE 0 998c2ecf20Sopenharmony_ci#define RECV_WAIT_EVENT_HEADER 1 1008c2ecf20Sopenharmony_ci#define RECV_WAIT_ACL_HEADER 2 1018c2ecf20Sopenharmony_ci#define RECV_WAIT_SCO_HEADER 3 1028c2ecf20Sopenharmony_ci#define RECV_WAIT_DATA 4 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci/* ======================== Special I/O functions ======================== */ 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci#define DATA_L 0 1108c2ecf20Sopenharmony_ci#define DATA_H 1 1118c2ecf20Sopenharmony_ci#define ADDR_L 2 1128c2ecf20Sopenharmony_ci#define ADDR_H 3 1138c2ecf20Sopenharmony_ci#define CONTROL 4 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistatic inline void bt3c_address(unsigned int iobase, unsigned short addr) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci outb(addr & 0xff, iobase + ADDR_L); 1198c2ecf20Sopenharmony_ci outb((addr >> 8) & 0xff, iobase + ADDR_H); 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic inline void bt3c_put(unsigned int iobase, unsigned short value) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci outb(value & 0xff, iobase + DATA_L); 1268c2ecf20Sopenharmony_ci outb((value >> 8) & 0xff, iobase + DATA_H); 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic inline void bt3c_io_write(unsigned int iobase, unsigned short addr, unsigned short value) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci bt3c_address(iobase, addr); 1338c2ecf20Sopenharmony_ci bt3c_put(iobase, value); 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic inline unsigned short bt3c_get(unsigned int iobase) 1388c2ecf20Sopenharmony_ci{ 1398c2ecf20Sopenharmony_ci unsigned short value = inb(iobase + DATA_L); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci value |= inb(iobase + DATA_H) << 8; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci return value; 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_cistatic inline unsigned short bt3c_read(unsigned int iobase, unsigned short addr) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci bt3c_address(iobase, addr); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return bt3c_get(iobase); 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci/* ======================== Interrupt handling ======================== */ 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int bt3c_write(unsigned int iobase, int fifo_size, __u8 *buf, int len) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci int actual = 0; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci bt3c_address(iobase, 0x7080); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci /* Fill FIFO with current frame */ 1668c2ecf20Sopenharmony_ci while (actual < len) { 1678c2ecf20Sopenharmony_ci /* Transmit next byte */ 1688c2ecf20Sopenharmony_ci bt3c_put(iobase, buf[actual]); 1698c2ecf20Sopenharmony_ci actual++; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci bt3c_io_write(iobase, 0x7005, actual); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci return actual; 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic void bt3c_write_wakeup(struct bt3c_info *info) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci if (!info) { 1818c2ecf20Sopenharmony_ci BT_ERR("Unknown device"); 1828c2ecf20Sopenharmony_ci return; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) 1868c2ecf20Sopenharmony_ci return; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci do { 1898c2ecf20Sopenharmony_ci unsigned int iobase = info->p_dev->resource[0]->start; 1908c2ecf20Sopenharmony_ci register struct sk_buff *skb; 1918c2ecf20Sopenharmony_ci int len; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (!pcmcia_dev_present(info->p_dev)) 1948c2ecf20Sopenharmony_ci break; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci skb = skb_dequeue(&(info->txq)); 1978c2ecf20Sopenharmony_ci if (!skb) { 1988c2ecf20Sopenharmony_ci clear_bit(XMIT_SENDING, &(info->tx_state)); 1998c2ecf20Sopenharmony_ci break; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci /* Send frame */ 2038c2ecf20Sopenharmony_ci len = bt3c_write(iobase, 256, skb->data, skb->len); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci if (len != skb->len) 2068c2ecf20Sopenharmony_ci BT_ERR("Very strange"); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci kfree_skb(skb); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci info->hdev->stat.byte_tx += len; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci } while (0); 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic void bt3c_receive(struct bt3c_info *info) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci unsigned int iobase; 2198c2ecf20Sopenharmony_ci int size = 0, avail; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci if (!info) { 2228c2ecf20Sopenharmony_ci BT_ERR("Unknown device"); 2238c2ecf20Sopenharmony_ci return; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci iobase = info->p_dev->resource[0]->start; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci avail = bt3c_read(iobase, 0x7006); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci bt3c_address(iobase, 0x7480); 2318c2ecf20Sopenharmony_ci while (size < avail) { 2328c2ecf20Sopenharmony_ci size++; 2338c2ecf20Sopenharmony_ci info->hdev->stat.byte_rx++; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* Allocate packet */ 2368c2ecf20Sopenharmony_ci if (!info->rx_skb) { 2378c2ecf20Sopenharmony_ci info->rx_state = RECV_WAIT_PACKET_TYPE; 2388c2ecf20Sopenharmony_ci info->rx_count = 0; 2398c2ecf20Sopenharmony_ci info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); 2408c2ecf20Sopenharmony_ci if (!info->rx_skb) { 2418c2ecf20Sopenharmony_ci BT_ERR("Can't allocate mem for new packet"); 2428c2ecf20Sopenharmony_ci return; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci } 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci if (info->rx_state == RECV_WAIT_PACKET_TYPE) { 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci hci_skb_pkt_type(info->rx_skb) = inb(iobase + DATA_L); 2508c2ecf20Sopenharmony_ci inb(iobase + DATA_H); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci switch (hci_skb_pkt_type(info->rx_skb)) { 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci case HCI_EVENT_PKT: 2558c2ecf20Sopenharmony_ci info->rx_state = RECV_WAIT_EVENT_HEADER; 2568c2ecf20Sopenharmony_ci info->rx_count = HCI_EVENT_HDR_SIZE; 2578c2ecf20Sopenharmony_ci break; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci case HCI_ACLDATA_PKT: 2608c2ecf20Sopenharmony_ci info->rx_state = RECV_WAIT_ACL_HEADER; 2618c2ecf20Sopenharmony_ci info->rx_count = HCI_ACL_HDR_SIZE; 2628c2ecf20Sopenharmony_ci break; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci case HCI_SCODATA_PKT: 2658c2ecf20Sopenharmony_ci info->rx_state = RECV_WAIT_SCO_HEADER; 2668c2ecf20Sopenharmony_ci info->rx_count = HCI_SCO_HDR_SIZE; 2678c2ecf20Sopenharmony_ci break; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci default: 2708c2ecf20Sopenharmony_ci /* Unknown packet */ 2718c2ecf20Sopenharmony_ci BT_ERR("Unknown HCI packet with type 0x%02x received", 2728c2ecf20Sopenharmony_ci hci_skb_pkt_type(info->rx_skb)); 2738c2ecf20Sopenharmony_ci info->hdev->stat.err_rx++; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci kfree_skb(info->rx_skb); 2768c2ecf20Sopenharmony_ci info->rx_skb = NULL; 2778c2ecf20Sopenharmony_ci break; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci } 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci } else { 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci __u8 x = inb(iobase + DATA_L); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci skb_put_u8(info->rx_skb, x); 2868c2ecf20Sopenharmony_ci inb(iobase + DATA_H); 2878c2ecf20Sopenharmony_ci info->rx_count--; 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci if (info->rx_count == 0) { 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci int dlen; 2928c2ecf20Sopenharmony_ci struct hci_event_hdr *eh; 2938c2ecf20Sopenharmony_ci struct hci_acl_hdr *ah; 2948c2ecf20Sopenharmony_ci struct hci_sco_hdr *sh; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci switch (info->rx_state) { 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci case RECV_WAIT_EVENT_HEADER: 2998c2ecf20Sopenharmony_ci eh = hci_event_hdr(info->rx_skb); 3008c2ecf20Sopenharmony_ci info->rx_state = RECV_WAIT_DATA; 3018c2ecf20Sopenharmony_ci info->rx_count = eh->plen; 3028c2ecf20Sopenharmony_ci break; 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci case RECV_WAIT_ACL_HEADER: 3058c2ecf20Sopenharmony_ci ah = hci_acl_hdr(info->rx_skb); 3068c2ecf20Sopenharmony_ci dlen = __le16_to_cpu(ah->dlen); 3078c2ecf20Sopenharmony_ci info->rx_state = RECV_WAIT_DATA; 3088c2ecf20Sopenharmony_ci info->rx_count = dlen; 3098c2ecf20Sopenharmony_ci break; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci case RECV_WAIT_SCO_HEADER: 3128c2ecf20Sopenharmony_ci sh = hci_sco_hdr(info->rx_skb); 3138c2ecf20Sopenharmony_ci info->rx_state = RECV_WAIT_DATA; 3148c2ecf20Sopenharmony_ci info->rx_count = sh->dlen; 3158c2ecf20Sopenharmony_ci break; 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci case RECV_WAIT_DATA: 3188c2ecf20Sopenharmony_ci hci_recv_frame(info->hdev, info->rx_skb); 3198c2ecf20Sopenharmony_ci info->rx_skb = NULL; 3208c2ecf20Sopenharmony_ci break; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci } 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci } 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci } 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci } 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci bt3c_io_write(iobase, 0x7006, 0x0000); 3318c2ecf20Sopenharmony_ci} 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_cistatic irqreturn_t bt3c_interrupt(int irq, void *dev_inst) 3358c2ecf20Sopenharmony_ci{ 3368c2ecf20Sopenharmony_ci struct bt3c_info *info = dev_inst; 3378c2ecf20Sopenharmony_ci unsigned int iobase; 3388c2ecf20Sopenharmony_ci int iir; 3398c2ecf20Sopenharmony_ci irqreturn_t r = IRQ_NONE; 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci if (!info || !info->hdev) 3428c2ecf20Sopenharmony_ci /* our irq handler is shared */ 3438c2ecf20Sopenharmony_ci return IRQ_NONE; 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci iobase = info->p_dev->resource[0]->start; 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci spin_lock(&(info->lock)); 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci iir = inb(iobase + CONTROL); 3508c2ecf20Sopenharmony_ci if (iir & 0x80) { 3518c2ecf20Sopenharmony_ci int stat = bt3c_read(iobase, 0x7001); 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci if ((stat & 0xff) == 0x7f) { 3548c2ecf20Sopenharmony_ci BT_ERR("Very strange (stat=0x%04x)", stat); 3558c2ecf20Sopenharmony_ci } else if ((stat & 0xff) != 0xff) { 3568c2ecf20Sopenharmony_ci if (stat & 0x0020) { 3578c2ecf20Sopenharmony_ci int status = bt3c_read(iobase, 0x7002) & 0x10; 3588c2ecf20Sopenharmony_ci bt_dev_info(info->hdev, "Antenna %s", 3598c2ecf20Sopenharmony_ci status ? "out" : "in"); 3608c2ecf20Sopenharmony_ci } 3618c2ecf20Sopenharmony_ci if (stat & 0x0001) 3628c2ecf20Sopenharmony_ci bt3c_receive(info); 3638c2ecf20Sopenharmony_ci if (stat & 0x0002) { 3648c2ecf20Sopenharmony_ci clear_bit(XMIT_SENDING, &(info->tx_state)); 3658c2ecf20Sopenharmony_ci bt3c_write_wakeup(info); 3668c2ecf20Sopenharmony_ci } 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci bt3c_io_write(iobase, 0x7001, 0x0000); 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci outb(iir, iobase + CONTROL); 3718c2ecf20Sopenharmony_ci } 3728c2ecf20Sopenharmony_ci r = IRQ_HANDLED; 3738c2ecf20Sopenharmony_ci } 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci spin_unlock(&(info->lock)); 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci return r; 3788c2ecf20Sopenharmony_ci} 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci/* ======================== HCI interface ======================== */ 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_cistatic int bt3c_hci_flush(struct hci_dev *hdev) 3868c2ecf20Sopenharmony_ci{ 3878c2ecf20Sopenharmony_ci struct bt3c_info *info = hci_get_drvdata(hdev); 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci /* Drop TX queue */ 3908c2ecf20Sopenharmony_ci skb_queue_purge(&(info->txq)); 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci return 0; 3938c2ecf20Sopenharmony_ci} 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_cistatic int bt3c_hci_open(struct hci_dev *hdev) 3978c2ecf20Sopenharmony_ci{ 3988c2ecf20Sopenharmony_ci return 0; 3998c2ecf20Sopenharmony_ci} 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_cistatic int bt3c_hci_close(struct hci_dev *hdev) 4038c2ecf20Sopenharmony_ci{ 4048c2ecf20Sopenharmony_ci bt3c_hci_flush(hdev); 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci return 0; 4078c2ecf20Sopenharmony_ci} 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_cistatic int bt3c_hci_send_frame(struct hci_dev *hdev, struct sk_buff *skb) 4118c2ecf20Sopenharmony_ci{ 4128c2ecf20Sopenharmony_ci struct bt3c_info *info = hci_get_drvdata(hdev); 4138c2ecf20Sopenharmony_ci unsigned long flags; 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci switch (hci_skb_pkt_type(skb)) { 4168c2ecf20Sopenharmony_ci case HCI_COMMAND_PKT: 4178c2ecf20Sopenharmony_ci hdev->stat.cmd_tx++; 4188c2ecf20Sopenharmony_ci break; 4198c2ecf20Sopenharmony_ci case HCI_ACLDATA_PKT: 4208c2ecf20Sopenharmony_ci hdev->stat.acl_tx++; 4218c2ecf20Sopenharmony_ci break; 4228c2ecf20Sopenharmony_ci case HCI_SCODATA_PKT: 4238c2ecf20Sopenharmony_ci hdev->stat.sco_tx++; 4248c2ecf20Sopenharmony_ci break; 4258c2ecf20Sopenharmony_ci } 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci /* Prepend skb with frame type */ 4288c2ecf20Sopenharmony_ci memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1); 4298c2ecf20Sopenharmony_ci skb_queue_tail(&(info->txq), skb); 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci spin_lock_irqsave(&(info->lock), flags); 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci bt3c_write_wakeup(info); 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&(info->lock), flags); 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci return 0; 4388c2ecf20Sopenharmony_ci} 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci/* ======================== Card services HCI interaction ======================== */ 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_cistatic int bt3c_load_firmware(struct bt3c_info *info, 4468c2ecf20Sopenharmony_ci const unsigned char *firmware, 4478c2ecf20Sopenharmony_ci int count) 4488c2ecf20Sopenharmony_ci{ 4498c2ecf20Sopenharmony_ci char *ptr = (char *) firmware; 4508c2ecf20Sopenharmony_ci char b[9]; 4518c2ecf20Sopenharmony_ci unsigned int iobase, tmp, tn; 4528c2ecf20Sopenharmony_ci unsigned long size, addr, fcs; 4538c2ecf20Sopenharmony_ci int i, err = 0; 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci iobase = info->p_dev->resource[0]->start; 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci /* Reset */ 4588c2ecf20Sopenharmony_ci bt3c_io_write(iobase, 0x8040, 0x0404); 4598c2ecf20Sopenharmony_ci bt3c_io_write(iobase, 0x8040, 0x0400); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci udelay(1); 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci bt3c_io_write(iobase, 0x8040, 0x0404); 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci udelay(17); 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ci /* Load */ 4688c2ecf20Sopenharmony_ci while (count) { 4698c2ecf20Sopenharmony_ci if (ptr[0] != 'S') { 4708c2ecf20Sopenharmony_ci BT_ERR("Bad address in firmware"); 4718c2ecf20Sopenharmony_ci err = -EFAULT; 4728c2ecf20Sopenharmony_ci goto error; 4738c2ecf20Sopenharmony_ci } 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci memset(b, 0, sizeof(b)); 4768c2ecf20Sopenharmony_ci memcpy(b, ptr + 2, 2); 4778c2ecf20Sopenharmony_ci if (kstrtoul(b, 16, &size) < 0) 4788c2ecf20Sopenharmony_ci return -EINVAL; 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_ci memset(b, 0, sizeof(b)); 4818c2ecf20Sopenharmony_ci memcpy(b, ptr + 4, 8); 4828c2ecf20Sopenharmony_ci if (kstrtoul(b, 16, &addr) < 0) 4838c2ecf20Sopenharmony_ci return -EINVAL; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci memset(b, 0, sizeof(b)); 4868c2ecf20Sopenharmony_ci memcpy(b, ptr + (size * 2) + 2, 2); 4878c2ecf20Sopenharmony_ci if (kstrtoul(b, 16, &fcs) < 0) 4888c2ecf20Sopenharmony_ci return -EINVAL; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci memset(b, 0, sizeof(b)); 4918c2ecf20Sopenharmony_ci for (tmp = 0, i = 0; i < size; i++) { 4928c2ecf20Sopenharmony_ci memcpy(b, ptr + (i * 2) + 2, 2); 4938c2ecf20Sopenharmony_ci if (kstrtouint(b, 16, &tn)) 4948c2ecf20Sopenharmony_ci return -EINVAL; 4958c2ecf20Sopenharmony_ci tmp += tn; 4968c2ecf20Sopenharmony_ci } 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci if (((tmp + fcs) & 0xff) != 0xff) { 4998c2ecf20Sopenharmony_ci BT_ERR("Checksum error in firmware"); 5008c2ecf20Sopenharmony_ci err = -EILSEQ; 5018c2ecf20Sopenharmony_ci goto error; 5028c2ecf20Sopenharmony_ci } 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci if (ptr[1] == '3') { 5058c2ecf20Sopenharmony_ci bt3c_address(iobase, addr); 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci memset(b, 0, sizeof(b)); 5088c2ecf20Sopenharmony_ci for (i = 0; i < (size - 4) / 2; i++) { 5098c2ecf20Sopenharmony_ci memcpy(b, ptr + (i * 4) + 12, 4); 5108c2ecf20Sopenharmony_ci if (kstrtouint(b, 16, &tmp)) 5118c2ecf20Sopenharmony_ci return -EINVAL; 5128c2ecf20Sopenharmony_ci bt3c_put(iobase, tmp); 5138c2ecf20Sopenharmony_ci } 5148c2ecf20Sopenharmony_ci } 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_ci ptr += (size * 2) + 6; 5178c2ecf20Sopenharmony_ci count -= (size * 2) + 6; 5188c2ecf20Sopenharmony_ci } 5198c2ecf20Sopenharmony_ci 5208c2ecf20Sopenharmony_ci udelay(17); 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci /* Boot */ 5238c2ecf20Sopenharmony_ci bt3c_address(iobase, 0x3000); 5248c2ecf20Sopenharmony_ci outb(inb(iobase + CONTROL) | 0x40, iobase + CONTROL); 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_cierror: 5278c2ecf20Sopenharmony_ci udelay(17); 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci /* Clear */ 5308c2ecf20Sopenharmony_ci bt3c_io_write(iobase, 0x7006, 0x0000); 5318c2ecf20Sopenharmony_ci bt3c_io_write(iobase, 0x7005, 0x0000); 5328c2ecf20Sopenharmony_ci bt3c_io_write(iobase, 0x7001, 0x0000); 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci return err; 5358c2ecf20Sopenharmony_ci} 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_cistatic int bt3c_open(struct bt3c_info *info) 5398c2ecf20Sopenharmony_ci{ 5408c2ecf20Sopenharmony_ci const struct firmware *firmware; 5418c2ecf20Sopenharmony_ci struct hci_dev *hdev; 5428c2ecf20Sopenharmony_ci int err; 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci spin_lock_init(&(info->lock)); 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_ci skb_queue_head_init(&(info->txq)); 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_ci info->rx_state = RECV_WAIT_PACKET_TYPE; 5498c2ecf20Sopenharmony_ci info->rx_count = 0; 5508c2ecf20Sopenharmony_ci info->rx_skb = NULL; 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci /* Initialize HCI device */ 5538c2ecf20Sopenharmony_ci hdev = hci_alloc_dev(); 5548c2ecf20Sopenharmony_ci if (!hdev) { 5558c2ecf20Sopenharmony_ci BT_ERR("Can't allocate HCI device"); 5568c2ecf20Sopenharmony_ci return -ENOMEM; 5578c2ecf20Sopenharmony_ci } 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci info->hdev = hdev; 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci hdev->bus = HCI_PCCARD; 5628c2ecf20Sopenharmony_ci hci_set_drvdata(hdev, info); 5638c2ecf20Sopenharmony_ci SET_HCIDEV_DEV(hdev, &info->p_dev->dev); 5648c2ecf20Sopenharmony_ci 5658c2ecf20Sopenharmony_ci hdev->open = bt3c_hci_open; 5668c2ecf20Sopenharmony_ci hdev->close = bt3c_hci_close; 5678c2ecf20Sopenharmony_ci hdev->flush = bt3c_hci_flush; 5688c2ecf20Sopenharmony_ci hdev->send = bt3c_hci_send_frame; 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci /* Load firmware */ 5718c2ecf20Sopenharmony_ci err = request_firmware(&firmware, "BT3CPCC.bin", &info->p_dev->dev); 5728c2ecf20Sopenharmony_ci if (err < 0) { 5738c2ecf20Sopenharmony_ci BT_ERR("Firmware request failed"); 5748c2ecf20Sopenharmony_ci goto error; 5758c2ecf20Sopenharmony_ci } 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_ci err = bt3c_load_firmware(info, firmware->data, firmware->size); 5788c2ecf20Sopenharmony_ci 5798c2ecf20Sopenharmony_ci release_firmware(firmware); 5808c2ecf20Sopenharmony_ci 5818c2ecf20Sopenharmony_ci if (err < 0) { 5828c2ecf20Sopenharmony_ci BT_ERR("Firmware loading failed"); 5838c2ecf20Sopenharmony_ci goto error; 5848c2ecf20Sopenharmony_ci } 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_ci /* Timeout before it is safe to send the first HCI packet */ 5878c2ecf20Sopenharmony_ci msleep(1000); 5888c2ecf20Sopenharmony_ci 5898c2ecf20Sopenharmony_ci /* Register HCI device */ 5908c2ecf20Sopenharmony_ci err = hci_register_dev(hdev); 5918c2ecf20Sopenharmony_ci if (err < 0) { 5928c2ecf20Sopenharmony_ci BT_ERR("Can't register HCI device"); 5938c2ecf20Sopenharmony_ci goto error; 5948c2ecf20Sopenharmony_ci } 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_ci return 0; 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_cierror: 5998c2ecf20Sopenharmony_ci info->hdev = NULL; 6008c2ecf20Sopenharmony_ci hci_free_dev(hdev); 6018c2ecf20Sopenharmony_ci return err; 6028c2ecf20Sopenharmony_ci} 6038c2ecf20Sopenharmony_ci 6048c2ecf20Sopenharmony_ci 6058c2ecf20Sopenharmony_cistatic int bt3c_close(struct bt3c_info *info) 6068c2ecf20Sopenharmony_ci{ 6078c2ecf20Sopenharmony_ci struct hci_dev *hdev = info->hdev; 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci if (!hdev) 6108c2ecf20Sopenharmony_ci return -ENODEV; 6118c2ecf20Sopenharmony_ci 6128c2ecf20Sopenharmony_ci bt3c_hci_close(hdev); 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci hci_unregister_dev(hdev); 6158c2ecf20Sopenharmony_ci hci_free_dev(hdev); 6168c2ecf20Sopenharmony_ci 6178c2ecf20Sopenharmony_ci return 0; 6188c2ecf20Sopenharmony_ci} 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_cistatic int bt3c_probe(struct pcmcia_device *link) 6218c2ecf20Sopenharmony_ci{ 6228c2ecf20Sopenharmony_ci struct bt3c_info *info; 6238c2ecf20Sopenharmony_ci 6248c2ecf20Sopenharmony_ci /* Create new info device */ 6258c2ecf20Sopenharmony_ci info = devm_kzalloc(&link->dev, sizeof(*info), GFP_KERNEL); 6268c2ecf20Sopenharmony_ci if (!info) 6278c2ecf20Sopenharmony_ci return -ENOMEM; 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_ci info->p_dev = link; 6308c2ecf20Sopenharmony_ci link->priv = info; 6318c2ecf20Sopenharmony_ci 6328c2ecf20Sopenharmony_ci link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP | 6338c2ecf20Sopenharmony_ci CONF_AUTO_SET_IO; 6348c2ecf20Sopenharmony_ci 6358c2ecf20Sopenharmony_ci return bt3c_config(link); 6368c2ecf20Sopenharmony_ci} 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_ci 6398c2ecf20Sopenharmony_cistatic void bt3c_detach(struct pcmcia_device *link) 6408c2ecf20Sopenharmony_ci{ 6418c2ecf20Sopenharmony_ci bt3c_release(link); 6428c2ecf20Sopenharmony_ci} 6438c2ecf20Sopenharmony_ci 6448c2ecf20Sopenharmony_cistatic int bt3c_check_config(struct pcmcia_device *p_dev, void *priv_data) 6458c2ecf20Sopenharmony_ci{ 6468c2ecf20Sopenharmony_ci int *try = priv_data; 6478c2ecf20Sopenharmony_ci 6488c2ecf20Sopenharmony_ci if (!try) 6498c2ecf20Sopenharmony_ci p_dev->io_lines = 16; 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_ci if ((p_dev->resource[0]->end != 8) || (p_dev->resource[0]->start == 0)) 6528c2ecf20Sopenharmony_ci return -EINVAL; 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_ci p_dev->resource[0]->end = 8; 6558c2ecf20Sopenharmony_ci p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; 6568c2ecf20Sopenharmony_ci p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; 6578c2ecf20Sopenharmony_ci 6588c2ecf20Sopenharmony_ci return pcmcia_request_io(p_dev); 6598c2ecf20Sopenharmony_ci} 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_cistatic int bt3c_check_config_notpicky(struct pcmcia_device *p_dev, 6628c2ecf20Sopenharmony_ci void *priv_data) 6638c2ecf20Sopenharmony_ci{ 6648c2ecf20Sopenharmony_ci static unsigned int base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; 6658c2ecf20Sopenharmony_ci int j; 6668c2ecf20Sopenharmony_ci 6678c2ecf20Sopenharmony_ci if (p_dev->io_lines > 3) 6688c2ecf20Sopenharmony_ci return -ENODEV; 6698c2ecf20Sopenharmony_ci 6708c2ecf20Sopenharmony_ci p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; 6718c2ecf20Sopenharmony_ci p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; 6728c2ecf20Sopenharmony_ci p_dev->resource[0]->end = 8; 6738c2ecf20Sopenharmony_ci 6748c2ecf20Sopenharmony_ci for (j = 0; j < 5; j++) { 6758c2ecf20Sopenharmony_ci p_dev->resource[0]->start = base[j]; 6768c2ecf20Sopenharmony_ci p_dev->io_lines = base[j] ? 16 : 3; 6778c2ecf20Sopenharmony_ci if (!pcmcia_request_io(p_dev)) 6788c2ecf20Sopenharmony_ci return 0; 6798c2ecf20Sopenharmony_ci } 6808c2ecf20Sopenharmony_ci return -ENODEV; 6818c2ecf20Sopenharmony_ci} 6828c2ecf20Sopenharmony_ci 6838c2ecf20Sopenharmony_cistatic int bt3c_config(struct pcmcia_device *link) 6848c2ecf20Sopenharmony_ci{ 6858c2ecf20Sopenharmony_ci struct bt3c_info *info = link->priv; 6868c2ecf20Sopenharmony_ci int i; 6878c2ecf20Sopenharmony_ci unsigned long try; 6888c2ecf20Sopenharmony_ci 6898c2ecf20Sopenharmony_ci /* First pass: look for a config entry that looks normal. 6908c2ecf20Sopenharmony_ci * Two tries: without IO aliases, then with aliases 6918c2ecf20Sopenharmony_ci */ 6928c2ecf20Sopenharmony_ci for (try = 0; try < 2; try++) 6938c2ecf20Sopenharmony_ci if (!pcmcia_loop_config(link, bt3c_check_config, (void *) try)) 6948c2ecf20Sopenharmony_ci goto found_port; 6958c2ecf20Sopenharmony_ci 6968c2ecf20Sopenharmony_ci /* Second pass: try to find an entry that isn't picky about 6978c2ecf20Sopenharmony_ci * its base address, then try to grab any standard serial port 6988c2ecf20Sopenharmony_ci * address, and finally try to get any free port. 6998c2ecf20Sopenharmony_ci */ 7008c2ecf20Sopenharmony_ci if (!pcmcia_loop_config(link, bt3c_check_config_notpicky, NULL)) 7018c2ecf20Sopenharmony_ci goto found_port; 7028c2ecf20Sopenharmony_ci 7038c2ecf20Sopenharmony_ci BT_ERR("No usable port range found"); 7048c2ecf20Sopenharmony_ci goto failed; 7058c2ecf20Sopenharmony_ci 7068c2ecf20Sopenharmony_cifound_port: 7078c2ecf20Sopenharmony_ci i = pcmcia_request_irq(link, &bt3c_interrupt); 7088c2ecf20Sopenharmony_ci if (i != 0) 7098c2ecf20Sopenharmony_ci goto failed; 7108c2ecf20Sopenharmony_ci 7118c2ecf20Sopenharmony_ci i = pcmcia_enable_device(link); 7128c2ecf20Sopenharmony_ci if (i != 0) 7138c2ecf20Sopenharmony_ci goto failed; 7148c2ecf20Sopenharmony_ci 7158c2ecf20Sopenharmony_ci if (bt3c_open(info) != 0) 7168c2ecf20Sopenharmony_ci goto failed; 7178c2ecf20Sopenharmony_ci 7188c2ecf20Sopenharmony_ci return 0; 7198c2ecf20Sopenharmony_ci 7208c2ecf20Sopenharmony_cifailed: 7218c2ecf20Sopenharmony_ci bt3c_release(link); 7228c2ecf20Sopenharmony_ci return -ENODEV; 7238c2ecf20Sopenharmony_ci} 7248c2ecf20Sopenharmony_ci 7258c2ecf20Sopenharmony_ci 7268c2ecf20Sopenharmony_cistatic void bt3c_release(struct pcmcia_device *link) 7278c2ecf20Sopenharmony_ci{ 7288c2ecf20Sopenharmony_ci struct bt3c_info *info = link->priv; 7298c2ecf20Sopenharmony_ci 7308c2ecf20Sopenharmony_ci bt3c_close(info); 7318c2ecf20Sopenharmony_ci 7328c2ecf20Sopenharmony_ci pcmcia_disable_device(link); 7338c2ecf20Sopenharmony_ci} 7348c2ecf20Sopenharmony_ci 7358c2ecf20Sopenharmony_ci 7368c2ecf20Sopenharmony_cistatic const struct pcmcia_device_id bt3c_ids[] = { 7378c2ecf20Sopenharmony_ci PCMCIA_DEVICE_PROD_ID13("3COM", "Bluetooth PC Card", 0xefce0a31, 0xd4ce9b02), 7388c2ecf20Sopenharmony_ci PCMCIA_DEVICE_NULL 7398c2ecf20Sopenharmony_ci}; 7408c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pcmcia, bt3c_ids); 7418c2ecf20Sopenharmony_ci 7428c2ecf20Sopenharmony_cistatic struct pcmcia_driver bt3c_driver = { 7438c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 7448c2ecf20Sopenharmony_ci .name = "bt3c_cs", 7458c2ecf20Sopenharmony_ci .probe = bt3c_probe, 7468c2ecf20Sopenharmony_ci .remove = bt3c_detach, 7478c2ecf20Sopenharmony_ci .id_table = bt3c_ids, 7488c2ecf20Sopenharmony_ci}; 7498c2ecf20Sopenharmony_cimodule_pcmcia_driver(bt3c_driver); 750