aboutsummaryrefslogtreecommitdiffstats
path: root/recipes/linux/linux-2.6.18/at73c213-alsa-driver.patch
diff options
context:
space:
mode:
Diffstat (limited to 'recipes/linux/linux-2.6.18/at73c213-alsa-driver.patch')
-rw-r--r--recipes/linux/linux-2.6.18/at73c213-alsa-driver.patch1485
1 files changed, 1485 insertions, 0 deletions
diff --git a/recipes/linux/linux-2.6.18/at73c213-alsa-driver.patch b/recipes/linux/linux-2.6.18/at73c213-alsa-driver.patch
new file mode 100644
index 0000000000..ceb12cc950
--- /dev/null
+++ b/recipes/linux/linux-2.6.18/at73c213-alsa-driver.patch
@@ -0,0 +1,1485 @@
+From nobody Mon Sep 17 00:00:00 2001
+From: Hans-Christian Egtvedt <hcegtvedt@atmel.com>
+Date: Fri Apr 28 15:30:44 2006 +0200
+Subject: [PATCH] at73c213 ALSA driver
+
+This driver uses the SSC and SPI modules to communicate with an at73c213
+sound chip on the AT32STK1000.
+
+---
+
+ sound/avr32/Kconfig | 20
+ sound/avr32/Makefile | 3
+ sound/avr32/at73c213.c | 1296 +++++++++++++++++++++++++++++++++++++++++++++++++
+ sound/avr32/at73c213.h | 120 ++++
+ 4 files changed, 1439 insertions(+)
+ create mode 100644 sound/avr32/at73c213.c
+ create mode 100644 sound/avr32/at73c213.h
+
+859730d5cbe00b7935c4e30d179c5c5b096deb3c
+Index: linux-2.6.18-avr32/sound/avr32/Kconfig
+===================================================================
+--- linux-2.6.18-avr32.orig/sound/avr32/Kconfig 2006-11-02 15:56:20.000000000 +0100
++++ linux-2.6.18-avr32/sound/avr32/Kconfig 2006-11-02 15:56:20.000000000 +0100
+@@ -28,4 +28,24 @@ config SND_ATMEL_AC97C_USE_PDC
+ Say Y if PDC (Peripheral DMA Controller) is used for DMA transfers
+ to/from the Atmel AC97C instead of using the generic DMA framework.
+
++config SND_AT73C213
++ tristate "Atmel AT73C213 DAC driver"
++ depends on SND && SPI_ATMEL
++ select SND_PCM
++ help
++ Say Y here if you want to use the Atmel AT73C213 external
++ DAC on the ATSTK1000 development board.
++
++ To compile this driver as a module, choose M here: the
++ module will be called snd-at73c213.
++
++config SND_AT73C213_USE_ALSA_MALLOC_CALLS
++ bool "Use the built-in malloc calls in the alsa driver"
++ default n
++ depends on SND_AT73C213
++ help
++ Say Y if the built-in malloc calls in the alsa driver should be
++ used instead of the native dma_alloc_coherent and dma_free_coherent
++ function calls. Enabling this feature may brake the rmmod feature.
++
+ endmenu
+Index: linux-2.6.18-avr32/sound/avr32/Makefile
+===================================================================
+--- linux-2.6.18-avr32.orig/sound/avr32/Makefile 2006-11-02 15:56:20.000000000 +0100
++++ linux-2.6.18-avr32/sound/avr32/Makefile 2006-11-02 15:56:20.000000000 +0100
+@@ -4,3 +4,6 @@
+
+ snd-atmel-ac97-objs := ac97c.o
+ obj-$(CONFIG_SND_ATMEL_AC97) += snd-atmel-ac97.o
++
++snd-at73c213-objs := at73c213.o
++obj-$(CONFIG_SND_AT73C213) += snd-at73c213.o
+Index: linux-2.6.18-avr32/sound/avr32/at73c213.c
+===================================================================
+--- /dev/null 1970-01-01 00:00:00.000000000 +0000
++++ linux-2.6.18-avr32/sound/avr32/at73c213.c 2006-11-02 16:01:55.000000000 +0100
+@@ -0,0 +1,1296 @@
++/*
++ * Driver for the at73c213 16-bit stereo DAC on Atmel ATSTK1000
++ *
++ * Copyright (C) 2006 Atmel Norway
++ *
++ * 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.
++ *
++ * The full GNU General Public License is included in this
++ * distribution in the file called COPYING.
++ */
++#undef DEBUG
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/dma-mapping.h>
++#include <linux/init.h>
++#include <linux/interrupt.h>
++#include <linux/kmod.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++
++#include <sound/initval.h>
++#include <sound/driver.h>
++#include <sound/control.h>
++#include <sound/core.h>
++#include <sound/pcm.h>
++#ifndef SND_AT73C213_USE_ALSA_MALLOC_CALLS
++#include <sound/memalloc.h>
++#endif
++
++#include <linux/spi/spi.h>
++
++#include <asm/io.h>
++#include <asm/processor.h>
++
++#include "at73c213.h"
++
++/* module parameters */
++static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
++static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
++static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
++
++/* Register defines */
++#define PIOA_BASE 0xFFE02800
++#define SSC0_BASE 0xFFE01C00
++#define PM_BASE 0xFFF00000
++
++#define PM_CKSEL 0x04
++#define PM_APBAMASK 0x10
++#define PM_GCCTRL 0x60
++
++#define PIO_PER 0x00
++#define PIO_PDR 0x04
++#define PIO_PUER 0x64
++#define PIO_ASR 0x70
++#define PIO_BSR 0x74
++
++#define SSC_CMR 0x04
++#define SSC_CR 0x00
++#define SSC_TCMR 0x18
++#define SSC_TFMR 0x1C
++
++/* SSC register definitions */
++#define SSC_CR 0x00
++#define SSC_CMR 0x04
++#define SSC_TCMR 0x18
++#define SSC_TFMR 0x1C
++#define SSC_THR 0x24
++#define SSC_SR 0x40
++#define SSC_IER 0x44
++#define SSC_IDR 0x48
++#define SSC_IMR 0x4C
++
++/* SSC fields definitions */
++#define SSC_CR_TXEN 0x00000100
++#define SSC_CR_TXDIS 0x00000200
++#define SSC_CR_SWRST 0x00008000
++
++/* SSC interrupt definitions */
++#define SSC0_IRQ 10
++#define SSC_INT_ENDTX 0x00000004
++#define SSC_INT_TXBUFE 0x00000008
++
++/* PDC register definitions */
++#define PDC_RPR 0x100
++#define PDC_RCR 0x104
++#define PDC_TPR 0x108
++#define PDC_TCR 0x10c
++#define PDC_RNPR 0x110
++#define PDC_RNCR 0x114
++#define PDC_TNPR 0x118
++#define PDC_TNCR 0x11c
++#define PDC_PTCR 0x120
++#define PDC_PTSR 0x124
++
++/* PDC fields definitions */
++#define PDC_PTCR_RXTEN 0x0001
++#define PDC_PTCR_RXTDIS 0x0002
++#define PDC_PTCR_TXTEN 0x0100
++#define PDC_PTCR_TXTDIS 0x0200
++
++static int bitrate;
++static int gclk_div;
++static int ssc_div;
++static int spi = 0;
++static int ssc = 1;
++
++module_param(spi, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
++MODULE_PARM_DESC(spi, "Which SPI interface to use to communicate with the at73c213");
++module_param(ssc, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
++MODULE_PARM_DESC(ssc, "Which SSC interface to use to communicate with the at73c213");
++
++/* Initial AT73C213 register values */
++static unsigned char snd_at73c213_original_image[18] =
++{
++ 0x00, /* 00 - CTRL */
++ 0x05, /* 01 - LLIG */
++ 0x05, /* 02 - RLIG */
++ 0x08, /* 03 - LPMG */
++ 0x08, /* 04 - RPMG */
++ 0x00, /* 05 - LLOG */
++ 0x00, /* 06 - RLOG */
++ 0x22, /* 07 - OLC */
++ 0x09, /* 08 - MC */
++ 0x00, /* 09 - CSFC */
++ 0x00, /* 0A - MISC */
++ 0x00, /* 0B - */
++ 0x00, /* 0C - PRECH */
++ 0x05, /* 0D - AUXG */
++ 0x00, /* 0E - */
++ 0x00, /* 0F - */
++ 0x00, /* 10 - RST */
++ 0x00, /* 11 - PA_CTRL */
++};
++
++/* chip-specific data */
++struct snd_at73c213 {
++ snd_card_t *card;
++ snd_pcm_t *pcm;
++ snd_pcm_substream_t *substream;
++ int irq;
++ int period;
++ void __iomem *regs;
++ struct clk *ssc_clk;
++ struct spi_device *spi;
++ u8 spi_wbuffer[2];
++ u8 spi_rbuffer[2];
++ /* image of the SPI registers in AT73C213 */
++ u8 image[18];
++ spinlock_t lock;
++ struct platform_device *pdev;
++};
++
++#define get_chip(card) ((struct snd_at73c213 *)card->private_data)
++
++static int
++snd_at73c213_write_reg(struct snd_at73c213 *chip, u8 reg, u8 val)
++{
++ struct spi_message msg;
++ struct spi_transfer msg_xfer = {
++ .len = 2,
++ .cs_change = 0,
++ };
++
++ spi_message_init(&msg);
++
++ chip->spi_wbuffer[0] = reg;
++ chip->spi_wbuffer[1] = val;
++
++ msg_xfer.tx_buf = chip->spi_wbuffer;
++ msg_xfer.rx_buf = chip->spi_rbuffer;
++ spi_message_add_tail(&msg_xfer, &msg);
++
++ return spi_sync(chip->spi, &msg);
++}
++
++#define write_reg(_spi, reg, val) \
++ do { \
++ retval = snd_at73c213_write_reg(_spi, reg, val); \
++ if (retval) \
++ goto out; \
++ } while (0)
++
++static snd_pcm_hardware_t snd_at73c213_playback_hw = {
++ .info = SNDRV_PCM_INFO_INTERLEAVED |
++ SNDRV_PCM_INFO_BLOCK_TRANSFER,
++ .formats = SNDRV_PCM_FMTBIT_S16_BE,
++ .rates = SNDRV_PCM_RATE_CONTINUOUS,
++ .rate_min = 8000, /* This will be overwritten with bitrate */
++ .rate_max = 50000, /* This will be overwritten with bitrate */
++ .channels_min = 2,
++ .channels_max = 2,
++ .buffer_bytes_max = 64 * 1024 - 1,
++ .period_bytes_min = 512,
++ .period_bytes_max = 64 * 1024 - 1,
++ .periods_min = 4,
++ .periods_max = 1024,
++};
++
++/* calculate and set bitrate and divisions */
++static int snd_at73c213_set_bitrate_and_div(void)
++{
++ extern struct avr32_cpuinfo boot_cpu_data;
++ unsigned long pll0_hz, apba_hz;
++ unsigned long apba_realdiv, gclk_realdiv, ssc_realdiv, wanted_bitrate;
++ char cpusel, ahbsel, apbasel;
++ int regval;
++
++ regval = __raw_readl((void __iomem *)PM_BASE + PM_CKSEL);
++ wanted_bitrate = 48000;
++
++ cpusel = regval & 0x07;
++ ahbsel = (regval>>8) & 0x07;
++ apbasel = (regval>>16) & 0x07;
++
++ /* FIXME: Use the clk framework for this */
++ if ((regval&(1<<7)) != 0) {
++ pll0_hz = clk_get_rate(boot_cpu_data.clk)/(1<<(cpusel+1));
++ } else {
++ pll0_hz = clk_get_rate(boot_cpu_data.clk);
++ }
++
++ if ((regval&(1<<23)) != 0) {
++ apba_hz = pll0_hz/(1<<(apbasel+1));
++ apba_realdiv = (1<<(apbasel+1));
++ } else {
++ apba_hz = pll0_hz;
++ apba_realdiv = 1;
++ }
++
++calculate:
++ /* Adjust bitrate as close as possible to 48000 Hz */
++ gclk_realdiv = pll0_hz/(wanted_bitrate*256);
++ ssc_realdiv = 2 * apba_realdiv * gclk_realdiv;
++
++ if ((gclk_realdiv % 2) == 0)
++ goto setbitrates;
++
++ if(wanted_bitrate >= 22050 && wanted_bitrate <= 48000)
++ wanted_bitrate -= 50;
++ else if (wanted_bitrate < 22050)
++ wanted_bitrate = 48050;
++ else if (wanted_bitrate <= 50000)
++ wanted_bitrate += 50;
++ else {
++ printk(KERN_ERR "at73c213 could not set dividers for a valid bitrate\n");
++ return -EINVAL;
++ }
++
++ goto calculate;
++
++setbitrates:
++ bitrate = pll0_hz/(gclk_realdiv*256);
++ gclk_div = (gclk_realdiv/2)-1;
++ ssc_realdiv = 2*apba_realdiv*gclk_realdiv;
++ ssc_div = ssc_realdiv/(2*apba_realdiv);
++
++ printk(KERN_INFO "at73c213: bitrate is %d Hz\n", bitrate);
++
++ return 0;
++}
++
++/* open callback */
++static int snd_at73c213_pcm_open(snd_pcm_substream_t *substream)
++{
++ struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
++ snd_pcm_runtime_t *runtime = substream->runtime;
++
++ snd_at73c213_playback_hw.rate_min = bitrate;
++ snd_at73c213_playback_hw.rate_max = bitrate;
++ runtime->hw = snd_at73c213_playback_hw;
++ chip->substream = substream;
++
++ return 0;
++}
++
++/* close callback */
++static int snd_at73c213_pcm_close(snd_pcm_substream_t *substream)
++{
++ struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
++ chip->substream = NULL;
++ return 0;
++}
++
++/* hw_params callback */
++static int snd_at73c213_pcm_hw_params(snd_pcm_substream_t *substream,
++ snd_pcm_hw_params_t *hw_params)
++{
++#ifdef SND_AT73C213_USE_ALSA_MALLOC_CALLS
++ return snd_pcm_lib_malloc_pages(substream,
++ params_buffer_bytes(hw_params));
++#else
++ int pg;
++ size_t size = params_buffer_bytes(hw_params);
++ struct snd_pcm_runtime *runtime;
++ struct snd_dma_buffer *dmab = NULL;
++
++ substream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV;
++ snd_assert(substream != NULL, return -EINVAL);
++ runtime = substream->runtime;
++ snd_assert(runtime != NULL, return -EINVAL);
++
++ /* check if buffer is already allocated */
++ if (runtime->dma_buffer_p) {
++ size_t size_previouse;
++ int pg_previouse;
++
++ /* new buffer is smaler than previouse allocated buffer */
++ if (runtime->dma_buffer_p->bytes >= size) {
++ runtime->dma_bytes = size;
++ return 0; /* don't change buffer size */
++ }
++
++ size_previouse = runtime->dma_buffer_p->bytes;
++ pg_previouse = get_order(size_previouse);
++
++ dma_free_coherent(runtime->dma_buffer_p->dev.dev,
++ PAGE_SIZE << pg_previouse,
++ runtime->dma_buffer_p->area,
++ runtime->dma_buffer_p->addr);
++
++ kfree(runtime->dma_buffer_p);
++ }
++
++ dmab = kzalloc(sizeof(*dmab), GFP_KERNEL);
++ if (!dmab)
++ return -ENOMEM;
++
++ dmab->dev = substream->dma_buffer.dev;
++ dmab->bytes = 0;
++
++ pg = get_order(size);
++
++ dmab->area = dma_alloc_coherent(
++ substream->dma_buffer.dev.dev,
++ PAGE_SIZE << pg,
++ (dma_addr_t *)&dmab->addr,
++ GFP_KERNEL);
++
++ if (!dmab->area) {
++ kfree(dmab);
++ return -ENOMEM;
++ }
++
++ dmab->bytes = size;
++ snd_pcm_set_runtime_buffer(substream, dmab);
++ runtime->dma_bytes = size;
++ return 1;
++#endif
++}
++
++/* hw_free callback */
++static int snd_at73c213_pcm_hw_free(snd_pcm_substream_t *substream)
++{
++#ifdef SND_AT73C213_USE_ALSA_MALLOC_CALLS
++ return snd_pcm_lib_free_pages(substream);
++#else
++ int pg;
++ struct snd_pcm_runtime *runtime;
++ struct snd_dma_buffer *dmab = NULL;
++
++ snd_assert(substream != NULL, return -EINVAL);
++ runtime = substream->runtime;
++ snd_assert(runtime != NULL, return -EINVAL);
++ dmab = runtime->dma_buffer_p;
++
++ if (!dmab)
++ return 0;
++
++ if (!dmab->area)
++ return 0;
++
++ pg = get_order(dmab->bytes);
++ dma_free_coherent(dmab->dev.dev, PAGE_SIZE << pg, dmab->area, dmab->addr);
++ kfree(runtime->dma_buffer_p);
++ snd_pcm_set_runtime_buffer(substream, NULL);
++ return 0;
++#endif
++}
++
++/* prepare callback */
++static int snd_at73c213_pcm_prepare(snd_pcm_substream_t *substream)
++{
++ struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
++ struct platform_device *pdev = chip->pdev;
++ snd_pcm_runtime_t *runtime = substream->runtime;
++ int block_size;
++
++ block_size = frames_to_bytes(runtime, runtime->period_size);
++
++ chip->period = 0;
++
++ /* Make sure that our data are actually readable by the SSC */
++ dma_sync_single_for_device(&pdev->dev, runtime->dma_addr,
++ block_size, DMA_TO_DEVICE);
++ dma_sync_single_for_device(&pdev->dev, runtime->dma_addr + block_size,
++ block_size, DMA_TO_DEVICE);
++
++ __raw_writel(runtime->dma_addr, chip->regs + PDC_TPR);
++ __raw_writel(runtime->period_size * 2, chip->regs + PDC_TCR);
++ __raw_writel(runtime->dma_addr + block_size, chip->regs + PDC_TNPR);
++ __raw_writel(runtime->period_size * 2, chip->regs + PDC_TNCR);
++
++ return 0;
++}
++
++/* trigger callback */
++static int snd_at73c213_pcm_trigger(snd_pcm_substream_t *substream,
++ int cmd)
++{
++ struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
++ int retval = 0;
++ int flags = 0;
++
++ spin_lock_irqsave(&chip->lock, flags);
++
++ switch (cmd) {
++ case SNDRV_PCM_TRIGGER_START:
++ __raw_writel(SSC_INT_ENDTX, chip->regs + SSC_IER);
++ __raw_writel(PDC_PTCR_TXTEN, chip->regs + PDC_PTCR);
++ break;
++ case SNDRV_PCM_TRIGGER_STOP:
++ __raw_writel(PDC_PTCR_TXTDIS, chip->regs + PDC_PTCR);
++ __raw_writel(SSC_INT_ENDTX, chip->regs + SSC_IDR);
++ break;
++ default:
++ printk(KERN_WARNING "at73c213: spuriouse command %x\n", cmd);
++ retval = -EINVAL;
++ break;
++ }
++
++ spin_unlock_irqrestore(&chip->lock, flags);
++
++ return retval;
++}
++
++/* pointer callback */
++static snd_pcm_uframes_t snd_at73c213_pcm_pointer(snd_pcm_substream_t *substream)
++{
++ struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
++ snd_pcm_runtime_t *runtime = substream->runtime;
++ snd_pcm_uframes_t pos;
++ unsigned long bytes;
++
++ bytes = __raw_readl(chip->regs + PDC_TPR) - runtime->dma_addr;
++
++ pos = bytes_to_frames(runtime, bytes);
++ if (pos >= runtime->buffer_size)
++ pos -= runtime->buffer_size;
++
++ return pos;
++}
++
++/* operators */
++static snd_pcm_ops_t at73c213_playback_ops = {
++ .open = snd_at73c213_pcm_open,
++ .close = snd_at73c213_pcm_close,
++ .ioctl = snd_pcm_lib_ioctl,
++ .hw_params = snd_at73c213_pcm_hw_params,
++ .hw_free = snd_at73c213_pcm_hw_free,
++ .prepare = snd_at73c213_pcm_prepare,
++ .trigger = snd_at73c213_pcm_trigger,
++ .pointer = snd_at73c213_pcm_pointer,
++};
++
++/* free a pcm device */
++static void snd_at73c213_pcm_free(snd_pcm_t *pcm)
++{
++ struct snd_at73c213 *chip = snd_pcm_chip(pcm);
++ if (chip->pcm != 0 ) {
++#ifdef SND_AT73C213_USE_ALSA_MALLOC_CALLS
++ snd_pcm_lib_preallocate_free_for_all(chip->pcm);
++#endif
++ chip->pcm = NULL;
++ }
++}
++
++/* create a new pcm device */
++static int __devinit snd_at73c213_new_pcm(struct snd_at73c213 *chip, int device)
++{
++ snd_pcm_t *pcm;
++ int retval;
++
++ retval = snd_pcm_new(chip->card, chip->card->shortname, device, 1, 0, &pcm);
++ if (retval < 0)
++ return retval;
++
++ pcm->private_data = chip;
++ pcm->private_free = snd_at73c213_pcm_free;
++ pcm->info_flags = SNDRV_PCM_INFO_BLOCK_TRANSFER;
++ strcpy(pcm->name, "at73c213");
++ chip->pcm = pcm;
++
++ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &at73c213_playback_ops);
++
++#ifdef SND_AT73C213_USE_ALSA_MALLOC_CALLS
++ snd_pcm_lib_preallocate_pages_for_all(chip->pcm, SNDRV_DMA_TYPE_DEV,
++ &chip->pdev->dev, 64 * 1024, 64 * 1024);
++#endif
++
++ return 0;
++}
++
++static irqreturn_t snd_at73c213_interrupt(int irq, void *dev_id,
++ struct pt_regs *regs)
++{
++ struct snd_at73c213 *chip = dev_id;
++ struct platform_device *pdev = chip->pdev;
++ snd_pcm_runtime_t *runtime = chip->substream->runtime;
++ u32 status;
++ int offset, next_period, block_size;
++
++ spin_lock(&chip->lock);
++
++ block_size = frames_to_bytes(runtime, runtime->period_size);
++
++ status = __raw_readl(chip->regs + SSC_IMR);
++
++ if (status & SSC_INT_ENDTX) {
++ chip->period++;
++ if (chip->period == runtime->periods)
++ chip->period = 0;
++ next_period = chip->period + 1;
++ if (next_period == runtime->periods)
++ next_period = 0;
++
++ offset = block_size * next_period;
++
++ /* Make sure that our data are actually readable by the SSC */
++ dma_sync_single_for_device(&pdev->dev, runtime->dma_addr + offset,
++ block_size, DMA_TO_DEVICE);
++ __raw_writel(runtime->dma_addr + offset, chip->regs + PDC_TNPR);
++ __raw_writel(runtime->period_size * 2, chip->regs + PDC_TNCR);
++
++ if (next_period == 0) {
++ (void)__raw_readl(chip->regs + PDC_TPR);
++ (void)__raw_readl(chip->regs + PDC_TCR);
++ }
++ } else {
++ printk(KERN_WARNING
++ "Spurious SSC interrupt, status = 0x%08lx\n",
++ (unsigned long)status);
++ __raw_writel(status, chip->regs + SSC_IDR);
++ }
++
++ (void)__raw_readl(chip->regs + SSC_IMR);
++ spin_unlock(&chip->lock);
++
++ if (status & SSC_INT_ENDTX)
++ snd_pcm_period_elapsed(chip->substream);
++
++ return IRQ_HANDLED;
++}
++
++/*
++ * Mixer functions
++ */
++#if 0 /* Function not in use */
++static int snd_at73c213_mono_info(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
++{
++ unsigned long mask = (kcontrol->private_value >> 16) & 0xff;
++
++ uinfo->type = (mask == 1) ?
++ SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
++ uinfo->count = 1;
++ uinfo->value.integer.min = 0;
++ uinfo->value.integer.max = mask;
++
++ return 0;
++}
++#endif
++
++static int snd_at73c213_mono_get(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
++ unsigned long flags;
++ int reg = kcontrol->private_value & 0xff;
++ int shift = (kcontrol->private_value >> 8) & 0xff;
++ int mask = (kcontrol->private_value >> 16) & 0xff;
++ int invert = (kcontrol->private_value >> 24) & 0xff;
++
++ spin_lock_irqsave(&chip->lock, flags);
++
++ ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask;
++
++ if (invert)
++ ucontrol->value.integer.value[0] =
++ (mask - ucontrol->value.integer.value[0]);
++
++ spin_unlock_irqrestore(&chip->lock, flags);
++
++ return 0;
++}
++
++static int snd_at73c213_mono_put(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
++ unsigned long flags;
++ int reg = kcontrol->private_value & 0xff;
++ int shift = (kcontrol->private_value >> 8) & 0xff;
++ int mask = (kcontrol->private_value >> 16) & 0xff;
++ int invert = (kcontrol->private_value >> 24) & 0xff;
++ int change, retval;
++ unsigned short val;
++
++ val = (ucontrol->value.integer.value[0] & mask);
++ if (invert)
++ val = mask - val;
++ val <<= shift;
++
++ spin_lock_irqsave(&chip->lock, flags);
++
++ val = (chip->image[reg] & ~(mask << shift)) | val;
++ change = val != chip->image[reg];
++ write_reg(chip, reg, val);
++
++ chip->image[reg] = val;
++
++ spin_unlock_irqrestore(&chip->lock, flags);
++
++ return change;
++
++out:
++ return retval;
++}
++
++static int snd_at73c213_stereo_info(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
++{
++ int mask = (kcontrol->private_value >> 24) & 0xFF;
++
++ uinfo->type = mask == 1 ?
++ SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
++ uinfo->count = 2;
++ uinfo->value.integer.min = 0;
++ uinfo->value.integer.max = mask;
++
++ return 0;
++}
++
++static int snd_at73c213_stereo_get(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
++ unsigned long flags;
++ int left_reg = kcontrol->private_value & 0xff;
++ int right_reg = (kcontrol->private_value >> 8) & 0xff;
++ int shift_left = (kcontrol->private_value >> 16) & 0x07;
++ int shift_right = (kcontrol->private_value >> 19) & 0x07;
++ int mask = (kcontrol->private_value >> 24) & 0xff;
++ int invert = (kcontrol->private_value >> 22) & 1;
++
++ spin_lock_irqsave(&chip->lock, flags);
++
++ ucontrol->value.integer.value[0] =
++ (chip->image[left_reg] >> shift_left) & mask;
++ ucontrol->value.integer.value[1] =
++ (chip->image[right_reg] >> shift_right) & mask;
++
++ if (invert) {
++ ucontrol->value.integer.value[0] =
++ (mask - ucontrol->value.integer.value[0]);
++ ucontrol->value.integer.value[1] =
++ (mask - ucontrol->value.integer.value[1]);
++ }
++
++ spin_unlock_irqrestore(&chip->lock, flags);
++
++ return 0;
++}
++
++static int snd_at73c213_stereo_put(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
++ unsigned long flags;
++ int left_reg = kcontrol->private_value & 0xff;
++ int right_reg = (kcontrol->private_value >> 8) & 0xff;
++ int shift_left = (kcontrol->private_value >> 16) & 0x07;
++ int shift_right = (kcontrol->private_value >> 19) & 0x07;
++ int mask = (kcontrol->private_value >> 24) & 0xff;
++ int invert = (kcontrol->private_value >> 22) & 1;
++ int change, retval;
++ unsigned short val1, val2;
++
++ val1 = ucontrol->value.integer.value[0] & mask;
++ val2 = ucontrol->value.integer.value[1] & mask;
++ if (invert) {
++ val1 = mask - val1;
++ val2 = mask - val2;
++ }
++ val1 <<= shift_left;
++ val2 <<= shift_right;
++
++ spin_lock_irqsave(&chip->lock, flags);
++
++ val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
++ val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
++ change = val1 != chip->image[left_reg] || val2 != chip->image[right_reg];
++ write_reg(chip, left_reg, val1);
++ write_reg(chip, right_reg, val2);
++
++ chip->image[left_reg] = val1;
++ chip->image[right_reg] = val2;
++
++ spin_unlock_irqrestore(&chip->lock, flags);
++
++ return change;
++
++out:
++ return retval;
++}
++
++static int snd_at73c213_mono_switch_info(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
++{
++ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
++ uinfo->count = 1;
++ uinfo->value.integer.min = 0;
++ uinfo->value.integer.max = 1;
++
++ return 0;
++}
++
++static int snd_at73c213_mono_switch_get(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
++ unsigned long flags;
++ int reg = kcontrol->private_value & 0xff;
++ int shift = (kcontrol->private_value >> 8) & 0xff;
++ int invert = (kcontrol->private_value >> 24) & 0xff;
++
++ spin_lock_irqsave(&chip->lock, flags);
++
++ ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & 0x01;
++
++ if (invert)
++ ucontrol->value.integer.value[0] =
++ (0x01 - ucontrol->value.integer.value[0]);
++
++ spin_unlock_irqrestore(&chip->lock, flags);
++
++ return 0;
++}
++
++static int snd_at73c213_mono_switch_put(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
++ unsigned long flags;
++ int reg = kcontrol->private_value & 0xff;
++ int shift = (kcontrol->private_value >> 8) & 0xff;
++ int mask = (kcontrol->private_value >> 16) & 0xff;
++ int invert = (kcontrol->private_value >> 24) & 0xff;
++ int change, retval;
++ unsigned short val;
++
++ if (ucontrol->value.integer.value[0])
++ val = mask;
++ else
++ val = 0;
++
++ if (invert)
++ val = mask - val;
++ val <<= shift;
++
++ spin_lock_irqsave(&chip->lock, flags);
++
++ val |= (chip->image[reg] & ~(mask << shift));
++ change = val != chip->image[reg];
++
++ write_reg(chip, reg, val);
++
++ chip->image[reg] = val;
++
++ spin_unlock_irqrestore(&chip->lock, flags);
++
++ return change;
++
++out:
++ return retval;
++}
++
++static int snd_at73c213_pa_volume_info(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
++{
++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
++ uinfo->count = 1;
++ uinfo->value.integer.min = 0;
++ uinfo->value.integer.max = ((kcontrol->private_value >> 16) & 0xFF) - 1;
++
++ return 0;
++}
++
++static int snd_at73c213_line_capture_volume_info(
++ struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
++{
++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
++ uinfo->count = 2;
++ uinfo->value.integer.min = 14;
++ uinfo->value.integer.max = 31;
++
++ return 0;
++}
++
++static int snd_at73c213_aux_capture_volume_info(
++ struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
++{
++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
++ uinfo->count = 1;
++ uinfo->value.integer.min = 14;
++ uinfo->value.integer.max = 31;
++
++ return 0;
++}
++
++#define AT73C213_MONO(xname, xindex, reg, shift, mask, invert) \
++{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
++ .info = snd_at73c213_mono_info, \
++ .get = snd_at73c213_mono_get, .put = snd_at73c213_mono_put, \
++ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
++
++#define AT73C213_MONO_SWITCH(xname, xindex, reg, shift, mask, invert) \
++{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
++ .info = snd_at73c213_mono_switch_info, \
++ .get = snd_at73c213_mono_switch_get, .put = snd_at73c213_mono_switch_put, \
++ .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
++
++#define AT73C213_STEREO(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
++{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
++ .info = snd_at73c213_stereo_info, \
++ .get = snd_at73c213_stereo_get, .put = snd_at73c213_stereo_put, \
++ .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
++
++static struct snd_kcontrol_new snd_at73c213_controls[] __devinitdata = {
++AT73C213_STEREO("Master Playback Volume", 0, DAC_LMPG, DAC_RMPG, 0, 0, 0x1F, 1),
++AT73C213_STEREO("Master Playback Switch", 0, DAC_LMPG, DAC_RMPG, 5, 5, 1, 1),
++AT73C213_STEREO("PCM Playback Volume", 0, DAC_LLOG, DAC_RLOG, 0, 0, 0x1F, 1),
++AT73C213_STEREO("PCM Playback Switch", 0, DAC_LLOG, DAC_RLOG, 5, 5, 1, 1),
++AT73C213_MONO_SWITCH("Mono PA Playback Switch", 0, DAC_CTRL, DAC_CTRL_ONPADRV, 0x01, 0),
++{
++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
++ .name = "PA Playback Volume",
++ .index = 0,
++ .info = snd_at73c213_pa_volume_info,
++ .get = snd_at73c213_mono_get,
++ .put = snd_at73c213_mono_put,
++ .private_value = PA_CTRL|(PA_CTRL_APAGAIN<<8)|(0x0F<<16)|(1<<24),
++},
++AT73C213_MONO_SWITCH("PA High Gain Playback Switch", 0, PA_CTRL, PA_CTRL_APALP, 0x01, 1),
++AT73C213_MONO_SWITCH("PA Playback Switch", 0, PA_CTRL, PA_CTRL_APAON, 0x01, 0),
++{
++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
++ .name = "Aux Capture Volume",
++ .index = 0,
++ .info = snd_at73c213_aux_capture_volume_info,
++ .get = snd_at73c213_mono_get,
++ .put = snd_at73c213_mono_put,
++ .private_value = DAC_AUXG|(0<<8)|(0x1F<<16)|(1<<24),
++},
++AT73C213_MONO_SWITCH("Aux Capture Switch", 0, DAC_CTRL, DAC_CTRL_ONAUXIN, 0x01, 0),
++{
++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
++ .name = "Line Capture Volume",
++ .index = 0,
++ .info = snd_at73c213_line_capture_volume_info,
++ .get = snd_at73c213_stereo_get,
++ .put = snd_at73c213_stereo_put,
++ .private_value = DAC_LLIG|(DAC_RLIG<<8)|(0<<16)|(0<<19)|(0x1F<<24)|(1<<22),
++},
++AT73C213_MONO_SWITCH("Line Capture Switch", 0, DAC_CTRL, 0, 0x03, 0),
++};
++
++static int __devinit snd_at73c213_mixer(struct snd_at73c213 *chip)
++{
++ struct snd_card *card;
++ int errval, idx;
++
++ if (chip == NULL || chip->pcm == NULL)
++ return -EINVAL;
++
++ card = chip->card;
++
++ strcpy(card->mixername, chip->pcm->name);
++
++ for (idx = 0; idx < ARRAY_SIZE(snd_at73c213_controls); idx++) {
++ if ((errval = snd_ctl_add(card,
++ snd_ctl_new1(&snd_at73c213_controls[idx],
++ chip))) < 0)
++ return errval;
++ }
++
++ return 0;
++}
++
++/*
++ * Device functions
++ */
++static int snd_at73c213_chip_init(struct snd_at73c213 *chip)
++{
++ int retval;
++ unsigned char dac_ctrl = 0;
++
++ /* XXX: Unmask the APB clock for SSC0 */
++ __raw_writel(__raw_readl((void __iomem *)PM_BASE + PM_APBAMASK)|(1<<7),
++ (void __iomem *)PM_BASE + PM_APBAMASK);
++
++ /* Wait for clock to be stable */
++ msleep(10);
++
++ retval = snd_at73c213_set_bitrate_and_div();
++ if (retval)
++ goto out;
++
++ /* Reset the SSC */
++ __raw_writel(SSC_CR_SWRST, chip->regs + SSC_CR);
++
++ /* Enable GCLK0 */
++ __raw_writel((1<<30), (void __iomem *)(PIOA_BASE + PIO_PDR));
++ __raw_writel((1<<30), (void __iomem *)(PIOA_BASE + PIO_ASR));
++ __raw_writel(((gclk_div<<8)|0x10|0x04|0x02), (void __iomem *)(PM_BASE + PM_GCCTRL));
++
++ /* Enable SSC and setup for I2S */
++ __raw_writel(ssc_div, chip->regs + SSC_CMR);
++
++ /* CKO, START, STTDLY, PERIOD */
++ __raw_writel((1<<2)|(4<<8)|(1<<16)|(15<<24), chip->regs + SSC_TCMR);
++
++ /* DATLEN, MSBF, DATNB, FSLEN, FSOS */
++ __raw_writel((15<<0)|(1<<7)|(1<<8)|(15<<16)|(1<<20), chip->regs + SSC_TFMR);
++
++ /* Initialize at73c213 on SPI bus */
++ /* Reset the device */
++ write_reg(chip, DAC_RST, 0x04);
++ msleep(1);
++ write_reg(chip, DAC_RST, 0x03);
++
++ /* Turn on precharge */
++ write_reg(chip, DAC_PRECH, 0xFF);
++ write_reg(chip, PA_CTRL, (1<<PA_CTRL_APAPRECH));
++ write_reg(chip, DAC_CTRL, (1<<DAC_CTRL_ONLNOL)|(1<<DAC_CTRL_ONLNOR));
++
++ msleep(50);
++
++ /* Stop precharging PA */
++ write_reg(chip, PA_CTRL, (1<<PA_CTRL_APALP)|0x0F);
++ chip->image[PA_CTRL] = (1<<PA_CTRL_APALP)|0x0F;
++
++ msleep(450);
++
++ /* Stop precharging, turn on master power */
++ write_reg(chip, DAC_PRECH, (1<<DAC_PRECH_ONMSTR));
++ chip->image[DAC_PRECH] = (1<<DAC_PRECH_ONMSTR);
++
++ msleep(1);
++
++ /* Turn on DAC */
++ dac_ctrl = (1<<DAC_CTRL_ONDACL)|(1<<DAC_CTRL_ONDACR)|
++ (1<<DAC_CTRL_ONLNOL)|(1<<DAC_CTRL_ONLNOR);
++
++ write_reg(chip, DAC_CTRL, dac_ctrl);
++ chip->image[DAC_CTRL] = dac_ctrl;
++
++ /* Mute sound */
++ write_reg(chip, DAC_LMPG, 0x3F);
++ chip->image[DAC_LMPG] = 0x3F;
++ write_reg(chip, DAC_RMPG, 0x3F);
++ chip->image[DAC_RMPG] = 0x3F;
++ write_reg(chip, DAC_LLOG, 0x3F);
++ chip->image[DAC_LLOG] = 0x3F;
++ write_reg(chip, DAC_RLOG, 0x3F);
++ chip->image[DAC_RLOG] = 0x3F;
++ write_reg(chip, DAC_LLIG, 0x11);
++ chip->image[DAC_LLIG] = 0x11;
++ write_reg(chip, DAC_RLIG, 0x11);
++ chip->image[DAC_RLIG] = 0x11;
++ write_reg(chip, DAC_AUXG, 0x11);
++ chip->image[DAC_AUXG] = 0x11;
++
++ /* Turn on SSC transmitter */
++ __raw_writel(SSC_CR_TXEN, chip->regs + SSC_CR);
++
++out:
++ return retval;
++}
++
++static int snd_at73c213_dev_free(snd_device_t *device)
++{
++ struct snd_at73c213 *chip = device->device_data;
++
++ if (chip->regs) {
++ __raw_writel(SSC_CR_TXDIS, chip->regs + SSC_CR);
++ iounmap(chip->regs);
++ }
++
++ if (chip->irq >= 0)
++ free_irq(chip->irq, chip);
++
++ if (chip->ssc_clk) {
++ clk_disable(chip->ssc_clk);
++ clk_put(chip->ssc_clk);
++ }
++
++ return 0;
++}
++
++static int __devinit snd_at73c213_create(snd_card_t *card,
++ struct platform_device *pdev)
++{
++ static snd_device_ops_t ops = {
++ .dev_free = snd_at73c213_dev_free,
++ };
++ struct snd_at73c213 *chip = get_chip(card);
++ struct resource *regs;
++ struct clk *ssc_clk;
++ int irq, retval;
++
++ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++ if (!regs)
++ return -ENXIO;
++ irq = platform_get_irq(pdev, 0);
++ if (irq < 0)
++ return irq;
++
++ ssc_clk = clk_get(&pdev->dev, "mck");
++ if (IS_ERR(ssc_clk))
++ return PTR_ERR(ssc_clk);
++ clk_enable(ssc_clk);
++ chip->ssc_clk = ssc_clk;
++
++ spin_lock_init(&chip->lock);
++ chip->card = card;
++ chip->pdev = pdev;
++ chip->irq = -1;
++
++ retval = -ENOMEM;
++
++ retval = spi_setup(chip->spi);
++ if (retval)
++ goto out;
++
++ chip->regs = ioremap(regs->start, regs->end - regs->start + 1);
++ if (!chip->regs)
++ goto out;
++
++ retval = request_irq(irq, snd_at73c213_interrupt, 0, "at73c213", chip);
++ if (retval) {
++ snd_printk("unable to request IRQ%d\n", irq);
++ goto out;
++ }
++ chip->irq = irq;
++
++ memcpy(&chip->image, &snd_at73c213_original_image,
++ sizeof(snd_at73c213_original_image));
++
++ retval = snd_at73c213_chip_init(chip);
++ if (retval)
++ goto out;
++
++ retval = snd_at73c213_new_pcm(chip, 0);
++ if (retval)
++ goto out;
++
++ retval = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
++ if (retval)
++ goto out;
++
++ retval = snd_at73c213_mixer(chip);
++ if (retval)
++ goto out;
++
++ snd_card_set_dev(card, &pdev->dev);
++
++out:
++ return retval;
++}
++
++static int __devinit snd_at73c213_probe(struct platform_device *pdev)
++{
++ static int dev;
++ struct spi_board_info *binfo;
++ struct spi_master *smaster;
++ struct snd_at73c213 *chip;
++ snd_card_t *card;
++ int retval;
++
++ if (dev >= SNDRV_CARDS)
++ return -ENODEV;
++ if (!enable[dev]) {
++ dev++;
++ return -ENOENT;
++ }
++
++ if (spi < 0 || ssc < 0)
++ return -ENODEV;
++
++ retval = -ENOMEM;
++ card = snd_card_new(index[dev], id[dev], THIS_MODULE,
++ sizeof(struct snd_at73c213));
++ if (!card)
++ goto out;
++
++ chip = card->private_data;
++
++ retval = -ENODEV;
++
++ /* Get the SPI bus */
++ binfo = pdev->dev.platform_data;
++ if (!binfo) {
++ printk(KERN_WARNING "at73c213: could not get platform data\n");
++ goto out;
++ }
++
++ smaster = spi_busnum_to_master(spi);
++ if (!smaster) {
++ request_module("spi1");
++ smaster = spi_busnum_to_master(spi);
++ if (!smaster) {
++ printk(KERN_WARNING
++ "at73c213: could not get "
++ "SPI bus %d, remembered to load "
++ "the spi_atmel module?\n", spi);
++ goto out;
++ }
++ }
++
++ chip->spi = spi_new_device(smaster, binfo);
++ if (!chip->spi) {
++ printk(KERN_WARNING "at73c213: could not get SPI device %d\n", spi);
++ goto out;
++ }
++
++ chip->spi->mode = SPI_MODE_1;
++ chip->spi->bits_per_word = 8;
++
++ retval = snd_at73c213_create(card, pdev);
++ if (retval)
++ goto out_free_card;
++
++ strcpy(card->driver, "at73c213");
++ strcpy(card->shortname, "at73c213 (AVR32 STK1000)");
++ sprintf(card->longname, "%s at %p (irq %i)", card->shortname, chip->regs, chip->irq);
++
++ retval = snd_card_register(card);
++ if (retval)
++ goto out_free_card;
++
++ platform_set_drvdata(pdev, card);
++ dev++;
++ return 0;
++
++out_free_card:
++ snd_card_free(card);
++out:
++ return retval;
++}
++
++static int __devexit snd_at73c213_remove(struct platform_device *pdev)
++{
++ struct snd_card *card = platform_get_drvdata(pdev);
++ struct snd_at73c213 *chip = card->private_data;
++ int retval;
++
++ /* Stop playback */
++ __raw_writel(SSC_CR_TXDIS, chip->regs + SSC_CR);
++
++ /* Stop GLCK0 */
++ __raw_writel(0, (void __iomem *)PM_BASE + PM_GCCTRL);
++
++ /* Mute sound */
++ write_reg(chip, DAC_LMPG, 0x3F);
++ chip->image[DAC_LMPG] = 0x3F;
++ write_reg(chip, DAC_RMPG, 0x3F);
++ chip->image[DAC_RMPG] = 0x3F;
++ write_reg(chip, DAC_LLOG, 0x3F);
++ chip->image[DAC_LLOG] = 0x3F;
++ write_reg(chip, DAC_RLOG, 0x3F);
++ chip->image[DAC_RLOG] = 0x3F;
++ write_reg(chip, DAC_LLIG, 0x11);
++ chip->image[DAC_LLIG] = 0x11;
++ write_reg(chip, DAC_RLIG, 0x11);
++ chip->image[DAC_RLIG] = 0x11;
++ write_reg(chip, DAC_AUXG, 0x11);
++ chip->image[DAC_AUXG] = 0x11;
++
++ /* Turn off PA */
++ write_reg(chip, PA_CTRL, (chip->image[PA_CTRL]|0x0F));
++ chip->image[PA_CTRL] |= 0x0F;
++ msleep(10);
++ write_reg(chip, PA_CTRL, (1<<PA_CTRL_APALP)|0x0F);
++ chip->image[PA_CTRL] = (1<<PA_CTRL_APALP)|0x0F;
++
++ /* Turn off external DAC */
++ write_reg(chip, DAC_CTRL, 0x0C);
++ chip->image[DAC_CTRL] = 0x0C;
++ msleep(2);
++ write_reg(chip, DAC_CTRL, 0x00);
++ chip->image[DAC_CTRL] = 0x00;
++
++ /* Turn off master power */
++ write_reg(chip, DAC_PRECH, 0x00);
++ chip->image[DAC_PRECH] = 0x00;
++
++ msleep(10);
++
++out:
++ if (chip->spi)
++ spi_unregister_device(chip->spi);
++
++ if (card) {
++ snd_card_free(card);
++ platform_set_drvdata(pdev, NULL);
++ }
++
++ return 0;
++}
++
++#ifdef CONFIG_PM
++static int snd_at73c213_suspend(struct platform_device *pdev, pm_message_t state, u32 level)
++{
++ struct snd_card *card = at32_get_drvdata(pdev);
++ struct snd_at73c213 *chip = card->private_data;
++
++ printk(KERN_DEBUG "at73c213: suspending\n");
++
++ /* Stop SSC and GCLK0 */
++
++ spi_suspend(chip->spi, state);
++
++ return 0;
++}
++
++static int snd_at73c213_resume(struct platform_device *pdev, u32 level)
++{
++ struct snd_card *card = at32_get_drvdata(pdev);
++ struct snd_at73c213 *chip = card->private_data;
++
++ printk(KERN_DEBUG "at73c213: resuming\n");
++
++ /* Start GLCK0 and SSC */
++
++ spi_resume(chip->spi);
++
++ return 0;
++}
++#endif /* CONFIG_PM */
++
++/* Driver core initialization */
++static struct platform_driver at73c213_driver = {
++ .probe = snd_at73c213_probe,
++ .remove = __devexit_p(snd_at73c213_remove),
++ .driver = {
++ .name = "at73c213",
++ }
++#ifdef CONFIG_PM
++ .resume = snd_at73c213_resume,
++ .suspend = snd_at73c213_suspend,
++#endif
++};
++
++static int __init at73c213_init(void)
++{
++ return platform_driver_register(&at73c213_driver);
++}
++
++static void __exit at73c213_exit(void)
++{
++ platform_driver_unregister(&at73c213_driver);
++}
++
++MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>");
++MODULE_DESCRIPTION("Sound driver for at73c213 on STK1000");
++MODULE_LICENSE("GPL");
++
++module_init(at73c213_init);
++module_exit(at73c213_exit);
++
+Index: linux-2.6.18-avr32/sound/avr32/at73c213.h
+===================================================================
+--- /dev/null 1970-01-01 00:00:00.000000000 +0000
++++ linux-2.6.18-avr32/sound/avr32/at73c213.h 2006-11-02 15:56:20.000000000 +0100
+@@ -0,0 +1,120 @@
++/*
++ * Driver for the AT73C213 16-bit stereo DAC on Atmel ATSTK1000
++ *
++ * Copyright (C) 2006 Atmel Norway
++ *
++ * 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.
++ *
++ * The full GNU General Public License is included in this
++ * distribution in the file called COPYING.
++ */
++
++#ifndef _SND_AT73C213_MIXER_H_
++#define _SND_AT73C213_MIXER_H_
++
++/* DAC control register */
++#define DAC_CTRL 0x00
++#define DAC_CTRL_ONPADRV 7
++#define DAC_CTRL_ONAUXIN 6
++#define DAC_CTRL_ONDACR 5
++#define DAC_CTRL_ONDACL 4
++#define DAC_CTRL_ONLNOR 3
++#define DAC_CTRL_ONLNOL 2
++#define DAC_CTRL_ONLNIR 1
++#define DAC_CTRL_ONLNIL 0
++
++/* DAC left line in gain register */
++#define DAC_LLIG 0x01
++#define DAC_LLIG_LLIG 0
++
++/* DAC right line in gain register */
++#define DAC_RLIG 0x02
++#define DAC_RLIG_RLIG 0
++
++/* DAC Left Master Playback Gain Register */
++#define DAC_LMPG 0x03
++#define DAC_LMPG_LMPG 0
++
++/* DAC Right Master Playback Gain Register */
++#define DAC_RMPG 0x04
++#define DAC_RMPG_RMPG 0
++
++/* DAC Left Line Out Gain Register */
++#define DAC_LLOG 0x05
++#define DAC_LLOG_LLOG 0
++
++/* DAC Right Line Out Gain Register */
++#define DAC_RLOG 0x06
++#define DAC_RLOG_RLOG 0
++
++/* DAC Output Level Control Register */
++#define DAC_OLC 0x07
++#define DAC_OLC_RSHORT 7
++#define DAC_OLC_ROLC 4
++#define DAC_OLC_LSHORT 3
++#define DAC_OLC_LOLC 0
++
++/* DAC Mixer Control Register */
++#define DAC_MC 0x08
++#define DAC_MC_INVR 5
++#define DAC_MC_INVL 4
++#define DAC_MC_RMSMIN2 3
++#define DAC_MC_RMSMIN1 2
++#define DAC_MC_LMSMIN2 1
++#define DAC_MC_LMSMIN1 0
++
++/* DAC Clock and Sampling Frequency Control Register */
++#define DAC_CSFC 0x09
++#define DAC_CSFC_OVRSEL 4
++
++/* DAC Miscellaneous Register */
++#define DAC_MISC 0x0A
++#define DAC_MISC_VCMCAPSEL 7
++#define DAC_MISC_DINTSEL 4
++#define DAC_MISC_DITHEN 3
++#define DAC_MISC_DEEMPEN 2
++#define DAC_MISC_NBITS 0
++
++/* DAC Precharge Control Register */
++#define DAC_PRECH 0x0C
++#define DAC_PRECH_PRCHGPDRV 7
++#define DAC_PRECH_PRCHGAUX1 6
++#define DAC_PRECH_PRCHGLNOR 5
++#define DAC_PRECH_PRCHGLNOL 4
++#define DAC_PRECH_PRCHGLNIR 3
++#define DAC_PRECH_PRCHGLNIL 2
++#define DAC_PRECH_PRCHG 1
++#define DAC_PRECH_ONMSTR 0
++
++/* DAC Auxiliary Input Gain Control Register */
++#define DAC_AUXG 0x0D
++#define DAC_AUXG_AUXG 0
++
++/* DAC Reset Register */
++#define DAC_RST 0x10
++#define DAC_RST_RESMASK 2
++#define DAC_RST_RESFILZ 1
++#define DAC_RST_RSTZ 0
++
++/* Power Amplifier Control Register */
++#define PA_CTRL 0x11
++#define PA_CTRL_APAON 6
++#define PA_CTRL_APAPRECH 5
++#define PA_CTRL_APALP 4
++#define PA_CTRL_APAGAIN 0
++
++#endif
++