/*****************************************************************************/
/* Copyright (c) 2009 NXP B.V.                                               */
/*                                                                           */
/* 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, using version 2 of the License.             */
/*                                                                           */
/* 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.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
    @file   phStbAlsa.c
    @brief  Linux implementation of the ALSA audio driver for STB225.

    - Based on Linux Dummy soundcard driver.
    - Copyright (c) by Jaroslav Kysela <perex@suse.cz>

    @b  Module:  phStbAlsa
*//*

    This is the Linux implementation of the ALSA audio driver for STB225.

Rev Date        Author      Comments
--------------------------------------------------------------------------------
  1 20061113   Mike Neill  Ported from STB810
  2 20070220   burningh    Update for 2.6.19.1
2.1  20070629  barnish     rename phStbAudio -> phStbAlsa
  3 20070731   barnish     revise for additional controls
  4 20070803   barnish     rework
  5 20070809   barnish     disable AK470X and PCM if drivers not built
  6 20070813   barnish     add spdif pcm output routing control
  7 20070816   barnish     get volume returns last put value.
  8 20071018   barnish     call audio init & term
  9 20080130   barnish     use GetSpdifSource to fetch current routing
 10 20080205   batelaan    update license
 11 20080318   neill       Cope with missing SCART device
 12 20080408   laird       Make PCM playback work (v1.0)
 13 20080410   barnish     Use integer for sample rates
 14 20080418   barnish     Remove mixer spinlock as it conflicts with lower level sempahore
 15 20080421   awest       New default for LPCM setup
 16 20080430   laird       Adding more sample rate support.
16.1.1 20080522 barnish    Added "Output Amplification" controls
16.2.1 20080522 awest      Flush alsa before closing
16.2.2 20080527 barnish    merge
16.2.2 20081015 ocourson   AK470x calls made only if this specific SCART switch is selected
16.2.2.1 20081022 legendrd add support for uda3000
 17 20080620   burningh    Using 2nd decoder instance, if available.
 18 20080704   burningh    Merge 16.2.2 with 17 (Mixer)
 19 20080708   burningh    Fix 2nd decoder instance on full.config
 20 20080729   burningh    Both decoders use I2S0 (Mixer)
 21 20080829   batelaan    Remove compilation warning.
 22 20080903   awest       fix linear buffer allocation issue.
 23 20080908   burningh    ALSA PCM EOS fixes
 24 20081003   burningh    Unify copy/silence code. Don't change AIO sync mode.
 25 20081020   laird       Add muting of SPDIF.
 26 20081203   laird       Merged from 'phStbAlsa.c-25:csrc:SOUsb225#1'
                           and 'phStbAlsa.c-CFR_STB#16.2.2.1:csrc:SOUsb225#1' by laird.
 26 20081106   ashetty     Initialise LPCM header nChannels
 27 20090121   batelaan    Update author/license.
 28 20090123   laird       Merged from 'phStbAlsa.c-27:csrc:SOUsb225#1' and 'phStbAlsa.c-ds14#26:csrc:SOUsb225#1' by laird.
 29 20090202   neill       Add Alsa capture device
 30 20090203   batelaan    Rm phStbAudio init/term calls (done in phStbCore)
30.2.1 20090226 laird      Tidying up enum values.
30.1.1 20090224 burningh   Refactor SPDIF routing calls (PRs 1400, 1401 et al)
30.1.2 20090226 laird      Merged from 'phStbAlsa.c-30.1.1:csrc:SOUsb225#1' and 'phStbAlsa.c-30.2.1:csrc:SOUsb225#1' by laird.
30.1.3 20090304 ashetty    Remove separate spdif source setting for multichannel stream.
30.1.4 20090310 burningh   Fix bugs relating to routing & use of integer vs enumerated (PRs 2114, 2115, 2117)
 31 20090205   myers       Use macro from tmdlAudio when seting up audio linear buffers
 32 20090317   myers       Merge 31 & 30.1.4
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

#include <sound/driver.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#include <sound/initval.h>

#include <tmNxCompId.h> // for TM_OK

#include <prjconfig.h>
#include <linux/autoconf.h> // contains the linux build options CONFIG_...

#ifdef CONFIG_SCART_AK470X
#include <ak470x.h>
#elif CONFIG_SCART_UDA3000
#include "phStbScartSwitch.h"
#include "phStbScartSwitchApi.h"
#endif
#include <tmosal.h>
#include <tmdlAudio.h>
#include <tmdlAudioIO.h>
#include <phStbSbmSink.h>
#include <phStbKBufMgr.h>
#include <phStbStreamingSystem_Server.h>
#include <phStbStreamingSystem_Errors.h>

#define THIS_MODULE_DESCRIPTION     "STB225 ALSA Audio Driver"

MODULE_AUTHOR("NXP B.V.");
MODULE_DESCRIPTION(THIS_MODULE_DESCRIPTION);
MODULE_LICENSE("GPL v2");
MODULE_SUPPORTED_DEVICE("{{ALSA,STB225 audio}}");

#define MAX_PCM_DEVICES     4
#define MAX_PCM_SUBSTREAMS  16
#define MAX_MIDI_DEVICES    2

#define NUM_CAPTURE_STORES (4)
#define CAPTURE_STORE_SIZE (32*1024)

/* Common macros */
#define STB225_OK       0

/* defaults */
#ifndef MAX_BUFFER_SIZE
#define MAX_BUFFER_SIZE     (128*1024)
#endif
#ifndef MAX_PERIOD_SIZE
#define MAX_PERIOD_SIZE     (64*1024)
#endif
#ifndef USE_FORMATS
#define USE_FORMATS         (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S32_BE)
#endif
#ifndef USE_RATE
#define USE_RATE            (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000)
#define USE_RATE_MIN        8000
#define USE_RATE_MAX        96000
#endif
#ifndef USE_CHANNELS_MIN
#define USE_CHANNELS_MIN    1
#endif
#ifndef USE_CHANNELS_MAX
#define USE_CHANNELS_MAX    2
#endif
#ifndef USE_PERIODS_MIN
#define USE_PERIODS_MIN     2
#endif
#ifndef USE_PERIODS_MAX
#define USE_PERIODS_MAX     1024
#endif

/** do not need the mixer spinlock as it is protected at the audio driver level */
//#define USE_MIXER_SPINLOCKS


/** Macro to check a status being zero, and if not report it and goto a "failure" label. */
#define FAIL_ON_BAD_STATUS(_status)                                                         \
    do                                                                                      \
    {                                                                                       \
        if((_status) != TM_OK)                                                              \
        {                                                                                   \
            printk("ERROR: %s, %d -> %x\n", strrchr(__FILE__, '/') + 1, __LINE__, _status); \
            return -1;                                                                      \
        }                                                                                   \
    } while(0)

#define SBMSINK_OUTPUT_1   0
#define LAYER_INPUT_1      0
#define AUDIO_DEC_INPUT_1  0
#define AUDIO_DEC_OUTPUT_1 0
#define AUDIO_PP_INPUT_1   0

/** An invalid handle to be used for determination of handle states. */
#define INVALID_HANDLE (0x0fffffff)

static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;  /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;   /* ID for this card */
static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
static int pcm_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
static uint8_t  captureStore[NUM_CAPTURE_STORES][CAPTURE_STORE_SIZE];
static uint32_t captureStoreLength[NUM_CAPTURE_STORES] = {0};
static uint32_t activeCaptureStore = 0;

module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for stb225 audio.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for stb225 audio.");
module_param_array(enable,bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable this stb225 audio.");
module_param_array(pcm_devs, int, NULL, 0444);
MODULE_PARM_DESC(pcm_devs, "PCM devices # (0-4) for stb225 driver.");
module_param_array(pcm_substreams, int, NULL, 0444);
MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-16) for stb225 driver.");

static struct platform_device *devices[SNDRV_CARDS];

typedef enum MIXER_ADDR_VOLUME
{
    MIXER_ADDR_SCARTSWITCH_VOLUME_1,
    MIXER_ADDR_TVOUT_ROUTING,
    MIXER_ADDR_VCROUT_ROUTING,
    MIXER_ADDR_DECODER1_VOLUME,
    MIXER_ADDR_DECODER1_BALANCE,
    MIXER_ADDR_DECODER1_ROUTING,
    MIXER_ADDR_DECODER2_VOLUME,
    MIXER_ADDR_DECODER2_BALANCE,
    MIXER_ADDR_DECODER2_ROUTING,
    MIXER_ADDR_DECODER3_VOLUME,
    MIXER_ADDR_DECODER3_BALANCE,
    MIXER_ADDR_DECODER3_ROUTING,
    MIXER_ADDR_DECODER4_VOLUME,
    MIXER_ADDR_DECODER4_BALANCE,
    MIXER_ADDR_DECODER4_ROUTING,
    MIXER_ADDR_SPDIFOUT_ROUTING,
    MIXER_ADDR_HDMIOUT_ROUTING,
    MIXER_ADDR_COUNT,
    MIXER_ADDR_LAST     =   MIXER_ADDR_COUNT-1
}   MIXER_ADDR_VOLUME_t;

