Subject: misc/30036: device driver for 3Com Bluetooth PC Card
To: None <misc-bug-people@netbsd.org, gnats-admin@netbsd.org,>
From: Iain Hibbert <plunky@rya-online.net>
List: netbsd-bugs
Date: 04/22/2005 19:44:00
>Number:         30036
>Category:       misc
>Synopsis:       New device driver for 3Com Bluetooth PC Card, called bt3c
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    misc-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Fri Apr 22 19:44:00 +0000 2005
>Originator:     Iain Hibbert
>Release:        NetBSD 2.0
>Organization:
>Environment:
System: NetBSD galant 2.0 NetBSD 2.0 (GALANT) #12: Tue Mar 15 22:08:38 GMT 2005 plunky@galant:/home/plunky/src/sys/arch/i386/compile/GALANT i386
Architecture: i386
Machine: i386
>Description:
	Please find attached a device driver "bt3c" providing support for the
	3Com Bluetooth PC Card to the bthci device layer which is currently
	implemented as a character device.  The included patch is against
	NetBSD 2.0 source distribution, and creates or modifies the following
	files:

sys/dev/DEVNAMES			add the bt3c device name to the list
sys/dev/bluetooth/files.bluetooth	fix a wrongness
sys/dev/bluetooth/bluetooth.h		add some missing definitions
sys/dev/pcmcia/pcmciadevs		add the bt3c device
sys/dev/pcmcia/files.pcmcia		add the bt3c device
sys/dev/pcmcia/bt3cfw.h			include file for firmware loader (new)
sys/dev/pcmcia/bt3c.c			device driver (new)
sys/lkm/misc/bt3cpcc/Makefile		firmware loader Makefile (new)
sys/lkm/misc/bt3cpcc/bt3cpcc.awk	firmware conversion script (new)
sys/lkm/misc/bt3cpcc/lkminit_bt3cpcc.c	firmware loader	(new)
share/man/man4/bt3c.4			manual page for bt3c device (new)
share/man/man4/Makefile			update the Makefile

	I havent tested the patch against NetBSD-current but I think it should be Ok
	apart from hunk #2 which is already in.

	you will need to 'make -f Makefile.pcmciadevs' in sys/dev/pcmcia to update
	the pcmciadevs.h and pcmciadevs_data.h files.

>How-To-Repeat:
>Fix:
--- /usr/src/sys/dev/DEVNAMES	2004-08-30 10:46:47.000000000 +0100
+++ sys/dev/DEVNAMES	2005-03-08 16:44:33.000000000 +0000
@@ -131,6 +131,7 @@
 boca			MI		
 bonito			algor
 bpp			MI		
+bt3c			MI
 bt_dac			sparc		Attribute
 bt_dac			sparc64		Attribute
 btl			arc
--- /usr/src/sys/dev/bluetooth/files.bluetooth	2003-01-11 05:46:11.000000000 +0000
+++ sys/dev/bluetooth/files.bluetooth	2005-03-03 00:03:40.000000000 +0000
@@ -7,6 +7,6 @@
 device	bthci { }: bthcidrv
 attach	bthci at btbus
 
-file	dev/bluetooth/bluetooth.c	bthcidrv
+file	dev/bluetooth/bluetooth.c	btbus
 file	dev/bluetooth/bthci.c		bthcidrv		needs-flag
 file	dev/bluetooth/bthci_util.c	bthcidrv
--- /usr/src/sys/dev/bluetooth/bluetooth.h	2004-01-04 05:47:43.000000000 +0000
+++ sys/dev/bluetooth/bluetooth.h	2005-03-02 11:10:31.000000000 +0000
@@ -113,6 +113,12 @@
 #define BTHCI_ACL_DATA_LEN_OFFT		2
 #define BTHCI_ACL_DATA_LEN_LENGTH	2
 
+/* Maximum SCO data packet length, including header */
+#define BTHCI_SCO_DATA_MIN_LEN		3
+#define BTHCI_SCO_DATA_MAX_LEN		(0xff + BTHCI_SCO_DATA_MIN_LEN)
+#define BTHCI_SCO_DATA_LEN_OFFT		2
+#define BTHCI_SCO_DATA_LEN_LENGTH	1
+
 /* HCI consumer interface constants */
 #define BTHCI_PKTID_COMMAND		1
 #define BTHCI_PKTID_ACL_DATA		2
--- /usr/src/sys/dev/pcmcia/pcmciadevs	2004-02-01 12:35:59.000000000 +0000
+++ sys/dev/pcmcia/pcmciadevs	2005-02-10 18:09:28.000000000 +0000
@@ -121,6 +121,7 @@
 product 3COM 3CXM056BNW		0x002f 3Com/NoteWorthy 3CXM056-BNW 56K Modem
 product 3COM 3CXEM556		0x0035 3Com/Megahertz 3CXEM556 Ethernet/Modem
 product 3COM 3CXEM556INT	0x003d 3Com/Megahertz 3CXEM556-INT Ethernet/Modem
+product 3COM 3CRWB6096		0x0040 3Com Bluetooth PC Card
 product 3COM 3CCFEM556BI	0x0556 3Com/Megahertz 3CCFEM556BI Ethernet/Modem
 product 3COM 3C562		0x0562 3Com 3c562 33.6 Modem/10Mbps Ethernet
 product 3COM 3C589		0x0589 3Com 3c589 10Mbps Ethernet
--- /usr/src/sys/dev/pcmcia/files.pcmcia	2002-04-22 10:41:22.000000000 +0100
+++ sys/dev/pcmcia/files.pcmcia	2005-02-22 19:30:04.000000000 +0000
@@ -132,3 +132,7 @@
 attach	opl at esl with opl_esl
 file	dev/pcmcia/opl_esl.c			opl_esl
 
