1 | /* $NetBSD: cs428x.c,v 1.17 2012/10/27 17:18:31 chs Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2000 Tatoku Ogaito. All rights reserved. |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * 1. Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * 2. Redistributions in binary form must reproduce the above copyright |
12 | * notice, this list of conditions and the following disclaimer in the |
13 | * documentation and/or other materials provided with the distribution. |
14 | * 3. All advertising materials mentioning features or use of this software |
15 | * must display the following acknowledgement: |
16 | * This product includes software developed by Tatoku Ogaito |
17 | * for the NetBSD Project. |
18 | * 4. The name of the author may not be used to endorse or promote products |
19 | * derived from this software without specific prior written permission |
20 | * |
21 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
22 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
23 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
24 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
25 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
26 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
30 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | */ |
32 | |
33 | /* Common functions for CS4280 and CS4281 */ |
34 | |
35 | #include <sys/cdefs.h> |
36 | __KERNEL_RCSID(0, "$NetBSD: cs428x.c,v 1.17 2012/10/27 17:18:31 chs Exp $" ); |
37 | |
38 | #include <sys/param.h> |
39 | #include <sys/systm.h> |
40 | #include <sys/kernel.h> |
41 | #include <sys/kmem.h> |
42 | #include <sys/device.h> |
43 | #include <sys/audioio.h> |
44 | #include <sys/bus.h> |
45 | |
46 | #include <dev/audio_if.h> |
47 | #include <dev/midi_if.h> |
48 | #include <dev/mulaw.h> |
49 | #include <dev/auconv.h> |
50 | |
51 | #include <dev/ic/ac97reg.h> |
52 | #include <dev/ic/ac97var.h> |
53 | |
54 | #include <dev/pci/pcidevs.h> |
55 | #include <dev/pci/pcivar.h> |
56 | #include <dev/pci/cs428xreg.h> |
57 | #include <dev/pci/cs428x.h> |
58 | |
59 | #if defined(CS4280_DEBUG) || defined(CS4281_DEBUG) |
60 | int cs428x_debug = 0; |
61 | #endif |
62 | |
63 | int |
64 | cs428x_round_blocksize(void *addr, int blk, |
65 | int mode, const audio_params_t *param) |
66 | { |
67 | struct cs428x_softc *sc; |
68 | int retval; |
69 | |
70 | DPRINTFN(5,("cs428x_round_blocksize blk=%d -> " , blk)); |
71 | |
72 | sc = addr; |
73 | if (blk < sc->hw_blocksize) |
74 | retval = sc->hw_blocksize; |
75 | else |
76 | retval = blk & -(sc->hw_blocksize); |
77 | |
78 | DPRINTFN(5,("%d\n" , retval)); |
79 | |
80 | return retval; |
81 | } |
82 | |
83 | int |
84 | cs428x_mixer_set_port(void *addr, mixer_ctrl_t *cp) |
85 | { |
86 | struct cs428x_softc *sc; |
87 | int val; |
88 | |
89 | sc = addr; |
90 | val = sc->codec_if->vtbl->mixer_set_port(sc->codec_if, cp); |
91 | DPRINTFN(3,("mixer_set_port: val=%d\n" , val)); |
92 | return (val); |
93 | } |
94 | |
95 | int |
96 | cs428x_mixer_get_port(void *addr, mixer_ctrl_t *cp) |
97 | { |
98 | struct cs428x_softc *sc; |
99 | |
100 | sc = addr; |
101 | return (sc->codec_if->vtbl->mixer_get_port(sc->codec_if, cp)); |
102 | } |
103 | |
104 | int |
105 | cs428x_query_devinfo(void *addr, mixer_devinfo_t *dip) |
106 | { |
107 | struct cs428x_softc *sc; |
108 | |
109 | sc = addr; |
110 | return (sc->codec_if->vtbl->query_devinfo(sc->codec_if, dip)); |
111 | } |
112 | |
113 | void * |
114 | cs428x_malloc(void *addr, int direction, size_t size) |
115 | { |
116 | struct cs428x_softc *sc; |
117 | struct cs428x_dma *p; |
118 | int error; |
119 | |
120 | sc = addr; |
121 | |
122 | p = kmem_alloc(sizeof(*p), KM_SLEEP); |
123 | if (p == NULL) |
124 | return 0; |
125 | |
126 | error = cs428x_allocmem(sc, size, p); |
127 | |
128 | if (error) { |
129 | kmem_free(p, sizeof(*p)); |
130 | return 0; |
131 | } |
132 | |
133 | p->next = sc->sc_dmas; |
134 | sc->sc_dmas = p; |
135 | return BUFADDR(p); |
136 | } |
137 | |
138 | void |
139 | cs428x_free(void *addr, void *ptr, size_t size) |
140 | { |
141 | struct cs428x_softc *sc; |
142 | struct cs428x_dma **pp, *p; |
143 | |
144 | sc = addr; |
145 | for (pp = &sc->sc_dmas; (p = *pp) != NULL; pp = &p->next) { |
146 | if (BUFADDR(p) == ptr) { |
147 | bus_dmamap_unload(sc->sc_dmatag, p->map); |
148 | bus_dmamap_destroy(sc->sc_dmatag, p->map); |
149 | bus_dmamem_unmap(sc->sc_dmatag, p->addr, p->size); |
150 | bus_dmamem_free(sc->sc_dmatag, p->segs, p->nsegs); |
151 | kmem_free(p->dum, p->size); |
152 | *pp = p->next; |
153 | kmem_free(p, sizeof(*p)); |
154 | return; |
155 | } |
156 | } |
157 | } |
158 | |
159 | size_t |
160 | cs428x_round_buffersize(void *addr, int direction, |
161 | size_t size) |
162 | { |
163 | /* The real DMA buffersize are 4KB for CS4280 |
164 | * and 64kB/MAX_CHANNELS for CS4281. |
165 | * But they are too small for high quality audio, |
166 | * let the upper layer(audio) use a larger buffer. |
167 | * (originally suggested by Lennart Augustsson.) |
168 | */ |
169 | return size; |
170 | } |
171 | |
172 | paddr_t |
173 | cs428x_mappage(void *addr, void *mem, off_t off, int prot) |
174 | { |
175 | struct cs428x_softc *sc; |
176 | struct cs428x_dma *p; |
177 | |
178 | sc = addr; |
179 | |
180 | if (off < 0) |
181 | return -1; |
182 | |
183 | for (p = sc->sc_dmas; p && BUFADDR(p) != mem; p = p->next) |
184 | continue; |
185 | |
186 | if (p == NULL) { |
187 | DPRINTF(("cs428x_mappage: bad buffer address\n" )); |
188 | return -1; |
189 | } |
190 | |
191 | return (bus_dmamem_mmap(sc->sc_dmatag, p->segs, p->nsegs, |
192 | off, prot, BUS_DMA_WAITOK)); |
193 | } |
194 | |
195 | int |
196 | cs428x_get_props(void *addr) |
197 | { |
198 | int retval; |
199 | |
200 | retval = AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX; |
201 | #ifdef MMAP_READY |
202 | /* How can I mmap ? */ |
203 | retval |= AUDIO_PROP_MMAP; |
204 | #endif |
205 | return retval; |
206 | } |
207 | |
208 | /* AC97 */ |
209 | int |
210 | cs428x_attach_codec(void *addr, struct ac97_codec_if *codec_if) |
211 | { |
212 | struct cs428x_softc *sc; |
213 | |
214 | DPRINTF(("cs428x_attach_codec:\n" )); |
215 | sc = addr; |
216 | sc->codec_if = codec_if; |
217 | return 0; |
218 | } |
219 | |
220 | int |
221 | cs428x_read_codec(void *addr, uint8_t ac97_addr, uint16_t *ac97_data) |
222 | { |
223 | struct cs428x_softc *sc; |
224 | uint32_t acctl; |
225 | int n; |
226 | |
227 | sc = addr; |
228 | |
229 | DPRINTFN(5,("read_codec: add=0x%02x " , ac97_addr)); |
230 | /* |
231 | * Make sure that there is not data sitting around from a previous |
232 | * uncompleted access. |
233 | */ |
234 | BA0READ4(sc, CS428X_ACSDA); |
235 | |
236 | /* Set up AC97 control registers. */ |
237 | BA0WRITE4(sc, CS428X_ACCAD, ac97_addr); |
238 | BA0WRITE4(sc, CS428X_ACCDA, 0); |
239 | |
240 | acctl = ACCTL_ESYN | ACCTL_VFRM | ACCTL_CRW | ACCTL_DCV; |
241 | if (sc->type == TYPE_CS4280) |
242 | acctl |= ACCTL_RSTN; |
243 | BA0WRITE4(sc, CS428X_ACCTL, acctl); |
244 | |
245 | if (cs428x_src_wait(sc) < 0) { |
246 | printf("%s: AC97 read prob. (DCV!=0) for add=0x%0x\n" , |
247 | device_xname(sc->sc_dev), ac97_addr); |
248 | return 1; |
249 | } |
250 | |
251 | /* wait for valid status bit is active */ |
252 | n = 0; |
253 | while ((BA0READ4(sc, CS428X_ACSTS) & ACSTS_VSTS) == 0) { |
254 | delay(1); |
255 | while (++n > 1000) { |
256 | printf("%s: AC97 read fail (VSTS==0) for add=0x%0x\n" , |
257 | device_xname(sc->sc_dev), ac97_addr); |
258 | return 1; |
259 | } |
260 | } |
261 | *ac97_data = BA0READ4(sc, CS428X_ACSDA); |
262 | DPRINTFN(5,("data=0x%04x\n" , *ac97_data)); |
263 | return 0; |
264 | } |
265 | |
266 | int |
267 | cs428x_write_codec(void *addr, uint8_t ac97_addr, uint16_t ac97_data) |
268 | { |
269 | struct cs428x_softc *sc; |
270 | uint32_t acctl; |
271 | |
272 | sc = addr; |
273 | |
274 | DPRINTFN(5,("write_codec: add=0x%02x data=0x%04x\n" , ac97_addr, ac97_data)); |
275 | BA0WRITE4(sc, CS428X_ACCAD, ac97_addr); |
276 | BA0WRITE4(sc, CS428X_ACCDA, ac97_data); |
277 | |
278 | acctl = ACCTL_ESYN | ACCTL_VFRM | ACCTL_DCV; |
279 | if (sc->type == TYPE_CS4280) |
280 | acctl |= ACCTL_RSTN; |
281 | BA0WRITE4(sc, CS428X_ACCTL, acctl); |
282 | |
283 | if (cs428x_src_wait(sc) < 0) { |
284 | printf("%s: AC97 write fail (DCV!=0) for add=0x%02x data=" |
285 | "0x%04x\n" , device_xname(sc->sc_dev), ac97_addr, ac97_data); |
286 | return 1; |
287 | } |
288 | return 0; |
289 | } |
290 | |
291 | /* Internal functions */ |
292 | int |
293 | cs428x_allocmem(struct cs428x_softc *sc, size_t size, struct cs428x_dma *p) |
294 | { |
295 | int error; |
296 | size_t align; |
297 | |
298 | align = sc->dma_align; |
299 | p->size = sc->dma_size; |
300 | /* allocate memory for upper audio driver */ |
301 | p->dum = kmem_alloc(size, KM_SLEEP); |
302 | if (p->dum == NULL) |
303 | return 1; |
304 | |
305 | error = bus_dmamem_alloc(sc->sc_dmatag, p->size, align, 0, |
306 | p->segs, sizeof(p->segs)/sizeof(p->segs[0]), |
307 | &p->nsegs, BUS_DMA_WAITOK); |
308 | if (error) { |
309 | aprint_error_dev(sc->sc_dev, "unable to allocate DMA. error=%d\n" , |
310 | error); |
311 | goto allfree; |
312 | } |
313 | |
314 | error = bus_dmamem_map(sc->sc_dmatag, p->segs, p->nsegs, p->size, |
315 | &p->addr, BUS_DMA_WAITOK|BUS_DMA_COHERENT); |
316 | if (error) { |
317 | aprint_error_dev(sc->sc_dev, "unable to map DMA, error=%d\n" , |
318 | error); |
319 | goto free; |
320 | } |
321 | |
322 | error = bus_dmamap_create(sc->sc_dmatag, p->size, 1, p->size, |
323 | 0, BUS_DMA_WAITOK, &p->map); |
324 | if (error) { |
325 | aprint_error_dev(sc->sc_dev, "unable to create DMA map, error=%d\n" , |
326 | error); |
327 | goto unmap; |
328 | } |
329 | |
330 | error = bus_dmamap_load(sc->sc_dmatag, p->map, p->addr, p->size, NULL, |
331 | BUS_DMA_WAITOK); |
332 | if (error) { |
333 | aprint_error_dev(sc->sc_dev, "unable to load DMA map, error=%d\n" , |
334 | error); |
335 | goto destroy; |
336 | } |
337 | return 0; |
338 | |
339 | destroy: |
340 | bus_dmamap_destroy(sc->sc_dmatag, p->map); |
341 | unmap: |
342 | bus_dmamem_unmap(sc->sc_dmatag, p->addr, p->size); |
343 | free: |
344 | bus_dmamem_free(sc->sc_dmatag, p->segs, p->nsegs); |
345 | allfree: |
346 | kmem_free(p->dum, size); |
347 | |
348 | return error; |
349 | } |
350 | |
351 | int |
352 | cs428x_src_wait(struct cs428x_softc *sc) |
353 | { |
354 | int n; |
355 | |
356 | n = 0; |
357 | while ((BA0READ4(sc, CS428X_ACCTL) & ACCTL_DCV)) { |
358 | delay(1000); |
359 | while (++n > 1000) { |
360 | printf("cs428x_src_wait: 0x%08x\n" , |
361 | BA0READ4(sc, CS428X_ACCTL)); |
362 | return -1; |
363 | } |
364 | } |
365 | return 0; |
366 | } |
367 | |
368 | void |
369 | cs428x_get_locks(void *addr, kmutex_t **intr, kmutex_t **thread) |
370 | { |
371 | struct cs428x_softc *sc; |
372 | |
373 | sc = addr; |
374 | *intr = &sc->sc_intr_lock; |
375 | *thread = &sc->sc_lock; |
376 | } |
377 | |