typedef enum MIXER_DEVICE_CHANNEL
{
    MIXER_DEVICE_SCARTSWITCH_CHANNEL_1   = 0,
    MIXER_DEVICE_DEC1_CHANNEL = 0,
    MIXER_DEVICE_DEC2_CHANNEL = 1,
    MIXER_DEVICE_DEC3_CHANNEL = 2,
    MIXER_DEVICE_DEC4_CHANNEL = 3,
}   MIXER_DEVICE_CHANNEL_t;

struct snd_stb225
{
    struct snd_card *card;
    struct snd_pcm *pcm;
#ifdef USE_MIXER_SPINLOCKS
    spinlock_t mixer_lock;
#endif
    int mixer_value[MIXER_ADDR_COUNT][2];
};

/** @brief Enumeration of resources in the array. */
typedef enum
{
    phStbAlsa_Resource_Input,      /**< Input Resource (File Reader).*/
    phStbAlsa_Resource_Demux,      /**< Demux Resource HW/SW.*/
    phStbAlsa_Resource_Msvd,       /**< MSVD resource.*/
    phStbAlsa_Resource_Layer,      /**< Layer Resource.*/
    phStbAlsa_Resource_AudioDec,   /**< Audio Decoder Resource.*/
    phStbAlsa_Resource_AudioPP,    /**< Audio Post Processor Resource.*/
    phStbAlsa_Resource_AudioOut,   /**< Audio Output Resource.*/
    phStbAlsa_Resource_AudioOut2,  /**< Audio Output2 Resource.*/
    phStbAlsa_Resource_Guard       /**< Guard.*/
} phStbAlsaResource_t;

struct snd_stb225_capture {
    spinlock_t lock;
    struct timer_list timer;
    unsigned int pcm_size;
    unsigned int pcm_count;
    unsigned int pcm_bps;       /* bytes per second */
    unsigned int pcm_jiffie;    /* bytes per one jiffie */
    unsigned int pcm_irq_pos;   /* IRQ position */
    unsigned int pcm_buf_pos;   /* position in buffer */
    unsigned int active;
    unsigned int offset;
    struct snd_pcm_substream *substream;
};

struct snd_stb225_pcm {
    struct snd_stb225 *stb225;
    spinlock_t lock;
    struct timer_list timer;
    unsigned int pcm_size;
    unsigned int pcm_count;
    unsigned int pcm_bps;       /* bytes per second */
    unsigned int pcm_jiffie;    /* bytes per one jiffie */
    unsigned int pcm_irq_pos;   /* IRQ position */
    unsigned int pcm_buf_pos;   /* position in buffer */
    struct snd_pcm_substream *substream;

    phStbSbmSink_t sbmSink;
    char_t sinkDeviceName[32];
    tmInstance_t audioDec;                              /**< Audio decoder instance handle. */

    /** Streaming System data */
    phStbStreamingSystem_GraphID_t      graph;                              /**< The Stream Graph.*/
    phStbStreamingSystem_ResourceID_t   resource[phStbAlsa_Resource_Guard];   /**< The streaming resources.*/
    phStbStreamingSystem_ConnectionID_t connection[phStbAlsa_Resource_Guard]; /**< The streaming connections.*/

    tmosalEventHandle_t eosEvent; /**< The event used to signal End of Stream. */

    uint8_t* pData; /**< Scratch data used for various purposes in the driver. */
};

/**********************************************************************************
/ PCM
**********************************************************************************/
#ifdef CONFIG_SND_PCM_MODULE /* If Alsa build includes PCM support */

/* The actual rates supported by the card. */
static unsigned int samplerates[] =
{
    8000,
    11025,
    16000,
    22050,
    32000,
    44100,
    48000,
    96000
};


static struct snd_pcm_hw_constraint_list constraints_rates =
{
    .count = ARRAY_SIZE(samplerates),
    .list = samplerates,
    .mask = 0,
};

/*
 * Return the index of the target rate
 */
static int snd_card_stb225_pcm_fixed_rate(unsigned int rate)
{
    unsigned int i;
    for (i = 0; i < ARRAY_SIZE(samplerates); i++) {
            if (rate == samplerates[i])
                    return i;
    }
    snd_BUG();
    return 0;
}

static inline void snd_card_stb225_pcm_timer_start(struct snd_stb225_pcm *dpcm)
{
    dpcm->timer.expires = 1 + jiffies;
    add_timer(&dpcm->timer);
}

static inline void snd_card_stb225_pcm_timer_stop(struct snd_stb225_pcm *dpcm)
{
    del_timer(&dpcm->timer);
}

static int snd_card_stb225_pcm_trigger(struct snd_pcm_substream *substream,
                                       int cmd)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_pcm *dpcm = runtime->private_data;
    int err = 0;

    spin_lock(&dpcm->lock);
    switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
        case SNDRV_PCM_TRIGGER_RESUME:
            snd_card_stb225_pcm_timer_start(dpcm);
            break;
        case SNDRV_PCM_TRIGGER_STOP:
        case SNDRV_PCM_TRIGGER_SUSPEND:
            snd_card_stb225_pcm_timer_stop(dpcm);
            break;
        default:
            err = -EINVAL;
            break;
    }
    spin_unlock(&dpcm->lock);
    return 0;
}

static int snd_card_stb225_pcm_prepare(struct snd_pcm_substream *substream)
{
    unsigned int bps;
    tmErrorCode_t status;
    tmdlAudioDecoderParameter_t parameters;
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_pcm *dpcm = runtime->private_data;
    int rate_index = snd_card_stb225_pcm_fixed_rate(runtime->rate);

    bps = runtime->rate * runtime->channels;
    bps *= snd_pcm_format_width(runtime->format);
    bps /= 8;
    if (bps <= 0)
        return -EINVAL;

    dpcm->pcm_bps = bps;
    dpcm->pcm_jiffie = bps / HZ;
    dpcm->pcm_size = snd_pcm_lib_buffer_bytes(substream);
    dpcm->pcm_count = snd_pcm_lib_period_bytes(substream);
    dpcm->pcm_irq_pos = 0;
    dpcm->pcm_buf_pos = 0;
    runtime->rate = samplerates[rate_index];

    /* Now set up tmdlAudio properly.*/
    parameters.LPCM.header.bufferType = tmdlAudioLpcmBufferTypeInterleaved;
    parameters.LPCM.header.samplePairs = 1000;

    if (snd_pcm_format_width(runtime->format) == 16)
    {
        parameters.LPCM.header.sampleSize  = tmdlAudioLpcmSampleSize16Bit;
    }
    else if (snd_pcm_format_width(runtime->format) == 32)
    {
        parameters.LPCM.header.sampleSize  = tmdlAudioLpcmSampleSize32Bit;
    }
    if (runtime->channels > 1)
    {
        parameters.LPCM.header.channelType = tmdlAudioLpcmChannelStereo;
        parameters.LPCM.header.nChannels = 2;
    }
    else
    {
        parameters.LPCM.header.channelType = tmdlAudioLpcmChannelMono;
        parameters.LPCM.header.nChannels = 1;
    }
    if ((runtime->format % 2))
    {
        /* All BE formats are odd so will have a remainder - therefore this will work.*/
        parameters.LPCM.endian = tmdlAudioLpcmEndianBig;
    }
    else
    {
        /* All LE formats are a multiple of 2 therefore this will work.*/
        parameters.LPCM.endian = tmdlAudioLpcmEndianLittle;
    }
    parameters.LPCM.header.sampleFreq = runtime->rate;

    status = tmdlAudio_SetDecParam(dpcm->audioDec,
                                   tmdlAudioParamUpdateLPCM,
                                   &parameters);

    /* Must do this here, otherwise the new settings will not be applied
       and it will sound rubbish.  Believe it to be safe as this function
       should only be called once before a device is closed and a new file
       is played.*/
    status = phStbStreamingSystem_Server_Start(dpcm->graph);
    FAIL_ON_BAD_STATUS(status);

    return 0;
}

static void snd_card_stb225_pcm_timer_function(unsigned long data)
{
    struct snd_stb225_pcm *dpcm = (struct snd_stb225_pcm *)data;
    unsigned long flags;

    spin_lock_irqsave(&dpcm->lock, flags);
    dpcm->timer.expires = 1 + jiffies;
    add_timer(&dpcm->timer);
    dpcm->pcm_irq_pos += dpcm->pcm_jiffie;
    dpcm->pcm_buf_pos += dpcm->pcm_jiffie;
    dpcm->pcm_buf_pos %= dpcm->pcm_size;
    if (dpcm->pcm_irq_pos >= dpcm->pcm_count) {
        dpcm->pcm_irq_pos %= dpcm->pcm_count;
        spin_unlock_irqrestore(&dpcm->lock, flags);
        snd_pcm_period_elapsed(dpcm->substream);
    } else
        spin_unlock_irqrestore(&dpcm->lock, flags);
}