+# 3Com Bluetooth card 3CRW6096
+device	bt3c: btbus
+attach	bt3c at pcmcia
+file	dev/pcmcia/bt3c.c			bt3c
--- /dev/null	2005-04-22 10:38:10.000000000 +0100
+++ sys/dev/pcmcia/bt3cfw.h	2005-03-11 10:41:55.000000000 +0000
@@ -0,0 +1,37 @@
+/*	$NetBSD: bt3cfw.h$	*/
+
+/*
+ * Copyright (c) 2005 Iain D. Hibbert,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+struct bt3c_firmware_block {
+	u_int16_t addr;
+	u_int8_t len;
+	u_int16_t data[15];	/* 15 words max in each block */
+};
+
+extern struct bt3c_firmware_block *bt3c_firmware;
--- /dev/null	2005-04-22 10:38:10.000000000 +0100
+++ sys/dev/pcmcia/bt3c.c	2005-03-14 17:47:32.000000000 +0000
@@ -0,0 +1,925 @@
+/*	$NetBSD: bt3c.c$	*/
+
+/*
+ * Copyright (c) 2005 Iain D. Hibbert,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * Driver for the 3Com Bluetooth PC Card 3CRWB6096, written with reference to
+ *  FreeBSD and BlueZ drivers for same, with credit for those going to:
+ *
+ *		Maksim Yevmenkin <m_evmenkin@yahoo.com>		(FreeBSD)
+ *		Marcel Holtmann <marcel@holtmann.org>		(BlueZ)
+ *		Jose Orlando Pereira <jop@di.uminho.pt>		(BlueZ)
+ *		David Hinds <dahinds@users.sourceforge.net>	(Original Code)
+ */
+
+/*
+ * The CIS info from the card:
+ *
+ *	pcmcia1: CIS tuple chain:
+ *	CISTPL_DEVICE type=null speed=null
+ *	 01 03 00 00 ff
+ *	CISTPL_VERS_1
+ *	 15 24 05 00 33 43 4f 4d 00 33 43 52 57 42 36 30
+ *	 2d 41 00 42 6c 75 65 74 6f 6f 74 68 20 50 43 20
+ *	 43 61 72 64 00 ff
+ *	CISTPL_MANFID
+ *	 20 04 01 01 40 00
+ *	CISTPL_FUNCID
+ *	 21 02 02 01
+ *	CISTPL_CONFIG
+ *	 1a 06 05 30 20 03 17 00
+ *	CISTPL_CFTABLE_ENTRY
+ *	 1b 09 f0 41 18 a0 40 07 30 ff ff
+ *	unhandled CISTPL 80
+ *	 80 0a 02 01 40 00 2d 00 00 00 00 ff
+ *	CISTPL_NO_LINK
+ *	 14 00
+ *	CISTPL_END
+ *	 ff
+ *	pcmcia1: CIS version PC Card Standard 5.0
+ *	pcmcia1: CIS info: 3COM, 3CRWB60-A, Bluetooth PC Card
+ *	pcmcia1: Manufacturer code 0x101, product 0x40
+ *	pcmcia1: function 0: serial port, ccr addr 320 mask 17
+ *	pcmcia1: function 0, config table entry 48: I/O card; irq mask ffff; iomask 0, iospace 0-7; rdybsy_active io8 irqlevel
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: bt3c.c$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/mbuf.h>
+
+#include <machine/cpu.h>
+#include <machine/bus.h>
+#include <machine/intr.h>
+
+#include <dev/pcmcia/pcmciareg.h>
+#include <dev/pcmcia/pcmciavar.h>
+#include <dev/pcmcia/pcmciadevs.h>
+
+#include <dev/bluetooth/bluetooth.h>
+
+/**************************************************************************
+ *
+ * The firmware is loaded in a kernel module, see sys/lkm/misc/bt3cpcc
+ */
+
+#include <dev/pcmcia/bt3cfw.h>
+
+struct bt3c_firmware_block *bt3c_firmware = NULL;
+
+/**************************************************************************
+ *
+ *	bt3c autoconfig glue
+ */
+
+static int bt3c_match __P((struct device *, struct cfdata *, void *));
+static void bt3c_attach __P((struct device *, struct device *, void *));
+static int bt3c_detach __P((struct device *, int));
+static int bt3c_activate __P((struct device *, enum devact));
+static void bt3c_power __P((int, void *));
+
+struct bt3c_softc {
+	struct device sc_dev;			/* required */
+
+	/* PCMCIA specific */
+	struct pcmcia_function *sc_pf;		/* our PCMCIA function */
+	struct pcmcia_io_handle sc_pcioh;	/* PCMCIA i/o space info */
+	int sc_iow;				/* our i/o window */
+
+	/* the bthci layer */
+	struct device *sc_bthci;
+	struct btframe_callback_methods const *sc_cb;	
+
+	void *sc_hard;				/* hardware interrupt handler */
+	void *sc_soft;				/* soft interrupt handler */
+	u_int16_t sc_sw_isr;			/* soft interrupt status register */
+	u_int16_t sc_flags;			/* bt3c device flags */
+
+	int sc_state;				/* receive state */
+	int sc_want;				/* how many bytes we are expecting */
+	struct mbuf *sc_m;			/* current frame */
+	struct mbuf *sc_rxq;			/* received packets queue */
+	struct mbuf *sc_txq;			/* transmit queue */
+
+	void *sc_powerhook;			/* power hook descriptor */
+};
+
+/* sc_state */
+#define BT3C_RECV_PKT_ID	0		/* waiting for packet id */
+#define BT3C_RECV_ACL_HDR	1		/* waiting for acl header */
+#define BT3C_RECV_SCO_HDR	2		/* waiting for sco header */
+#define BT3C_RECV_EVENT_HDR	3		/* waiting for event header */
+#define BT3C_RECV_PKT_DATA	4		/* waiting for packet data */
+
+/* sc_flags */
+#define	BT3C_DYING		(1<<0)		/* we've been asked to die */
+#define BT3C_ANTENNA		(1<<1)		/* antenna position */
+#define BT3C_XMIT		(1<<2)		/* transmitting */
+#define BT3C_OPEN		(1<<3)		/* if we are enabled, for the powerhook */
+
+CFATTACH_DECL(bt3c, sizeof(struct bt3c_softc),
+    bt3c_match, bt3c_attach, bt3c_detach, bt3c_activate);
+
+
+/**************************************************************************
+ *
+ *	bthci callback methods
+ */
+
+static int bt3c_open __P((void *, int, int, struct proc *));
+static int bt3c_close __P((void *, int, int, struct proc *));
+static u_int8_t *bt3c_alloc_control __P((void *, size_t, struct btframe_buffer **));
+static u_int8_t *bt3c_alloc_acldata __P((void *, size_t, struct btframe_buffer **));
+static u_int8_t *bt3c_alloc_scodata __P((void *, size_t, struct btframe_buffer **));
+static int bt3c_send __P((void *, struct btframe_buffer *, size_t));
+static int bt3c_splraise __P((void));
+
+static struct btframe_methods const bt3c_methods = {
+	bt3c_open,		bt3c_close,
+	{bt3c_alloc_control,	bt3c_send},
+	{bt3c_alloc_acldata,	bt3c_send},
+	{bt3c_alloc_scodata,	bt3c_send},
+	bt3c_splraise
+};
+
+
+/**************************************************************************
+ *
+ *	Hardware Definitions & IO routines
+ *	I made up the names for most of these defs since we dont have
+ *	manufacturers recommendations, but I dont like raw numbers..
+ */
+#define BT3C_ISR		0x7001		/* Interrupt Status Register */
+#define BT3C_ISR_RXRDY			(1<<0)
+#define BT3C_ISR_TXRDY			(1<<1)
+#define BT3C_ISR_ANTENNA		(1<<5)
+
+#define BT3C_CSR		0x7002		/* Card Status Register */
+#define BT3C_CSR_ANTENNA		(1<<4)
+
+#define BT3C_TX_COUNT		0x7005		/* Tx fifo contents */
+#define BT3C_TX_FIFO		0x7080		/* Transmit Fifo */
+#define BT3C_RX_COUNT		0x7006		/* Rx fifo contents */
+#define BT3C_RX_FIFO		0x7480		/* Receive Fifo */
+#define BT3C_FIFO_SIZE			256
+
+/* IO Registers */
+#define BT3C_IOR_DATA_L		0x00		/* data low byte */
+#define BT3C_IOR_DATA_H		0x01		/* data high byte */
+#define BT3C_IOR_ADDR_L		0x02		/* address low byte */
+#define BT3C_IOR_ADDR_H		0x03		/* address high byte */
+#define BT3C_IOR_CNTL		0x04		/* control byte */
+#define BT3C_IOR_CNTL_BOOT		(1<<6)	/* Boot Card */
+#define BT3C_IOR_CNTL_INTR		(1<<7)	/* Interrupt Requested */
+#define BT3C_IOR_LEN		0x05
+
+static inline u_int16_t
+bt3c_get(struct bt3c_softc *sc)
+{
+u_int16_t data;
+
+	bus_space_barrier(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, 0, BT3C_IOR_LEN, BUS_SPACE_BARRIER_READ);
+	data = bus_space_read_1(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, BT3C_IOR_DATA_L);
+	data |= bus_space_read_1(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, BT3C_IOR_DATA_H) << 8;
+
+	return data;
+}
+
+static inline void
+bt3c_put(struct bt3c_softc *sc, u_int16_t data)	
+{
+	bus_space_barrier(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, 0, BT3C_IOR_LEN, BUS_SPACE_BARRIER_WRITE);
+	bus_space_write_1(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, BT3C_IOR_DATA_L, data & 0xff);
+	bus_space_write_1(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, BT3C_IOR_DATA_H, (data >> 8) & 0xff);
+}
+
+static inline u_int8_t
+bt3c_read_control(struct bt3c_softc *sc)
+{
+	bus_space_barrier(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, 0, BT3C_IOR_LEN, BUS_SPACE_BARRIER_READ);
+	return bus_space_read_1(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, BT3C_IOR_CNTL);
+}
+
+static inline void
+bt3c_write_control(struct bt3c_softc *sc, u_int8_t data)
+{
+	bus_space_barrier(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, 0, BT3C_IOR_LEN, BUS_SPACE_BARRIER_WRITE);
+	bus_space_write_1(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, BT3C_IOR_CNTL, data);
+}
+
+static inline void
+bt3c_set_address(struct bt3c_softc *sc, u_int16_t addr)	
+{
+	bus_space_barrier(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, 0, BT3C_IOR_LEN, BUS_SPACE_BARRIER_WRITE);
+	bus_space_write_1(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, BT3C_IOR_ADDR_L, addr & 0xff);
+	bus_space_write_1(sc->sc_pcioh.iot, sc->sc_pcioh.ioh, BT3C_IOR_ADDR_H, (addr >> 8) & 0xff);
+}
+
+static inline u_int16_t
+bt3c_read(struct bt3c_softc *sc, u_int16_t addr)
+{
+	bt3c_set_address(sc, addr);
+	return bt3c_get(sc);
+}
+
+static inline void
+bt3c_write(struct bt3c_softc *sc, u_int16_t addr, u_int16_t data)
+{
+	bt3c_set_address(sc, addr);
+	bt3c_put(sc, data);
+}
+
+/*
+ * receive incoming data from FIFO
+ * running at IPL_SERIAL
+ */
+static void
+bt3c_receive(struct bt3c_softc *sc)
+{
+	u_int16_t i, count;
+	u_int8_t data;
+
+	/* Receive data from the card */
+	count = bt3c_read(sc, BT3C_RX_COUNT);
+
+	bt3c_set_address(sc, BT3C_RX_FIFO);
+
+	for (i = 0 ; i < count ; i++) {
+		if (sc->sc_m == NULL) {		/* Allocate new mbuf if needed */
+			MGETHDR(sc->sc_m, M_DONTWAIT, MT_DATA);
+			if (sc->sc_m == NULL) {
+				printf("%s: Could not get mbuf\n", sc->sc_dev.dv_xname);
+				break;
+			}
+
+			MCLGET(sc->sc_m, M_DONTWAIT);
+			if (!(sc->sc_m->m_flags & M_EXT)) {
+				m_free(sc->sc_m);
+				sc->sc_m = NULL;
+				printf("%s: Could not get mbuf cluster\n", sc->sc_dev.dv_xname);
+				break;
+			}
+
+			sc->sc_m->m_len = sc->sc_m->m_pkthdr.len = 0;
+			sc->sc_state = BT3C_RECV_PKT_ID;
+			sc->sc_want = 1;	/* sizeof packet ID */
+		}
+
+		data = bt3c_get(sc);
+		if (sc->sc_m->m_len < MCLBYTES) {
+			/* store char in mbuf if we have room */
+			mtod(sc->sc_m, u_int8_t *)[sc->sc_m->m_len++] = data;
+			sc->sc_m->m_pkthdr.len ++;
+		}
+
+		sc->sc_want--;
+		if (sc->sc_want)
+			continue; /* wait for more */
+
+		switch (sc->sc_state) {
+		case BT3C_RECV_PKT_ID:		/* Got packet ID */
+
+			switch (*mtod(sc->sc_m, u_int8_t *)) {
+			case BTHCI_PKTID_ACL_DATA:
+				sc->sc_state = BT3C_RECV_ACL_HDR;
+				sc->sc_want = BTHCI_ACL_DATA_MIN_LEN;
+				break;
+
+			case BTHCI_PKTID_SCO_DATA:
+				sc->sc_state = BT3C_RECV_SCO_HDR;
+				sc->sc_want = BTHCI_SCO_DATA_MIN_LEN;
+				break;
+
+			case BTHCI_PKTID_EVENT:
+				sc->sc_state = BT3C_RECV_EVENT_HDR;
+				sc->sc_want = BTHCI_EVENT_MIN_LEN;
+				break;
+
+			default:
+				printf("%s: Unknown packet type=%#x! (ignoring)\n", sc->sc_dev.dv_xname, *mtod(sc->sc_m, u_int8_t *));
+
+				m_free(sc->sc_m);
+				sc->sc_m = NULL;
+				break;		/* XXX lost sync */
+			}
+
+			break;
+
+		case BT3C_RECV_ACL_HDR:		/* Got ACL Header */
+			sc->sc_state = BT3C_RECV_PKT_DATA;
+			sc->sc_want = BTGETW(mtod(sc->sc_m, u_int8_t *) + 1 + BTHCI_ACL_DATA_LEN_OFFT);
+			break;
+
+		case BT3C_RECV_SCO_HDR:		/* Got SCO Header */
+			sc->sc_state = BT3C_RECV_PKT_DATA;
+			sc->sc_want = mtod(sc->sc_m, u_int8_t *)[1 + BTHCI_SCO_DATA_LEN_OFFT];
+			break;
+
+		case BT3C_RECV_EVENT_HDR:	/* Got Event Header */
+			sc->sc_state = BT3C_RECV_PKT_DATA;
+			sc->sc_want = mtod(sc->sc_m, u_int8_t *)[1 + BTHCI_EVENT_LEN_OFFT];
+			break;
+
+		case BT3C_RECV_PKT_DATA:	/* Got packet data */
+			/* queue the packet */
+			/* XX should there be a limit to how many we queue? */
+			sc->sc_m->m_nextpkt = sc->sc_rxq;
+			sc->sc_rxq = sc->sc_m;
+			sc->sc_m = NULL;
+			break;
+
+		default:
+			panic("%s: invalid state %d!\n", sc->sc_dev.dv_xname, sc->sc_state);
+		}
+	}
+
+	bt3c_write(sc, BT3C_RX_COUNT, 0x0000);
+}
+
+/*
+ * transmit packet queue to device
+ * running at IPL_SERIAL
+ */
+static void
+bt3c_transmit(struct bt3c_softc *sc)
+{
+	struct mbuf *m = sc->sc_txq;
+	int count, rlen;
+	u_int8_t *rptr;
+
+	sc->sc_flags &= ~BT3C_XMIT;
+
+	if (m == NULL)
+		return;
+
+	bt3c_set_address(sc, BT3C_TX_FIFO);
+
+	count = rlen = 0;
+	rptr = mtod(m, u_int8_t *);
+	for(;;) {
+		if (rlen == m->m_len) {
+			sc->sc_txq = m->m_nextpkt;
+			m_free(m);
+			m = sc->sc_txq;
+			if (m) {
+				rlen = 0;
+				rptr = mtod(m, u_int8_t *);
+				continue;
+			} else {
+				break;
+			}
+		}
+		if (count == BT3C_FIFO_SIZE) {
+			m_adj(m, rlen);
+			break;
+		}
+		bt3c_put(sc, *rptr++);
+		rlen++;
+		count++;
+	}
+
+	if (count > 0) {
+		bt3c_write(sc, BT3C_TX_COUNT, count);
+		sc->sc_flags |= BT3C_XMIT;
+	}
+}
+
+/*
+ * hard interrupt routine
+ * running at IPL_SERIAL
+ */
+static int
+bt3c_hard_intr(void *arg)
+{
+	struct bt3c_softc *sc = arg;
+	u_int16_t control, isr;
+	int serviced = 0, needsoftint = 0;
+
+	control = bt3c_read_control(sc);
+	if (control & BT3C_IOR_CNTL_INTR) {
+		isr = bt3c_read(sc, BT3C_ISR);	/* fetch Interrupt Status Register */
+		if ((isr & 0xff) == 0x7f) {
+			printf("%s: bt3c_hard_intr got strange ISR=%04x\n", sc->sc_dev.dv_xname, isr);
+		} else if ((isr & 0xff) != 0xff) {
+
+			if (isr & BT3C_ISR_RXRDY) {		/* Receive complete */
+				bt3c_receive(sc);
+				if (sc->sc_rxq)
+					needsoftint = 1;
+			}
+
+			if (isr & BT3C_ISR_TXRDY) {		/* Transmit complete */
+				bt3c_transmit(sc);
+			}
+
+			if (isr & BT3C_ISR_ANTENNA) {		/* Antenna position changed */
+				if (bt3c_read(sc, BT3C_CSR) & BT3C_CSR_ANTENNA)
+					sc->sc_flags |= BT3C_ANTENNA;
+				else
+					sc->sc_flags &= ~BT3C_ANTENNA;
+				needsoftint = 1;
+			}
+
+			bt3c_write(sc, BT3C_ISR, 0x0000);	/* clear Interrupt Status */
+			bt3c_write_control(sc, control);	/* and let the card know */
+
+			/* record the reason and trigger the soft interrupt */
+			if (needsoftint) {
+				sc->sc_sw_isr |= isr;
+				softintr_schedule(sc->sc_soft);
+			}
+			serviced = 1;
+		}
+	}
+
+	return(serviced);
+}
+
+/*
+ * forward a (queue of) packet(s) to the HCI layer.
+ * running at IPL_SOFTSERIAL
+ */
+static void
+bt3c_forward(struct bt3c_softc *sc, struct mbuf *pkt)
+{
+	u_int8_t *buf;
+
+	/* list is backward, 'nextpkt' gets sent first */
+	if (pkt->m_nextpkt)
+		bt3c_forward(sc, pkt->m_nextpkt);
+
+	buf = mtod(pkt, u_int8_t *);
+	switch (buf[0]) {
+	case BTHCI_PKTID_ACL_DATA:
+		sc->sc_cb->bt_recvacldata(sc->sc_bthci, buf + 1, pkt->m_len - 1);
+		break;
+	case BTHCI_PKTID_SCO_DATA:
+		sc->sc_cb->bt_recvscodata(sc->sc_bthci, buf + 1, pkt->m_len - 1);
+		break;
+	case BTHCI_PKTID_EVENT:
+		sc->sc_cb->bt_recveventdata(sc->sc_bthci, buf + 1, pkt->m_len - 1);
+		break;
+	}
+	m_free(pkt);
+}
+
+/* soft interrupt routine
+ * running at IPL_SOFTSERIAL
+ */
+static void
+bt3c_soft_intr(void *arg)
+{
+	struct bt3c_softc *sc = arg;
+	u_int16_t isr;
+	int s;
+
+	s = splserial();
+	isr = sc->sc_sw_isr;
+	sc->sc_sw_isr = 0;
+	splx(s);
+
+	if (isr & BT3C_ISR_RXRDY) {	/* Receive complete */
+		struct mbuf *rxq;
+
+		s = splserial();
+		rxq = sc->sc_rxq;	/* empty the packet queue */
+		sc->sc_rxq = NULL;
+		splx(s);
+
+		if (rxq) bt3c_forward(sc, rxq);
+	}
+
+	if (isr & BT3C_ISR_TXRDY) {	/* Transmit complete */
+	}
+
+	if (isr & BT3C_ISR_ANTENNA) {	/* Antenna position changed */
+		printf("%s: Antenna %s\n", sc->sc_dev.dv_xname,
+					   (sc->sc_flags & BT3C_ANTENNA) ? "Out" : "In");
+	}
+}
+
+/*
+ * write firmware blocks to the device
+ */
+static void
+bt3c_write_firmware(struct bt3c_softc *sc)
+{
+	struct bt3c_firmware_block *b = bt3c_firmware;
+	int i;
+
+	/* Reset */
+	bt3c_write(sc, 0x8040, 0x0404);
+	bt3c_write(sc, 0x8040, 0x0400);
+	DELAY(1);
+	bt3c_write(sc, 0x8040, 0x0404);
+	DELAY(17);
+
+	/* write the firmware */
+	while (b->len) {
+		bt3c_set_address(sc, b->addr);
+		for (i = 0 ; i < b->len ; i++)
+			bt3c_put(sc, b->data[i]);
+		b++;
+	}
+	DELAY(17);
+
+	/* Boot */
+	bt3c_set_address(sc, 0x3000);
+	bt3c_write_control(sc, (bt3c_read_control(sc) | BT3C_IOR_CNTL_BOOT));
+	DELAY(17);
+
+	/* Clear Registers */
+	bt3c_write(sc, BT3C_RX_COUNT, 0x0000);
+	bt3c_write(sc, BT3C_TX_COUNT, 0x0000);
+	bt3c_write(sc, BT3C_ISR, 0x0000);
+	DELAY(1000);
+}
+
+
+/**************************************************************************
+ *
+ *	bthci callback routines
+ *		see dev/bluetooth/bluetooth.h
+ */
+
+static int
+bt3c_open(h, flag, mode, p)
+	void *h;
+	int flag;
+	int mode;
+	struct proc *p;
+{
+	struct bt3c_softc *sc = h;
+	int err;
+
+	if (bt3c_firmware == NULL) {
+		printf("%s: no firmware\n", sc->sc_dev.dv_xname);
+		err = ENXIO;
+		goto bad;
+	}
+
+	if (pcmcia_function_enable(sc->sc_pf)) {
+		err = EIO;
+		goto bad;
+	}
+
+	bt3c_write_firmware(sc);
+
+	sc->sc_hard = pcmcia_intr_establish(sc->sc_pf, IPL_SERIAL, bt3c_hard_intr, sc);
+	if (sc->sc_hard == NULL) {
+		err = EIO;
+		goto bad1;
+	}
+
+	sc->sc_soft = softintr_establish(IPL_SOFTSERIAL, bt3c_soft_intr, sc);
+	if (sc->sc_soft == NULL) {
+		err = EIO;
+		goto bad2;
+	}
+
+	sc->sc_flags |= BT3C_OPEN;
+	return 0; /* ok */
+
+bad2:
+	pcmcia_intr_disestablish(sc->sc_pf, sc->sc_hard);
+	sc->sc_hard = NULL;
+bad1:
+	pcmcia_function_disable(sc->sc_pf);
+bad:
+	return err;
+}
+
+static int
+bt3c_close(h, flag, mode, p)
+	void *h;
+	int flag;
+	int mode;
+	struct proc *p;
+{
+	struct bt3c_softc *sc = h;
+
+	if (sc->sc_hard) {
+		pcmcia_intr_disestablish(sc->sc_pf, sc->sc_hard);
+		sc->sc_hard = NULL;
+	}
+
+	if (sc->sc_soft) {
+		softintr_disestablish(sc->sc_soft);
+		sc->sc_soft = NULL;
+	}
+
+	sc->sc_flags &= ~BT3C_OPEN;
+
+	pcmcia_function_disable(sc->sc_pf);
+
+	return 0;
+}
+
+static u_int8_t *
+bt3c_alloc_control(h, len, buf)
+	void *h;
+	size_t len;
+	struct btframe_buffer **buf;
+{
+//	struct bt3c_softc *sc = h;
+	u_int8_t *p;
+
+	MALLOC(p, u_int8_t *, len + 1, M_TEMP, M_NOWAIT);
+	if (p) {
+		*p++ = BTHCI_PKTID_COMMAND;
+		*buf = (struct btframe_buffer *)p;
+	}
+
+	return p;
+}
+
+static u_int8_t *
+bt3c_alloc_acldata(h, len, buf)
+	void *h;
+	size_t len;
+	struct btframe_buffer **buf;
+{
+//	struct bt3c_softc *sc = h;
+	u_int8_t *p;
+
+	MALLOC(p, u_int8_t *, len + 1, M_TEMP, M_NOWAIT);
+	if (p) {
+		*p++ = BTHCI_PKTID_ACL_DATA;
+		*buf = (struct btframe_buffer *)p;
+	}
+
+	return p;
+}
+
+static u_int8_t *
+bt3c_alloc_scodata(h, len, buf)
+	void *h;
+	size_t len;
+	struct btframe_buffer **buf;
+{
+//	struct bt3c_softc *sc = h;
+	u_int8_t *p;
+
+	MALLOC(p, u_int8_t *, len + 1, M_TEMP, M_NOWAIT);
+	if (p) {
+		*p++ = BTHCI_PKTID_SCO_DATA;
+		*buf = (struct btframe_buffer *)p;
+	}
+
+	return p;
+}
+
+static int
+bt3c_send(h, buf, len)
+	void *h;
+	struct btframe_buffer *buf;
+	size_t len;
+{
+	struct bt3c_softc *sc = h;
+	u_int8_t *b = (u_int8_t *)buf;
+	struct mbuf **txq, *m;
+	int s;
+
+	if (sc->sc_flags & BT3C_DYING)
+		return(EIO);
+
+	/*
+	 * allocate an mbuf, using our pre-allocated btframe_buffer for data area
+	 */
+	MGETHDR(m, M_DONTWAIT, MT_DATA);
+	if (m == NULL)
+		return ENOMEM;
+
+	MEXTADD(m, b - 1, len + 1, M_TEMP, NULL, NULL);
+	m->m_len = m->m_pkthdr.len = len + 1;
+
+	/*
+	 * tag it on the end of the queue, and trigger
+	 * a transmit if necessary
+	 */
+	s = splserial();
+	txq = &sc->sc_txq;
+	while (*txq)
+		txq = &(*txq)->m_nextpkt;
+
+	*txq = m;
+
+	if ((sc->sc_flags & BT3C_XMIT) == 0)
+		bt3c_transmit(sc);
+	splx(s);
+
+	return 0;
+}
+
+static int
+bt3c_splraise(void)
+{
+	/* to block callbacks on this device (in bt3c_forward) */
+	return splsoftserial();
+}
+
+
+/**************************************************************************
+ *
+ *	bt3c PCMCIA autoconfig glue
+ */
+
+static int
+bt3c_match(parent, match, aux)
+	struct device *parent;
+	struct cfdata *match;
+	void *aux;
+{
+	struct pcmcia_attach_args *pa = aux;
+
+	if (pa->manufacturer == PCMCIA_VENDOR_3COM &&
+	    pa->product == PCMCIA_PRODUCT_3COM_3CRWB6096)
+	    return 10;		/* 'com' also claims this, so trump them */
+
+	return 0;
+}
+
+static void
+bt3c_attach(parent, self, aux)
+	struct device *parent;
+	struct device *self;
+	void *aux;
+{
+	struct bt3c_softc *sc = (struct bt3c_softc *)self;
+	struct pcmcia_attach_args *pa = aux;
+	struct pcmcia_config_entry *cfe;
+	struct bt_attach_args bt;
+	char devinfo[256];
+
+	sc->sc_pf = pa->pf;
+
+	pcmcia_devinfo(&pa->pf->sc->card, 0, devinfo, sizeof(devinfo));
+	printf(": %s\n", devinfo);
+
+	/* Find a PCMCIA config entry we can use */
+	SIMPLEQ_FOREACH(cfe, &pa->pf->cfe_head, cfe_list) {
+		if (cfe->num_memspace != 0) 
+			continue;
+
+		if (cfe->num_iospace != 1)
+			continue;
+
+		if (pcmcia_io_alloc(pa->pf, cfe->iospace[0].start, cfe->iospace[0].length, 0, &sc->sc_pcioh) == 0)
+			break;
+	}
+
+	if (cfe == 0) {
+		printf("bt3c_attach: cannot allocate io space\n");
+		goto no_config_entry;
+	}
+
+	/* Map in the io space */
+	if (pcmcia_io_map(pa->pf, PCMCIA_WIDTH_AUTO, 0, sc->sc_pcioh.size, &sc->sc_pcioh, &sc->sc_iow)) {
+		printf("bt3c_attach: cannot map io space\n");
+		goto iomap_failed;
+	}
+
+	/* Enable the card */
+	pcmcia_function_init(pa->pf, cfe);
+	if (pcmcia_function_enable(pa->pf)) {
+		printf("bt3c_attach: function enable failed\n");
+		goto enable_failed;
+	}
+
+	/* configure the bthci device */
+	bt.bt_methods = &bt3c_methods;
+	bt.bt_cb = &sc->sc_cb;
+	bt.bt_handle = sc;
+	sc->sc_bthci = config_found_sm(self, &bt, bt_print, NULL);
+
+	/* establish a power change hook */
+	sc->sc_powerhook = powerhook_establish(bt3c_power, sc);
+
+	/* and shut down the device for now */
+	pcmcia_function_disable(pa->pf);
+	return;
+
+enable_failed:
+	/* unmap io window */
+	pcmcia_io_unmap(pa->pf, sc->sc_iow);
+
+iomap_failed:
+	/* unmap io space */
+	pcmcia_io_free(pa->pf, &sc->sc_pcioh);
+
+no_config_entry:
+	sc->sc_iow = -1;
+}
+
+static int
+bt3c_detach(self, flags)
+	struct device *self;
+	int flags;
+{
+	struct bt3c_softc *sc = (struct bt3c_softc *)self;
+
+	sc->sc_flags |= BT3C_DYING;
+
+	if (sc->sc_hard)
+		pcmcia_intr_disestablish(sc->sc_pf, sc->sc_hard);
+
+	if (sc->sc_soft)
+		softintr_disestablish(sc->sc_soft);
+
+	if (sc->sc_powerhook)
+		powerhook_disestablish(sc->sc_powerhook);
+
+	if (sc->sc_bthci)
+		config_detach(sc->sc_bthci, flags);
+
+	if (sc->sc_iow != -1) {
+		pcmcia_io_unmap(sc->sc_pf, sc->sc_iow);
+		pcmcia_io_free(sc->sc_pf, &sc->sc_pcioh);
+	}
+
+	return (0);
+}
+
+static int
+bt3c_activate(self, act)
+	struct device *self;
+	enum devact act;
+{
+	struct bt3c_softc *sc = (struct bt3c_softc *)self;
+	int rv = 0;
+
+	switch(act) {
+	case DVACT_ACTIVATE:
+		rv = EOPNOTSUPP;
+		printf("bt3c_activate: activate\n");
+		break;
+
+	case DVACT_DEACTIVATE:
+		sc->sc_flags |= BT3C_DYING;
+		if (sc->sc_bthci)
+			rv = config_deactivate(sc->sc_bthci);
+		break;	
+	}
+
+	return(rv);
+}
+
+static void
+bt3c_power(why, arg)
+	int why;
+	void *arg;
+{
+	struct bt3c_softc *sc = arg;
+
+	switch(why) {
+	case PWR_SUSPEND:
+	case PWR_STANDBY:
+		if (sc->sc_flags & BT3C_OPEN) {
+			printf("%s: sleeping\n", sc->sc_dev.dv_xname);
+			pcmcia_function_disable(sc->sc_pf);
+		}
+		break;
+
+	case PWR_RESUME:
+		if (sc->sc_flags & BT3C_OPEN) {
+			printf("%s: waking up\n", sc->sc_dev.dv_xname);
+			pcmcia_function_enable(sc->sc_pf);
+			bt3c_write_firmware(sc);
+		}
+		break;
+
+	case PWR_SOFTSUSPEND:
+	case PWR_SOFTSTANDBY:
+	case PWR_SOFTRESUME:
+		break;
+	}
+}
--- /dev/null	2005-04-22 10:38:10.000000000 +0100
+++ sys/lkm/misc/bt3cpcc/Makefile	2005-03-11 10:57:56.000000000 +0000
@@ -0,0 +1,37 @@
+#	$NetBSD: Makefile$
+#
+# The firmware for the 3COM Bluetooth PC Card cannot be distributed with
+# the driver for copyright reasons. The user is assumed to use the one
+# delivered with the card on CD, or download the newest version from
+#	http://www.3com.com
+#
+# Use the command
+#
+#	make BT3CPCC=<path to firmware file>
+#
+# to generate a kernel module containing the firmware which you can then
+# load for the device driver to find.
+#
+# If the BT3CPCC.bin file does not exist, the Makefile will generate
+# a empty byte array and the module will load but the device wont
+# actually work
+
+BT3CPCC?=	BT3CPCC.bin
+
+.NOPATH: ${BT3CPCC}
+
+.if !exists(${BT3CPCC})
+BT3CPCC=	/dev/null
+.endif
+
+KMOD=		bt3cpcc
+SRCS=		lkminit_bt3cpcc.c
+CLEANFILES+=	bt3cpcc.h
+
+bt3cpcc.h:	${BT3CPCC}
+	@rm -f bt3cpcc.h
+	${AWK} -f ./bt3cpcc.awk ${BT3CPCC}
+
+lkminit_bt3cpcc.c: bt3cpcc.h
+
+.include <bsd.kmod.mk>
--- /dev/null	2005-04-22 10:38:10.000000000 +0100
+++ sys/lkm/misc/bt3cpcc/bt3cpcc.awk	2005-03-11 11:07:25.000000000 +0000
@@ -0,0 +1,73 @@
+#! /usr/bin/awk -f
+#
+# Copyright (c) 2005 Iain D. Hibbert,
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. The name of the author may not be used to endorse or promote products
+#    derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#
+# Convert the 3Com Bluetooth PC Card firmware file into C source to
+# compile into our module. The file is ASCII and lines are of the
+# form:
+#
+#	S<type><num><addr><word1><word2> ... <wordN><cksum>
+#
+
+function hexdigit(c) {
+	return  index("0123456789ABCDEF", toupper(c)) - 1
+}
+
+function hexbyte(s, i) {
+	return (hexdigit(substr(s, i, 1)) * 16) + hexdigit(substr(s, i + 1, 1))
+}
+
+BEGIN {
+	header = "bt3cpcc.h"
+	printf "static struct bt3c_firmware_block bt3cpcc[] = {\n" > header
+}
+
+substr($0, 0, 2) == "S3" {
+	sum = 0
+	for( i = 3 ; i < length($0) ; i = i + 2 )
+		sum = (sum + hexbyte($0, i)) % 256
+	if( sum != 255 ) {
+		printf "ERROR checksum 0x%x invalid\n", sum
+		exit 1
+	}
+
+	len = (hexbyte($0, 3) - 5) / 2
+	if( len > 15 ) {
+		printf "ERROR block length %d too large\n", len
+		exit 1
+	}
+	printf "\t{ 0x%s, %2d, ", substr($0, 9, 4), len > header
+	for( i = 0 ; i < len ; i++ ) {
+		printf "%s 0x%s", (i == 0 ? "{" : ","), substr($0, 13 + 4 * i, 4) > header
+	}
+	printf " } },\n" > header
+}
+
+END {
+	printf "\t{ 0x0000,  0 }\n" > header
+	printf "};\n" > header
+};
--- /dev/null	2005-04-22 10:38:10.000000000 +0100
+++ sys/lkm/misc/bt3cpcc/lkminit_bt3cpcc.c	2005-03-11 10:44:22.000000000 +0000
@@ -0,0 +1,104 @@
+/*	$NetBSD: lkminit_bt3cpcc.c$	*/
+
+/*
+ * Copyright (c) 2005 Iain D. Hibbert,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * The firmware for the 3COM Bluetooth PC Card cannot be distributed with
+ * NetBSD for copyright reasons. The user is assumed to use the one
+ * delivered with the card on CD, or download the newest version from
+ *	http://www.3com.com
+ *
+ * use 'make BT3CPCC=<path to firmware file>' to generate a kernel module
+ * containing the firmware which you can then load for the device driver
+ * to find. If the firmware file does not exist, an empty byte array will
+ * be generated.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: lkminit_bt3cpcc.c$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/lkm.h>
+#include <sys/errno.h>
+
+#include <dev/pcmcia/bt3cfw.h>
+#include "bt3cpcc.h"
+
+int bt3cpcc_lkmentry __P((struct lkm_table *lkmtp, int, int));
+static int bt3cpcc_handle __P((struct lkm_table *, int));
+
+MOD_MISC("bt3cpcc");
+
+static int
+bt3cpcc_handle(lkmtp, cmd)
+	struct lkm_table *lkmtp;
+	int cmd;
+{
+	int err = 0;
+
+	switch(cmd) {
+	case LKM_E_LOAD:
+
+		/*
+		 * We only need to patch our firmware block list into
+		 * the pointer defined with the device driver.
+		 * Don't do it twice.
+		 */
+		if (bt3c_firmware)
+			err = EEXIST;
+		else
+			bt3c_firmware = bt3cpcc;
+		break;
+
+	case LKM_E_UNLOAD:
+
+		bt3c_firmware = NULL;
+		break;
+
+	default:	/* we only understand load/unload */
+
+		err = EINVAL;
+		break;
+	}
+
+	return(err);
+}
+
+
+/*
+ * External entry point
+ */
+int
+bt3cpcc_lkmentry(lkmtp, cmd, ver)
+	struct lkm_table *lkmtp;	
+	int cmd, ver;
+{
+	DISPATCH(lkmtp, cmd, ver, bt3cpcc_handle, bt3cpcc_handle, lkm_nofunc)
+}
--- /dev/null	2005-04-22 10:38:10.000000000 +0100
+++ share/man/man4/bt3c.4	2005-03-16 12:58:32.000000000 +0000
@@ -0,0 +1,76 @@
+.\" $NetBSD: bt3c.4 $
+.\"
+.\" Copyright (c) 2005 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Iain Hibbert.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\"    must display the following acknowledgement:
+.\"        This product includes software developed by the NetBSD
+.\"        Foundation, Inc. and its contributors.
+.\" 4. Neither the name of The NetBSD Foundation nor the names of its
+.\"    contributors may be used to endorse or promote products derived
+.\"    from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd Feb 28, 2005
+.Dt BT3C 4
+.Os
+.Sh NAME
+.Nm bt3c
+.Nd 3Com Bluetooth PC Card
+.Sh SYNOPSIS
+.Cd "bt3c*  at pcmcia? function ?
+.Cd "bthci* at bt3c?
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the 3Com Bluetooth PC Card, model 3CRWB6096, to
+the bthci device layer.
+.Sh FIRMWARE
+This card needs firmware loaded before it will work. Due to copyright restrictions
+we cannot distribute the firmware with NetBSD, but if you have the card then you
+should have received a CD with the drivers on, or you may download the latest
+version from the 3Com website.  Extract the archive and find the firmware file
+called
+.Nm "BT3CPCC.bin" .
+Now execute the command:
+.Bd -literal -offset indent
+make BT3CPCC=<path to firmware file>
+.Ed
+.Pp
+in the
+.Nm "sys/lkm/misc/bt3cpcc"
+directory to generate a kernel module which will attach the firmware to the
+device driver.
+.Sh SEE ALSO
+.Xr bthci 4 ,
+.Xr pcmcia 4 ,
+.Xr lkm 4
+.Sh HISTORY
+This
+.Nm
+device driver was written by Iain D. Hibbert, with generous reference to the
+FreeBSD and BlueZ drivers for the same card.
+.Sh BUGS
--- /usr/src/share/man/man4/Makefile	2004-08-30 10:46:53.000000000 +0100
+++ share/man/man4/Makefile	2005-03-01 20:50:00.000000000 +0000
@@ -75,7 +75,7 @@
 	wds.4 we.4 wss.4 wt.4
 
 # machine-independent PCMCIA devices
-MAN+=	pcic.4 tcic.4 pcmcom.4 xi.4
+MAN+=	bt3c.4 pcic.4 tcic.4 pcmcom.4 xi.4
 
 # machine-independent obio (mac68k and macppc) devices
 MAN+=	adb.4 akbd.4 ams.4 mc.4