xref: /freebsd/sys/dev/sound/pcm/feeder_matrix.c (revision c824383b269d8abe175ea4751194660716d5600e)
190da2b28SAriff Abdullah /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3718cf2ccSPedro F. Giffuni  *
490da2b28SAriff Abdullah  * Copyright (c) 2008-2009 Ariff Abdullah <ariff@FreeBSD.org>
590da2b28SAriff Abdullah  * All rights reserved.
6*c824383bSChristos Margiolis  * Copyright (c) 2024-2025 The FreeBSD Foundation
7*c824383bSChristos Margiolis  *
8*c824383bSChristos Margiolis  * Portions of this software were developed by Christos Margiolis
9*c824383bSChristos Margiolis  * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
1090da2b28SAriff Abdullah  *
1190da2b28SAriff Abdullah  * Redistribution and use in source and binary forms, with or without
1290da2b28SAriff Abdullah  * modification, are permitted provided that the following conditions
1390da2b28SAriff Abdullah  * are met:
1490da2b28SAriff Abdullah  * 1. Redistributions of source code must retain the above copyright
1590da2b28SAriff Abdullah  *    notice, this list of conditions and the following disclaimer.
1690da2b28SAriff Abdullah  * 2. Redistributions in binary form must reproduce the above copyright
1790da2b28SAriff Abdullah  *    notice, this list of conditions and the following disclaimer in the
1890da2b28SAriff Abdullah  *    documentation and/or other materials provided with the distribution.
1990da2b28SAriff Abdullah  *
2090da2b28SAriff Abdullah  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2190da2b28SAriff Abdullah  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2290da2b28SAriff Abdullah  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2390da2b28SAriff Abdullah  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2490da2b28SAriff Abdullah  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2590da2b28SAriff Abdullah  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2690da2b28SAriff Abdullah  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2790da2b28SAriff Abdullah  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2890da2b28SAriff Abdullah  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2990da2b28SAriff Abdullah  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3090da2b28SAriff Abdullah  * SUCH DAMAGE.
3190da2b28SAriff Abdullah  */
3290da2b28SAriff Abdullah 
3390da2b28SAriff Abdullah /*
3490da2b28SAriff Abdullah  * feeder_matrix: Generic any-to-any channel matrixing. Probably not the
3590da2b28SAriff Abdullah  *                accurate way of doing things, but it should be fast and
3690da2b28SAriff Abdullah  *                transparent enough, not to mention capable of handling
3790da2b28SAriff Abdullah  *                possible non-standard way of multichannel interleaving
3890da2b28SAriff Abdullah  *                order. In other words, it is tough to break.
3990da2b28SAriff Abdullah  *
4090da2b28SAriff Abdullah  * The Good:
4190da2b28SAriff Abdullah  * + very generic and compact, provided that the supplied matrix map is in a
4290da2b28SAriff Abdullah  *   sane form.
4390da2b28SAriff Abdullah  * + should be fast enough.
4490da2b28SAriff Abdullah  *
4590da2b28SAriff Abdullah  * The Bad:
4690da2b28SAriff Abdullah  * + somebody might disagree with it.
4790da2b28SAriff Abdullah  * + 'matrix' is kind of 0x7a69, due to prolong mental block.
4890da2b28SAriff Abdullah  */
4990da2b28SAriff Abdullah 
5090da2b28SAriff Abdullah #ifdef _KERNEL
5190da2b28SAriff Abdullah #ifdef HAVE_KERNEL_OPTION_HEADERS
5290da2b28SAriff Abdullah #include "opt_snd.h"
5390da2b28SAriff Abdullah #endif
5490da2b28SAriff Abdullah #include <dev/sound/pcm/sound.h>
5590da2b28SAriff Abdullah #include <dev/sound/pcm/pcm.h>
5690da2b28SAriff Abdullah #include "feeder_if.h"
5790da2b28SAriff Abdullah 
5890da2b28SAriff Abdullah #define SND_USE_FXDIV
5990da2b28SAriff Abdullah #include "snd_fxdiv_gen.h"
6090da2b28SAriff Abdullah #endif
6190da2b28SAriff Abdullah 
6290da2b28SAriff Abdullah #define FEEDMATRIX_RESERVOIR	(SND_CHN_MAX * PCM_32_BPS)
6390da2b28SAriff Abdullah 
6490da2b28SAriff Abdullah #define SND_CHN_T_EOF		0x00e0fe0f
6590da2b28SAriff Abdullah #define SND_CHN_T_NULL		0x0e0e0e0e
6690da2b28SAriff Abdullah 
6790da2b28SAriff Abdullah struct feed_matrix_info {
68ac24c9daSChristos Margiolis 	uint32_t fmt;
6990da2b28SAriff Abdullah 	uint32_t bps;
7090da2b28SAriff Abdullah 	uint32_t ialign, oalign;
7190da2b28SAriff Abdullah 	uint32_t in, out;
7290da2b28SAriff Abdullah 	struct {
7390da2b28SAriff Abdullah 		int chn[SND_CHN_T_MAX + 1];
7490da2b28SAriff Abdullah 		int mul, shift;
7590da2b28SAriff Abdullah 	} matrix[SND_CHN_T_MAX + 1];
7690da2b28SAriff Abdullah 	uint8_t reservoir[FEEDMATRIX_RESERVOIR];
7790da2b28SAriff Abdullah };
7890da2b28SAriff Abdullah 
7990da2b28SAriff Abdullah static struct pcmchan_matrix feeder_matrix_maps[SND_CHN_MATRIX_MAX] = {
8090da2b28SAriff Abdullah 	[SND_CHN_MATRIX_1_0] = SND_CHN_MATRIX_MAP_1_0,
8190da2b28SAriff Abdullah 	[SND_CHN_MATRIX_2_0] = SND_CHN_MATRIX_MAP_2_0,
8290da2b28SAriff Abdullah 	[SND_CHN_MATRIX_2_1] = SND_CHN_MATRIX_MAP_2_1,
8390da2b28SAriff Abdullah 	[SND_CHN_MATRIX_3_0] = SND_CHN_MATRIX_MAP_3_0,
841fc2a614SAlexander Motin 	[SND_CHN_MATRIX_3_1] = SND_CHN_MATRIX_MAP_3_1,
8590da2b28SAriff Abdullah 	[SND_CHN_MATRIX_4_0] = SND_CHN_MATRIX_MAP_4_0,
8690da2b28SAriff Abdullah 	[SND_CHN_MATRIX_4_1] = SND_CHN_MATRIX_MAP_4_1,
8790da2b28SAriff Abdullah 	[SND_CHN_MATRIX_5_0] = SND_CHN_MATRIX_MAP_5_0,
8890da2b28SAriff Abdullah 	[SND_CHN_MATRIX_5_1] = SND_CHN_MATRIX_MAP_5_1,
8990da2b28SAriff Abdullah 	[SND_CHN_MATRIX_6_0] = SND_CHN_MATRIX_MAP_6_0,
9090da2b28SAriff Abdullah 	[SND_CHN_MATRIX_6_1] = SND_CHN_MATRIX_MAP_6_1,
911fc2a614SAlexander Motin 	[SND_CHN_MATRIX_7_0] = SND_CHN_MATRIX_MAP_7_0,
9290da2b28SAriff Abdullah 	[SND_CHN_MATRIX_7_1] = SND_CHN_MATRIX_MAP_7_1
9390da2b28SAriff Abdullah };
9490da2b28SAriff Abdullah 
9590da2b28SAriff Abdullah static int feeder_matrix_default_ids[9] = {
9690da2b28SAriff Abdullah 	[0] = SND_CHN_MATRIX_UNKNOWN,
9790da2b28SAriff Abdullah 	[1] = SND_CHN_MATRIX_1,
9890da2b28SAriff Abdullah 	[2] = SND_CHN_MATRIX_2,
9990da2b28SAriff Abdullah 	[3] = SND_CHN_MATRIX_3,
10090da2b28SAriff Abdullah 	[4] = SND_CHN_MATRIX_4,
10190da2b28SAriff Abdullah 	[5] = SND_CHN_MATRIX_5,
10290da2b28SAriff Abdullah 	[6] = SND_CHN_MATRIX_6,
10390da2b28SAriff Abdullah 	[7] = SND_CHN_MATRIX_7,
10490da2b28SAriff Abdullah 	[8] = SND_CHN_MATRIX_8
10590da2b28SAriff Abdullah };
10690da2b28SAriff Abdullah 
10790da2b28SAriff Abdullah #ifdef _KERNEL
10890da2b28SAriff Abdullah #define FEEDMATRIX_CLIP_CHECK(...)
10990da2b28SAriff Abdullah #else
11090da2b28SAriff Abdullah #define FEEDMATRIX_CLIP_CHECK(v, BIT)	do {				\
11190da2b28SAriff Abdullah 	if ((v) < PCM_S##BIT##_MIN || (v) > PCM_S##BIT##_MAX)		\
11290da2b28SAriff Abdullah 	    errx(1, "\n\n%s(): Sample clipping: %jd\n",			\
11390da2b28SAriff Abdullah 		__func__, (intmax_t)(v));				\
11490da2b28SAriff Abdullah } while (0)
11590da2b28SAriff Abdullah #endif
11690da2b28SAriff Abdullah 
117ac24c9daSChristos Margiolis __always_inline static void
feed_matrix_apply(struct feed_matrix_info * info,uint8_t * src,uint8_t * dst,uint32_t count,const uint32_t fmt)118ac24c9daSChristos Margiolis feed_matrix_apply(struct feed_matrix_info *info, uint8_t *src, uint8_t *dst,
119ac24c9daSChristos Margiolis     uint32_t count, const uint32_t fmt)
120ac24c9daSChristos Margiolis {
121ac24c9daSChristos Margiolis 	intpcm64_t accum;
122ac24c9daSChristos Margiolis 	intpcm_t v;
123ac24c9daSChristos Margiolis 	int i, j;
124ac24c9daSChristos Margiolis 
125ac24c9daSChristos Margiolis 	do {
126ac24c9daSChristos Margiolis 		for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF; i++) {
127ac24c9daSChristos Margiolis 			if (info->matrix[i].chn[0] == SND_CHN_T_NULL) {
128ac24c9daSChristos Margiolis 				pcm_sample_write(dst, 0, fmt);
129ac24c9daSChristos Margiolis 				dst += info->bps;
130ac24c9daSChristos Margiolis 				continue;
131ac24c9daSChristos Margiolis 			} else if (info->matrix[i].chn[1] == SND_CHN_T_EOF) {
132ac24c9daSChristos Margiolis 				v = pcm_sample_read(src +
133ac24c9daSChristos Margiolis 				    info->matrix[i].chn[0], fmt);
134ac24c9daSChristos Margiolis 				pcm_sample_write(dst, v, fmt);
135ac24c9daSChristos Margiolis 				dst += info->bps;
136ac24c9daSChristos Margiolis 				continue;
13790da2b28SAriff Abdullah 			}
13890da2b28SAriff Abdullah 
139ac24c9daSChristos Margiolis 			accum = 0;
140ac24c9daSChristos Margiolis 			for (j = 0; info->matrix[i].chn[j] != SND_CHN_T_EOF;
141ac24c9daSChristos Margiolis 			    j++) {
142ac24c9daSChristos Margiolis 				v = pcm_sample_read(src +
143ac24c9daSChristos Margiolis 				    info->matrix[i].chn[j], fmt);
144ac24c9daSChristos Margiolis 				accum += v;
14590da2b28SAriff Abdullah 			}
14690da2b28SAriff Abdullah 
147ac24c9daSChristos Margiolis 			accum = (accum * info->matrix[i].mul) >>
148ac24c9daSChristos Margiolis 			    info->matrix[i].shift;
149ac24c9daSChristos Margiolis 
150ac24c9daSChristos Margiolis 			FEEDMATRIX_CLIP_CHECK(accum, AFMT_BIT(fmt));
151ac24c9daSChristos Margiolis 
152ac24c9daSChristos Margiolis 			v = pcm_clamp(accum, fmt);
153ac24c9daSChristos Margiolis 			pcm_sample_write(dst, v, fmt);
154ac24c9daSChristos Margiolis 			dst += info->bps;
155ac24c9daSChristos Margiolis 		}
156ac24c9daSChristos Margiolis 		src += info->ialign;
157ac24c9daSChristos Margiolis 	} while (--count != 0);
158ac24c9daSChristos Margiolis }
15990da2b28SAriff Abdullah 
16090da2b28SAriff Abdullah static void
feed_matrix_reset(struct feed_matrix_info * info)16190da2b28SAriff Abdullah feed_matrix_reset(struct feed_matrix_info *info)
16290da2b28SAriff Abdullah {
16390da2b28SAriff Abdullah 	uint32_t i, j;
16490da2b28SAriff Abdullah 
165c597c557SChristos Margiolis 	for (i = 0; i < nitems(info->matrix); i++) {
16690da2b28SAriff Abdullah 		for (j = 0;
16790da2b28SAriff Abdullah 		    j < (sizeof(info->matrix[i].chn) /
16890da2b28SAriff Abdullah 		    sizeof(info->matrix[i].chn[0])); j++) {
16990da2b28SAriff Abdullah 			info->matrix[i].chn[j] = SND_CHN_T_EOF;
17090da2b28SAriff Abdullah 		}
17190da2b28SAriff Abdullah 		info->matrix[i].mul   = 1;
17290da2b28SAriff Abdullah 		info->matrix[i].shift = 0;
17390da2b28SAriff Abdullah 	}
17490da2b28SAriff Abdullah }
17590da2b28SAriff Abdullah 
17690da2b28SAriff Abdullah static int
feed_matrix_setup(struct feed_matrix_info * info,struct pcmchan_matrix * m_in,struct pcmchan_matrix * m_out)17790da2b28SAriff Abdullah feed_matrix_setup(struct feed_matrix_info *info, struct pcmchan_matrix *m_in,
17890da2b28SAriff Abdullah     struct pcmchan_matrix *m_out)
17990da2b28SAriff Abdullah {
18090da2b28SAriff Abdullah 	uint32_t i, j, ch, in_mask, merge_mask;
18190da2b28SAriff Abdullah 	int mul, shift;
18290da2b28SAriff Abdullah 
18390da2b28SAriff Abdullah 	if (info == NULL || m_in == NULL || m_out == NULL ||
18490da2b28SAriff Abdullah 	    AFMT_CHANNEL(info->in) != m_in->channels ||
18590da2b28SAriff Abdullah 	    AFMT_CHANNEL(info->out) != m_out->channels ||
18690da2b28SAriff Abdullah 	    m_in->channels < SND_CHN_MIN || m_in->channels > SND_CHN_MAX ||
18790da2b28SAriff Abdullah 	    m_out->channels < SND_CHN_MIN || m_out->channels > SND_CHN_MAX)
18890da2b28SAriff Abdullah 		return (EINVAL);
18990da2b28SAriff Abdullah 
19090da2b28SAriff Abdullah 	feed_matrix_reset(info);
19190da2b28SAriff Abdullah 
19290da2b28SAriff Abdullah 	/*
19390da2b28SAriff Abdullah 	 * If both in and out are part of standard matrix and identical, skip
19470311ccfSGordon Bergling 	 * everything altogether.
19590da2b28SAriff Abdullah 	 */
19690da2b28SAriff Abdullah 	if (m_in->id == m_out->id && !(m_in->id < SND_CHN_MATRIX_BEGIN ||
19790da2b28SAriff Abdullah 	    m_in->id > SND_CHN_MATRIX_END))
19890da2b28SAriff Abdullah 		return (0);
19990da2b28SAriff Abdullah 
20090da2b28SAriff Abdullah 	/*
20190da2b28SAriff Abdullah 	 * Special case for mono input matrix. If the output supports
20290da2b28SAriff Abdullah 	 * possible 'center' channel, route it there. Otherwise, let it be
20390da2b28SAriff Abdullah 	 * matrixed to left/right.
20490da2b28SAriff Abdullah 	 */
20590da2b28SAriff Abdullah 	if (m_in->id == SND_CHN_MATRIX_1_0) {
20690da2b28SAriff Abdullah 		if (m_out->id == SND_CHN_MATRIX_1_0)
20790da2b28SAriff Abdullah 			in_mask = SND_CHN_T_MASK_FL;
20890da2b28SAriff Abdullah 		else if (m_out->mask & SND_CHN_T_MASK_FC)
20990da2b28SAriff Abdullah 			in_mask = SND_CHN_T_MASK_FC;
21090da2b28SAriff Abdullah 		else
21190da2b28SAriff Abdullah 			in_mask = SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR;
21290da2b28SAriff Abdullah 	} else
21390da2b28SAriff Abdullah 		in_mask = m_in->mask;
21490da2b28SAriff Abdullah 
21590da2b28SAriff Abdullah 	/* Merge, reduce, expand all possibilites. */
21690da2b28SAriff Abdullah 	for (ch = SND_CHN_T_BEGIN; ch <= SND_CHN_T_END &&
21790da2b28SAriff Abdullah 	    m_out->map[ch].type != SND_CHN_T_MAX; ch += SND_CHN_T_STEP) {
21890da2b28SAriff Abdullah 		merge_mask = m_out->map[ch].members & in_mask;
21990da2b28SAriff Abdullah 		if (merge_mask == 0) {
22090da2b28SAriff Abdullah 			info->matrix[ch].chn[0] = SND_CHN_T_NULL;
22190da2b28SAriff Abdullah 			continue;
22290da2b28SAriff Abdullah 		}
22390da2b28SAriff Abdullah 
22490da2b28SAriff Abdullah 		j = 0;
22590da2b28SAriff Abdullah 		for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END;
22690da2b28SAriff Abdullah 		    i += SND_CHN_T_STEP) {
22790da2b28SAriff Abdullah 			if (merge_mask & (1 << i)) {
22890da2b28SAriff Abdullah 				if (m_in->offset[i] >= 0 &&
22990da2b28SAriff Abdullah 				    m_in->offset[i] < (int)m_in->channels)
23090da2b28SAriff Abdullah 					info->matrix[ch].chn[j++] =
23190da2b28SAriff Abdullah 					    m_in->offset[i] * info->bps;
23290da2b28SAriff Abdullah 				else {
23390da2b28SAriff Abdullah 					info->matrix[ch].chn[j++] =
23490da2b28SAriff Abdullah 					    SND_CHN_T_EOF;
23590da2b28SAriff Abdullah 					break;
23690da2b28SAriff Abdullah 				}
23790da2b28SAriff Abdullah 			}
23890da2b28SAriff Abdullah 		}
23990da2b28SAriff Abdullah 
24090da2b28SAriff Abdullah #define FEEDMATRIX_ATTN_SHIFT	16
24190da2b28SAriff Abdullah 
24290da2b28SAriff Abdullah 		if (j > 1) {
24390da2b28SAriff Abdullah 			/*
24490da2b28SAriff Abdullah 			 * XXX For channel that require accumulation from
24590da2b28SAriff Abdullah 			 * multiple channels, apply a slight attenuation to
24690da2b28SAriff Abdullah 			 * avoid clipping.
24790da2b28SAriff Abdullah 			 */
24890da2b28SAriff Abdullah 			mul   = (1 << (FEEDMATRIX_ATTN_SHIFT - 1)) + 143 - j;
24990da2b28SAriff Abdullah 			shift = FEEDMATRIX_ATTN_SHIFT;
25090da2b28SAriff Abdullah 			while ((mul & 1) == 0 && shift > 0) {
25190da2b28SAriff Abdullah 				mul >>= 1;
25290da2b28SAriff Abdullah 				shift--;
25390da2b28SAriff Abdullah 			}
25490da2b28SAriff Abdullah 			info->matrix[ch].mul   = mul;
25590da2b28SAriff Abdullah 			info->matrix[ch].shift = shift;
25690da2b28SAriff Abdullah 		}
25790da2b28SAriff Abdullah 	}
25890da2b28SAriff Abdullah 
25990da2b28SAriff Abdullah #ifndef _KERNEL
26090da2b28SAriff Abdullah 	fprintf(stderr, "Total: %d\n", ch);
26190da2b28SAriff Abdullah 
26290da2b28SAriff Abdullah 	for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF; i++) {
26390da2b28SAriff Abdullah 		fprintf(stderr, "%d: [", i);
26490da2b28SAriff Abdullah 		for (j = 0; info->matrix[i].chn[j] != SND_CHN_T_EOF; j++) {
26590da2b28SAriff Abdullah 			if (j != 0)
26690da2b28SAriff Abdullah 				fprintf(stderr, ", ");
26790da2b28SAriff Abdullah 			fprintf(stderr, "%d",
26890da2b28SAriff Abdullah 			    (info->matrix[i].chn[j] == SND_CHN_T_NULL) ?
26990da2b28SAriff Abdullah 			    0xffffffff : info->matrix[i].chn[j] / info->bps);
27090da2b28SAriff Abdullah 		}
27190da2b28SAriff Abdullah 		fprintf(stderr, "] attn: (x * %d) >> %d\n",
27290da2b28SAriff Abdullah 		    info->matrix[i].mul, info->matrix[i].shift);
27390da2b28SAriff Abdullah 	}
27490da2b28SAriff Abdullah #endif
27590da2b28SAriff Abdullah 
27690da2b28SAriff Abdullah 	return (0);
27790da2b28SAriff Abdullah }
27890da2b28SAriff Abdullah 
27990da2b28SAriff Abdullah static int
feed_matrix_init(struct pcm_feeder * f)28090da2b28SAriff Abdullah feed_matrix_init(struct pcm_feeder *f)
28190da2b28SAriff Abdullah {
28290da2b28SAriff Abdullah 	struct feed_matrix_info *info;
28390da2b28SAriff Abdullah 	struct pcmchan_matrix *m_in, *m_out;
28490da2b28SAriff Abdullah 	int ret;
28590da2b28SAriff Abdullah 
28690da2b28SAriff Abdullah 	if (AFMT_ENCODING(f->desc->in) != AFMT_ENCODING(f->desc->out))
28790da2b28SAriff Abdullah 		return (EINVAL);
28890da2b28SAriff Abdullah 
28990da2b28SAriff Abdullah 	info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO);
29090da2b28SAriff Abdullah 	if (info == NULL)
29190da2b28SAriff Abdullah 		return (ENOMEM);
29290da2b28SAriff Abdullah 
29390da2b28SAriff Abdullah 	info->in = f->desc->in;
29490da2b28SAriff Abdullah 	info->out = f->desc->out;
295ac24c9daSChristos Margiolis 	info->fmt = AFMT_ENCODING(info->in);
29690da2b28SAriff Abdullah 	info->bps = AFMT_BPS(info->in);
29790da2b28SAriff Abdullah 	info->ialign = AFMT_ALIGN(info->in);
29890da2b28SAriff Abdullah 	info->oalign = AFMT_ALIGN(info->out);
29990da2b28SAriff Abdullah 
30090da2b28SAriff Abdullah 	m_in  = feeder_matrix_format_map(info->in);
30190da2b28SAriff Abdullah 	m_out = feeder_matrix_format_map(info->out);
30290da2b28SAriff Abdullah 
30390da2b28SAriff Abdullah 	ret = feed_matrix_setup(info, m_in, m_out);
30490da2b28SAriff Abdullah 	if (ret != 0) {
30590da2b28SAriff Abdullah 		free(info, M_DEVBUF);
30690da2b28SAriff Abdullah 		return (ret);
30790da2b28SAriff Abdullah 	}
30890da2b28SAriff Abdullah 
30990da2b28SAriff Abdullah 	f->data = info;
31090da2b28SAriff Abdullah 
31190da2b28SAriff Abdullah 	return (0);
31290da2b28SAriff Abdullah }
31390da2b28SAriff Abdullah 
31490da2b28SAriff Abdullah static int
feed_matrix_free(struct pcm_feeder * f)31590da2b28SAriff Abdullah feed_matrix_free(struct pcm_feeder *f)
31690da2b28SAriff Abdullah {
31790da2b28SAriff Abdullah 	struct feed_matrix_info *info;
31890da2b28SAriff Abdullah 
31990da2b28SAriff Abdullah 	info = f->data;
32090da2b28SAriff Abdullah 	if (info != NULL)
32190da2b28SAriff Abdullah 		free(info, M_DEVBUF);
32290da2b28SAriff Abdullah 
32390da2b28SAriff Abdullah 	f->data = NULL;
32490da2b28SAriff Abdullah 
32590da2b28SAriff Abdullah 	return (0);
32690da2b28SAriff Abdullah }
32790da2b28SAriff Abdullah 
32890da2b28SAriff Abdullah static int
feed_matrix_feed(struct pcm_feeder * f,struct pcm_channel * c,uint8_t * b,uint32_t count,void * source)32990da2b28SAriff Abdullah feed_matrix_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
33090da2b28SAriff Abdullah     uint32_t count, void *source)
33190da2b28SAriff Abdullah {
33290da2b28SAriff Abdullah 	struct feed_matrix_info *info;
33390da2b28SAriff Abdullah 	uint32_t j, inmax;
33490da2b28SAriff Abdullah 	uint8_t *src, *dst;
33590da2b28SAriff Abdullah 
33690da2b28SAriff Abdullah 	info = f->data;
33790da2b28SAriff Abdullah 	if (info->matrix[0].chn[0] == SND_CHN_T_EOF)
33890da2b28SAriff Abdullah 		return (FEEDER_FEED(f->source, c, b, count, source));
33990da2b28SAriff Abdullah 
34090da2b28SAriff Abdullah 	dst = b;
34190da2b28SAriff Abdullah 	count = SND_FXROUND(count, info->oalign);
34290da2b28SAriff Abdullah 	inmax = info->ialign + info->oalign;
34390da2b28SAriff Abdullah 
34490da2b28SAriff Abdullah 	/*
34590da2b28SAriff Abdullah 	 * This loop might look simmilar to other feeder_* loops, but be
34690da2b28SAriff Abdullah 	 * advised: matrixing might involve overlapping (think about
34790da2b28SAriff Abdullah 	 * swapping end to front or something like that). In this regard it
34890da2b28SAriff Abdullah 	 * might be simmilar to feeder_format, but feeder_format works on
34990da2b28SAriff Abdullah 	 * 'sample' domain where it can be fitted into single 32bit integer
35090da2b28SAriff Abdullah 	 * while matrixing works on 'sample frame' domain.
35190da2b28SAriff Abdullah 	 */
35290da2b28SAriff Abdullah 	do {
35390da2b28SAriff Abdullah 		if (count < info->oalign)
35490da2b28SAriff Abdullah 			break;
35590da2b28SAriff Abdullah 
35690da2b28SAriff Abdullah 		if (count < inmax) {
35790da2b28SAriff Abdullah 			src = info->reservoir;
35890da2b28SAriff Abdullah 			j = info->ialign;
35990da2b28SAriff Abdullah 		} else {
36090da2b28SAriff Abdullah 			if (info->ialign == info->oalign)
36190da2b28SAriff Abdullah 				j = count - info->oalign;
36290da2b28SAriff Abdullah 			else if (info->ialign > info->oalign)
36390da2b28SAriff Abdullah 				j = SND_FXROUND(count - info->oalign,
36490da2b28SAriff Abdullah 				    info->ialign);
36590da2b28SAriff Abdullah 			else
36690da2b28SAriff Abdullah 				j = (SND_FXDIV(count, info->oalign) - 1) *
36790da2b28SAriff Abdullah 				    info->ialign;
36890da2b28SAriff Abdullah 			src = dst + count - j;
36990da2b28SAriff Abdullah 		}
37090da2b28SAriff Abdullah 
37190da2b28SAriff Abdullah 		j = SND_FXDIV(FEEDER_FEED(f->source, c, src, j, source),
37290da2b28SAriff Abdullah 		    info->ialign);
37390da2b28SAriff Abdullah 		if (j == 0)
37490da2b28SAriff Abdullah 			break;
37590da2b28SAriff Abdullah 
376ac24c9daSChristos Margiolis 		/* Optimize some common formats. */
377ac24c9daSChristos Margiolis 		switch (info->fmt) {
378ac24c9daSChristos Margiolis 		case AFMT_S16_NE:
379ac24c9daSChristos Margiolis 			feed_matrix_apply(info, src, dst, j, AFMT_S16_NE);
380ac24c9daSChristos Margiolis 			break;
381ac24c9daSChristos Margiolis 		case AFMT_S24_NE:
382ac24c9daSChristos Margiolis 			feed_matrix_apply(info, src, dst, j, AFMT_S24_NE);
383ac24c9daSChristos Margiolis 			break;
384ac24c9daSChristos Margiolis 		case AFMT_S32_NE:
385ac24c9daSChristos Margiolis 			feed_matrix_apply(info, src, dst, j, AFMT_S32_NE);
386ac24c9daSChristos Margiolis 			break;
387ac24c9daSChristos Margiolis 		default:
388ac24c9daSChristos Margiolis 			feed_matrix_apply(info, src, dst, j, info->fmt);
389ac24c9daSChristos Margiolis 			break;
390ac24c9daSChristos Margiolis 		}
39190da2b28SAriff Abdullah 
39290da2b28SAriff Abdullah 		j *= info->oalign;
39390da2b28SAriff Abdullah 		dst += j;
39490da2b28SAriff Abdullah 		count -= j;
39590da2b28SAriff Abdullah 
39690da2b28SAriff Abdullah 	} while (count != 0);
39790da2b28SAriff Abdullah 
39890da2b28SAriff Abdullah 	return (dst - b);
39990da2b28SAriff Abdullah }
40090da2b28SAriff Abdullah 
40190da2b28SAriff Abdullah static struct pcm_feederdesc feeder_matrix_desc[] = {
40290da2b28SAriff Abdullah 	{ FEEDER_MATRIX, 0, 0, 0, 0 },
40390da2b28SAriff Abdullah 	{ 0, 0, 0, 0, 0 }
40490da2b28SAriff Abdullah };
40590da2b28SAriff Abdullah 
40690da2b28SAriff Abdullah static kobj_method_t feeder_matrix_methods[] = {
40790da2b28SAriff Abdullah 	KOBJMETHOD(feeder_init,		feed_matrix_init),
40890da2b28SAriff Abdullah 	KOBJMETHOD(feeder_free,		feed_matrix_free),
40990da2b28SAriff Abdullah 	KOBJMETHOD(feeder_feed,		feed_matrix_feed),
41090da2b28SAriff Abdullah 	KOBJMETHOD_END
41190da2b28SAriff Abdullah };
41290da2b28SAriff Abdullah 
41390da2b28SAriff Abdullah FEEDER_DECLARE(feeder_matrix, NULL);
41490da2b28SAriff Abdullah 
41590da2b28SAriff Abdullah /* External */
41690da2b28SAriff Abdullah int
feeder_matrix_setup(struct pcm_feeder * f,struct pcmchan_matrix * m_in,struct pcmchan_matrix * m_out)41790da2b28SAriff Abdullah feeder_matrix_setup(struct pcm_feeder *f, struct pcmchan_matrix *m_in,
41890da2b28SAriff Abdullah     struct pcmchan_matrix *m_out)
41990da2b28SAriff Abdullah {
42090da2b28SAriff Abdullah 
42190da2b28SAriff Abdullah 	if (f == NULL || f->desc == NULL || f->desc->type != FEEDER_MATRIX ||
42290da2b28SAriff Abdullah 	    f->data == NULL)
42390da2b28SAriff Abdullah 		return (EINVAL);
42490da2b28SAriff Abdullah 
42590da2b28SAriff Abdullah 	return (feed_matrix_setup(f->data, m_in, m_out));
42690da2b28SAriff Abdullah }
42790da2b28SAriff Abdullah 
42890da2b28SAriff Abdullah /*
42990da2b28SAriff Abdullah  * feeder_matrix_default_id(): For a given number of channels, return
430513ee901SGordon Bergling  *                             default preferred id (example: both 5.1 and
43190da2b28SAriff Abdullah  *                             6.0 are simply 6 channels, but 5.1 is more
43290da2b28SAriff Abdullah  *                             preferable).
43390da2b28SAriff Abdullah  */
43490da2b28SAriff Abdullah int
feeder_matrix_default_id(uint32_t ch)43590da2b28SAriff Abdullah feeder_matrix_default_id(uint32_t ch)
43690da2b28SAriff Abdullah {
43790da2b28SAriff Abdullah 
43890da2b28SAriff Abdullah 	if (ch < feeder_matrix_maps[SND_CHN_MATRIX_BEGIN].channels ||
43990da2b28SAriff Abdullah 	    ch > feeder_matrix_maps[SND_CHN_MATRIX_END].channels)
44090da2b28SAriff Abdullah 		return (SND_CHN_MATRIX_UNKNOWN);
44190da2b28SAriff Abdullah 
44290da2b28SAriff Abdullah 	return (feeder_matrix_maps[feeder_matrix_default_ids[ch]].id);
44390da2b28SAriff Abdullah }
44490da2b28SAriff Abdullah 
44590da2b28SAriff Abdullah /*
44690da2b28SAriff Abdullah  * feeder_matrix_default_channel_map(): Ditto, but return matrix map
44790da2b28SAriff Abdullah  *                                      instead.
44890da2b28SAriff Abdullah  */
44990da2b28SAriff Abdullah struct pcmchan_matrix *
feeder_matrix_default_channel_map(uint32_t ch)45090da2b28SAriff Abdullah feeder_matrix_default_channel_map(uint32_t ch)
45190da2b28SAriff Abdullah {
45290da2b28SAriff Abdullah 
45390da2b28SAriff Abdullah 	if (ch < feeder_matrix_maps[SND_CHN_MATRIX_BEGIN].channels ||
45490da2b28SAriff Abdullah 	    ch > feeder_matrix_maps[SND_CHN_MATRIX_END].channels)
45590da2b28SAriff Abdullah 		return (NULL);
45690da2b28SAriff Abdullah 
45790da2b28SAriff Abdullah 	return (&feeder_matrix_maps[feeder_matrix_default_ids[ch]]);
45890da2b28SAriff Abdullah }
45990da2b28SAriff Abdullah 
46090da2b28SAriff Abdullah /*
46190da2b28SAriff Abdullah  * feeder_matrix_default_format(): For a given audio format, return the
46290da2b28SAriff Abdullah  *                                 proper audio format based on preferable
46390da2b28SAriff Abdullah  *                                 matrix.
46490da2b28SAriff Abdullah  */
46590da2b28SAriff Abdullah uint32_t
feeder_matrix_default_format(uint32_t format)46690da2b28SAriff Abdullah feeder_matrix_default_format(uint32_t format)
46790da2b28SAriff Abdullah {
46890da2b28SAriff Abdullah 	struct pcmchan_matrix *m;
46990da2b28SAriff Abdullah 	uint32_t i, ch, ext;
47090da2b28SAriff Abdullah 
47190da2b28SAriff Abdullah 	ch = AFMT_CHANNEL(format);
47290da2b28SAriff Abdullah 	ext = AFMT_EXTCHANNEL(format);
47390da2b28SAriff Abdullah 
47490da2b28SAriff Abdullah 	if (ext != 0) {
47590da2b28SAriff Abdullah 		for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
47690da2b28SAriff Abdullah 			if (feeder_matrix_maps[i].channels == ch &&
47790da2b28SAriff Abdullah 			    feeder_matrix_maps[i].ext == ext)
47890da2b28SAriff Abdullah 			return (SND_FORMAT(format, ch, ext));
47990da2b28SAriff Abdullah 		}
48090da2b28SAriff Abdullah 	}
48190da2b28SAriff Abdullah 
48290da2b28SAriff Abdullah 	m = feeder_matrix_default_channel_map(ch);
48390da2b28SAriff Abdullah 	if (m == NULL)
48490da2b28SAriff Abdullah 		return (0x00000000);
48590da2b28SAriff Abdullah 
48690da2b28SAriff Abdullah 	return (SND_FORMAT(format, ch, m->ext));
48790da2b28SAriff Abdullah }
48890da2b28SAriff Abdullah 
48990da2b28SAriff Abdullah /*
49090da2b28SAriff Abdullah  * feeder_matrix_format_id(): For a given audio format, return its matrix
49190da2b28SAriff Abdullah  *                            id.
49290da2b28SAriff Abdullah  */
49390da2b28SAriff Abdullah int
feeder_matrix_format_id(uint32_t format)49490da2b28SAriff Abdullah feeder_matrix_format_id(uint32_t format)
49590da2b28SAriff Abdullah {
49690da2b28SAriff Abdullah 	uint32_t i, ch, ext;
49790da2b28SAriff Abdullah 
49890da2b28SAriff Abdullah 	ch = AFMT_CHANNEL(format);
49990da2b28SAriff Abdullah 	ext = AFMT_EXTCHANNEL(format);
50090da2b28SAriff Abdullah 
50190da2b28SAriff Abdullah 	for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
50290da2b28SAriff Abdullah 		if (feeder_matrix_maps[i].channels == ch &&
50390da2b28SAriff Abdullah 		    feeder_matrix_maps[i].ext == ext)
50490da2b28SAriff Abdullah 			return (feeder_matrix_maps[i].id);
50590da2b28SAriff Abdullah 	}
50690da2b28SAriff Abdullah 
50790da2b28SAriff Abdullah 	return (SND_CHN_MATRIX_UNKNOWN);
50890da2b28SAriff Abdullah }
50990da2b28SAriff Abdullah 
51090da2b28SAriff Abdullah /*
51190da2b28SAriff Abdullah  * feeder_matrix_format_map(): For a given audio format, return its matrix
51290da2b28SAriff Abdullah  *                             map.
51390da2b28SAriff Abdullah  */
51490da2b28SAriff Abdullah struct pcmchan_matrix *
feeder_matrix_format_map(uint32_t format)51590da2b28SAriff Abdullah feeder_matrix_format_map(uint32_t format)
51690da2b28SAriff Abdullah {
51790da2b28SAriff Abdullah 	uint32_t i, ch, ext;
51890da2b28SAriff Abdullah 
51990da2b28SAriff Abdullah 	ch = AFMT_CHANNEL(format);
52090da2b28SAriff Abdullah 	ext = AFMT_EXTCHANNEL(format);
52190da2b28SAriff Abdullah 
52290da2b28SAriff Abdullah 	for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
52390da2b28SAriff Abdullah 		if (feeder_matrix_maps[i].channels == ch &&
52490da2b28SAriff Abdullah 		    feeder_matrix_maps[i].ext == ext)
52590da2b28SAriff Abdullah 			return (&feeder_matrix_maps[i]);
52690da2b28SAriff Abdullah 	}
52790da2b28SAriff Abdullah 
52890da2b28SAriff Abdullah 	return (NULL);
52990da2b28SAriff Abdullah }
53090da2b28SAriff Abdullah 
53190da2b28SAriff Abdullah /*
53290da2b28SAriff Abdullah  * feeder_matrix_id_map(): For a given matrix id, return its matrix map.
53390da2b28SAriff Abdullah  */
53490da2b28SAriff Abdullah struct pcmchan_matrix *
feeder_matrix_id_map(int id)53590da2b28SAriff Abdullah feeder_matrix_id_map(int id)
53690da2b28SAriff Abdullah {
53790da2b28SAriff Abdullah 
53890da2b28SAriff Abdullah 	if (id < SND_CHN_MATRIX_BEGIN || id > SND_CHN_MATRIX_END)
53990da2b28SAriff Abdullah 		return (NULL);
54090da2b28SAriff Abdullah 
54190da2b28SAriff Abdullah 	return (&feeder_matrix_maps[id]);
54290da2b28SAriff Abdullah }
54390da2b28SAriff Abdullah 
54490da2b28SAriff Abdullah /*
54590da2b28SAriff Abdullah  * feeder_matrix_compare(): Compare the simmilarities of matrices.
54690da2b28SAriff Abdullah  */
54790da2b28SAriff Abdullah int
feeder_matrix_compare(struct pcmchan_matrix * m_in,struct pcmchan_matrix * m_out)54890da2b28SAriff Abdullah feeder_matrix_compare(struct pcmchan_matrix *m_in, struct pcmchan_matrix *m_out)
54990da2b28SAriff Abdullah {
55090da2b28SAriff Abdullah 	uint32_t i;
55190da2b28SAriff Abdullah 
55290da2b28SAriff Abdullah 	if (m_in == m_out)
55390da2b28SAriff Abdullah 		return (0);
55490da2b28SAriff Abdullah 
55590da2b28SAriff Abdullah 	if (m_in->channels != m_out->channels || m_in->ext != m_out->ext ||
55690da2b28SAriff Abdullah 	    m_in->mask != m_out->mask)
55790da2b28SAriff Abdullah 		return (1);
55890da2b28SAriff Abdullah 
559c597c557SChristos Margiolis 	for (i = 0; i < nitems(m_in->map); i++) {
56090da2b28SAriff Abdullah 		if (m_in->map[i].type != m_out->map[i].type)
56190da2b28SAriff Abdullah 			return (1);
56290da2b28SAriff Abdullah 		if (m_in->map[i].type == SND_CHN_T_MAX)
56390da2b28SAriff Abdullah 			break;
56490da2b28SAriff Abdullah 		if (m_in->map[i].members != m_out->map[i].members)
56590da2b28SAriff Abdullah 			return (1);
56690da2b28SAriff Abdullah 		if (i <= SND_CHN_T_END) {
56790da2b28SAriff Abdullah 			if (m_in->offset[m_in->map[i].type] !=
56890da2b28SAriff Abdullah 			    m_out->offset[m_out->map[i].type])
56990da2b28SAriff Abdullah 				return (1);
57090da2b28SAriff Abdullah 		}
57190da2b28SAriff Abdullah 	}
57290da2b28SAriff Abdullah 
57390da2b28SAriff Abdullah 	return (0);
57490da2b28SAriff Abdullah }
57590da2b28SAriff Abdullah 
57690da2b28SAriff Abdullah /*
57758d868c8SGordon Bergling  * XXX 4front interpretation of "surround" is ambigous and sort of
57890da2b28SAriff Abdullah  *     conflicting with "rear"/"back". Map it to "side". Well..
57990da2b28SAriff Abdullah  *     who cares?
58090da2b28SAriff Abdullah  */
58190da2b28SAriff Abdullah static int snd_chn_to_oss[SND_CHN_T_MAX] = {
58290da2b28SAriff Abdullah 	[SND_CHN_T_FL] = CHID_L,
58390da2b28SAriff Abdullah 	[SND_CHN_T_FR] = CHID_R,
58490da2b28SAriff Abdullah 	[SND_CHN_T_FC] = CHID_C,
58590da2b28SAriff Abdullah 	[SND_CHN_T_LF] = CHID_LFE,
58690da2b28SAriff Abdullah 	[SND_CHN_T_SL] = CHID_LS,
58790da2b28SAriff Abdullah 	[SND_CHN_T_SR] = CHID_RS,
58890da2b28SAriff Abdullah 	[SND_CHN_T_BL] = CHID_LR,
58990da2b28SAriff Abdullah 	[SND_CHN_T_BR] = CHID_RR
59090da2b28SAriff Abdullah };
59190da2b28SAriff Abdullah 
59290da2b28SAriff Abdullah #define SND_CHN_OSS_VALIDMASK						\
59390da2b28SAriff Abdullah 			(SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR |	\
59490da2b28SAriff Abdullah 			 SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF |	\
59590da2b28SAriff Abdullah 			 SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR |	\
59690da2b28SAriff Abdullah 			 SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR)
59790da2b28SAriff Abdullah 
59890da2b28SAriff Abdullah #define SND_CHN_OSS_MAX		8
59990da2b28SAriff Abdullah #define SND_CHN_OSS_BEGIN	CHID_L
60090da2b28SAriff Abdullah #define SND_CHN_OSS_END		CHID_RR
60190da2b28SAriff Abdullah 
60290da2b28SAriff Abdullah static int oss_to_snd_chn[SND_CHN_OSS_END + 1] = {
60390da2b28SAriff Abdullah 	[CHID_L]   = SND_CHN_T_FL,
60490da2b28SAriff Abdullah 	[CHID_R]   = SND_CHN_T_FR,
60590da2b28SAriff Abdullah 	[CHID_C]   = SND_CHN_T_FC,
60690da2b28SAriff Abdullah 	[CHID_LFE] = SND_CHN_T_LF,
60790da2b28SAriff Abdullah 	[CHID_LS]  = SND_CHN_T_SL,
60890da2b28SAriff Abdullah 	[CHID_RS]  = SND_CHN_T_SR,
60990da2b28SAriff Abdullah 	[CHID_LR]  = SND_CHN_T_BL,
61090da2b28SAriff Abdullah 	[CHID_RR]  = SND_CHN_T_BR
61190da2b28SAriff Abdullah };
61290da2b28SAriff Abdullah 
61390da2b28SAriff Abdullah /*
61490da2b28SAriff Abdullah  * Used by SNDCTL_DSP_GET_CHNORDER.
61590da2b28SAriff Abdullah  */
61690da2b28SAriff Abdullah int
feeder_matrix_oss_get_channel_order(struct pcmchan_matrix * m,unsigned long long * map)61790da2b28SAriff Abdullah feeder_matrix_oss_get_channel_order(struct pcmchan_matrix *m,
61890da2b28SAriff Abdullah     unsigned long long *map)
61990da2b28SAriff Abdullah {
62090da2b28SAriff Abdullah 	unsigned long long tmpmap;
62190da2b28SAriff Abdullah 	uint32_t i;
62290da2b28SAriff Abdullah 
62390da2b28SAriff Abdullah 	if (m == NULL || map == NULL || (m->mask & ~SND_CHN_OSS_VALIDMASK) ||
62490da2b28SAriff Abdullah 	    m->channels > SND_CHN_OSS_MAX)
62590da2b28SAriff Abdullah 		return (EINVAL);
62690da2b28SAriff Abdullah 
62790da2b28SAriff Abdullah 	tmpmap = 0x0000000000000000ULL;
62890da2b28SAriff Abdullah 
629336c5fb5SPedro F. Giffuni 	for (i = 0; i < SND_CHN_OSS_MAX && m->map[i].type != SND_CHN_T_MAX;
630336c5fb5SPedro F. Giffuni 	    i++) {
63190da2b28SAriff Abdullah 		if ((1 << m->map[i].type) & ~SND_CHN_OSS_VALIDMASK)
63290da2b28SAriff Abdullah 			return (EINVAL);
63390da2b28SAriff Abdullah 		tmpmap |=
63490da2b28SAriff Abdullah 		    (unsigned long long)snd_chn_to_oss[m->map[i].type] <<
63590da2b28SAriff Abdullah 		    (i * 4);
63690da2b28SAriff Abdullah 	}
63790da2b28SAriff Abdullah 
63890da2b28SAriff Abdullah 	*map = tmpmap;
63990da2b28SAriff Abdullah 
64090da2b28SAriff Abdullah 	return (0);
64190da2b28SAriff Abdullah }
64290da2b28SAriff Abdullah 
64390da2b28SAriff Abdullah /*
64490da2b28SAriff Abdullah  * Used by SNDCTL_DSP_SET_CHNORDER.
64590da2b28SAriff Abdullah  */
64690da2b28SAriff Abdullah int
feeder_matrix_oss_set_channel_order(struct pcmchan_matrix * m,unsigned long long * map)64790da2b28SAriff Abdullah feeder_matrix_oss_set_channel_order(struct pcmchan_matrix *m,
64890da2b28SAriff Abdullah     unsigned long long *map)
64990da2b28SAriff Abdullah {
65090da2b28SAriff Abdullah 	struct pcmchan_matrix tmp;
65190da2b28SAriff Abdullah 	uint32_t chmask, i;
65290da2b28SAriff Abdullah 	int ch, cheof;
65390da2b28SAriff Abdullah 
65490da2b28SAriff Abdullah 	if (m == NULL || map == NULL || (m->mask & ~SND_CHN_OSS_VALIDMASK) ||
65590da2b28SAriff Abdullah 	    m->channels > SND_CHN_OSS_MAX || (*map & 0xffffffff00000000ULL))
65690da2b28SAriff Abdullah 		return (EINVAL);
65790da2b28SAriff Abdullah 
65890da2b28SAriff Abdullah 	tmp = *m;
65990da2b28SAriff Abdullah 	tmp.channels = 0;
66090da2b28SAriff Abdullah 	tmp.ext = 0;
66190da2b28SAriff Abdullah 	tmp.mask = 0;
66290da2b28SAriff Abdullah 	memset(tmp.offset, -1, sizeof(tmp.offset));
66390da2b28SAriff Abdullah 	cheof = 0;
66490da2b28SAriff Abdullah 
66590da2b28SAriff Abdullah 	for (i = 0; i < SND_CHN_OSS_MAX; i++) {
66690da2b28SAriff Abdullah 		ch = (*map >> (i * 4)) & 0xf;
66790da2b28SAriff Abdullah 		if (ch < SND_CHN_OSS_BEGIN) {
66890da2b28SAriff Abdullah 			if (cheof == 0 && m->map[i].type != SND_CHN_T_MAX)
66990da2b28SAriff Abdullah 				return (EINVAL);
67090da2b28SAriff Abdullah 			cheof++;
67190da2b28SAriff Abdullah 			tmp.map[i] = m->map[i];
67290da2b28SAriff Abdullah 			continue;
67390da2b28SAriff Abdullah 		} else if (ch > SND_CHN_OSS_END)
67490da2b28SAriff Abdullah 			return (EINVAL);
67590da2b28SAriff Abdullah 		else if (cheof != 0)
67690da2b28SAriff Abdullah 			return (EINVAL);
67790da2b28SAriff Abdullah 		ch = oss_to_snd_chn[ch];
67890da2b28SAriff Abdullah 		chmask = 1 << ch;
67990da2b28SAriff Abdullah 		/* channel not exist in matrix */
68090da2b28SAriff Abdullah 		if (!(chmask & m->mask))
68190da2b28SAriff Abdullah 			return (EINVAL);
68290da2b28SAriff Abdullah 		/* duplicated channel */
68390da2b28SAriff Abdullah 		if (chmask & tmp.mask)
68490da2b28SAriff Abdullah 			return (EINVAL);
68590da2b28SAriff Abdullah 		tmp.map[i] = m->map[m->offset[ch]];
68690da2b28SAriff Abdullah 		if (tmp.map[i].type != ch)
68790da2b28SAriff Abdullah 			return (EINVAL);
68890da2b28SAriff Abdullah 		tmp.offset[ch] = i;
68990da2b28SAriff Abdullah 		tmp.mask |= chmask;
69090da2b28SAriff Abdullah 		tmp.channels++;
69190da2b28SAriff Abdullah 		if (chmask & SND_CHN_T_MASK_LF)
69290da2b28SAriff Abdullah 			tmp.ext++;
69390da2b28SAriff Abdullah 	}
69490da2b28SAriff Abdullah 
69590da2b28SAriff Abdullah 	if (tmp.channels != m->channels || tmp.ext != m->ext ||
69690da2b28SAriff Abdullah 	    tmp.mask != m->mask ||
69790da2b28SAriff Abdullah 	    tmp.map[m->channels].type != SND_CHN_T_MAX)
69890da2b28SAriff Abdullah 		return (EINVAL);
69990da2b28SAriff Abdullah 
70090da2b28SAriff Abdullah 	*m = tmp;
70190da2b28SAriff Abdullah 
70290da2b28SAriff Abdullah 	return (0);
70390da2b28SAriff Abdullah }
704