/* * STMicroelectronics System-on-Chips' I2S to SPDIF converter driver * * Copyright (c) 2005-2011 STMicroelectronics Limited * * Author: Pawel Moll * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include "common.h" #include "reg_aud_spdifpc.h" static int snd_stm_debug_level; module_param_named(debug, snd_stm_debug_level, int, S_IRUGO | S_IWUSR); /* * Hardware-related definitions */ #define DEFAULT_OVERSAMPLING 128 /* * Converter instance structure */ struct snd_stm_conv_i2sspdif { /* System informations */ struct snd_stm_conv_converter *converter; struct snd_stm_conv_i2sspdif_info *info; struct device *device; int index; /* ALSA controls index */ int ver; /* IP version, used by register access macros */ /* Resources */ struct resource *mem_region; void *base; /* Default configuration */ struct snd_aes_iec958 iec958_default; spinlock_t iec958_default_lock; /* Protects iec958_default */ /* Runtime data */ int enabled; struct snd_info_entry *proc_entry; snd_stm_magic_field; }; /* * Internal routines */ /* Such a empty (zeroed) structure is pretty useful later... ;-) */ static struct snd_aes_iec958 snd_stm_conv_i2sspdif_iec958_zeroed; #define CHA_STA_TRIES 50000 static int snd_stm_conv_i2sspdif_iec958_set(struct snd_stm_conv_i2sspdif *conv_i2sspdif, struct snd_aes_iec958 *iec958) { int i, j, ok; unsigned long status[6]; snd_stm_printd(1, "snd_stm_conv_i2sspdif_iec958_set(conv_i2sspdif=%p" ", iec958=%p)\n", conv_i2sspdif, iec958); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); /* I2S to SPDIF converter should be used only for playing * PCM (non compressed) data, so validity bit should be always * zero... (it means "valid linear PCM data") */ set__AUD_SPDIFPC_VAL__VALIDITY_BITS(conv_i2sspdif, 0); /* Well... User data bit... Frankly speaking there is no way * of correctly setting them with a mechanism provided by * converter hardware, so it is better not to do this at all... */ set__AUD_SPDIFPC_DATA__USER_DATA_BITS(conv_i2sspdif, 0); BUG_ON(memcmp(snd_stm_conv_i2sspdif_iec958_zeroed.subcode, iec958->subcode, sizeof(iec958->subcode)) != 0); if (conv_i2sspdif->ver < 4) { /* Converter hardware by default puts every single bit of * status to separate SPDIF subframe (instead of putting * the same bit to both left and right subframes). * So we have to prepare a "duplicated" version of * status bits... Note that in such way status will be * transmitted twice in every block! This is definitely * out of spec, but fortunately most of receivers pay * attention only to first 36 bits... */ for (i = 0; i < 6; i++) { unsigned long word = 0; for (j = 1; j >= 0; j--) { unsigned char byte = iec958->status[i * 2 + j]; int k; for (k = 0; k < 8; k++) { word |= ((byte & 0x80) != 0); if (!(j == 0 && k == 7)) { word <<= 2; byte <<= 1; } } } status[i] = word | (word << 1); } } else { /* Fortunately in some hardware there is a "sane" mode * of channel status registers operation... :-) */ for (i = 0; i < 6; i++) status[i] = iec958->status[i * 4] | iec958->status[i * 4 + 1] << 8 | iec958->status[i * 4 + 2] << 16 | iec958->status[i * 4 + 3] << 24; } /* Set converter's channel status registers - they are realised * in such a ridiculous way that write to them is enabled only * in (about) 300us time window after CHL_STS_BUFF_EMPTY bit * is asserted... And this happens once every 2ms (only when * converter is enabled and gets data...) */ ok = 0; for (i = 0; i < CHA_STA_TRIES; i++) { if (get__AUD_SPDIFPC_STA__CHL_STS_BUFF_EMPTY(conv_i2sspdif)) { for (j = 0; j < 6; j++) set__AUD_SPDIFPC_CHA_STA(conv_i2sspdif, j, status[j]); ok = 1; for (j = 0; j < 6; j++) if (get__AUD_SPDIFPC_CHA_STA(conv_i2sspdif, j) != status[j]) { ok = 0; break; } if (ok) break; } } if (!ok) { snd_stm_printe("WARNING! Failed to set channel status registers" " for converter %s! (tried %d times)\n", dev_name(conv_i2sspdif->device), i); return -EINVAL; } snd_stm_printd(1, "Channel status registers set successfully " "in %i tries.\n", i); /* Set SPDIF player's VUC registers (these are used only * for mute data formatting, and it should never happen ;-) */ set__AUD_SPDIFPC_SUV__VAL_LEFT(conv_i2sspdif, 0); set__AUD_SPDIFPC_SUV__VAL_RIGHT(conv_i2sspdif, 0); set__AUD_SPDIFPC_SUV__DATA_LEFT(conv_i2sspdif, 0); set__AUD_SPDIFPC_SUV__DATA_RIGHT(conv_i2sspdif, 0); /* And this time the problem is that SPDIF player lets * to set only first 36 bits of channel status bits... * Hopefully no one needs more ever ;-) And well - at least * it puts channel status bits to both subframes :-) */ status[0] = iec958->status[0] | iec958->status[1] << 8 | iec958->status[2] << 16 | iec958->status[3] << 24; set__AUD_SPDIFPC_CL1__CHANNEL_STATUS(conv_i2sspdif, status[0]); set__AUD_SPDIFPC_SUV__CH_STA_LEFT(conv_i2sspdif, iec958->status[4] & 0xf); set__AUD_SPDIFPC_CR1__CH_STA(conv_i2sspdif, status[0]); set__AUD_SPDIFPC_SUV__CH_STA_RIGHT(conv_i2sspdif, iec958->status[4] & 0xf); return 0; } static int snd_stm_conv_i2sspdif_oversampling(struct snd_stm_conv_i2sspdif *conv_i2sspdif) { snd_stm_printd(1, "snd_stm_conv_i2sspdif_oversampling(" "conv_i2sspdif=%p)\n", conv_i2sspdif); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); return DEFAULT_OVERSAMPLING; } static int snd_stm_conv_i2sspdif_enable(struct snd_stm_conv_i2sspdif *conv_i2sspdif) { int oversampling; struct snd_aes_iec958 iec958; snd_stm_printd(1, "snd_stm_conv_i2sspdif_enable(conv_i2sspdif=%p)\n", conv_i2sspdif); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); BUG_ON(conv_i2sspdif->enabled); oversampling = snd_stm_conv_i2sspdif_oversampling(conv_i2sspdif); BUG_ON(oversampling <= 0); BUG_ON((oversampling % 128) != 0); set__AUD_SPDIFPC_CFG(conv_i2sspdif, mask__AUD_SPDIFPC_CFG__DEVICE_EN__ENABLED(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__SW_RESET__RUNNING(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__FIFO_EN__ENABLED(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__AUDIO_WORD_SIZE__24_BITS(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__REQ_ACK_EN__ENABLED(conv_i2sspdif)); set__AUD_SPDIFPC_CTRL(conv_i2sspdif, mask__AUD_SPDIFPC_CTRL__OPERATION__PCM(conv_i2sspdif) | mask__AUD_SPDIFPC_CTRL__ROUNDING__NO_ROUNDING(conv_i2sspdif)); set__AUD_SPDIFPC_CTRL__DIVIDER(conv_i2sspdif, oversampling / 128); /* Full channel status processing - an undocumented feature that * exists in some hardware... Normally channel status registers * provides bits for each subframe, so only for 96 frames (a half * of SPDIF block) - pathetic! ;-) Setting bit 6 of config register * enables a mode in which channel status bits in L/R subframes * are identical, and whole block is served... */ if (conv_i2sspdif->ver >= 4) set__AUD_SPDIFPC_CFG__CHA_STA_BITS__FRAME(conv_i2sspdif); spin_lock(&conv_i2sspdif->iec958_default_lock); iec958 = conv_i2sspdif->iec958_default; spin_unlock(&conv_i2sspdif->iec958_default_lock); if (snd_stm_conv_i2sspdif_iec958_set(conv_i2sspdif, &iec958) != 0) snd_stm_printe("WARNING! Can't set channel status " "registers!\n"); conv_i2sspdif->enabled = 1; return 0; } static int snd_stm_conv_i2sspdif_disable(struct snd_stm_conv_i2sspdif *conv_i2sspdif) { snd_stm_printd(1, "snd_stm_conv_i2sspdif_disable(conv_i2sspdif=%p)\n", conv_i2sspdif); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); BUG_ON(!conv_i2sspdif->enabled); if (snd_stm_conv_i2sspdif_iec958_set(conv_i2sspdif, &snd_stm_conv_i2sspdif_iec958_zeroed) != 0) snd_stm_printe("WARNING! Failed to clear channel status " "registers!\n"); set__AUD_SPDIFPC_CFG(conv_i2sspdif, mask__AUD_SPDIFPC_CFG__DEVICE_EN__DISABLED(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__SW_RESET__RESET(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__FIFO_EN__DISABLED(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__REQ_ACK_EN__DISABLED(conv_i2sspdif)); set__AUD_SPDIFPC_CTRL(conv_i2sspdif, mask__AUD_SPDIFPC_CTRL__OPERATION__OFF(conv_i2sspdif)); conv_i2sspdif->enabled = 0; return 0; } /* * Converter interface implementation */ static unsigned int snd_stm_conv_i2sspdif_get_format(void *priv) { snd_stm_printd(1, "snd_stm_conv_i2sspdif_get_format(priv=%p)\n", priv); return SND_STM_FORMAT__I2S | SND_STM_FORMAT__SUBFRAME_32_BITS; } static int snd_stm_conv_i2sspdif_get_oversampling(void *priv) { struct snd_stm_conv_i2sspdif *conv_i2sspdif = priv; snd_stm_printd(1, "snd_stm_conv_i2sspdif_get_oversampling(priv=%p)\n", priv); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); return snd_stm_conv_i2sspdif_oversampling(conv_i2sspdif); } static int snd_stm_conv_i2sspdif_set_enabled(int enabled, void *priv) { struct snd_stm_conv_i2sspdif *conv_i2sspdif = priv; snd_stm_printd(1, "snd_stm_conv_i2sspdif_set_enabled(enabled=%d, " "priv=%p)\n", enabled, priv); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); snd_stm_printd(1, "%sabling I2S to SPDIF converter '%s'.\n", enabled ? "En" : "Dis", dev_name(conv_i2sspdif->device)); if (enabled) return snd_stm_conv_i2sspdif_enable(conv_i2sspdif); else return snd_stm_conv_i2sspdif_disable(conv_i2sspdif); } static struct snd_stm_conv_ops snd_stm_conv_i2sspdif_ops = { .get_format = snd_stm_conv_i2sspdif_get_format, .get_oversampling = snd_stm_conv_i2sspdif_get_oversampling, .set_enabled = snd_stm_conv_i2sspdif_set_enabled, }; /* * ALSA controls */ static int snd_stm_conv_i2sspdif_ctl_default_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_stm_conv_i2sspdif *conv_i2sspdif = snd_kcontrol_chip(kcontrol); snd_stm_printd(1, "snd_stm_conv_i2sspdif_ctl_default_get(" "kcontrol=0x%p, ucontrol=0x%p)\n", kcontrol, ucontrol); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); spin_lock(&conv_i2sspdif->iec958_default_lock); ucontrol->value.iec958 = conv_i2sspdif->iec958_default; spin_unlock(&conv_i2sspdif->iec958_default_lock); return 0; } static int snd_stm_conv_i2sspdif_ctl_default_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_stm_conv_i2sspdif *conv_i2sspdif = snd_kcontrol_chip(kcontrol); int changed = 0; snd_stm_printd(1, "snd_stm_conv_i2sspdif_ctl_default_put(" "kcontrol=0x%p, ucontrol=0x%p)\n", kcontrol, ucontrol); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); spin_lock(&conv_i2sspdif->iec958_default_lock); if (snd_stm_iec958_cmp(&conv_i2sspdif->iec958_default, &ucontrol->value.iec958) != 0) { conv_i2sspdif->iec958_default = ucontrol->value.iec958; changed = 1; } spin_unlock(&conv_i2sspdif->iec958_default_lock); return changed; } static struct snd_kcontrol_new snd_stm_conv_i2sspdif_ctls[] = { { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), .info = snd_stm_ctl_iec958_info, .get = snd_stm_conv_i2sspdif_ctl_default_get, .put = snd_stm_conv_i2sspdif_ctl_default_put, }, { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), .info = snd_stm_ctl_iec958_info, .get = snd_stm_ctl_iec958_mask_get_con, }, { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK), .info = snd_stm_ctl_iec958_info, .get = snd_stm_ctl_iec958_mask_get_pro, }, }; /* * ALSA lowlevel device implementation */ #define DUMP_REGISTER(r) \ snd_iprintf(buffer, "AUD_SPDIFPC_%s (offset 0x%03x) =" \ " 0x%08x\n", __stringify(r), \ offset__AUD_SPDIFPC_##r(conv_i2sspdif), \ get__AUD_SPDIFPC_##r(conv_i2sspdif)) static void snd_stm_conv_i2sspdif_dump_registers(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct snd_stm_conv_i2sspdif *conv_i2sspdif = entry->private_data; int i; BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); snd_iprintf(buffer, "--- %s ---\n", dev_name(conv_i2sspdif->device)); snd_iprintf(buffer, "base = 0x%p\n", conv_i2sspdif->base); DUMP_REGISTER(CFG); DUMP_REGISTER(STA); DUMP_REGISTER(IT_EN); DUMP_REGISTER(ITS); DUMP_REGISTER(IT_CLR); DUMP_REGISTER(VAL); DUMP_REGISTER(DATA); for (i = 0; i <= 5; i++) snd_iprintf(buffer, "AUD_SPDIFPC_CHA_STA%d_CHANNEL_STATUS_BITS" " (offset 0x%03x) = 0x%08x\n", i, offset__AUD_SPDIFPC_CHA_STA(conv_i2sspdif, i), get__AUD_SPDIFPC_CHA_STA(conv_i2sspdif, i)); DUMP_REGISTER(CTRL); DUMP_REGISTER(SPDIFSTA); DUMP_REGISTER(PAUSE); DUMP_REGISTER(DATA_BURST); DUMP_REGISTER(PA_PB); DUMP_REGISTER(PC_PD); DUMP_REGISTER(CL1); DUMP_REGISTER(CR1); DUMP_REGISTER(SUV); snd_iprintf(buffer, "\n"); } static int snd_stm_conv_i2sspdif_register(struct snd_device *snd_device) { struct snd_stm_conv_i2sspdif *conv_i2sspdif = snd_device->device_data; int i; snd_stm_printd(1, "%s(snd_device=0x%p)\n", __func__, snd_device); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); BUG_ON(conv_i2sspdif->enabled); /* Initialize converter's input & SPDIF player as disabled */ set__AUD_SPDIFPC_CFG(conv_i2sspdif, mask__AUD_SPDIFPC_CFG__DEVICE_EN__DISABLED(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__SW_RESET__RESET(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__FIFO_EN__DISABLED(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__REQ_ACK_EN__DISABLED(conv_i2sspdif)); set__AUD_SPDIFPC_CTRL(conv_i2sspdif, mask__AUD_SPDIFPC_CTRL__OPERATION__OFF(conv_i2sspdif)); /* Additional procfs info */ snd_stm_info_register(&conv_i2sspdif->proc_entry, dev_name(conv_i2sspdif->device), snd_stm_conv_i2sspdif_dump_registers, conv_i2sspdif); /* Create ALSA controls */ for (i = 0; i < ARRAY_SIZE(snd_stm_conv_i2sspdif_ctls); i++) { int result; snd_stm_conv_i2sspdif_ctls[i].device = snd_stm_conv_get_card_device( conv_i2sspdif->converter); snd_stm_conv_i2sspdif_ctls[i].index = conv_i2sspdif->index; result = snd_ctl_add(snd_stm_card_get(), snd_ctl_new1(&snd_stm_conv_i2sspdif_ctls[i], conv_i2sspdif)); if (result < 0) { snd_stm_printe("Failed to add I2S-SPDIF converter " "ALSA control!\n"); return result; } } return 0; } static int snd_stm_conv_i2sspdif_disconnect(struct snd_device *snd_device) { struct snd_stm_conv_i2sspdif *conv_i2sspdif = snd_device->device_data; snd_stm_printd(1, "%s(snd_device=0x%p)\n", __func__, snd_device); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); BUG_ON(conv_i2sspdif->enabled); /* Remove procfs entry */ snd_stm_info_unregister(conv_i2sspdif->proc_entry); /* Power done mode, just to be sure :-) */ set__AUD_SPDIFPC_CFG(conv_i2sspdif, mask__AUD_SPDIFPC_CFG__DEVICE_EN__DISABLED(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__SW_RESET__RESET(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__FIFO_EN__DISABLED(conv_i2sspdif) | mask__AUD_SPDIFPC_CFG__REQ_ACK_EN__DISABLED(conv_i2sspdif)); set__AUD_SPDIFPC_CTRL(conv_i2sspdif, mask__AUD_SPDIFPC_CTRL__OPERATION__OFF(conv_i2sspdif)); return 0; } static struct snd_device_ops snd_stm_conv_i2sspdif_snd_device_ops = { .dev_register = snd_stm_conv_i2sspdif_register, .dev_disconnect = snd_stm_conv_i2sspdif_disconnect, }; /* * Platform driver routines */ static int snd_stm_conv_i2sspdif_probe(struct platform_device *pdev) { int result = 0; struct snd_stm_conv_i2sspdif_info *conv_i2sspdif_info = pdev->dev.platform_data; struct snd_stm_conv_i2sspdif *conv_i2sspdif; snd_stm_printd(0, "%s('%s')\n", __func__, dev_name(&pdev->dev)); BUG_ON(!conv_i2sspdif_info); conv_i2sspdif = kzalloc(sizeof(*conv_i2sspdif), GFP_KERNEL); if (!conv_i2sspdif) { snd_stm_printe("Can't allocate memory " "for a device description!\n"); result = -ENOMEM; goto error_alloc; } snd_stm_magic_set(conv_i2sspdif); conv_i2sspdif->ver = conv_i2sspdif_info->ver; BUG_ON(conv_i2sspdif->ver <= 0); conv_i2sspdif->info = conv_i2sspdif_info; conv_i2sspdif->device = &pdev->dev; spin_lock_init(&conv_i2sspdif->iec958_default_lock); /* Get resources */ result = snd_stm_memory_request(pdev, &conv_i2sspdif->mem_region, &conv_i2sspdif->base); if (result < 0) { snd_stm_printe("Memory region request failed!\n"); goto error_memory_request; } /* Get connections */ BUG_ON(!conv_i2sspdif_info->source_bus_id); snd_stm_printd(0, "This I2S-SPDIF converter is attached to PCM player" " '%s'.\n", conv_i2sspdif_info->source_bus_id); conv_i2sspdif->converter = snd_stm_conv_register_converter( "HDMI Output", &snd_stm_conv_i2sspdif_ops, conv_i2sspdif, &platform_bus_type, conv_i2sspdif_info->source_bus_id, conv_i2sspdif_info->channel_from, conv_i2sspdif_info->channel_to, &conv_i2sspdif->index); if (!conv_i2sspdif->converter) { snd_stm_printe("Can't attach to PCM player!\n"); result = -EINVAL; goto error_attach; } /* Create ALSA lowlevel device*/ result = snd_device_new(snd_stm_card_get(), SNDRV_DEV_LOWLEVEL, conv_i2sspdif, &snd_stm_conv_i2sspdif_snd_device_ops); if (result < 0) { snd_stm_printe("ALSA low level device creation failed!\n"); goto error_device; } /* Done now */ platform_set_drvdata(pdev, conv_i2sspdif); return result; error_device: error_attach: snd_stm_memory_release(conv_i2sspdif->mem_region, conv_i2sspdif->base); error_memory_request: snd_stm_magic_clear(conv_i2sspdif); kfree(conv_i2sspdif); error_alloc: return result; } static int snd_stm_conv_i2sspdif_remove(struct platform_device *pdev) { struct snd_stm_conv_i2sspdif *conv_i2sspdif = platform_get_drvdata(pdev); BUG_ON(!conv_i2sspdif); BUG_ON(!snd_stm_magic_valid(conv_i2sspdif)); snd_stm_conv_unregister_converter(conv_i2sspdif->converter); snd_stm_memory_release(conv_i2sspdif->mem_region, conv_i2sspdif->base); snd_stm_magic_clear(conv_i2sspdif); kfree(conv_i2sspdif); return 0; } static struct platform_driver snd_stm_conv_i2sspdif_driver = { .driver.name = "snd_conv_i2sspdif", .probe = snd_stm_conv_i2sspdif_probe, .remove = snd_stm_conv_i2sspdif_remove, }; /* * Initialization */ static int __init snd_stm_conv_i2sspdif_init(void) { return platform_driver_register(&snd_stm_conv_i2sspdif_driver); } static void __exit snd_stm_conv_i2sspdif_exit(void) { platform_driver_unregister(&snd_stm_conv_i2sspdif_driver); } MODULE_AUTHOR("Pawel Moll "); MODULE_DESCRIPTION("STMicroelectronics I2S to SPDIF converter driver"); MODULE_LICENSE("GPL"); module_init(snd_stm_conv_i2sspdif_init); module_exit(snd_stm_conv_i2sspdif_exit);