summaryrefslogtreecommitdiff
path: root/kernel/framework/audio/oss_spdif.c
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2013-05-03 21:08:42 +0400
committerIgor Pashev <pashev.igor@gmail.com>2013-05-03 21:08:42 +0400
commit1058def8e7827e56ce4a70afb4aeacb5dc44148f (patch)
tree4495d23e7b54ab5700e3839081e797c1eafe0db9 /kernel/framework/audio/oss_spdif.c
downloadoss4-1058def8e7827e56ce4a70afb4aeacb5dc44148f.tar.gz
Imported Upstream version 4.2-build2006upstream/4.2-build2006upstream
Diffstat (limited to 'kernel/framework/audio/oss_spdif.c')
-rw-r--r--kernel/framework/audio/oss_spdif.c627
1 files changed, 627 insertions, 0 deletions
diff --git a/kernel/framework/audio/oss_spdif.c b/kernel/framework/audio/oss_spdif.c
new file mode 100644
index 0000000..9b8c312
--- /dev/null
+++ b/kernel/framework/audio/oss_spdif.c
@@ -0,0 +1,627 @@
+/*
+ * Purpose: S/PDIF (IEC958) control bit and mixer extension management
+ *
+ * This code is used by some drivers to handle S/PDIF specific control
+ * functions (see {!xlink spdif_control} for more info.
+ */
+/*
+ *
+ * This file is part of Open Sound System.
+ *
+ * Copyright (C) 4Front Technologies 1996-2008.
+ *
+ * This this source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ */
+
+#include "oss_config.h"
+#include "spdif.h"
+
+#define MAX_DEV 10
+static int ndevs = 0;
+static spdif_devc *devc_list[MAX_DEV] = { 0 };
+
+static void
+init_ctl (spdif_devc * devc, oss_digital_control * ctl)
+{
+ memset (ctl, 0, sizeof (*ctl));
+
+ ctl->caps = devc->caps;
+}
+
+static void
+block_mixer (spdif_devc * devc)
+{
+ devc->mixer_blocked = 1;
+ memcpy (&devc->cold_ctl, &devc->hot_ctl, sizeof (devc->hot_ctl));
+ devc->ctl = &devc->cold_ctl;
+
+}
+
+static void
+unblock_mixer (spdif_devc * devc)
+{
+ memcpy (&devc->hot_ctl, &devc->cold_ctl, sizeof (devc->hot_ctl));
+ devc->ctl = &devc->hot_ctl;
+ devc->mixer_blocked = 0;
+}
+
+int
+oss_spdif_install (spdif_devc * devc, oss_device_t * osdev,
+ spdif_driver_t * d, int driver_size, void *host_devc,
+ void *host_portc, int mixer_dev, int flags,
+ unsigned int caps)
+{
+ int i, pos;
+
+ if (devc->is_ok)
+ {
+ /* cmn_err (CE_WARN, "spdif: Bad usage - double init\n"); */
+ return 0;
+ }
+
+ if (d == NULL || driver_size != sizeof (spdif_driver_t))
+ {
+ cmn_err (CE_WARN, "spdif: Bad driver\n");
+ return OSS_EIO;
+ }
+
+ memset (devc, 0, sizeof (*devc));
+
+ devc->open_mode = 0;
+ devc->osdev = osdev;
+ MUTEX_INIT (devc->osdev, devc->mutex, MH_FRAMEW + 2);
+ devc->d = d;
+ devc->flags = flags;
+ devc->host_devc = host_devc;
+ devc->host_portc = host_portc;
+ devc->mixer_dev = mixer_dev;
+ devc->ctl = &devc->hot_ctl;
+ devc->caps = caps;
+ devc->is_ok = 1;
+
+ pos=-1;
+
+ for (i=0;pos==-1 && i<ndevs;i++)
+ if (devc_list[i]==NULL)
+ {
+ pos = i;
+ }
+
+ if (pos==-1)
+ {
+ if (ndevs >= MAX_DEV)
+ {
+ cmn_err(CE_WARN, "Too many S/PDIF devices\n");
+ return OSS_EBUSY;
+ }
+
+ pos=ndevs++;
+ }
+
+ devc_list[pos] = devc;
+
+ init_ctl (devc, &devc->hot_ctl);
+ init_ctl (devc, &devc->cold_ctl);
+
+ return 0;
+}
+
+void
+oss_spdif_uninstall (spdif_devc * devc)
+{
+ int i;
+
+ for (i=0;i<ndevs;i++)
+ if (devc_list[i]==devc)
+ devc_list[i]=NULL;
+}
+
+int
+oss_spdif_open (spdif_devc * devc, int open_mode)
+{
+ oss_native_word flags;
+ flags = 0;
+ if (!devc->is_ok)
+ {
+ cmn_err (CE_WARN, "spdif: Bad usage - open\n");
+ return OSS_EIO;
+ }
+ MUTEX_ENTER (devc->mutex, flags);
+ devc->open_mode |= open_mode;
+ MUTEX_EXIT (devc->mutex, flags);
+ return 0;
+}
+
+void
+oss_spdif_close (spdif_devc * devc, int open_mode)
+{
+ oss_native_word flags;
+ flags = 0;
+ if (!devc->is_ok)
+ {
+ cmn_err (CE_WARN, "spdif: Bad usage - close\n");
+ return;
+ }
+ MUTEX_ENTER (devc->mutex, flags);
+ devc->open_mode &= ~open_mode;
+
+ if (devc->mixer_blocked && (open_mode & OPEN_WRITE))
+ {
+ unblock_mixer (devc);
+ }
+ MUTEX_EXIT (devc->mutex, flags);
+}
+
+static void
+update_rate_bits (spdif_devc * devc, int rate)
+{
+ unsigned char spd;
+/*
+ * Set S/PDIF rate control (TODO: Pro mode)
+ */
+
+ devc->hot_ctl.srate_out = rate;
+
+ switch (rate)
+ {
+ case 48000:
+ spd = 2;
+ break;
+ case 32000:
+ spd = 3;
+ break;
+ case 88200:
+ spd = 4;
+ break;
+ case 96000:
+ spd = 5;
+ break;
+ case 176400:
+ spd = 7;
+ break;
+ case 192000:
+ spd = 6;
+ break;
+ default:
+ spd = 0;
+ rate = 44100;
+ }
+
+ devc->hot_ctl.rate_bits = spd;
+
+ devc->hot_ctl.cbitout[3] &= ~0x0f;
+ devc->hot_ctl.cbitout[3] |= spd & 0x0f;
+ devc->hot_ctl.srate_out = rate;
+}
+
+void
+oss_spdif_setrate (spdif_devc * devc, int rate)
+{
+ oss_native_word flags;
+ flags = 0;
+ if (!devc->is_ok)
+ {
+ cmn_err (CE_WARN, "spdif: Bad usage - setrate\n");
+ return;
+ }
+ MUTEX_ENTER (devc->mutex, flags);
+
+ update_rate_bits (devc, rate);
+
+ if (devc->d->reprogram_device)
+ devc->d->reprogram_device (devc->host_devc, devc->host_portc,
+ &devc->hot_ctl, REPGM_RATE | REPGM_CBIT3);
+ MUTEX_EXIT (devc->mutex, flags);
+}
+
+static int spdif_mix_init (spdif_devc * devc);
+
+/*ARGSUSED*/
+static void
+switch_mode (spdif_devc * devc, oss_digital_control * ctl, int smurf)
+{
+ ctl->pro_mode = ctl->cbitout[0] & 0x01;
+
+ if (ctl == devc->ctl) /* Visible to mixer */
+ spdif_mix_init (devc); /* Re-create mixer extensions */
+}
+
+static void
+update_hardware (spdif_devc * devc, int repgm)
+{
+ if (repgm & REPGM_RATE)
+ update_rate_bits (devc, devc->hot_ctl.srate_out);
+ if (devc->d->reprogram_device)
+ devc->d->reprogram_device (devc->host_devc, devc->host_portc,
+ &devc->hot_ctl, repgm);
+}
+
+static int
+readctl (spdif_devc * devc, oss_digital_control * c, int open_mode)
+{
+ int valid = c->valid & ~(VAL_OUTMASK);
+ int ret = 0;
+ oss_native_word flags;
+ flags = 0;
+
+ MUTEX_ENTER (devc->mutex, flags);
+
+ memcpy (c, &devc->hot_ctl, sizeof (devc->hot_ctl));
+ memset (c->filler, 0, sizeof (c->filler)); /* Hide internal variables */
+ c->valid &= (VAL_OUTMASK);
+
+ if (open_mode & OPEN_READ)
+ {
+ if (devc->d->get_status)
+ ret = devc->d->get_status (devc->host_devc, devc->host_portc,
+ c, valid);
+ else
+ {
+ if (valid != 0) /* Can't get input status */
+ ret = OSS_EIO;
+ }
+ }
+
+ MUTEX_EXIT (devc->mutex, flags);
+ return ret;
+}
+
+/*ARGSUSED*/
+static int
+handle_request (spdif_devc * devc, oss_digital_control * c)
+{
+ return OSS_EIO;
+}
+
+static int
+writectl (spdif_devc * devc, oss_digital_control * c)
+{
+ int valid = c->valid;
+ int repgm = 0;
+ int err = 0;
+ oss_native_word flags;
+ flags = 0;
+
+ MUTEX_ENTER (devc->mutex, flags);
+ if (!devc->mixer_blocked)
+ block_mixer (devc);
+ c->valid = 0;
+ c->caps = devc->caps;
+
+ if ((valid & VAL_CBITOUT) &&
+ ((devc->caps & DIG_CBITOUT_MASK) != DIG_CBITOUT_NONE))
+ {
+ int smurf;
+
+ smurf = (devc->hot_ctl.cbitout[0] ^ c->cbitout[0]) & 1;
+ c->valid |= VAL_CBITOUT;
+ repgm |= REPGM_CBITALL;
+
+ if (smurf) /* Mode changet CONSUMER<->PRO */
+ {
+ repgm |= REPGM_ALL;
+ switch_mode (devc, &devc->hot_ctl, 0);
+ }
+
+ memcpy (&devc->hot_ctl.cbitout, &c->cbitout, sizeof (c->cbitout));
+ }
+
+ if ((valid & VAL_UBITOUT) && (devc->caps & DIG_UBITOUT))
+ {
+ c->valid |= VAL_UBITOUT;
+ repgm |= REPGM_UBITALL;
+ memcpy (&devc->hot_ctl.ubitout, &c->ubitout, sizeof (c->ubitout));
+ }
+
+ if ((c->out_vbit != VBIT_NOT_INDICATED) && (devc->caps & DIG_VBITOUT))
+ {
+ devc->hot_ctl.out_vbit = c->out_vbit;
+ repgm |= REPGM_VBIT;
+ }
+ else
+ c->out_vbit = VBIT_NOT_INDICATED;
+ c->in_vbit = VBIT_NOT_INDICATED;
+
+ devc->hot_ctl.valid = valid;
+
+ if ((valid & VAL_ORATE))
+ {
+ /* TODO: Check the rate */
+ update_rate_bits (devc, c->srate_out);
+ c->valid |= VAL_ORATE;
+ c->srate_out = devc->hot_ctl.srate_out;
+ }
+
+ if (valid & VAL_REQUEST)
+ {
+ err = handle_request (devc, c);
+ if (err >= 0)
+ c->valid |= VAL_REQUEST;
+ }
+
+ MUTEX_EXIT (devc->mutex, flags);
+ return err;
+}
+
+int
+oss_spdif_ioctl (spdif_devc * devc, int open_mode, unsigned int cmd,
+ ioctl_arg arg)
+{
+ if (!devc->is_ok)
+ {
+ cmn_err (CE_WARN, "spdif: Bad usage - ioctl\n");
+ return SPDIF_NOIOCTL; /* Not for me */
+ }
+
+ switch (cmd)
+ {
+ case SNDCTL_DSP_WRITECTL:
+ if (!(open_mode & OPEN_WRITE)) /* Not opened for output */
+ return OSS_ENOTSUP;
+ if (!(devc->flags & SPDF_OUT))
+ return OSS_ENOTSUP;
+ return writectl (devc, (oss_digital_control *) arg);
+ break;
+
+ case SNDCTL_DSP_READCTL:
+ if (!(devc->flags & (SPDF_OUT | SPDF_IN)))
+ return OSS_ENOTSUP;
+ return readctl (devc, (oss_digital_control *) arg, open_mode);
+ break;
+ }
+
+ return SPDIF_NOIOCTL; /* Not for me too */
+}
+
+static int
+spdif_set_control (spdif_devc * devc, int ctrl, unsigned int cmd, int value)
+{
+ int val;
+
+ if (cmd == SNDCTL_MIX_READ)
+ {
+ switch (ctrl)
+ {
+ case 1: /* spdif.enable (OFF/ON) */
+ return !!(devc->ctl->outsel & OUTSEL_DIGITAL);
+ break;
+
+ case 2: /* spdif.mode (CONSUMER|PRO) */
+ return !!(devc->ctl->cbitout[0] & 1);
+ break;
+
+ case 3: /* spdif.audio (AUDIO/DATA) */
+ return !!(devc->ctl->cbitout[0] & 2);
+ break;
+
+ case 4: /* spdif.vbit (OFF/ON) */
+ return (devc->ctl->out_vbit == VBIT_ON);
+ break;
+
+ case 5: /* spdif.copyright (YES/NO) */
+ return get_cbit (devc->ctl->cbitout, 0, 2);
+ break;
+
+ case 6: /* spdif.generation (COPY/ORIGINAL) */
+ return get_cbit (devc->ctl->cbitout, 1, 0);
+ break;
+
+ case 7: /* spdif.preemph (OFF 50/16usec) */
+ return devc->ctl->emphasis_type;
+ }
+
+ return OSS_EINVAL;
+ }
+
+ if (cmd == SNDCTL_MIX_WRITE)
+ {
+ switch (ctrl)
+ {
+ case 1: /* spdif.enable */
+ if (value)
+ devc->ctl->outsel |= OUTSEL_DIGITAL;
+ else
+ devc->ctl->outsel &= ~OUTSEL_DIGITAL;
+ update_hardware (devc, REPGM_OUTSEL);
+ return !!(devc->ctl->outsel & OUTSEL_DIGITAL);
+ break;
+
+ case 2: /* spdif.mode (CONSUMER|PRO) */
+ val = devc->ctl->cbitout[0] & 1;
+ value = !!value;
+
+ set_cbit (devc->ctl->cbitout, 0, 0, value);
+ if (val != value) /* Mode change */
+ switch_mode (devc, devc->ctl, 1);
+ update_hardware (devc, REPGM_ALL);
+ return get_cbit (devc->ctl->cbitout, 0, 0);
+ break;
+
+ case 3: /* spdif.audio (AUDIO|DATA) */
+ value = !!value;
+
+ set_cbit (devc->ctl->cbitout, 0, 1, value);
+ update_hardware (devc, REPGM_CBIT0);
+ return get_cbit (devc->ctl->cbitout, 0, 1);
+ break;
+
+ case 4: /* spdif.vbit (OFF/ON) */
+ devc->ctl->out_vbit = (value) ? VBIT_ON : VBIT_OFF;
+ update_hardware (devc, REPGM_VBIT);
+ return (devc->ctl->out_vbit == VBIT_ON);
+ break;
+
+ case 5: /* spdif.copyright (YES|NO) */
+ value = !!value;
+
+ set_cbit (devc->ctl->cbitout, 0, 2, value);
+ update_hardware (devc, REPGM_CBIT0);
+ return get_cbit (devc->ctl->cbitout, 0, 2);
+ break;
+
+ case 6: /* spdif.generation (COPY|ORIGINAL) */
+ value = !!value;
+
+ set_cbit (devc->ctl->cbitout, 1, 0, value);
+ update_hardware (devc, REPGM_CBIT1);
+ return get_cbit (devc->ctl->cbitout, 1, 0);
+ break;
+
+ case 7: /* spdif.preemph (OFF 50/15usec) */
+ value = !!value;
+ devc->ctl->emphasis_type = value;
+ if (devc->ctl->pro_mode)
+ {
+ devc->ctl->cbitout[0] &= ~0x1c;
+ if (value)
+ devc->ctl->cbitout[0] |= 0x0c;
+ }
+ else
+ {
+ devc->ctl->cbitout[0] &= ~0x28;
+ if (value)
+ devc->ctl->cbitout[0] |= 0x08;
+ }
+ update_hardware (devc, REPGM_CBIT0);
+ return value;
+
+ break;
+ }
+
+ return OSS_EINVAL;
+ }
+ return OSS_EINVAL;
+}
+
+static int
+spdif_set_mixer_control (int dev, int ctrl, unsigned int cmd, int value)
+{
+ int i, ret;
+ spdif_devc *devc = NULL;
+ oss_native_word flags;
+ flags = 0;
+
+ for (i = 0; i < ndevs && devc == NULL; i++)
+ if (devc_list[i]->mixer_dev == dev)
+ devc = devc_list[i];
+
+ if (devc == NULL)
+ return OSS_EINVAL;
+ MUTEX_ENTER (devc->mutex, flags);
+ ret = spdif_set_control (devc, ctrl, cmd, value);
+ MUTEX_EXIT (devc->mutex, flags);
+ return ret;
+}
+
+static int
+spdif_mix_init (spdif_devc * devc)
+{
+ int group, err, dev;
+
+ dev = devc->mixer_dev;
+
+ if (!devc->is_ok)
+ {
+ cmn_err (CE_WARN, "spdif: Bad usage - mix_init\n");
+ return OSS_EIO;
+ }
+
+ if (devc->group > 0)
+ mixer_ext_truncate (devc->mixer_dev, devc->group);
+ devc->group = 0;
+
+ if ((group = mixer_ext_create_group_flags (dev, 0, "SPDIF", MIXF_FLAT)) < 0)
+ return group;
+
+ devc->group = group;
+
+ if (devc->flags & SPDF_ENABLE)
+ if ((err = mixer_ext_create_control (dev, group,
+ 1, spdif_set_mixer_control,
+ MIXT_ONOFF,
+ "SPDIF_ENABLE", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return err;
+
+ if ((devc->caps & DIG_CBITOUT_MASK))
+ {
+ if ((err = mixer_ext_create_control (dev, group,
+ 3, spdif_set_mixer_control,
+ MIXT_ENUM,
+ "SPDIF_AUDIO", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) <
+ 0)
+ return err;
+ }
+
+ if (devc->caps & DIG_VBITOUT)
+ {
+ if ((err = mixer_ext_create_control (dev, group,
+ 4, spdif_set_mixer_control,
+ MIXT_ONOFF,
+ "SPDIF_VBIT", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) <
+ 0)
+ return err;
+ }
+
+ if ((devc->caps & DIG_CBITOUT_MASK))
+ {
+ if ((err = mixer_ext_create_control (dev, group,
+ 7, spdif_set_mixer_control,
+ MIXT_ENUM,
+ "SPDIF_PREEMPH", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) <
+ 0)
+ return err;
+ }
+
+ if ((devc->caps & DIG_PRO) && (devc->caps & DIG_CONSUMER))
+ {
+ /* Device supports both consumer and pro */
+ if ((err = mixer_ext_create_control (dev, group,
+ 2, spdif_set_mixer_control,
+ MIXT_ENUM,
+ "SPDIF_MODE", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) <
+ 0)
+ return err;
+ }
+
+ /* Copyright management */
+ if ((devc->caps & DIG_CBITOUT_MASK) && !devc->ctl->pro_mode)
+ {
+ if ((err = mixer_ext_create_control (dev, group,
+ 5, spdif_set_mixer_control,
+ MIXT_ENUM,
+ "SPDIF_COPYRIGHT", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) <
+ 0)
+ return err;
+
+ if ((err = mixer_ext_create_control (dev, group,
+ 6, spdif_set_mixer_control,
+ MIXT_ENUM,
+ "SPDIF_GENERAT", 2,
+ MIXF_READABLE | MIXF_WRITEABLE)) <
+ 0)
+ return err;
+ }
+
+ return 0;
+}
+
+int
+oss_spdif_mix_init (spdif_devc * devc)
+{
+ int ret;
+ oss_native_word flags;
+ flags = 0;
+ MUTEX_ENTER (devc->mutex, flags);
+ ret = spdif_mix_init (devc);
+ MUTEX_EXIT (devc->mutex, flags);
+ return ret;
+}