690 lines
19 KiB
C
690 lines
19 KiB
C
|
/*
|
||
|
* STMicroelectronics System-on-Chips' I2S to SPDIF converter driver
|
||
|
*
|
||
|
* Copyright (c) 2005-2011 STMicroelectronics Limited
|
||
|
*
|
||
|
* Author: Pawel Moll <pawel.moll@st.com>
|
||
|
*
|
||
|
* 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 <linux/init.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/list.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <sound/core.h>
|
||
|
#include <sound/info.h>
|
||
|
#include <sound/stm.h>
|
||
|
|
||
|
#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 <pawel.moll@st.com>");
|
||
|
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);
|