/*
 *   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);