static snd_pcm_uframes_t snd_card_stb225_pcm_pointer(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_pcm *dpcm = runtime->private_data;

    return bytes_to_frames(runtime, dpcm->pcm_buf_pos);
}


/* Copy length bytes of pData into sbmSink. */
static int snd_card_stb225_pcm_internal_copy(phStbSbmSink_t sbmSink,
                                             char *pData,
                                             size_t length)
{
    unsigned int written = 0;
    tmErrorCode_t status;

    /* We need to pass into phStbSbmSink the num bytes to write
       However it overwrites it with the number actually written.*/
    do
    {
        unsigned int to_write = (length - written);

        /* Stop us becoming a processor hog if we are trying to fill sbm sink.*/
        if (written > 0)
        {
            msleep(100);
        }
        status = phStbSbmSink_Write(sbmSink,
                                    pData + written,
                                    &to_write);
        if(status != TM_OK)
        {
            return -EIO;
        }

        written += to_write;
    } while (written < length);

    return 0;
}

static int snd_card_stb225_pcm_silence(struct snd_pcm_substream *substream,
                                       int channel,     /* not used (interleaved data) */
                                       snd_pcm_uframes_t pos,
                                       snd_pcm_uframes_t count)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_pcm *dpcm = runtime->private_data;
    size_t num_bytes_write;

    num_bytes_write = frames_to_bytes(runtime, count);

    /* No need to copy from user we just memset the number of bytes
       they have indicated and play that into sbmsink.*/
    memset(dpcm->pData, 0, num_bytes_write);

    return snd_card_stb225_pcm_internal_copy(dpcm->sbmSink, dpcm->pData, num_bytes_write);
}

static int snd_card_stb225_pcm_copy(struct snd_pcm_substream *substream,
                                    int channel,        /* not used (interleaved data) */
                                    snd_pcm_uframes_t pos,
                                    void __user *src,
                                    snd_pcm_uframes_t count)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_pcm *dpcm = runtime->private_data;
    size_t num_bytes_write;

    num_bytes_write = frames_to_bytes(runtime, count);

    if (copy_from_user(dpcm->pData, src, num_bytes_write))
        return -EFAULT;

    return snd_card_stb225_pcm_internal_copy(dpcm->sbmSink, dpcm->pData, num_bytes_write);
}

static struct snd_pcm_hardware snd_card_stb225_playback =
{
    .info =            (SNDRV_PCM_INFO_MMAP |
                        SNDRV_PCM_INFO_INTERLEAVED |
                        SNDRV_PCM_INFO_RESUME |
                        SNDRV_PCM_INFO_MMAP_VALID),
    .formats =          USE_FORMATS,
    .rates =            USE_RATE,
    .rate_min =         USE_RATE_MIN,
    .rate_max =         USE_RATE_MAX,
    .channels_min =     USE_CHANNELS_MIN,
    .channels_max =     USE_CHANNELS_MAX,
    .buffer_bytes_max = MAX_BUFFER_SIZE,
    .period_bytes_min = 64,
    .period_bytes_max = MAX_PERIOD_SIZE,
    .periods_min =      USE_PERIODS_MIN,
    .periods_max =      USE_PERIODS_MAX,
    .fifo_size =        0,
};

static struct snd_pcm_hardware snd_card_stb225_capture =
{
    .info =             (SNDRV_PCM_INFO_MMAP |
                        SNDRV_PCM_INFO_INTERLEAVED |
                        SNDRV_PCM_INFO_RESUME |
                        SNDRV_PCM_INFO_MMAP_VALID),
    .formats =          SNDRV_PCM_FMTBIT_S32_LE,
    .rates =            SNDRV_PCM_RATE_8000_48000,
    .rate_min =         8000,
    .rate_max =         48000,
    .channels_min =     2,
    .channels_max =     2,
    .buffer_bytes_max = 16*1024,
    .period_bytes_min = 64,
    .period_bytes_max = 16*1024,
    .periods_min =      2,
    .periods_max =      1024,
    .fifo_size =        0,
};

static void snd_card_stb225_runtime_free(struct snd_pcm_runtime *runtime)
{
    kfree(runtime->private_data);
}

static int snd_card_stb225_hw_params(struct snd_pcm_substream *substream,
                                     struct snd_pcm_hw_params *hw_params)
{
    return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
}

static int snd_card_stb225_hw_free(struct snd_pcm_substream *substream)
{
    return snd_pcm_lib_free_pages(substream);
}

static struct snd_stb225_pcm *new_pcm_stream(struct snd_pcm_substream *substream)
{
    struct snd_stb225_pcm *dpcm;

    dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
    if (! dpcm)
        return dpcm;
    init_timer(&dpcm->timer);
    dpcm->timer.data = (unsigned long) dpcm;
    dpcm->timer.function = snd_card_stb225_pcm_timer_function;
    spin_lock_init(&dpcm->lock);
    dpcm->substream = substream;
    return dpcm;
}

static void snd_card_stb225_event_callback(tmInstance_t instance, tmdlAudio_Event_t event, tmdlAudioEventData_t *pData, void *pUserData)
{
    if (event == tmdlAudio_EventDecEOS)
    {
        struct snd_stb225_pcm *dpcm = (struct snd_stb225_pcm *)pUserData;
        tmErrorCode_t err;

        err = tmosalEventSend(dpcm->eosEvent, tmosalEventSendFlagNone);
        if (err != TM_OK)
        {
            printk(KERN_WARNING "snd_card_stb225_event_callback: tmosalEventSend failed with error %x\n", err);
        }
    }
    else
    {
        printk(KERN_WARNING "snd_card_stb225_event_callback: unexpected event %d\n", event);
    }
}

