diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
commit | 1058def8e7827e56ce4a70afb4aeacb5dc44148f (patch) | |
tree | 4495d23e7b54ab5700e3839081e797c1eafe0db9 /kernel/framework/audio/oss_spdif.c | |
download | oss4-upstream.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.c | 627 |
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; +} |