xref: /freebsd/sys/dev/firewire/fwisound_pcm.c (revision c4fde87a3236b500246d4fce76dbca2a3d64f73a)
1 /*
2  * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 /* newpcm channel integration for fwisound(4). */
8 
9 #include <sys/param.h>
10 #include <sys/systm.h>
11 #include <sys/kernel.h>
12 #include <sys/bus.h>
13 #include <sys/malloc.h>
14 #include <sys/lock.h>
15 #include <sys/mutex.h>
16 #include <sys/taskqueue.h>
17 
18 #include <dev/firewire/firewire.h>
19 #include <dev/firewire/firewirereg.h>
20 #include <dev/firewire/fwisound.h>
21 
22 #include <dev/sound/pcm/sound.h>
23 
24 #define FWISOUND_BUFSZ	65536
25 
26 
27 static void *
28 fwisound_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
29     struct pcm_channel *c, int dir)
30 {
31 	struct fwisound_softc *sc = devinfo;
32 	struct fwisound_chan *chan;
33 	void *buf;
34 	unsigned int bufsz;
35 
36 	if (dir != PCMDIR_REC)
37 		return (NULL);
38 
39 	chan = malloc(sizeof(*chan), M_DEVBUF, M_WAITOK | M_ZERO);
40 	chan->sc = sc;
41 	chan->pcm_chan = c;
42 	chan->buf = b;
43 
44 	chan->fmts[0] = SND_FORMAT(AFMT_S16_BE, 2, 0);
45 	chan->fmts[1] = 0;
46 
47 	chan->caps.minspeed = 48000;
48 	chan->caps.maxspeed = 48000;
49 	chan->caps.fmtlist = chan->fmts;
50 	chan->caps.caps = 0;
51 
52 	bufsz = pcm_getbuffersize(sc->pcm_dev, 4096, FWISOUND_BUFSZ, 131072);
53 	buf = malloc(bufsz, M_DEVBUF, M_WAITOK | M_ZERO);
54 	if (sndbuf_setup(b, buf, bufsz) != 0) {
55 		free(buf, M_DEVBUF);
56 		free(chan, M_DEVBUF);
57 		return (NULL);
58 	}
59 
60 	FWISOUND_LOCK(sc);
61 	sc->chan = chan;
62 	FWISOUND_UNLOCK(sc);
63 
64 	return (chan);
65 }
66 
67 static int
68 fwisound_chan_free(kobj_t obj, void *data)
69 {
70 	struct fwisound_chan *chan = data;
71 	struct fwisound_softc *sc = chan->sc;
72 	void *buf;
73 
74 	if (chan->running) {
75 		taskqueue_enqueue(taskqueue_thread, &sc->stop_task);
76 		taskqueue_drain(taskqueue_thread, &sc->stop_task);
77 	}
78 
79 	FWISOUND_LOCK(sc);
80 	sc->chan = NULL;
81 	FWISOUND_UNLOCK(sc);
82 
83 	buf = sndbuf_getbufofs(chan->buf, 0);
84 	if (buf != NULL)
85 		free(buf, M_DEVBUF);
86 
87 	free(chan, M_DEVBUF);
88 	return (0);
89 }
90 
91 static int
92 fwisound_chan_setformat(kobj_t obj, void *data, uint32_t fmt)
93 {
94 
95 	if (fmt != SND_FORMAT(AFMT_S16_BE, 2, 0))
96 		return (EINVAL);
97 	return (0);
98 }
99 
100 static uint32_t
101 fwisound_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
102 {
103 
104 	return (48000);
105 }
106 
107 static uint32_t
108 fwisound_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz)
109 {
110 	struct fwisound_chan *chan = data;
111 
112 	blksz = rounddown(blksz, 4);
113 	if (blksz < 4)
114 		blksz = 4;
115 
116 	if (sndbuf_resize(chan->buf, chan->buf->bufsize / blksz, blksz) != 0)
117 		return (0);
118 
119 	return (chan->buf->blksz);
120 }
121 
122 static int
123 fwisound_chan_trigger(kobj_t obj, void *data, int go)
124 {
125 	struct fwisound_chan *chan = data;
126 	struct fwisound_softc *sc = chan->sc;
127 
128 	switch (go) {
129 	case PCMTRIG_START:
130 		chan->hw_ptr = 0;
131 		sc->dropped = 0;
132 		sc->sample_total = 0;
133 		chan->running = 1;
134 		taskqueue_enqueue(taskqueue_thread, &sc->start_task);
135 		return (0);
136 
137 	case PCMTRIG_STOP:
138 	case PCMTRIG_ABORT:
139 		chan->running = 0;
140 		taskqueue_drain(taskqueue_thread, &sc->start_task);
141 		taskqueue_enqueue(taskqueue_thread, &sc->stop_task);
142 		return (0);
143 
144 	default:
145 		return (0);
146 	}
147 }
148 
149 static uint32_t
150 fwisound_chan_getptr(kobj_t obj, void *data)
151 {
152 	struct fwisound_chan *chan = data;
153 
154 	return (chan->hw_ptr % chan->buf->bufsize);
155 }
156 
157 static struct pcmchan_caps *
158 fwisound_chan_getcaps(kobj_t obj, void *data)
159 {
160 	struct fwisound_chan *chan = data;
161 
162 	return (&chan->caps);
163 }
164 
165 static kobj_method_t fwisound_chan_methods[] = {
166 	KOBJMETHOD(channel_init,	fwisound_chan_init),
167 	KOBJMETHOD(channel_free,	fwisound_chan_free),
168 	KOBJMETHOD(channel_setformat,	fwisound_chan_setformat),
169 	KOBJMETHOD(channel_setspeed,	fwisound_chan_setspeed),
170 	KOBJMETHOD(channel_setblocksize, fwisound_chan_setblocksize),
171 	KOBJMETHOD(channel_trigger,	fwisound_chan_trigger),
172 	KOBJMETHOD(channel_getptr,	fwisound_chan_getptr),
173 	KOBJMETHOD(channel_getcaps,	fwisound_chan_getcaps),
174 	KOBJMETHOD_END
175 };
176 
177 CHANNEL_DECLARE(fwisound_chan);
178 
179 static int
180 fwisound_pcm_probe(device_t dev)
181 {
182 
183 	device_set_desc(dev, "Apple FireWire audio");
184 	return (0);
185 }
186 
187 static int
188 fwisound_pcm_attach(device_t dev)
189 {
190 	struct fwisound_softc *sc;
191 	char status[SND_STATUSLEN];
192 	int err;
193 
194 	sc = device_get_softc(device_get_parent(dev));
195 	sc->pcm_dev = dev;
196 
197 	pcm_init(dev, sc);
198 	pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
199 
200 	err = pcm_addchan(dev, PCMDIR_REC, &fwisound_chan_class, sc);
201 	if (err != 0) {
202 		device_printf(dev, "pcm_addchan failed: %d\n", err);
203 		return (err);
204 	}
205 
206 	snprintf(status, sizeof(status), "Apple FireWire audio at %s",
207 	    device_get_nameunit(device_get_parent(dev)));
208 	return (pcm_register(dev, status));
209 }
210 
211 static int
212 fwisound_pcm_detach(device_t dev)
213 {
214 
215 	return (pcm_unregister(dev));
216 }
217 
218 static device_method_t fwisound_pcm_methods[] = {
219 	DEVMETHOD(device_probe,		fwisound_pcm_probe),
220 	DEVMETHOD(device_attach,	fwisound_pcm_attach),
221 	DEVMETHOD(device_detach,	fwisound_pcm_detach),
222 
223 	DEVMETHOD_END
224 };
225 
226 static driver_t fwisound_pcm_driver = {
227 	"pcm",
228 	fwisound_pcm_methods,
229 	PCM_SOFTC_SIZE,
230 };
231 
232 DRIVER_MODULE(fwisound_pcm, fwisound, fwisound_pcm_driver, 0, 0);
233 MODULE_DEPEND(fwisound_pcm, fwisound, 1, 1, 1);
234 MODULE_DEPEND(fwisound_pcm, sound, 1, 1, 1);
235