static int snd_card_stb225_playback_open(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_pcm *dpcm;
    int connectionIndex = 0;
    uint32_t pinFlags = PH_STBSBMSINK_SBMPINFLAGS_NOFLAGSET;
    tmdlAudio_InstanceSetup_t *pAudioSetup;
    tmErrorCode_t status;
    uint32_t i,n;

    if ((dpcm = new_pcm_stream(substream)) == NULL)
        return -ENOMEM;

    /* Create the End of Stream event. */
    status = tmosalEventCreate(&dpcm->eosEvent, tmosalEventCreateFlagNone);
    FAIL_ON_BAD_STATUS(status);

    dpcm->graph = (phStbStreamingSystem_GraphID_t) INVALID_HANDLE;
    dpcm->audioDec = (tmInstance_t) INVALID_HANDLE;
    dpcm->sbmSink = (phStbSbmSink_t)INVALID_HANDLE;
    for (n = 0; n < phStbAlsa_Resource_Guard; n++)
    {
        dpcm->connection[n] = (phStbStreamingSystem_ConnectionID_t) INVALID_HANDLE;
    }
    for (n = 0; n < phStbAlsa_Resource_Guard; n++)
    {
        dpcm->resource[n] = (phStbStreamingSystem_ResourceID_t) INVALID_HANDLE;
    }

    /* Set up the stream graph.*/
    status = phStbSbmSink_Create(pinFlags, &dpcm->sbmSink, dpcm->sinkDeviceName);
    FAIL_ON_BAD_STATUS(status);

    /* Open Audio Decoder unit 1; always use this for PCM decoding.
     * Even if we don't have buffers allocated for dual audio, we can still use the 2nd decoder.
     * This just prevents conflicts in the case where we do. */
    status = tmdlAudio_OpenM(&dpcm->audioDec, tmUnit1, False);
    FAIL_ON_BAD_STATUS(status);

    /* Use the sync mode to set the source - this disables s/w discontinuity checking in PVR mode. */
    status = tmdlAudio_SetSource(dpcm->audioDec, tmdlAudioSourceMemory);
    FAIL_ON_BAD_STATUS(status);

    status = tmdlAudio_GetInstanceSetup(dpcm->audioDec, &pAudioSetup);
    FAIL_ON_BAD_STATUS(status);

    // TODO: Audio buffer requirements are too complex - it would be better if we just gave audio
    // TODO: a big lump of memory and let it sort out the allocations (PR#776)
    /* Allocate the SBM buffers */
    for (i = 0; i < tmdlAudio_NumSBMBuffers; i++)
    {
        status = phStbKBufMgr_GetBuffer(tmdlAudio_SBMBufType2KBufMgrBufType(i),
                                        &pAudioSetup->SBMBuffer[i].address,
                                        &pAudioSetup->SBMBuffer[i].size);
        FAIL_ON_BAD_STATUS(status);
    }
    /* Allocate the linear buffers */
    for (i = 0; i < tmdlAudio_NumLinBuffers; i++)
    {
        status = phStbKBufMgr_GetBuffer(tmdlAudio_LinBufType2KBufMgrBufType(i),
                                        &pAudioSetup->LinBuffer[i].address,
                                        &pAudioSetup->LinBuffer[i].size);
        FAIL_ON_BAD_STATUS(status);
    }
    /* Allocate the Parser buffers */
    for (i = 0; i < tmdlAudio_NumParserBuffers; i++)
    {
        status = phStbKBufMgr_GetBuffer(tmdlAudio_ParserBufType2KBufMgrBufType(i),
                                        &pAudioSetup->ParserBuffer[i].address,
                                        &pAudioSetup->ParserBuffer[i].size );
        FAIL_ON_BAD_STATUS(status);
    }

    status = tmdlAudio_InstanceSetup(dpcm->audioDec, pAudioSetup);
    FAIL_ON_BAD_STATUS(status);

    status = tmdlAudio_SetFormat(dpcm->audioDec, tmdlAudioFormatES, tmdlAudioTypeLPCM, NULL);
    FAIL_ON_BAD_STATUS(status);

    status = tmdlAudio_SetEventCallback(dpcm->audioDec, snd_card_stb225_event_callback, dpcm);
    FAIL_ON_BAD_STATUS(status);

    status = tmdlAudio_SetEventMask(dpcm->audioDec, tmdlAudio_EventDecEOS);
    FAIL_ON_BAD_STATUS(status);

    status = phStbStreamingSystem_Server_CreateStreamGraph(&dpcm->graph);
    FAIL_ON_BAD_STATUS(status);

    status = phStbStreamingSystem_Server_AddResource(dpcm->graph,
                                                     "StbAMS_DEC1",
                                                     &dpcm->resource[phStbAlsa_Resource_AudioDec]);
    FAIL_ON_BAD_STATUS(status);

    status = phStbStreamingSystem_Server_AddResource(dpcm->graph,
                                                     "StbAMS_APP1",
                                                     &dpcm->resource[phStbAlsa_Resource_AudioPP]);
    FAIL_ON_BAD_STATUS(status);

    status = phStbStreamingSystem_Server_AddResource(dpcm->graph,
                                                     dpcm->sinkDeviceName,
                                                     &dpcm->resource[phStbAlsa_Resource_Input]);
    FAIL_ON_BAD_STATUS(status);


    status = phStbStreamingSystem_Server_Connect(dpcm->graph,
                                                 dpcm->resource[phStbAlsa_Resource_Input],
                                                 SBMSINK_OUTPUT_1,
                                                 dpcm->resource[phStbAlsa_Resource_AudioDec],
                                                 AUDIO_DEC_INPUT_1,
                                                 &dpcm->connection[connectionIndex++]);
    FAIL_ON_BAD_STATUS(status);

    status = phStbStreamingSystem_Server_Connect(dpcm->graph,
                                                 dpcm->resource[phStbAlsa_Resource_AudioDec],
                                                 AUDIO_DEC_OUTPUT_1,
                                                 dpcm->resource[phStbAlsa_Resource_AudioPP],
                                                 AUDIO_PP_INPUT_1,
                                                 &dpcm->connection[connectionIndex++]);
    FAIL_ON_BAD_STATUS(status);

    status = phStbStreamingSystem_Server_Commit(dpcm->graph);
    FAIL_ON_BAD_STATUS(status);

    dpcm->pData = kzalloc(MAX_BUFFER_SIZE, GFP_KERNEL | GFP_DMA);
    if (!dpcm->pData)
        return -ENOMEM;

    runtime->private_data = dpcm;
    runtime->private_free = snd_card_stb225_runtime_free;
    runtime->hw = snd_card_stb225_playback;
    if (substream->pcm->device & 1)
    {
        runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
        runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
    }
    if (substream->pcm->device & 2)
    {
        runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP|SNDRV_PCM_INFO_MMAP_VALID);
    }

    snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);

    return 0;
}

static int snd_card_stb225_playback_close(struct snd_pcm_substream *substream)
{
    int n = 0;
    Int32 timeout = 500;
    tmErrorCode_t status;
    struct snd_stb225_pcm *dpcm = substream->runtime->private_data;

    phStbSbmSink_Flush(dpcm->sbmSink);

    /* Wait for "EOS" event from audio decoder before closing. */
    status = tmosalEventWait(dpcm->eosEvent, &timeout, tmosalEventWaitFlagNone);
    if (status == TMOSAL_ERR_EVT_TIMEOUT)
    {
        printk(KERN_WARNING "phStbAlsa: Timeout waiting for End of Stream.\n");
        status = TM_OK;
    }
    FAIL_ON_BAD_STATUS(status);

    status = tmdlAudio_SetEventCallback(dpcm->audioDec, Null, Null);
    FAIL_ON_BAD_STATUS(status);

    status = tmosalEventDestroy(dpcm->eosEvent);
    FAIL_ON_BAD_STATUS(status);

    if (dpcm->graph != (phStbStreamingSystem_GraphID_t) INVALID_HANDLE)
    {
        status = phStbStreamingSystem_Server_Stop(dpcm->graph);
        FAIL_ON_BAD_STATUS(status);

        for (n = 0; n < phStbAlsa_Resource_Guard; n++)
        {
            if (dpcm->connection[n] != (phStbStreamingSystem_ConnectionID_t)INVALID_HANDLE)
            {
                status = phStbStreamingSystem_Server_Disconnect(dpcm->graph, dpcm->connection[n]);
                FAIL_ON_BAD_STATUS(status);
            }
        }

        status = phStbStreamingSystem_Server_Commit(dpcm->graph);
        FAIL_ON_BAD_STATUS(status);

        for (n = 0; n < phStbAlsa_Resource_Guard; n++)
        {
            if (dpcm->resource[n] != (phStbStreamingSystem_ResourceID_t)INVALID_HANDLE)
            {
                status = phStbStreamingSystem_Server_RemoveResource(dpcm->graph, dpcm->resource[n]);
                FAIL_ON_BAD_STATUS(status);
            }
        }

        status = phStbStreamingSystem_Server_DestroyStreamGraph(dpcm->graph);
        FAIL_ON_BAD_STATUS(status);
    }

    /* Close the Audio Decoder if it's open. */
    if (dpcm->audioDec != (tmInstance_t)INVALID_HANDLE)
    {
        tmdlAudio_InstanceSetup_t *pAudioSetup;
        uint32_t i;

        status = tmdlAudio_GetInstanceSetup(dpcm->audioDec, &pAudioSetup);
        FAIL_ON_BAD_STATUS(status);

        for (i = 0; i < tmdlAudio_NumSBMBuffers; i++)
        {
            status = phStbKBufMgr_ReleaseBuffer(tmdlAudio_SBMBufType2KBufMgrBufType(i),
                                                pAudioSetup->SBMBuffer[i].address);
            FAIL_ON_BAD_STATUS(status);
        }
 
        for (i = 0; i < tmdlAudio_NumLinBuffers; i++)
        {
            status = phStbKBufMgr_ReleaseBuffer(tmdlAudio_LinBufType2KBufMgrBufType(i),
                                                pAudioSetup->LinBuffer[i].address);
            FAIL_ON_BAD_STATUS(status);
        }

        for (i = 0; i < tmdlAudio_NumParserBuffers; i++)
        {
            status = phStbKBufMgr_ReleaseBuffer(tmdlAudio_ParserBufType2KBufMgrBufType(i),
                                                pAudioSetup->ParserBuffer[i].address);
            FAIL_ON_BAD_STATUS(status);
        }
    }

    /* Bad things happen if this is not initialised....*/
    status = phStbSbmSink_Destroy(dpcm->sbmSink);
    FAIL_ON_BAD_STATUS(status);
    dpcm->sinkDeviceName[0] = '\0';

    kfree(dpcm->pData);
    return 0;
}

static struct snd_pcm_ops snd_card_stb225_playback_ops = {
    .open =         snd_card_stb225_playback_open,
    .close =        snd_card_stb225_playback_close,
    .ioctl =        snd_pcm_lib_ioctl,
    .hw_params =    snd_card_stb225_hw_params,
    .hw_free =      snd_card_stb225_hw_free,
    .prepare =      snd_card_stb225_pcm_prepare,
    .trigger =      snd_card_stb225_pcm_trigger,
    .pointer =      snd_card_stb225_pcm_pointer,
    .copy =         snd_card_stb225_pcm_copy,
    .silence =      snd_card_stb225_pcm_silence,
};

static void snd_card_stb225_capture_timer_function(unsigned long data)
{
    struct snd_stb225_capture *dpcm = (struct snd_stb225_capture *)data;
    unsigned long flags;

    spin_lock_irqsave(&dpcm->lock, flags);
    dpcm->timer.expires = 1 + jiffies;
    add_timer(&dpcm->timer);
    dpcm->pcm_irq_pos += dpcm->pcm_jiffie;
    dpcm->pcm_buf_pos += dpcm->pcm_jiffie;
    dpcm->pcm_buf_pos %= dpcm->pcm_size;
    if (dpcm->pcm_irq_pos >= dpcm->pcm_count) {
        dpcm->pcm_irq_pos %= dpcm->pcm_count;
        spin_unlock_irqrestore(&dpcm->lock, flags);
    } else
        spin_unlock_irqrestore(&dpcm->lock, flags);
    snd_pcm_period_elapsed(dpcm->substream);
}

