diff -urN linux-2.6.28-rc6/sound/soc/at91/at91sam9g20ek_wm8731.c linux-2.6.28-rc6-0rig/sound/soc/at91/at91sam9g20ek_wm8731.c --- linux-2.6.28-rc6/sound/soc/at91/at91sam9g20ek_wm8731.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.28-rc6-0rig/sound/soc/at91/at91sam9g20ek_wm8731.c 2008-11-29 20:33:23.000000000 +0100 @@ -0,0 +1,383 @@ +/* + * at91sam9g20ek_wm8731 -- SoC audio for AT91SAM9G20-based Atmel AT91SAM9G20EK board. + * + * Author: Ulf Samuelsson + * Atmel Nordic AB. + * Created: Mar 29, 2006 + * + * Based on eti_b1_wm8731.c by: + * + * Authors: Frank Mandarino + * + * which is based on corgi.c by: + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * Authors: Liam Girdwood + * Richard Purdie + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../codecs/wm8731.h" +#include "at91-pcm.h" +#include "at91-ssc.h" + +#define AT91SAM9G20_BASE_SSC AT91SAM9260_BASE_SSC +#define AT91SAM9G20_ID_SSC AT91SAM9260_ID_SSC + + +#if 0 +#define DBG(x...) printk(KERN_INFO "at91sam9g20ek_wm8731: " x) +#else +#define DBG(x...) +#endif + +static struct clk *pck1_clk; +static struct clk *pllb_clk; + + +static int at91sam9g20ek_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* cpu clock is the AT91 master clock sent to the SSC */ + ret = snd_soc_dai_set_sysclk(cpu_dai, AT91_SYSCLK_MCK, + 100000000, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* codec system clock is supplied by PCK1, set to 12MHz */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, + 12000000, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* Start PCK1 clock. */ + clk_enable(pck1_clk); + DBG("pck1 started\n"); + + return 0; +} + +static void at91sam9g20ek_shutdown(struct snd_pcm_substream *substream) +{ + /* Stop PCK1 clock. */ + clk_disable(pck1_clk); + DBG("pck1 stopped\n"); +} + +static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + +#ifdef CONFIG_SND_AT91_SOC_AT91SAM9G20EK_SLAVE + unsigned int rate; + int cmr_div, period; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* + * The SSC clock dividers depend on the sample rate. The CMR.DIV + * field divides the system master clock MCK to drive the SSC TK + * signal which provides the codec BCLK. The TCMR.PERIOD and + * RCMR.PERIOD fields further divide the BCLK signal to drive + * the SSC TF and RF signals which provide the codec DACLRC and + * ADCLRC clocks. + * + * The dividers were determined through trial and error, where a + * CMR.DIV value is chosen such that the resulting BCLK value is + * divisible, or almost divisible, by (2 * sample rate), and then + * the TCMR.PERIOD or RCMR.PERIOD is BCLK / (2 * sample rate) - 1. + */ + + /* + * 8000: P=25 D=124 IF=2000000.00 OF= 8000.00 ERROR=0.000000 + * 16000: P=22 D=70 IF=2272727.27 OF=16005.12 ERROR=0.000320 + * 32000: P=17 D=45 IF=2941176.47 OF=31969.31 ERROR=0.000959 + * 48000: P=13 D=39 IF=3846153.85 OF=48076.92 ERROR=0.001603 + * 11025: P=14 D=161 IF=3571428.57 OF=11022.93 ERROR=0.000188 + * 22050: P=14 D=80 IF=3571428.57 OF=22045.86 ERROR=0.000188 + * 44100: P=21 D=26 IF=2380952.38 OF=44091.71 ERROR=0.000188 + */ + rate = params_rate(params); + + switch (rate) { + case 8000: + cmr_div = 25; /* BCLK = 100MHz/(2*25) = 2.000000MHz */ + period = 124; /* LRC = BCLK/(2*(124+1)) = 8000Hz */ + break; + case 16000: + cmr_div = 22; /* BCLK = 100MHz/(2*22) ~= 2,272727MHz */ + period = 70; /* LRC = BCLK/(2*(70+1)) = 16005.12Hz */ + break; + case 32000: + cmr_div = 17; /* BCLK = 100MHz/(2*17) ~= 2.941176MHz */ + period = 45; /* LRC = BCLK/(2*(45+1)) = 31969.31Hz */ + break; + case 48000: + cmr_div = 13; /* BCLK = 100MHz/(2*13) ~= 3.846154MHz */ + period = 39; /* LRC = BCLK/(2*(39+1)) = 48076.92Hz */ + break; + case 11025: + cmr_div = 14; /* BCLK = 100MHz/(2*14) ~= 3.571429MHz */ + period = 161; /* LRC = BCLK/(2*(161+1)) = 11022.93Hz */ + break; + case 22050: + cmr_div = 14; /* BCLK = 100MHz/(2*14) ~= 3.571429MHz */ + period = 80; /* LRC = BCLK/(2*(80+1)) = 22045.86Hz */ + break; + case 44100: + cmr_div = 21; /* BCLK = 100MHz/(2*21) ~= 2.380952MHz */ + period = 26; /* LRC = BCLK/(2*(26+1)) = 44091.71Hz */ + break; + default: + printk(KERN_WARNING "unsupported rate %d on AT91SAM9G20EK board\n", rate); + return -EINVAL; + } + + /* set the MCK divider for BCLK */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, AT91SSC_CMR_DIV, cmr_div); + if (ret < 0) + return ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* set the BCLK divider for DACLRC */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, + AT91SSC_TCMR_PERIOD, period); + } else { + /* set the BCLK divider for ADCLRC */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, + AT91SSC_RCMR_PERIOD, period); + } + if (ret < 0) + return ret; + +#else /* CONFIG_SND_AT91_SOC_AT91SAM9G20EK_SLAVE */ + /* + * Codec in Master Mode. + */ + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + +#endif /* CONFIG_SND_AT91_SOC_AT91SAM9G20EK_SLAVE */ + + return 0; +} + +static struct snd_soc_ops at91sam9g20ek_ops = { + .startup = at91sam9g20ek_startup, + .hw_params = at91sam9g20ek_hw_params, + .shutdown = at91sam9g20ek_shutdown, +}; + + +static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route intercon[] = { + + /* speaker connected to LHPOUT */ + {"Ext Spk", NULL, "LHPOUT"}, + + /* mic is connected to Mic Jack, with WM8731 Mic Bias */ + {"MICIN", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Int Mic"}, +}; + +/* + * Logic for a wm8731 as connected on a Atmel AT91SAM9G20EK board. + */ +static int at91sam9g20ek_wm8731_init(struct snd_soc_codec *codec) +{ + DBG("at91sam9g20ek_wm8731_init() called\n"); + + /* Add specific widgets */ + snd_soc_dapm_new_controls(codec, at91sam9g20ek_dapm_widgets, + ARRAY_SIZE(at91sam9g20ek_dapm_widgets)); + + /* Set up specific audio path interconnects */ + snd_soc_dapm_add_route(codec, intercon, ARRAY_SIZE(intercon)); + + /* not connected */ + snd_soc_dapm_disable_pin(codec, "RLINEIN"); + snd_soc_dapm_disable_pin(codec, "LLINEIN"); + + /* always connected */ + snd_soc_dapm_enable_pin(codec, "Int Mic"); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link at91sam9g20ek_dai = { + .name = "WM8731", + .stream_name = "WM8731 PCM", + .cpu_dai = &at91_ssc_dai[1], + .codec_dai = &wm8731_dai, + .init = at91sam9g20ek_wm8731_init, + .ops = &at91sam9g20ek_ops, +}; + +static struct snd_soc_machine snd_soc_machine_at91sam9g20ek = { + .name = "AT91SAM9G20EK_WM8731", + .dai_link = &at91sam9g20ek_dai, + .num_links = 1, +}; + +static struct wm8731_setup_data at91sam9g20ek_wm8731_setup = { + .i2c_address = 0x1a, +}; + +static struct snd_soc_device at91sam9g20ek_snd_devdata = { + .machine = &snd_soc_machine_at91sam9g20ek, + .platform = &at91_soc_platform, + .codec_dev = &soc_codec_dev_wm8731, + .codec_data = &at91sam9g20ek_wm8731_setup, +}; + +static struct platform_device *at91sam9g20ek_snd_device; + +static int __init at91sam9g20ek_init(void) +{ + int ret; + struct at91_ssc_periph *ssc = at91sam9g20ek_dai.cpu_dai->private_data; + + if (!request_mem_region(AT91SAM9G20_BASE_SSC, SZ_16K, "soc-audio")) { + DBG("SSC memory region is busy\n"); + return -EBUSY; + } + + ssc->base = ioremap(AT91SAM9G20_BASE_SSC, SZ_16K); + if (!ssc->base) { + DBG("SSC memory ioremap failed\n"); + ret = -ENOMEM; + goto fail_release_mem; + } + + ssc->pid = AT91SAM9G20_ID_SSC; + + at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1); + if (!at91sam9g20ek_snd_device) { + DBG("platform device allocation failed\n"); + ret = -ENOMEM; + goto fail_io_unmap; + } + + platform_set_drvdata(at91sam9g20ek_snd_device, &at91sam9g20ek_snd_devdata); + at91sam9g20ek_snd_devdata.dev = &at91sam9g20ek_snd_device->dev; + + ret = platform_device_add(at91sam9g20ek_snd_device); + if (ret) { + DBG("platform device add failed\n"); + platform_device_put(at91sam9g20ek_snd_device); + goto fail_io_unmap; + } + + at91_set_A_periph(AT91_PIN_PB16, 0); /* TK0 */ + at91_set_A_periph(AT91_PIN_PB17, 0); /* TF0 */ + at91_set_A_periph(AT91_PIN_PB18, 0); /* TD0 */ +#if 0 /* Bidirectional support not available on the SAM9G20EK */ + at91_set_A_periph(AT91_PIN_PB19, 0); /* RD0 */ + at91_set_A_periph(AT91_PIN_PB20, 0); /* RK0 */ + at91_set_A_periph(AT91_PIN_PB21, 0); /* RF0 */ +#endif + /* + * Set PCK1 parent to PLLB and its rate to 12 Mhz. + */ + pllb_clk = clk_get(NULL, "pllb"); + pck1_clk = clk_get(NULL, "pck1"); + + clk_set_parent(pck1_clk, pllb_clk); + clk_set_rate(pck1_clk, 12000000); + + DBG("MCLK rate %luHz\n", clk_get_rate(pck1_clk)); + + /* assign the GPIO pin to PCK1 */ + at91_set_B_periph(AT91_PIN_PC1, 0); + +#ifdef CONFIG_SND_AT91_SOC_AT91SAM9G20EK_SLAVE + printk(KERN_INFO "at91sam9g20ek_wm8731: Codec in Slave Mode\n"); +#else + printk(KERN_INFO "at91sam9g20ek_wm8731: Codec in Master Mode\n"); +#endif + return ret; + +fail_io_unmap: + iounmap(ssc->base); +fail_release_mem: + release_mem_region(AT91SAM9G20_BASE_SSC, SZ_16K); + return ret; +} + +static void __exit at91sam9g20ek_exit(void) +{ + struct at91_ssc_periph *ssc = at91sam9g20ek_dai.cpu_dai->private_data; + + clk_put(pck1_clk); + clk_put(pllb_clk); + + platform_device_unregister(at91sam9g20ek_snd_device); + + iounmap(ssc->base); + release_mem_region(AT91SAM9G20_BASE_SSC, SZ_16K); +} + +module_init(at91sam9g20ek_init); +module_exit(at91sam9g20ek_exit); + +/* Module information */ +MODULE_AUTHOR("Ulf Samuelsson "); +MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK-WM8731"); +MODULE_LICENSE("GPL"); diff -urN linux-2.6.28-rc6/sound/soc/at91/Kconfig linux-2.6.28-rc6-0rig/sound/soc/at91/Kconfig --- linux-2.6.28-rc6/sound/soc/at91/Kconfig 2008-10-10 00:13:53.000000000 +0200 +++ linux-2.6.28-rc6-0rig/sound/soc/at91/Kconfig 2008-11-29 20:39:30.000000000 +0100 @@ -25,3 +25,20 @@ help Say Y if you want to run with the AT91 SSC generating the BCLK and LRC signals on Endrelia boards. + +config SND_AT91_SOC_AT91SAM9G20EK_WM8731 + tristate "SoC Audio support for WM8731-based Atmel AT91SAM9G20EK boards" + depends on SND_AT91_SOC && (MACH_AT91SAM9G20EK) + select SND_AT91_SOC_SSC + select SND_SOC_WM8731 + help + Say Y if you want to add support for SoC audio on WM8731-based + Atmel AT91SAM9G20EK boards. + +config SND_AT91_SOC_AT91SAM9G20EK_SLAVE + bool "Run codec in slave Mode on the AT91SAM92G20EK board" + depends on SND_AT91_SOC_AT91SAM9G20EK_WM8731 + default n + help + Say Y if you want to run with the AT91 SSC generating the BCLK + and LRC signals on Atmel boards. diff -urN linux-2.6.28-rc6/sound/soc/at91/Makefile linux-2.6.28-rc6-0rig/sound/soc/at91/Makefile --- linux-2.6.28-rc6/sound/soc/at91/Makefile 2008-10-10 00:13:53.000000000 +0200 +++ linux-2.6.28-rc6-0rig/sound/soc/at91/Makefile 2008-11-29 20:39:35.000000000 +0100 @@ -7,5 +7,8 @@ # AT91 Machine Support snd-soc-eti-b1-wm8731-objs := eti_b1_wm8731.o +snd-soc-at91sam9g20ek-wm8731-objs := at91sam9g20ek_wm8731.o obj-$(CONFIG_SND_AT91_SOC_ETI_B1_WM8731) += snd-soc-eti-b1-wm8731.o +obj-$(CONFIG_SND_AT91_SOC_AT91SAM9G20_WM8731) += snd-soc-at91sam9g20ek-wm8731.o +