xref: /freebsd/sys/dev/firewire/fwisound.c (revision dd56711e83533ffe40f3f54b01565537f85381cc)
1cd7b6508SAbdelkader Boudih /*
2cd7b6508SAbdelkader Boudih  * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
3cd7b6508SAbdelkader Boudih  *
4cd7b6508SAbdelkader Boudih  * SPDX-License-Identifier: BSD-2-Clause
5cd7b6508SAbdelkader Boudih  */
6cd7b6508SAbdelkader Boudih 
7cd7b6508SAbdelkader Boudih /*
8cd7b6508SAbdelkader Boudih  * fwisound(4) - Apple FireWire audio driver
9cd7b6508SAbdelkader Boudih  *
10cd7b6508SAbdelkader Boudih  * Protocol reverse-engineered by Clemens Ladisch (Linux isight_audio).
11cd7b6508SAbdelkader Boudih  */
12cd7b6508SAbdelkader Boudih 
13cd7b6508SAbdelkader Boudih #include <sys/param.h>
14cd7b6508SAbdelkader Boudih #include <sys/systm.h>
15cd7b6508SAbdelkader Boudih #include <sys/module.h>
16cd7b6508SAbdelkader Boudih #include <sys/bus.h>
17cd7b6508SAbdelkader Boudih #include <sys/kernel.h>
18cd7b6508SAbdelkader Boudih #include <sys/malloc.h>
19cd7b6508SAbdelkader Boudih #include <sys/lock.h>
20cd7b6508SAbdelkader Boudih #include <sys/mutex.h>
21cd7b6508SAbdelkader Boudih #include <sys/sysctl.h>
22cd7b6508SAbdelkader Boudih #include <sys/taskqueue.h>
23cd7b6508SAbdelkader Boudih #include <sys/mbuf.h>
24cd7b6508SAbdelkader Boudih 
25cd7b6508SAbdelkader Boudih #include <dev/firewire/firewire.h>
26cd7b6508SAbdelkader Boudih #include <dev/firewire/firewirereg.h>
27cd7b6508SAbdelkader Boudih #include <dev/firewire/iec13213.h>
28cd7b6508SAbdelkader Boudih #include <dev/firewire/fwisound.h>
29cd7b6508SAbdelkader Boudih #include <dev/firewire/fw_helpers.h>
30cd7b6508SAbdelkader Boudih 
31cd7b6508SAbdelkader Boudih static MALLOC_DEFINE(M_FWISOUND, "fwisound", "Apple FireWire Audio");
32cd7b6508SAbdelkader Boudih 
33cd7b6508SAbdelkader Boudih static int debug = 0;
34cd7b6508SAbdelkader Boudih static int iso_channel = FWISOUND_ISO_CHANNEL;
35cd7b6508SAbdelkader Boudih SYSCTL_DECL(_hw_firewire);
36cd7b6508SAbdelkader Boudih static SYSCTL_NODE(_hw_firewire, OID_AUTO, fwisound,
37cd7b6508SAbdelkader Boudih     CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Apple FireWire Audio");
38cd7b6508SAbdelkader Boudih SYSCTL_INT(_hw_firewire_fwisound, OID_AUTO, debug, CTLFLAG_RWTUN, &debug, 0,
39cd7b6508SAbdelkader Boudih     "fwisound debug level");
40cd7b6508SAbdelkader Boudih SYSCTL_INT(_hw_firewire_fwisound, OID_AUTO, iso_channel, CTLFLAG_RWTUN,
41cd7b6508SAbdelkader Boudih     &iso_channel, 0, "ISO channel for isochronous receive (default 1)");
42cd7b6508SAbdelkader Boudih 
43cd7b6508SAbdelkader Boudih #define FWISOUND_DEBUG(lev, fmt, ...)					\
44cd7b6508SAbdelkader Boudih 	do {								\
45cd7b6508SAbdelkader Boudih 		if (debug >= (lev))					\
46cd7b6508SAbdelkader Boudih 			printf("fwisound: " fmt, ## __VA_ARGS__);	\
47cd7b6508SAbdelkader Boudih 	} while (0)
48cd7b6508SAbdelkader Boudih 
49cd7b6508SAbdelkader Boudih static int	fwisound_probe(device_t);
50cd7b6508SAbdelkader Boudih static int	fwisound_attach(device_t);
51cd7b6508SAbdelkader Boudih static int	fwisound_detach(device_t);
52cd7b6508SAbdelkader Boudih static void	fwisound_probe_task(void *, int);
53cd7b6508SAbdelkader Boudih static void	fwisound_start_task(void *, int);
54cd7b6508SAbdelkader Boudih static void	fwisound_stop_task(void *, int);
55cd7b6508SAbdelkader Boudih static void	fwisound_iso_input(struct fw_xferq *);
56cd7b6508SAbdelkader Boudih 
57cd7b6508SAbdelkader Boudih static int
fwisound_read_quadlet(struct fwisound_softc * sc,uint32_t offset,uint32_t * val)58cd7b6508SAbdelkader Boudih fwisound_read_quadlet(struct fwisound_softc *sc, uint32_t offset, uint32_t *val)
59cd7b6508SAbdelkader Boudih {
60cd7b6508SAbdelkader Boudih 	uint16_t dst;
61cd7b6508SAbdelkader Boudih 	uint8_t spd;
62cd7b6508SAbdelkader Boudih 	int err;
63cd7b6508SAbdelkader Boudih 
64cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
65cd7b6508SAbdelkader Boudih 	if (sc->fwdev == NULL) {
66cd7b6508SAbdelkader Boudih 		FWISOUND_UNLOCK(sc);
67cd7b6508SAbdelkader Boudih 		return (ENXIO);
68cd7b6508SAbdelkader Boudih 	}
69cd7b6508SAbdelkader Boudih 	dst = FWLOCALBUS | sc->fwdev->dst;
70cd7b6508SAbdelkader Boudih 	spd = min(sc->fwdev->speed, FWSPD_S400);
71cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
72cd7b6508SAbdelkader Boudih 
73cd7b6508SAbdelkader Boudih 	err = fw_read_quadlet(sc->fd.fc, M_FWISOUND, dst, spd,
74cd7b6508SAbdelkader Boudih 	    sc->cmd_hi, sc->cmd_lo + offset, val);
75cd7b6508SAbdelkader Boudih 	if (err)
76cd7b6508SAbdelkader Boudih 		FWISOUND_DEBUG(1, "read_quadlet: offset=0x%x err=%d\n",
77cd7b6508SAbdelkader Boudih 		    offset, err);
78cd7b6508SAbdelkader Boudih 	return (err);
79cd7b6508SAbdelkader Boudih }
80cd7b6508SAbdelkader Boudih 
81cd7b6508SAbdelkader Boudih static int
fwisound_write_quadlet(struct fwisound_softc * sc,uint32_t offset,uint32_t val)82cd7b6508SAbdelkader Boudih fwisound_write_quadlet(struct fwisound_softc *sc, uint32_t offset, uint32_t val)
83cd7b6508SAbdelkader Boudih {
84cd7b6508SAbdelkader Boudih 	uint16_t dst;
85cd7b6508SAbdelkader Boudih 	uint8_t spd;
86cd7b6508SAbdelkader Boudih 	int err;
87cd7b6508SAbdelkader Boudih 
88cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
89cd7b6508SAbdelkader Boudih 	if (sc->fwdev == NULL) {
90cd7b6508SAbdelkader Boudih 		FWISOUND_UNLOCK(sc);
91cd7b6508SAbdelkader Boudih 		return (ENXIO);
92cd7b6508SAbdelkader Boudih 	}
93cd7b6508SAbdelkader Boudih 	dst = FWLOCALBUS | sc->fwdev->dst;
94cd7b6508SAbdelkader Boudih 	spd = min(sc->fwdev->speed, FWSPD_S400);
95cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
96cd7b6508SAbdelkader Boudih 
97cd7b6508SAbdelkader Boudih 	err = fw_write_quadlet(sc->fd.fc, M_FWISOUND, dst, spd,
98cd7b6508SAbdelkader Boudih 	    sc->cmd_hi, sc->cmd_lo + offset, val);
99cd7b6508SAbdelkader Boudih 	if (err)
100cd7b6508SAbdelkader Boudih 		FWISOUND_DEBUG(1, "write_quadlet: offset=0x%x err=%d\n",
101cd7b6508SAbdelkader Boudih 		    offset, err);
102cd7b6508SAbdelkader Boudih 	return (err);
103cd7b6508SAbdelkader Boudih }
104cd7b6508SAbdelkader Boudih 
105c402d2e4SAbdelkader Boudih /*
106c402d2e4SAbdelkader Boudih  * Extract the audio command base register (key 0x40) from the unit directory.
107c402d2e4SAbdelkader Boudih  */
108cd7b6508SAbdelkader Boudih static uint32_t
fwisound_find_audio_base(struct fw_unit * unit)109c402d2e4SAbdelkader Boudih fwisound_find_audio_base(struct fw_unit *unit)
110cd7b6508SAbdelkader Boudih {
111c402d2e4SAbdelkader Boudih 	struct fw_device *fwdev = unit->fwdev;
112c402d2e4SAbdelkader Boudih 	struct csrdirectory *udir;
113c402d2e4SAbdelkader Boudih 	struct csrreg *reg;
114c402d2e4SAbdelkader Boudih 	uint32_t udir_qoff, udir_len, rom_quads;
115c402d2e4SAbdelkader Boudih 	int i;
116cd7b6508SAbdelkader Boudih 
117c402d2e4SAbdelkader Boudih 	rom_quads = CSRROMSIZE / 4;
118c402d2e4SAbdelkader Boudih 	udir_qoff = unit->dir_offset / 4;
119c402d2e4SAbdelkader Boudih 	if (udir_qoff >= rom_quads)
120cd7b6508SAbdelkader Boudih 		return (0);
121cd7b6508SAbdelkader Boudih 
122c402d2e4SAbdelkader Boudih 	udir = (struct csrdirectory *)&fwdev->csrrom[udir_qoff];
123c402d2e4SAbdelkader Boudih 	udir_len = udir->crc_len;
124c402d2e4SAbdelkader Boudih 	if (udir_qoff + 1 + udir_len > rom_quads)
125c402d2e4SAbdelkader Boudih 		return (0);
126d3259935SAbdelkader Boudih 	if (!crom_crc_valid((uint32_t *)&udir->entry[0], udir_len, udir->crc)) {
127d3259935SAbdelkader Boudih 		if (firewire_debug)
128d3259935SAbdelkader Boudih 			printf("fwisound: bad CRC in unit directory at 0x%x\n",
129d3259935SAbdelkader Boudih 			    udir_qoff);
130d3259935SAbdelkader Boudih 	}
131cd7b6508SAbdelkader Boudih 
132c402d2e4SAbdelkader Boudih 	for (i = 0; i < (int)udir_len; i++) {
133c402d2e4SAbdelkader Boudih 		if (udir_qoff + 1 + i >= rom_quads)
134c402d2e4SAbdelkader Boudih 			break;
135c402d2e4SAbdelkader Boudih 		reg = &udir->entry[i];
136c402d2e4SAbdelkader Boudih 		if (reg->key == IIDC_CROM_CMD_BASE && reg->val != 0)
137c402d2e4SAbdelkader Boudih 			return (reg->val);
138cd7b6508SAbdelkader Boudih 	}
139cd7b6508SAbdelkader Boudih 	return (0);
140cd7b6508SAbdelkader Boudih }
141cd7b6508SAbdelkader Boudih 
142cd7b6508SAbdelkader Boudih static void
fwisound_probe_task(void * arg,int pending __unused)143cd7b6508SAbdelkader Boudih fwisound_probe_task(void *arg, int pending __unused)
144cd7b6508SAbdelkader Boudih {
145cd7b6508SAbdelkader Boudih 	struct fwisound_softc *sc = (struct fwisound_softc *)arg;
146cd7b6508SAbdelkader Boudih 	uint32_t val;
147cd7b6508SAbdelkader Boudih 	int err;
148cd7b6508SAbdelkader Boudih 
149cd7b6508SAbdelkader Boudih 	if (sc->state == FWISOUND_STATE_DETACHING || sc->fwdev == NULL)
150cd7b6508SAbdelkader Boudih 		return;
151cd7b6508SAbdelkader Boudih 
152cd7b6508SAbdelkader Boudih 	err = fwisound_write_quadlet(sc, FWISOUND_REG_SAMPLE_RATE,
153cd7b6508SAbdelkader Boudih 	    FWISOUND_RATE_48000);
154cd7b6508SAbdelkader Boudih 	if (err) {
155cd7b6508SAbdelkader Boudih 		device_printf(sc->dev,
156cd7b6508SAbdelkader Boudih 		    "failed to set sample rate: %d\n", err);
157cd7b6508SAbdelkader Boudih 		return;
158cd7b6508SAbdelkader Boudih 	}
159cd7b6508SAbdelkader Boudih 
160cd7b6508SAbdelkader Boudih 	if (fwisound_read_quadlet(sc, FWISOUND_REG_GAIN_RAW_START, &val) == 0)
161cd7b6508SAbdelkader Boudih 		sc->gain_raw_min = (int32_t)val;
162cd7b6508SAbdelkader Boudih 	if (fwisound_read_quadlet(sc, FWISOUND_REG_GAIN_RAW_END, &val) == 0)
163cd7b6508SAbdelkader Boudih 		sc->gain_raw_max = (int32_t)val;
164cd7b6508SAbdelkader Boudih 
165cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
166cd7b6508SAbdelkader Boudih 	if (sc->state == FWISOUND_STATE_DETACHING) {
167cd7b6508SAbdelkader Boudih 		FWISOUND_UNLOCK(sc);
168cd7b6508SAbdelkader Boudih 		return;
169cd7b6508SAbdelkader Boudih 	}
170cd7b6508SAbdelkader Boudih 	sc->state = FWISOUND_STATE_PROBED;
171cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
172cd7b6508SAbdelkader Boudih }
173cd7b6508SAbdelkader Boudih 
174cd7b6508SAbdelkader Boudih static void
fwisound_start_task(void * arg,int pending __unused)175cd7b6508SAbdelkader Boudih fwisound_start_task(void *arg, int pending __unused)
176cd7b6508SAbdelkader Boudih {
177cd7b6508SAbdelkader Boudih 	struct fwisound_softc *sc = (struct fwisound_softc *)arg;
178cd7b6508SAbdelkader Boudih 
179cd7b6508SAbdelkader Boudih 	if (sc->state == FWISOUND_STATE_DETACHING)
180cd7b6508SAbdelkader Boudih 		return;
181cd7b6508SAbdelkader Boudih 
182cd7b6508SAbdelkader Boudih 	fwisound_iso_start(sc);
183cd7b6508SAbdelkader Boudih }
184cd7b6508SAbdelkader Boudih 
185cd7b6508SAbdelkader Boudih static void
fwisound_stop_task(void * arg,int pending __unused)186cd7b6508SAbdelkader Boudih fwisound_stop_task(void *arg, int pending __unused)
187cd7b6508SAbdelkader Boudih {
188cd7b6508SAbdelkader Boudih 	struct fwisound_softc *sc = (struct fwisound_softc *)arg;
189cd7b6508SAbdelkader Boudih 
190cd7b6508SAbdelkader Boudih 	fwisound_iso_stop(sc);
191cd7b6508SAbdelkader Boudih 
192cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
193cd7b6508SAbdelkader Boudih 	if (sc->state != FWISOUND_STATE_DETACHING)
194cd7b6508SAbdelkader Boudih 		sc->state = FWISOUND_STATE_PROBED;
195cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
196cd7b6508SAbdelkader Boudih }
197cd7b6508SAbdelkader Boudih 
198cd7b6508SAbdelkader Boudih int
fwisound_iso_start(struct fwisound_softc * sc)199cd7b6508SAbdelkader Boudih fwisound_iso_start(struct fwisound_softc *sc)
200cd7b6508SAbdelkader Boudih {
201cd7b6508SAbdelkader Boudih 	struct firewire_comm *fc = sc->fd.fc;
202cd7b6508SAbdelkader Boudih 	struct fw_xferq *xferq;
203cd7b6508SAbdelkader Boudih 	uint32_t val;
204cd7b6508SAbdelkader Boudih 	uint8_t speed;
205cd7b6508SAbdelkader Boudih 	int dma_ch, err;
206cd7b6508SAbdelkader Boudih 
207cd7b6508SAbdelkader Boudih 	mtx_assert(&sc->mtx, MA_NOTOWNED);
208cd7b6508SAbdelkader Boudih 
209cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
210cd7b6508SAbdelkader Boudih 	if (sc->dma_ch >= 0 || sc->state == FWISOUND_STATE_STREAMING) {
211cd7b6508SAbdelkader Boudih 		FWISOUND_UNLOCK(sc);
212cd7b6508SAbdelkader Boudih 		return (0);
213cd7b6508SAbdelkader Boudih 	}
214cd7b6508SAbdelkader Boudih 	if (sc->state == FWISOUND_STATE_DETACHING || sc->fwdev == NULL) {
215cd7b6508SAbdelkader Boudih 		FWISOUND_UNLOCK(sc);
216cd7b6508SAbdelkader Boudih 		return (ENXIO);
217cd7b6508SAbdelkader Boudih 	}
218cd7b6508SAbdelkader Boudih 	speed = min(sc->fwdev->speed, FWSPD_S400);
219cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
220cd7b6508SAbdelkader Boudih 
221cd7b6508SAbdelkader Boudih 	dma_ch = fw_open_isodma(fc, 0);
222cd7b6508SAbdelkader Boudih 	if (dma_ch < 0) {
223cd7b6508SAbdelkader Boudih 		device_printf(sc->dev, "no IR DMA channel available\n");
224cd7b6508SAbdelkader Boudih 		return (EBUSY);
225cd7b6508SAbdelkader Boudih 	}
226cd7b6508SAbdelkader Boudih 
227cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
228cd7b6508SAbdelkader Boudih 	if (sc->dma_ch >= 0) {
229cd7b6508SAbdelkader Boudih 		FWISOUND_UNLOCK(sc);
230cd7b6508SAbdelkader Boudih 		fc->ir[dma_ch]->flag &= ~FWXFERQ_OPEN;
231cd7b6508SAbdelkader Boudih 		return (0);
232cd7b6508SAbdelkader Boudih 	}
233cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
234cd7b6508SAbdelkader Boudih 
235cd7b6508SAbdelkader Boudih 	xferq = fc->ir[dma_ch];
236cd7b6508SAbdelkader Boudih 	xferq->flag |= FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_STREAM;
237cd7b6508SAbdelkader Boudih 
238cd7b6508SAbdelkader Boudih 	xferq->flag &= ~FWXFERQ_CHTAGMASK;
239cd7b6508SAbdelkader Boudih 	xferq->flag |= sc->iso_channel & FWXFERQ_CHTAGMASK;
240cd7b6508SAbdelkader Boudih 
241cd7b6508SAbdelkader Boudih 	xferq->sc = (caddr_t)sc;
242cd7b6508SAbdelkader Boudih 	xferq->hand = fwisound_iso_input;
243cd7b6508SAbdelkader Boudih 	xferq->bnchunk = FWISOUND_ISO_NCHUNK;
244cd7b6508SAbdelkader Boudih 	xferq->bnpacket = 1;
245cd7b6508SAbdelkader Boudih 	xferq->psize = FWISOUND_ISO_PKTSIZE;
246cd7b6508SAbdelkader Boudih 	xferq->queued = 0;
247cd7b6508SAbdelkader Boudih 	xferq->buf = NULL;
248cd7b6508SAbdelkader Boudih 
249cd7b6508SAbdelkader Boudih 	xferq->bulkxfer = malloc(
250cd7b6508SAbdelkader Boudih 	    sizeof(struct fw_bulkxfer) * xferq->bnchunk,
251cd7b6508SAbdelkader Boudih 	    M_FWISOUND, M_WAITOK | M_ZERO);
252cd7b6508SAbdelkader Boudih 
253cd7b6508SAbdelkader Boudih 	fw_iso_init_chunks(xferq);
254cd7b6508SAbdelkader Boudih 
255cd7b6508SAbdelkader Boudih 	sc->sample_total = 0;
256cd7b6508SAbdelkader Boudih 
257cd7b6508SAbdelkader Boudih 	val = sc->iso_channel | ((uint32_t)speed << 16);
258cd7b6508SAbdelkader Boudih 	err = fwisound_write_quadlet(sc, FWISOUND_REG_ISO_TX_CONFIG, val);
259cd7b6508SAbdelkader Boudih 	if (err) {
260cd7b6508SAbdelkader Boudih 		device_printf(sc->dev,
261cd7b6508SAbdelkader Boudih 		    "failed to set ISO_TX_CONFIG: %d\n", err);
262cd7b6508SAbdelkader Boudih 		goto fail;
263cd7b6508SAbdelkader Boudih 	}
264cd7b6508SAbdelkader Boudih 
265cd7b6508SAbdelkader Boudih 	err = fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE,
266cd7b6508SAbdelkader Boudih 	    FWISOUND_AUDIO_ENABLE);
267cd7b6508SAbdelkader Boudih 	if (err) {
268cd7b6508SAbdelkader Boudih 		device_printf(sc->dev,
269cd7b6508SAbdelkader Boudih 		    "failed to enable audio: %d\n", err);
270cd7b6508SAbdelkader Boudih 		goto fail;
271cd7b6508SAbdelkader Boudih 	}
272cd7b6508SAbdelkader Boudih 
273cd7b6508SAbdelkader Boudih 	err = fc->irx_enable(fc, dma_ch);
274cd7b6508SAbdelkader Boudih 	if (err) {
275cd7b6508SAbdelkader Boudih 		device_printf(sc->dev, "failed to start IR DMA: %d\n", err);
276cd7b6508SAbdelkader Boudih 		fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, 0);
277cd7b6508SAbdelkader Boudih 		goto fail;
278cd7b6508SAbdelkader Boudih 	}
279cd7b6508SAbdelkader Boudih 
280cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
281cd7b6508SAbdelkader Boudih 	if (sc->state == FWISOUND_STATE_DETACHING) {
282cd7b6508SAbdelkader Boudih 		FWISOUND_UNLOCK(sc);
283cd7b6508SAbdelkader Boudih 		fc->irx_disable(fc, dma_ch);
284cd7b6508SAbdelkader Boudih 		fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, 0);
285cd7b6508SAbdelkader Boudih 		err = ENXIO;
286cd7b6508SAbdelkader Boudih 		goto fail;
287cd7b6508SAbdelkader Boudih 	}
288cd7b6508SAbdelkader Boudih 	sc->dma_ch = dma_ch;
289cd7b6508SAbdelkader Boudih 	sc->state = FWISOUND_STATE_STREAMING;
290cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
291cd7b6508SAbdelkader Boudih 
292cd7b6508SAbdelkader Boudih 	return (0);
293cd7b6508SAbdelkader Boudih 
294cd7b6508SAbdelkader Boudih fail:
295cd7b6508SAbdelkader Boudih 	fw_iso_free_chunks(xferq, M_FWISOUND);
296cd7b6508SAbdelkader Boudih 	xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_OPEN | FWXFERQ_STREAM |
297cd7b6508SAbdelkader Boudih 	    FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK);
298cd7b6508SAbdelkader Boudih 	xferq->hand = NULL;
299cd7b6508SAbdelkader Boudih 	sc->dma_ch = -1;
300cd7b6508SAbdelkader Boudih 	return (err);
301cd7b6508SAbdelkader Boudih }
302cd7b6508SAbdelkader Boudih 
303cd7b6508SAbdelkader Boudih void
fwisound_iso_stop(struct fwisound_softc * sc)304cd7b6508SAbdelkader Boudih fwisound_iso_stop(struct fwisound_softc *sc)
305cd7b6508SAbdelkader Boudih {
306cd7b6508SAbdelkader Boudih 	struct firewire_comm *fc = sc->fd.fc;
307cd7b6508SAbdelkader Boudih 	struct fw_xferq *xferq;
308cd7b6508SAbdelkader Boudih 	int dma_ch;
309cd7b6508SAbdelkader Boudih 
310cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
311cd7b6508SAbdelkader Boudih 	dma_ch = sc->dma_ch;
312cd7b6508SAbdelkader Boudih 	if (dma_ch < 0) {
313cd7b6508SAbdelkader Boudih 		FWISOUND_UNLOCK(sc);
314cd7b6508SAbdelkader Boudih 		return;
315cd7b6508SAbdelkader Boudih 	}
316cd7b6508SAbdelkader Boudih 	sc->dma_ch = -1;
317cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
318cd7b6508SAbdelkader Boudih 
319cd7b6508SAbdelkader Boudih 	xferq = fc->ir[dma_ch];
320cd7b6508SAbdelkader Boudih 
321cd7b6508SAbdelkader Boudih 	xferq->flag &= ~FWXFERQ_OPEN;
322cd7b6508SAbdelkader Boudih 	xferq->hand = NULL;
323cd7b6508SAbdelkader Boudih 
324cd7b6508SAbdelkader Boudih 	if (xferq->flag & FWXFERQ_RUNNING)
325cd7b6508SAbdelkader Boudih 		fc->irx_disable(fc, dma_ch);
326cd7b6508SAbdelkader Boudih 
327cd7b6508SAbdelkader Boudih 	if (sc->fwdev != NULL)
328cd7b6508SAbdelkader Boudih 		fwisound_write_quadlet(sc, FWISOUND_REG_AUDIO_ENABLE, 0);
329cd7b6508SAbdelkader Boudih 
330cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
331cd7b6508SAbdelkader Boudih 	fw_iso_wait_inactive_locked(&sc->mtx, &sc->iso_active, "fwisis");
332cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
333cd7b6508SAbdelkader Boudih 
334cd7b6508SAbdelkader Boudih 	xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_STREAM |
335cd7b6508SAbdelkader Boudih 	    FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK);
336cd7b6508SAbdelkader Boudih 
337cd7b6508SAbdelkader Boudih 	fw_iso_free_chunks(xferq, M_FWISOUND);
338cd7b6508SAbdelkader Boudih }
339cd7b6508SAbdelkader Boudih 
340cd7b6508SAbdelkader Boudih static void
fwisound_iso_input(struct fw_xferq * xferq)341cd7b6508SAbdelkader Boudih fwisound_iso_input(struct fw_xferq *xferq)
342cd7b6508SAbdelkader Boudih {
343cd7b6508SAbdelkader Boudih 	struct fwisound_softc *sc = (struct fwisound_softc *)xferq->sc;
344cd7b6508SAbdelkader Boudih 	struct fwisound_chan *chan;
345cd7b6508SAbdelkader Boudih 	struct fw_bulkxfer *sxfer;
346cd7b6508SAbdelkader Boudih 	struct fw_pkt *fp;
347cd7b6508SAbdelkader Boudih 	struct fwisound_payload *pay;
348cd7b6508SAbdelkader Boudih 	struct mbuf *m;
349cd7b6508SAbdelkader Boudih 	uint8_t *ring;
350cd7b6508SAbdelkader Boudih 	uint32_t plen, nbytes, sample_count, ring_size, pos, chunk, old_ptr;
351cd7b6508SAbdelkader Boudih 	int dma_ch, need_intr;
352cd7b6508SAbdelkader Boudih 
353cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
354cd7b6508SAbdelkader Boudih 	dma_ch = sc->dma_ch;
355cd7b6508SAbdelkader Boudih 	if (dma_ch < 0) {
356cd7b6508SAbdelkader Boudih 		FWISOUND_UNLOCK(sc);
357cd7b6508SAbdelkader Boudih 		return;
358cd7b6508SAbdelkader Boudih 	}
359cd7b6508SAbdelkader Boudih 	sc->iso_active = 1;
360cd7b6508SAbdelkader Boudih 	chan = sc->chan;
361cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
362cd7b6508SAbdelkader Boudih 
363cd7b6508SAbdelkader Boudih 	while ((sxfer = STAILQ_FIRST(&xferq->stvalid)) != NULL) {
364cd7b6508SAbdelkader Boudih 		STAILQ_REMOVE_HEAD(&xferq->stvalid, link);
365cd7b6508SAbdelkader Boudih 
366cd7b6508SAbdelkader Boudih 		m = fw_iso_dequeue(xferq, sxfer, sc->fd.fc);
367cd7b6508SAbdelkader Boudih 		if (m == NULL)
368cd7b6508SAbdelkader Boudih 			continue;
369cd7b6508SAbdelkader Boudih 
370cd7b6508SAbdelkader Boudih 		fp = mtod(m, struct fw_pkt *);
371cd7b6508SAbdelkader Boudih 		plen = fp->mode.stream.len;
372cd7b6508SAbdelkader Boudih 		if (plen < 20 || (uint32_t)m->m_len < 4 + plen) {
373cd7b6508SAbdelkader Boudih 			m_freem(m);
374cd7b6508SAbdelkader Boudih 			continue;
375cd7b6508SAbdelkader Boudih 		}
376cd7b6508SAbdelkader Boudih 
377cd7b6508SAbdelkader Boudih 		pay = (struct fwisound_payload *)(mtod(m, uint8_t *) + 4);
378cd7b6508SAbdelkader Boudih 		if (ntohl(pay->signature) != FWISOUND_SIGNATURE) {
379cd7b6508SAbdelkader Boudih 			m_freem(m);
380cd7b6508SAbdelkader Boudih 			continue;
381cd7b6508SAbdelkader Boudih 		}
382cd7b6508SAbdelkader Boudih 
383cd7b6508SAbdelkader Boudih 		sample_count = ntohl(pay->sample_count);
384*dd56711eSAbdelkader Boudih 		if (sample_count == 0 || sample_count > FWISOUND_MAX_SAMPLES) {
385cd7b6508SAbdelkader Boudih 			m_freem(m);
386cd7b6508SAbdelkader Boudih 			continue;
387cd7b6508SAbdelkader Boudih 		}
388cd7b6508SAbdelkader Boudih 
389cd7b6508SAbdelkader Boudih 		nbytes = sample_count * 4;
390cd7b6508SAbdelkader Boudih 		if (plen < 16 + nbytes) {
391cd7b6508SAbdelkader Boudih 			m_freem(m);
392cd7b6508SAbdelkader Boudih 			continue;
393cd7b6508SAbdelkader Boudih 		}
394cd7b6508SAbdelkader Boudih 
395cd7b6508SAbdelkader Boudih 		if (sc->sample_total != 0 &&
396cd7b6508SAbdelkader Boudih 		    ntohl(pay->sample_total) != sc->sample_total)
397cd7b6508SAbdelkader Boudih 			sc->dropped++;
398cd7b6508SAbdelkader Boudih 		sc->sample_total = ntohl(pay->sample_total) + sample_count;
399cd7b6508SAbdelkader Boudih 
400cd7b6508SAbdelkader Boudih 		need_intr = 0;
401cd7b6508SAbdelkader Boudih 		if (chan != NULL && chan->running &&
402cd7b6508SAbdelkader Boudih 		    chan->buf != NULL && chan->pcm_chan != NULL) {
403cd7b6508SAbdelkader Boudih 			ring = sndbuf_getbufofs(chan->buf, 0);
404cd7b6508SAbdelkader Boudih 			ring_size = chan->buf->bufsize;
405cd7b6508SAbdelkader Boudih 			old_ptr = chan->hw_ptr;
406cd7b6508SAbdelkader Boudih 
407cd7b6508SAbdelkader Boudih 			uint8_t *src = (uint8_t *)pay->samples;
408cd7b6508SAbdelkader Boudih 			uint32_t rem = nbytes;
409cd7b6508SAbdelkader Boudih 
410cd7b6508SAbdelkader Boudih 			while (rem > 0) {
411cd7b6508SAbdelkader Boudih 				pos = chan->hw_ptr % ring_size;
412cd7b6508SAbdelkader Boudih 				chunk = MIN(rem, ring_size - pos);
413cd7b6508SAbdelkader Boudih 				memcpy(ring + pos, src, chunk);
414cd7b6508SAbdelkader Boudih 				chan->hw_ptr += chunk;
415cd7b6508SAbdelkader Boudih 				src += chunk;
416cd7b6508SAbdelkader Boudih 				rem -= chunk;
417cd7b6508SAbdelkader Boudih 			}
418cd7b6508SAbdelkader Boudih 
419cd7b6508SAbdelkader Boudih 			if (chan->buf->blksz == 0 ||
420cd7b6508SAbdelkader Boudih 			    (old_ptr / chan->buf->blksz) !=
421cd7b6508SAbdelkader Boudih 			    (chan->hw_ptr / chan->buf->blksz))
422cd7b6508SAbdelkader Boudih 				need_intr = 1;
423cd7b6508SAbdelkader Boudih 		}
424cd7b6508SAbdelkader Boudih 
425cd7b6508SAbdelkader Boudih 		m_freem(m);
426cd7b6508SAbdelkader Boudih 
427cd7b6508SAbdelkader Boudih 		if (need_intr) {
428cd7b6508SAbdelkader Boudih 			chn_intr(chan->pcm_chan);
429cd7b6508SAbdelkader Boudih 		}
430cd7b6508SAbdelkader Boudih 	}
431cd7b6508SAbdelkader Boudih 
432cd7b6508SAbdelkader Boudih 	fw_iso_rearm_done(xferq, sc->fd.fc, &sc->mtx, &sc->iso_active,
433cd7b6508SAbdelkader Boudih 	    &sc->dma_ch, dma_ch);
434cd7b6508SAbdelkader Boudih }
435cd7b6508SAbdelkader Boudih 
436cd7b6508SAbdelkader Boudih 
437cd7b6508SAbdelkader Boudih static int
fwisound_probe(device_t dev)438cd7b6508SAbdelkader Boudih fwisound_probe(device_t dev)
439cd7b6508SAbdelkader Boudih {
440c402d2e4SAbdelkader Boudih 	struct fw_unit *unit;
441c402d2e4SAbdelkader Boudih 
442c402d2e4SAbdelkader Boudih 	unit = fw_get_unit(dev);
443c402d2e4SAbdelkader Boudih 	if (unit == NULL)
444c402d2e4SAbdelkader Boudih 		return (ENXIO);
445c402d2e4SAbdelkader Boudih 
446c402d2e4SAbdelkader Boudih 	if (unit->spec_id != FWISOUND_SPEC_ID ||
447c402d2e4SAbdelkader Boudih 	    unit->sw_version != FWISOUND_VERSION)
448c402d2e4SAbdelkader Boudih 		return (ENXIO);
449cd7b6508SAbdelkader Boudih 
450cd7b6508SAbdelkader Boudih 	device_set_desc(dev, "Apple FireWire Audio");
451c402d2e4SAbdelkader Boudih 	return (BUS_PROBE_DEFAULT);
452cd7b6508SAbdelkader Boudih }
453cd7b6508SAbdelkader Boudih 
454cd7b6508SAbdelkader Boudih static int
fwisound_attach(device_t dev)455cd7b6508SAbdelkader Boudih fwisound_attach(device_t dev)
456cd7b6508SAbdelkader Boudih {
457cd7b6508SAbdelkader Boudih 	struct fwisound_softc *sc;
458c402d2e4SAbdelkader Boudih 	struct fw_unit *unit;
459c402d2e4SAbdelkader Boudih 	uint32_t audio_base;
460c402d2e4SAbdelkader Boudih 
461c402d2e4SAbdelkader Boudih 	unit = fw_get_unit(dev);
462c402d2e4SAbdelkader Boudih 	if (unit == NULL || unit->fwdev == NULL)
463c402d2e4SAbdelkader Boudih 		return (ENXIO);
464c402d2e4SAbdelkader Boudih 
465c402d2e4SAbdelkader Boudih 	audio_base = fwisound_find_audio_base(unit);
466c402d2e4SAbdelkader Boudih 	if (audio_base == 0) {
467c402d2e4SAbdelkader Boudih 		device_printf(dev, "no audio base in unit directory\n");
468c402d2e4SAbdelkader Boudih 		return (ENXIO);
469c402d2e4SAbdelkader Boudih 	}
470cd7b6508SAbdelkader Boudih 
471cd7b6508SAbdelkader Boudih 	sc = device_get_softc(dev);
472cd7b6508SAbdelkader Boudih 	sc->fd.dev = dev;
473c402d2e4SAbdelkader Boudih 	sc->fd.fc = fw_get_comm(dev);
474cd7b6508SAbdelkader Boudih 	sc->dev = dev;
475cd7b6508SAbdelkader Boudih 	mtx_init(&sc->mtx, "fwisound", NULL, MTX_DEF);
476cd7b6508SAbdelkader Boudih 
477c402d2e4SAbdelkader Boudih 	sc->fwdev = unit->fwdev;
478c402d2e4SAbdelkader Boudih 	sc->cmd_hi = 0xffff;
479c402d2e4SAbdelkader Boudih 	sc->cmd_lo = 0xf0000000 | (audio_base << 2);
480cd7b6508SAbdelkader Boudih 	sc->state = FWISOUND_STATE_IDLE;
481cd7b6508SAbdelkader Boudih 	sc->dma_ch = -1;
482cd7b6508SAbdelkader Boudih 	sc->iso_active = 0;
483cd7b6508SAbdelkader Boudih 	sc->iso_channel = (uint8_t)(iso_channel & FWXFERQ_CHTAGMASK);
484cd7b6508SAbdelkader Boudih 	sc->chan = NULL;
485cd7b6508SAbdelkader Boudih 	TASK_INIT(&sc->probe_task, 0, fwisound_probe_task, sc);
486cd7b6508SAbdelkader Boudih 	TASK_INIT(&sc->start_task, 0, fwisound_start_task, sc);
487cd7b6508SAbdelkader Boudih 	TASK_INIT(&sc->stop_task, 0, fwisound_stop_task, sc);
488cd7b6508SAbdelkader Boudih 
489cd7b6508SAbdelkader Boudih 	sc->pcm_dev = device_add_child(dev, "pcm", DEVICE_UNIT_ANY);
490cd7b6508SAbdelkader Boudih 	if (sc->pcm_dev == NULL) {
491cd7b6508SAbdelkader Boudih 		device_printf(dev, "failed to add pcm child\n");
492cd7b6508SAbdelkader Boudih 		mtx_destroy(&sc->mtx);
493cd7b6508SAbdelkader Boudih 		return (ENXIO);
494cd7b6508SAbdelkader Boudih 	}
495cd7b6508SAbdelkader Boudih 
496cd7b6508SAbdelkader Boudih 	bus_attach_children(dev);
497cd7b6508SAbdelkader Boudih 
498c402d2e4SAbdelkader Boudih 	if (taskqueue_enqueue(taskqueue_thread, &sc->probe_task) != 0)
499c402d2e4SAbdelkader Boudih 		device_printf(dev, "failed to enqueue probe task\n");
500cd7b6508SAbdelkader Boudih 
501cd7b6508SAbdelkader Boudih 	return (0);
502cd7b6508SAbdelkader Boudih }
503cd7b6508SAbdelkader Boudih 
504cd7b6508SAbdelkader Boudih static int
fwisound_detach(device_t dev)505cd7b6508SAbdelkader Boudih fwisound_detach(device_t dev)
506cd7b6508SAbdelkader Boudih {
507cd7b6508SAbdelkader Boudih 	struct fwisound_softc *sc;
508cd7b6508SAbdelkader Boudih 
509cd7b6508SAbdelkader Boudih 	sc = device_get_softc(dev);
510cd7b6508SAbdelkader Boudih 
511cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
512cd7b6508SAbdelkader Boudih 	sc->state = FWISOUND_STATE_DETACHING;
513cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
514cd7b6508SAbdelkader Boudih 
515cd7b6508SAbdelkader Boudih 	taskqueue_drain(taskqueue_thread, &sc->probe_task);
516cd7b6508SAbdelkader Boudih 	taskqueue_drain(taskqueue_thread, &sc->start_task);
517cd7b6508SAbdelkader Boudih 	taskqueue_drain(taskqueue_thread, &sc->stop_task);
518cd7b6508SAbdelkader Boudih 	fwisound_iso_stop(sc);
519cd7b6508SAbdelkader Boudih 
520cd7b6508SAbdelkader Boudih 	if (sc->pcm_dev != NULL)
521cd7b6508SAbdelkader Boudih 		device_delete_children(dev);
522cd7b6508SAbdelkader Boudih 
523cd7b6508SAbdelkader Boudih 	FWISOUND_LOCK(sc);
524cd7b6508SAbdelkader Boudih 	sc->fwdev = NULL;
525cd7b6508SAbdelkader Boudih 	FWISOUND_UNLOCK(sc);
526cd7b6508SAbdelkader Boudih 
527cd7b6508SAbdelkader Boudih 	mtx_destroy(&sc->mtx);
528cd7b6508SAbdelkader Boudih 	return (0);
529cd7b6508SAbdelkader Boudih }
530cd7b6508SAbdelkader Boudih 
531cd7b6508SAbdelkader Boudih static device_method_t fwisound_methods[] = {
532cd7b6508SAbdelkader Boudih 	DEVMETHOD(device_probe,		fwisound_probe),
533cd7b6508SAbdelkader Boudih 	DEVMETHOD(device_attach,	fwisound_attach),
534cd7b6508SAbdelkader Boudih 	DEVMETHOD(device_detach,	fwisound_detach),
535cd7b6508SAbdelkader Boudih 
536cd7b6508SAbdelkader Boudih 	DEVMETHOD_END
537cd7b6508SAbdelkader Boudih };
538cd7b6508SAbdelkader Boudih 
539cd7b6508SAbdelkader Boudih static driver_t fwisound_driver = {
540cd7b6508SAbdelkader Boudih 	"fwisound",
541cd7b6508SAbdelkader Boudih 	fwisound_methods,
542cd7b6508SAbdelkader Boudih 	sizeof(struct fwisound_softc),
543cd7b6508SAbdelkader Boudih };
544cd7b6508SAbdelkader Boudih 
545cd7b6508SAbdelkader Boudih DRIVER_MODULE(fwisound, firewire, fwisound_driver, 0, 0);
546cd7b6508SAbdelkader Boudih MODULE_VERSION(fwisound, 1);
547cd7b6508SAbdelkader Boudih MODULE_DEPEND(fwisound, firewire, 1, 1, 1);
548cd7b6508SAbdelkader Boudih MODULE_DEPEND(fwisound, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
549