static struct snd_stb225_capture *new_capture_stream(struct snd_pcm_substream *substream)
{
    struct snd_stb225_capture *dpcm;

    dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
    if (! dpcm)
        return dpcm;
    init_timer(&dpcm->timer);
    dpcm->timer.data = (unsigned long) dpcm;
    dpcm->timer.function = snd_card_stb225_capture_timer_function;
    spin_lock_init(&dpcm->lock);
    dpcm->substream = substream;
    return dpcm;
}

static void dataCallback(uint8_t *pData, uint32_t length)
{
    uint32_t active;

    /* Select the next available data store */
    active = ((activeCaptureStore + 1)%NUM_CAPTURE_STORES);

    /* Take a copy of the PCM data */
    (void)memcpy(captureStore[active], pData, length);
    captureStoreLength[active] = length;

    /* Update the reference to the latest data store */
    activeCaptureStore = active;
}

static int snd_card_stb225_capture_open(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_capture *dpcm;

    if ((dpcm = new_capture_stream(substream)) == NULL)
        return -ENOMEM;

    runtime->private_data = dpcm;
    runtime->private_free = snd_card_stb225_runtime_free;
    runtime->hw = snd_card_stb225_capture;

    snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates);

    /* Register the data callback function */
    tmdlAudioIO_RegisterDataCallback(tmdlAudioIO_Output_I2S_0, &dataCallback);

    return 0;
}

static int snd_card_stb225_capture_close(struct snd_pcm_substream *substream)
{
    /* Un-register the data callback function */
    tmdlAudioIO_RegisterDataCallback(tmdlAudioIO_Output_I2S_0, NULL);

    return 0;
}

static int snd_card_stb225_capture_prepare(struct snd_pcm_substream *substream)
{
    unsigned int bps;
    unsigned int whichStore;
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_capture *dpcm = runtime->private_data;
    int rate_index = snd_card_stb225_pcm_fixed_rate(runtime->rate);

    /* Calculate the data rate */
    bps = runtime->rate * runtime->channels;
    bps *= snd_pcm_format_width(runtime->format);
    bps /= 8;
    if (bps <= 0)
        return -EINVAL;

    /* Clear the data stores */
    dpcm->offset = 0;
    dpcm->active = 0;
    for(whichStore=0; whichStore<NUM_CAPTURE_STORES; whichStore++)
    {
        captureStoreLength[whichStore] = 0;
    }

    dpcm->pcm_bps = bps;
    dpcm->pcm_jiffie = bps / HZ;

    dpcm->pcm_size = snd_pcm_lib_buffer_bytes(substream);
    dpcm->pcm_count = snd_pcm_lib_period_bytes(substream);
    dpcm->pcm_irq_pos = 0;
    dpcm->pcm_buf_pos = 0;
    runtime->rate = samplerates[rate_index];

    return 0;
}

static inline void snd_card_stb225_capture_timer_start(struct snd_stb225_capture *dpcm)
{
    dpcm->timer.expires = 1 + jiffies;
    add_timer(&dpcm->timer);
}

static inline void snd_card_stb225_capture_timer_stop(struct snd_stb225_capture *dpcm)
{
    del_timer(&dpcm->timer);
}

static int snd_card_stb225_capture_trigger(struct snd_pcm_substream *substream,
                                       int cmd)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_capture *dpcm = runtime->private_data;
    int err = 0;

    spin_lock(&dpcm->lock);
    switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
        case SNDRV_PCM_TRIGGER_RESUME:
            snd_card_stb225_capture_timer_start(dpcm);
            break;
        case SNDRV_PCM_TRIGGER_STOP:
        case SNDRV_PCM_TRIGGER_SUSPEND:
            snd_card_stb225_capture_timer_stop(dpcm);
            break;
        default:
            err = -EINVAL;
            break;
    }
    spin_unlock(&dpcm->lock);
    return 0;
}

static snd_pcm_uframes_t snd_card_stb225_capture_pointer(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_capture *dpcm = runtime->private_data;

    return bytes_to_frames(runtime, dpcm->pcm_buf_pos);
}

static int snd_card_stb225_capture_copy(struct snd_pcm_substream *substream,
                                        int channel,        /* not used (interleaved data) */
                                        snd_pcm_uframes_t pos,
                                        void __user *src,
                                        snd_pcm_uframes_t count)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct snd_stb225_capture *dpcm = runtime->private_data;
    size_t num_bytes_read;

    spin_lock(&dpcm->lock);

    /* Calculate the number of bytes required */
    num_bytes_read = frames_to_bytes(runtime, count);

    if (dpcm->offset < captureStoreLength[dpcm->active])
    {
        /* There is data available - copy it to the user space buffer */
        copy_to_user(src, &captureStore[dpcm->active][dpcm->offset], num_bytes_read);
        dpcm->offset += num_bytes_read;
    }
    else
    {
        /* No data available - copy 'silence' */
        memset(src, 0, num_bytes_read);
    }

    /* Move onto the next data store */
    if (dpcm->offset >= captureStoreLength[dpcm->active])
    {
        captureStoreLength[dpcm->active] = 0;
        dpcm->active = ((dpcm->active + 1)%NUM_CAPTURE_STORES);
        dpcm->offset = 0;
    }

    spin_unlock(&dpcm->lock);

    return 0;
}

static int snd_card_stb225_capture_ioctl(struct snd_pcm_substream *substream,
                                  unsigned int cmd, void *arg)
{
    return 0;
}

static int snd_card_stb225_capture_hw_params(struct snd_pcm_substream *substream,
                                     struct snd_pcm_hw_params *hw_params)
{
    return 0;
}

static int snd_card_stb225_capture_hw_free(struct snd_pcm_substream *substream)
{
    return 0;
}

static struct snd_pcm_ops snd_card_stb225_capture_ops = {
    .open =         snd_card_stb225_capture_open,
    .close =        snd_card_stb225_capture_close,
    .ioctl =        snd_card_stb225_capture_ioctl,
    .hw_params =    snd_card_stb225_capture_hw_params,
    .hw_free =      snd_card_stb225_capture_hw_free,
    .prepare =      snd_card_stb225_capture_prepare,
    .trigger =      snd_card_stb225_capture_trigger,
    .pointer =      snd_card_stb225_capture_pointer,
    .copy =         snd_card_stb225_capture_copy,
};

static int __init snd_card_stb225_pcm(struct snd_stb225 *stb225,
                                      int device,
                                      int substreams)
{
    struct snd_pcm *pcm;
    int err;

    /* NAB: playback_count = substreams, capture_count = 1. */
    if ((err = snd_pcm_new(stb225->card, "STB225 PCM", device, substreams, 1, &pcm)) < 0)
        return err;
    stb225->pcm = pcm;
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_card_stb225_playback_ops);
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_stb225_capture_ops);
    pcm->private_data = stb225;
    pcm->info_flags = 0;
    strcpy(pcm->name, "STB225 PCM");
    snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
                                          snd_dma_continuous_data(GFP_KERNEL),
                                          64*1024, 128*1024);
    return 0;
}
#endif // CONFIG_SND_PCM_MODULE

/**********************************************************************************
/ Volume
**********************************************************************************/

static int scartswitch_std_volume_range_get(int channel, long *v_min, long *v_max)
{
    int err = 0;
#ifdef CONFIG_SCART_AK470X
    *v_min = AK470X_AUDIO_EXTERNAL_VOLUME_MIN;
    *v_max = AK470X_AUDIO_EXTERNAL_VOLUME_MAX;
#elif CONFIG_SCART_UDA3000
    *v_min = PH_STB_SCART_SWITCH_AUDIO_MUTE;
    *v_max = PH_STB_SCART_SWITCH_MAX_LEVEL;
#endif
    return err;
}

static int scartswitch_std_volume_get(int channel, int *v_left, int *v_right)
{
    int err = 0;
#ifdef CONFIG_SCART_AK470X
    err = ak470x_std_volume_get(channel, v_left, v_right);
#elif CONFIG_SCART_UDA3000
    uint8_t Volume;

    /* Get the TV SCART as this is the only one supported yet */
    err = phStbScartSwitchGetVolume(PHSTB_SCART_SWITCH_SCART_TV , &Volume);

    *v_left = Volume;
    *v_right = Volume;
#endif
    return err;
}

static int scartswitch_std_volume_set(int channel, int v_left, int v_right)
{
    int err = 0;
#ifdef CONFIG_SCART_AK470X
    err = ak470x_std_volume_set(channel, v_left, v_right);
#elif CONFIG_SCART_UDA3000
    uint8_t Volume = v_left;

    /* Set the TV SCART as this is the only one supported yet */
    err = phStbScartSwitchSetVolume(PHSTB_SCART_SWITCH_SCART_TV, Volume);
#endif
    return err;
}

static int snd_stb225_volume_info(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_info *uinfo)
{
    int retValue = -EINVAL;
    int addr = kcontrol->private_value;

    uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;

    switch ((MIXER_ADDR_VOLUME_t)addr)
    {
#ifdef CONFIG_SCART_SWITCH_PRESENT
        case MIXER_ADDR_SCARTSWITCH_VOLUME_1:
        {
            uinfo->count = 2;
            retValue = scartswitch_std_volume_range_get(MIXER_DEVICE_SCARTSWITCH_CHANNEL_1,
                                                        &uinfo->value.integer.min,
                                                        &uinfo->value.integer.max);
            break;
        }
#endif
        case MIXER_ADDR_DECODER1_VOLUME:
        case MIXER_ADDR_DECODER2_VOLUME:
        case MIXER_ADDR_DECODER3_VOLUME:
        case MIXER_ADDR_DECODER4_VOLUME:
            uinfo->count = 1;
            uinfo->value.integer.min = 0;
            uinfo->value.integer.max = 100;
            retValue = STB225_OK;
            break;
        case MIXER_ADDR_DECODER1_BALANCE:
        case MIXER_ADDR_DECODER2_BALANCE:
        case MIXER_ADDR_DECODER3_BALANCE:
        case MIXER_ADDR_DECODER4_BALANCE:
            uinfo->count = 1;
            uinfo->value.integer.min = -100;
            uinfo->value.integer.max = 100;
            retValue = STB225_OK;
            break;
        default:
            retValue = -EINVAL;
            break;
    }

    return retValue;
}

static int snd_stb225_volume_get(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol)
{
    tmErrorCode_t err = TM_ERR_BAD_PARAMETER;
    int retValue = -EINVAL;
    int addr = kcontrol->private_value;
    uint32_t volume;
    int32_t balance;
#ifdef CONFIG_SCART_SWITCH_PRESENT
    int left, right;
#endif

    // Lock
#ifdef USE_MIXER_SPINLOCKS
    spin_lock(&stb225->mixer_lock);
#endif

    switch ((MIXER_ADDR_VOLUME_t)addr)
    {
#ifdef CONFIG_SCART_SWITCH_PRESENT
        case MIXER_ADDR_SCARTSWITCH_VOLUME_1:
            err = scartswitch_std_volume_get(MIXER_DEVICE_SCARTSWITCH_CHANNEL_1, &left, &right);
            if (err == TM_OK)
            {
                ucontrol->value.integer.value[0] = left;
                ucontrol->value.integer.value[1] = right;
            }
            break;
#endif

        case MIXER_ADDR_DECODER1_VOLUME:
            err = tmdlAudio_GetVolume(MIXER_DEVICE_DEC1_CHANNEL, &volume);
            if (err == TM_OK)
            {
                ucontrol->value.integer.value[0] = volume;
            }
            break;

        case MIXER_ADDR_DECODER1_BALANCE:
            err = tmdlAudio_GetBalance(MIXER_DEVICE_DEC1_CHANNEL, &balance);
            if (err == TM_OK)
            {
                ucontrol->value.integer.value[0] = balance;
            }
            break;

        case MIXER_ADDR_DECODER2_VOLUME:
            err = tmdlAudio_GetVolume(MIXER_DEVICE_DEC2_CHANNEL, &volume);
            if (err == TM_OK)
            {
                ucontrol->value.integer.value[0] = volume;
            }
            break;

        case MIXER_ADDR_DECODER2_BALANCE:
            err = tmdlAudio_GetBalance(MIXER_DEVICE_DEC2_CHANNEL, &balance);
            if (err == TM_OK)
            {
                ucontrol->value.integer.value[0] = balance;
            }
            break;

        case MIXER_ADDR_DECODER3_VOLUME:
            err = tmdlAudio_GetVolume(MIXER_DEVICE_DEC3_CHANNEL, &volume);
            if (err == TM_OK)
            {
                ucontrol->value.integer.value[0] = volume;
            }
            break;

        case MIXER_ADDR_DECODER3_BALANCE:
            err = tmdlAudio_GetBalance(MIXER_DEVICE_DEC3_CHANNEL, &balance);
            if (err == TM_OK)
            {
                ucontrol->value.integer.value[0] = balance;
            }
            break;

        case MIXER_ADDR_DECODER4_VOLUME:
            err = tmdlAudio_GetVolume(MIXER_DEVICE_DEC4_CHANNEL, &volume);
            if (err == TM_OK)
            {
                ucontrol->value.integer.value[0] = volume;
            }
            break;

        case MIXER_ADDR_DECODER4_BALANCE:
            err = tmdlAudio_GetBalance(MIXER_DEVICE_DEC4_CHANNEL, &balance);
            if (err == TM_OK)
            {
                ucontrol->value.integer.value[0] = balance;
            }
            break;

        default:
            break;
    }

    // Unlock
#ifdef USE_MIXER_SPINLOCKS
    spin_unlock(&stb225->mixer_lock);
#endif

    if (err == TM_OK)
    {
        retValue = 0;
    }

    return retValue;
}

static int snd_stb225_volume_put(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol)
{
    tmErrorCode_t err = TM_ERR_BAD_PARAMETER;
    int change = 0;
    int addr = kcontrol->private_value;

    // Lock
#ifdef USE_MIXER_SPINLOCKS
    spin_lock(&stb225->mixer_lock);
#endif

    switch ((MIXER_ADDR_VOLUME_t)addr)
    {
#ifdef CONFIG_SCART_SWITCH_PRESENT
        case MIXER_ADDR_SCARTSWITCH_VOLUME_1:
            err = scartswitch_std_volume_set(MIXER_DEVICE_SCARTSWITCH_CHANNEL_1,
                                             ucontrol->value.integer.value[0],
                                             ucontrol->value.integer.value[1]);
            break;
#endif

        case MIXER_ADDR_DECODER1_VOLUME:
            err = tmdlAudio_SetVolume(MIXER_DEVICE_DEC1_CHANNEL,
                                      ucontrol->value.integer.value[0]);
            break;

        case MIXER_ADDR_DECODER1_BALANCE:
            err = tmdlAudio_SetBalance(MIXER_DEVICE_DEC1_CHANNEL,
                                       ucontrol->value.integer.value[0]);
            break;

        case MIXER_ADDR_DECODER2_VOLUME:
            err = tmdlAudio_SetVolume(MIXER_DEVICE_DEC2_CHANNEL,
                                      ucontrol->value.integer.value[0]);
            break;

        case MIXER_ADDR_DECODER2_BALANCE:
            err = tmdlAudio_SetBalance(MIXER_DEVICE_DEC2_CHANNEL,
                                       ucontrol->value.integer.value[0]);
            break;

        case MIXER_ADDR_DECODER3_VOLUME:
            err = tmdlAudio_SetVolume(MIXER_DEVICE_DEC3_CHANNEL,
                                      ucontrol->value.integer.value[0]);
            break;

        case MIXER_ADDR_DECODER3_BALANCE:
            err = tmdlAudio_SetBalance(MIXER_DEVICE_DEC3_CHANNEL,
                                       ucontrol->value.integer.value[0]);
            break;

        case MIXER_ADDR_DECODER4_VOLUME:
            err = tmdlAudio_SetVolume(MIXER_DEVICE_DEC4_CHANNEL,
                                      ucontrol->value.integer.value[0]);
            break;

        case MIXER_ADDR_DECODER4_BALANCE:
            err = tmdlAudio_SetBalance(MIXER_DEVICE_DEC4_CHANNEL,
                                       ucontrol->value.integer.value[0]);
            break;

        default:
            break;
    }

    if (err == TM_OK)
    {
        change = 1;
    }

    // Unlock
#ifdef USE_MIXER_SPINLOCKS
    spin_unlock(&stb225->mixer_lock);
#endif

    return change;
}

/**********************************************************************************
/ Routing
**********************************************************************************/
static int snd_stb225_routing_info(struct snd_kcontrol *kcontrol,
                                   struct snd_ctl_elem_info *uinfo)
{
    int addr = kcontrol->private_value;
    int retVal = 0;

    uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
    uinfo->count = 1;

    switch((MIXER_ADDR_VOLUME_t)addr)
    {
        case MIXER_ADDR_DECODER1_ROUTING:
        case MIXER_ADDR_DECODER2_ROUTING:
        case MIXER_ADDR_DECODER3_ROUTING:
        case MIXER_ADDR_DECODER4_ROUTING:
        {
            const char *names[] = {"Stereo", "Mono", "Left Only", "Right Only"};

            uinfo->value.enumerated.items = ARRAY_SIZE(names);
            if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
            {
                uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
            }
            strcpy(uinfo->value.enumerated.name, names[uinfo->value.enumerated.item]);
            break;
        }
        case MIXER_ADDR_TVOUT_ROUTING:
        {
            const char *names[] = {"DAC", "VCR In", "Mute"};

            uinfo->value.enumerated.items = ARRAY_SIZE(names);
            if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
            {
                uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
            }
            strcpy(uinfo->value.enumerated.name, names[uinfo->value.enumerated.item]);
            break;
        }
        case MIXER_ADDR_VCROUT_ROUTING:
        {
            const char *names[] = {"DAC", "TV In", "Mute", "TV Out"};

            uinfo->value.enumerated.items = ARRAY_SIZE(names);
            if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
            {
                uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
            }
            strcpy(uinfo->value.enumerated.name, names[uinfo->value.enumerated.item]);
            break;
        }
        case MIXER_ADDR_SPDIFOUT_ROUTING:
        {
            const char *names[] = {"Disabled", "PCM", "Burst"};

            uinfo->value.enumerated.items = ARRAY_SIZE(names);
            if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
            {
                uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
            }
            strcpy(uinfo->value.enumerated.name, names[uinfo->value.enumerated.item]);
            break;
        }
        case MIXER_ADDR_HDMIOUT_ROUTING:
        {
            const char *names[] = {"I2S", "SPDIF"};

            uinfo->value.enumerated.items = ARRAY_SIZE(names);
            if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
            {
                uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
            }
            strcpy(uinfo->value.enumerated.name, names[uinfo->value.enumerated.item]);
            break;
        }
        default:
        {
            retVal = -EINVAL;
            break;
        }
    }
    return retVal;
}

static int snd_stb225_routing_get(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
{
    int retValue = STB225_OK;
    struct snd_stb225 *stb225 = snd_kcontrol_chip(kcontrol);
    int addr = kcontrol->private_value;

    // Lock
#ifdef USE_MIXER_SPINLOCKS
    spin_lock(&stb225->mixer_lock);
#endif

    switch ((MIXER_ADDR_VOLUME_t)addr)
    {
        case MIXER_ADDR_DECODER1_ROUTING:
            tmdlAudio_GetRouting(MIXER_DEVICE_DEC1_CHANNEL,
                                 &ucontrol->value.enumerated.item[0]);
            break;

        case MIXER_ADDR_DECODER2_ROUTING:
            tmdlAudio_GetRouting(MIXER_DEVICE_DEC2_CHANNEL,
                                 &ucontrol->value.enumerated.item[0]);
            break;

        case MIXER_ADDR_DECODER3_ROUTING:
            tmdlAudio_GetRouting(MIXER_DEVICE_DEC3_CHANNEL,
                                 &ucontrol->value.enumerated.item[0]);
            break;

        case MIXER_ADDR_DECODER4_ROUTING:
            tmdlAudio_GetRouting(MIXER_DEVICE_DEC4_CHANNEL,
                                 &ucontrol->value.enumerated.item[0]);
            break;

        case MIXER_ADDR_TVOUT_ROUTING:
        case MIXER_ADDR_VCROUT_ROUTING:
        case MIXER_ADDR_HDMIOUT_ROUTING:
            ucontrol->value.enumerated.item[0] = stb225->mixer_value[addr][0];
            break;

        case MIXER_ADDR_SPDIFOUT_ROUTING:
            if (tmdlAudioIO_SpdifEnabled(0))
            {
                tmdlAudioIO_Output_t source;

                tmdlAudioIO_GetSpdifSource(0, &source);

                /* If SPDIF is enabled then it is either coming from i2s0 or burst. */
                switch (source)
                {
                    case tmdlAudioIO_Output_I2S_0: // PCM
                        ucontrol->value.enumerated.item[0] = 1;
                        break;
                    case tmdlAudioIO_Output_SPDIF: // Burst
                        ucontrol->value.enumerated.item[0] = 2;
                        break;
                    default:
                        retValue = -EINVAL;
                        break;
                }
            }
            else
            {
                /* SPDIF is disabled. */
                ucontrol->value.enumerated.item[0] = 0;
            }
            break;

        default:
            retValue = -EINVAL;
            break;
    }

    // Unlock
#ifdef USE_MIXER_SPINLOCKS
    spin_unlock(&stb225->mixer_lock);
#endif

    return retValue;
}

static int snd_stb225_routing_put(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
{
    struct snd_stb225 *stb225 = snd_kcontrol_chip(kcontrol);
    tmErrorCode_t err = TM_ERR_BAD_PARAMETER;
    int addr = kcontrol->private_value;
    int change = 0;

    // Lock
#ifdef USE_MIXER_SPINLOCKS
    spin_lock(&stb225->mixer_lock);
#endif

    switch ((MIXER_ADDR_VOLUME_t)addr)
    {
        case MIXER_ADDR_DECODER1_ROUTING:
            err = tmdlAudio_SetRouting(MIXER_DEVICE_DEC1_CHANNEL, ucontrol->value.enumerated.item[0]);
            break;

        case MIXER_ADDR_DECODER2_ROUTING:
            err = tmdlAudio_SetRouting(MIXER_DEVICE_DEC2_CHANNEL, ucontrol->value.enumerated.item[0]);
            break;

        case MIXER_ADDR_DECODER3_ROUTING:
            err = tmdlAudio_SetRouting(MIXER_DEVICE_DEC3_CHANNEL, ucontrol->value.enumerated.item[0]);
            break;

        case MIXER_ADDR_DECODER4_ROUTING:
            err = tmdlAudio_SetRouting(MIXER_DEVICE_DEC4_CHANNEL, ucontrol->value.enumerated.item[0]);
            break;
        case MIXER_ADDR_TVOUT_ROUTING:
        case MIXER_ADDR_VCROUT_ROUTING:
        case MIXER_ADDR_HDMIOUT_ROUTING:
            printk("alsa control not implemented yet\n");
            stb225->mixer_value[addr][0] = ucontrol->value.enumerated.item[0];
            err = TM_OK;
            break;

        case MIXER_ADDR_SPDIFOUT_ROUTING:
            switch (ucontrol->value.enumerated.item[0])
            {
                case 0: // Disable
                    err = tmdlAudioIO_SpdifEnable(0, False);
                    break;
                case 1: // PCM
                    err = tmdlAudioIO_SpdifEnable(0, True);
                    if (err == TM_OK)
                    {
                        err = tmdlAudioIO_SetSpdifSource(0, tmdlAudioIO_Output_I2S_0);
                    }
                    break;
                case 2: // Burst
                    err = tmdlAudioIO_SpdifEnable(0, True);
                    if (err == TM_OK)
                    {
                        err = tmdlAudioIO_SetSpdifSource(0, tmdlAudioIO_Output_SPDIF);
                    }
                    break;
                default:
                    break;
            }
            break;

        default:
            break;
    }

    if (err == TM_OK)
    {
        change = 1;
    }

    // Unlock
#ifdef USE_MIXER_SPINLOCKS
    spin_unlock(&stb225->mixer_lock);
#endif

    return change;
}


/**********************************************************************************
 Control definitions
**********************************************************************************/

// currently we deliberately avoid using standard names for controls to prevent ALSA mangling them
// TODO: use standard names

static struct snd_kcontrol_new __devinitdata stb225_scart_controls[] =
{
#ifdef CONFIG_SCART_SWITCH_PRESENT
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "SCART Volume",
        .index = 0,
        .info = snd_stb225_volume_info,
        .get = snd_stb225_volume_get,
        .put = snd_stb225_volume_put,
        .private_value = MIXER_ADDR_SCARTSWITCH_VOLUME_1
    },
#endif
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "TV Out",
        .index = 0,
        .info = snd_stb225_routing_info,
        .get = snd_stb225_routing_get,
        .put = snd_stb225_routing_put,
        .private_value = MIXER_ADDR_TVOUT_ROUTING
    },
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "VCR Out",
        .index = 0,
        .info = snd_stb225_routing_info,
        .get = snd_stb225_routing_get,
        .put = snd_stb225_routing_put,
        .private_value = MIXER_ADDR_VCROUT_ROUTING
    },
};

static struct snd_kcontrol_new __devinitdata stb225_dec1_controls[] =
{
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Volume",
        .index = 0,
        .info = snd_stb225_volume_info,
        .get = snd_stb225_volume_get,
        .put = snd_stb225_volume_put,
        .private_value = MIXER_ADDR_DECODER1_VOLUME
    },
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Balance",
        .index = 0,
        .info = snd_stb225_volume_info,
        .get = snd_stb225_volume_get,
        .put = snd_stb225_volume_put,
        .private_value = MIXER_ADDR_DECODER1_BALANCE
    },
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Routing",
        .index = 0,
        .info = snd_stb225_routing_info,
        .get = snd_stb225_routing_get,
        .put = snd_stb225_routing_put,
        .private_value = MIXER_ADDR_DECODER1_ROUTING
    }
};

static struct snd_kcontrol_new __devinitdata stb225_dec2_controls[] =
{
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Volume",
        .index = 1,
        .info = snd_stb225_volume_info,
        .get = snd_stb225_volume_get,
        .put = snd_stb225_volume_put,
        .private_value = MIXER_ADDR_DECODER2_VOLUME
    },
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Balance",
        .index = 1,
        .info = snd_stb225_volume_info,
        .get = snd_stb225_volume_get,
        .put = snd_stb225_volume_put,
        .private_value = MIXER_ADDR_DECODER2_BALANCE
    },
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Routing",
        .index = 1,
        .info = snd_stb225_routing_info,
        .get = snd_stb225_routing_get,
        .put = snd_stb225_routing_put,
        .private_value = MIXER_ADDR_DECODER2_ROUTING
    }
};

static struct snd_kcontrol_new __devinitdata stb225_dec3_controls[] =
{
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Volume",
        .index = 2,
        .info = snd_stb225_volume_info,
        .get = snd_stb225_volume_get,
        .put = snd_stb225_volume_put,
        .private_value = MIXER_ADDR_DECODER3_VOLUME
    },
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Balance",
        .index = 2,
        .info = snd_stb225_volume_info,
        .get = snd_stb225_volume_get,
        .put = snd_stb225_volume_put,
        .private_value = MIXER_ADDR_DECODER3_BALANCE
    },
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Routing",
        .index = 2,
        .info = snd_stb225_routing_info,
        .get = snd_stb225_routing_get,
        .put = snd_stb225_routing_put,
        .private_value = MIXER_ADDR_DECODER3_ROUTING
    }
};

static struct snd_kcontrol_new __devinitdata stb225_dec4_controls[] =
{
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Volume",
        .index = 3,
        .info = snd_stb225_volume_info,
        .get = snd_stb225_volume_get,
        .put = snd_stb225_volume_put,
        .private_value = MIXER_ADDR_DECODER4_VOLUME
    },
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Balance",
        .index = 3,
        .info = snd_stb225_volume_info,
        .get = snd_stb225_volume_get,
        .put = snd_stb225_volume_put,
        .private_value = MIXER_ADDR_DECODER4_BALANCE
    },
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Decoder Routing",
        .index = 3,
        .info = snd_stb225_routing_info,
        .get = snd_stb225_routing_get,
        .put = snd_stb225_routing_put,
        .private_value = MIXER_ADDR_DECODER4_ROUTING
    }
};

static struct snd_kcontrol_new __devinitdata stb225_spdif_controls[] =
{
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "SPDIF Routing",
        .index = 0,
        .info = snd_stb225_routing_info,
        .get = snd_stb225_routing_get,
        .put = snd_stb225_routing_put,
        .private_value = MIXER_ADDR_SPDIFOUT_ROUTING
    }
};

static struct snd_kcontrol_new __devinitdata stb225_hdmi_controls[] =
{
    {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "HDMI Routing",
        .index = 0,
        .info = snd_stb225_routing_info,
        .get = snd_stb225_routing_get,
        .put = snd_stb225_routing_put,
        .private_value = MIXER_ADDR_HDMIOUT_ROUTING
    },

};

/**********************************************************************************

**********************************************************************************/

static int __init snd_card_stb225_new_mixer(struct snd_stb225 *stb225)
{
    struct snd_card *card = stb225->card;
    unsigned int idx;
    int err;

    snd_assert(stb225 != NULL, return -EINVAL);
#ifdef USE_MIXER_SPINLOCKS
    spin_lock_init(&stb225->mixer_lock);
#endif
    strcpy(card->mixername, "STB225 Mixer");

    for (idx = 0; idx < ARRAY_SIZE(stb225_scart_controls); idx++)
    {
        if ((err = snd_ctl_add(card, snd_ctl_new1(&stb225_scart_controls[idx], stb225))) < 0)
        {
            return err;
        }
    }

    for (idx = 0; idx < ARRAY_SIZE(stb225_dec1_controls); idx++)
    {
        if ((err = snd_ctl_add(card, snd_ctl_new1(&stb225_dec1_controls[idx], stb225))) < 0)
        {
            return err;
        }
    }


    for (idx = 0; idx < ARRAY_SIZE(stb225_dec2_controls); idx++)
    {
        if ((err = snd_ctl_add(card, snd_ctl_new1(&stb225_dec2_controls[idx], stb225))) < 0)
        {
            return err;
        }
    }

    for (idx = 0; idx < ARRAY_SIZE(stb225_dec3_controls); idx++)
    {
        if ((err = snd_ctl_add(card, snd_ctl_new1(&stb225_dec3_controls[idx], stb225))) < 0)
        {
            return err;
        }
    }

    for (idx = 0; idx < ARRAY_SIZE(stb225_dec4_controls); idx++)
    {
        if ((err = snd_ctl_add(card, snd_ctl_new1(&stb225_dec4_controls[idx], stb225))) < 0)
        {
            return err;
        }
    }

    for (idx = 0; idx < ARRAY_SIZE(stb225_spdif_controls); idx++)
    {
        if ((err = snd_ctl_add(card, snd_ctl_new1(&stb225_spdif_controls[idx], stb225))) < 0)
        {
            return err;
        }
    }
    for (idx = 0; idx < ARRAY_SIZE(stb225_hdmi_controls); idx++)
    {
        if ((err = snd_ctl_add(card, snd_ctl_new1(&stb225_hdmi_controls[idx], stb225))) < 0)
        {
            return err;
        }
    }
    return 0;
}

static int __init snd_stb225_probe(struct platform_device *devptr)
{
    struct snd_card *card;
    struct snd_stb225 *stb225;
    int err;
    int dev = devptr->id;
#ifdef CONFIG_SND_PCM_MODULE
    int idx;
#endif

    card = snd_card_new(index[dev], id[dev], THIS_MODULE, sizeof(struct snd_stb225));
    if (card == NULL)
    {
        return -ENOMEM;
    }

    stb225 = (struct snd_stb225 *)card->private_data;
    stb225->card = card;

#ifdef CONFIG_SND_PCM_MODULE
    for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++)
    {
        if (pcm_substreams[dev] < 1)
        {
            pcm_substreams[dev] = 1;
        }
        if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS)
        {
            pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
        }
        if ((err = snd_card_stb225_pcm(stb225, idx, pcm_substreams[dev])) < 0)
        {
            goto __nodev;
        }
    }
#endif

    if ((err = snd_card_stb225_new_mixer(stb225)) < 0)
        goto __nodev;

    strcpy(card->driver, "STB225");
    strcpy(card->shortname, "STB225");
    sprintf(card->longname, "STB225 %i", dev + 1);

    snd_card_set_dev(card, &devptr->dev);

    if ((err = snd_card_register(card)) == 0)
    {
        platform_set_drvdata(devptr, card);
        return 0;
    }

__nodev:
    snd_card_free(card);
    return err;
}

static int snd_stb225_remove(struct platform_device *devptr)
{
    snd_card_free(platform_get_drvdata(devptr));
    platform_set_drvdata(devptr, NULL);
    return 0;
}

#ifdef CONFIG_PM
static int snd_stb225_suspend(struct platform_device *pdev,
                              pm_message_t state)
{
    struct snd_card *card = platform_get_drvdata(pdev);
    struct snd_stb225 *stb225 = card->private_data;

    snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
    snd_pcm_suspend_all(stb225->pcm);
    return 0;
}

static int snd_stb225_resume(struct platform_device *pdev)
{
    struct snd_card *card = platform_get_drvdata(pdev);

    snd_power_change_state(card, SNDRV_CTL_POWER_D0);
    return 0;
}
#endif


#define SND_STB225_DRIVER   "snd_STB225"

static struct platform_driver snd_stb225_driver = {
    .probe      = snd_stb225_probe,
    .remove     = snd_stb225_remove,
#ifdef CONFIG_PM
    .suspend    = snd_stb225_suspend,
    .resume     = snd_stb225_resume,
#endif
    .driver     = {
        .name   = SND_STB225_DRIVER
    },
};

static void __init_or_module snd_stb225_unregister_all(void)
{
    int i;

    for (i = 0; i < ARRAY_SIZE(devices); ++i)
        platform_device_unregister(devices[i]);
    platform_driver_unregister(&snd_stb225_driver);
}

static int __init alsa_card_stb225_init(void)
{
    int i, cards, err;

    if ((err = platform_driver_register(&snd_stb225_driver)) < 0)
    {
        return err;
    }

    cards = 0;
    for (i = 0; i < SNDRV_CARDS; i++)
    {
        struct platform_device *device;
        if (! enable[i])
        {
            continue;
        }
        device = platform_device_register_simple(SND_STB225_DRIVER,
                                                 i, NULL, 0);
        if (IS_ERR(device))
        {
            continue;
        }
        if (!platform_get_drvdata(device))
        {
            platform_device_unregister(device);
            continue;
        }
        devices[i] = device;
        cards++;
    }

    if (!cards)
    {
#ifdef MODULE
        printk(KERN_ERR "STB225 audio not found or device busy\n");
#endif
        snd_stb225_unregister_all();
        return -ENODEV;
    }

    printk("%s (%s-%s)\n", THIS_MODULE_DESCRIPTION, __DATE__, __TIME__);

    return 0;
}

static void __exit alsa_card_stb225_exit(void)
{
    snd_stb225_unregister_all();
    printk("Shutting down %s\n", THIS_MODULE_DESCRIPTION);
}

module_init(alsa_card_stb225_init)
module_exit(alsa_card_stb225_exit)

