Index: linux-2.6.24/sound/soc/codecs/pcap2.c =================================================================== --- /dev/null +++ linux-2.6.24/sound/soc/codecs/pcap2.c @@ -0,0 +1,796 @@ +/* + * pcap2.c - PCAP2 ASIC Audio driver + * + * Copyright (C) 2007 Daniel Ribeiro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcap2.h" + +#define AUDIO_NAME "pcap2-codec" +#define PCAP2_VERSION "0.1" + +extern int ezx_pcap_write(u_int8_t, u_int32_t); +extern int ezx_pcap_read(u_int8_t, u_int32_t *); +static struct snd_soc_device *pcap2_codec_socdev; + +/* + * Debug + */ + +//#define PCAP2_DEBUG + +#ifdef PCAP2_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) +#endif + +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +#define dump_registers() pcap2_codec_read(NULL, 13); \ + pcap2_codec_read(NULL, 12); \ + pcap2_codec_read(NULL, 11); \ + pcap2_codec_read(NULL, 26); + + + + +/* + * ASoC limits register value to 16 bits and pcap uses 32 bit registers + * to work around this, we get 16 bits from low, mid or high positions. + * ASoC limits register number to 8 bits we use 0x1f for register + * number and 0xe0 for register offset. -WM + */ +static int pcap2_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + unsigned int tmp; + + ezx_pcap_read((reg & 0x1f), &tmp); + + if (reg & SL) { + tmp &= 0xffff0000; + tmp |= (value & 0xffff); + } + else if (reg & SM) { + tmp &= 0xff0000ff; + tmp |= ((value << 8) & 0x00ffff00); + } + else if (reg & SH) { + tmp &= 0xffff; + tmp |= ((value << 16) & 0xffff0000); + } + else + tmp = value; + + dbg("codec_write reg=%x, rval=%08x, fval=%08x", reg, tmp, value); + ezx_pcap_write((reg & 0x1f), tmp); + return 0; + +} + +static unsigned int pcap2_codec_read(struct snd_soc_codec *codec, unsigned int reg) +{ + unsigned int tmp, ret; + + ezx_pcap_read((reg & 0x1f), &tmp); + ret = tmp; + if (reg & SL) + ret = (tmp & 0xffff); + else if (reg & SM) + ret = ((tmp >> 8) & 0xffff); + else if (reg & SH) + ret = ((tmp >> 16) & 0xffff); + + dbg("codec_read reg=%x, rval=%08x, fval=%08x", reg, tmp, ret); + return(ret); + +} + +static const char *pcap2_output_select[] = {"2ch", "2->1ch", "2->1ch -3db", "2->1ch -6db"}; + +static const struct soc_enum pcap2_enum[] = { +SOC_ENUM_SINGLE((PCAP2_OUTPUT_AMP|SH), 3, 4, pcap2_output_select), +}; + +static const struct snd_kcontrol_new pcap2_input_mixer_controls[] = { +SOC_DAPM_SINGLE("A3 Switch", (PCAP2_INPUT_AMP|SL), 6, 1, 0), +SOC_DAPM_SINGLE("A5 Switch", (PCAP2_INPUT_AMP|SL), 8, 1, 0), +}; + +static const struct snd_kcontrol_new pcap2_output_mixer_controls[] = { +SOC_DAPM_SINGLE("A1 Switch", (PCAP2_OUTPUT_AMP|SL), 0, 1, 0), +SOC_DAPM_SINGLE("A2 Switch", (PCAP2_OUTPUT_AMP|SL), 1, 1, 0), +SOC_DAPM_SINGLE("AR Switch", (PCAP2_OUTPUT_AMP|SL), 5, 1, 0), +SOC_DAPM_SINGLE("AL Switch", (PCAP2_OUTPUT_AMP|SL), 6, 1, 0), +}; + +/* pcap2 codec non DAPM controls */ +static const struct snd_kcontrol_new pcap2_codec_snd_controls[] = { +SOC_SINGLE("Output gain", (PCAP2_OUTPUT_AMP|SM), 5, 15, 0), +SOC_SINGLE("Input gain", (PCAP2_INPUT_AMP|SL), 0, 31, 0), +}; + +static const struct snd_kcontrol_new pcap2_codec_dm_mux_control[] = { + SOC_DAPM_ENUM("Output Mode", pcap2_enum[0]), +}; + +/* add non dapm controls */ +static int pcap2_codec_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(pcap2_codec_snd_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&pcap2_codec_snd_controls[i],codec, NULL))) < 0) + return err; + } + + return 0; +} + +/* pcap2 codec DAPM controls */ +static const struct snd_soc_dapm_widget pcap2_codec_dapm_widgets[] = { + SND_SOC_DAPM_DAC("ST_DAC", "ST_DAC playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("CDC_DAC", "CDC_DAC playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("CDC_ADC", "CDC_DAC capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_PGA("PGA_ST", (PCAP2_OUTPUT_AMP|SL), 9, 0, NULL, 0), + SND_SOC_DAPM_PGA("PGA_CDC", (PCAP2_OUTPUT_AMP|SL), 8, 0, NULL, 0), + SND_SOC_DAPM_PGA("PGA_R", (PCAP2_OUTPUT_AMP|SL), 11, 0, NULL, 0), + SND_SOC_DAPM_PGA("PGA_L", (PCAP2_OUTPUT_AMP|SL), 12, 0, NULL, 0), + SND_SOC_DAPM_MUX("Downmixer", SND_SOC_NOPM, 0, 0, pcap2_codec_dm_mux_control), + SND_SOC_DAPM_PGA("PGA_A1CTRL", (PCAP2_OUTPUT_AMP|SH), 1, 1, NULL, 0), + SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, &pcap2_output_mixer_controls[0], ARRAY_SIZE(pcap2_output_mixer_controls)), + SND_SOC_DAPM_OUTPUT("A1"), /* Earpiece */ + SND_SOC_DAPM_OUTPUT("A2"), /* LoudSpeaker */ + SND_SOC_DAPM_OUTPUT("AR"), /* headset right */ + SND_SOC_DAPM_OUTPUT("AL"), /* headset left */ + + SND_SOC_DAPM_MICBIAS("BIAS1", (PCAP2_INPUT_AMP|SL), 10, 0), + SND_SOC_DAPM_MICBIAS("BIAS2", (PCAP2_INPUT_AMP|SL), 11, 0), + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, &pcap2_input_mixer_controls[0], ARRAY_SIZE(pcap2_input_mixer_controls)), + SND_SOC_DAPM_INPUT("A3"), /* Headset Mic */ + SND_SOC_DAPM_INPUT("A5"), /* Builtin Mic */ +}; + +static const char *audio_map[][3] = { + { "A1", NULL, "Output Mixer" }, + { "A2", NULL, "Output Mixer" }, + { "AR", NULL, "Output Mixer" }, + { "AL", NULL, "Output Mixer" }, + + { "Output Mixer", "A1 Switch", "PGA_A1CTRL" }, + { "Output Mixer", "A2 Switch", "Downmixer" }, + { "Output Mixer", "AR Switch", "PGA_R" }, + { "Output Mixer", "AL Switch", "PGA_L" }, + + { "PGA_A1CTRL", NULL, "Downmixer" }, + + { "Downmixer", "2->1ch", "PGA_L" }, + { "Downmixer", "2->1ch", "PGA_R" }, + { "Downmixer", "2->1ch -3db", "PGA_L" }, + { "Downmixer", "2->1ch -3db", "PGA_R" }, + { "Downmixer", "2->1ch -6db", "PGA_L" }, + { "Downmixer", "2->1ch -6db", "PGA_R" }, + { "Downmixer", "2ch", "PGA_R" }, + + { "PGA_R", NULL, "PGA_ST" }, + { "PGA_L", NULL, "PGA_ST" }, + { "PGA_R", NULL, "PGA_CDC" }, + + { "PGA_ST", NULL, "ST_DAC" }, + { "PGA_CDC", NULL, "CDC_DAC" }, + + /* input path */ + { "BIAS1", NULL, "A3" }, + { "BIAS2", NULL, "A5" }, + + { "Input Mixer", "A3 Switch", "BIAS1" }, + { "Input Mixer", "A5 Switch", "BIAS2" }, + + { "PGA_R", NULL, "Input Mixer" }, + + { "PGA_CDC", NULL, "PGA_R" }, + { "CDC_ADC", NULL, "PGA_CDC" }, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int pcap2_codec_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(pcap2_codec_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &pcap2_codec_dapm_widgets[i]); + } + + /* set up audio path interconnects */ + for(i = 0; audio_map[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, audio_map[i][0], + audio_map[i][1], audio_map[i][2]); + } + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int pcap2_codec_dapm_event(struct snd_soc_codec *codec, int event) +{ + unsigned int input = pcap2_codec_read(codec, PCAP2_INPUT_AMP); + + input &= ~PCAP2_INPUT_AMP_LOWPWR; + + switch (event) { + case SNDRV_CTL_POWER_D0: + case SNDRV_CTL_POWER_D1: + case SNDRV_CTL_POWER_D2: + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + dbg("dapm: ON\n"); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + input |= PCAP2_INPUT_AMP_LOWPWR; + dbg("dapm: OFF\n"); + break; + } + codec->dapm_state = event; + pcap2_codec_write(codec, PCAP2_INPUT_AMP, input); + return 0; +} + +static int pcap2_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_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int tmp; + + if (codec_dai->id == PCAP2_STEREO_DAI) { + tmp = pcap2_codec_read(codec, PCAP2_ST_DAC); + + tmp &= ~PCAP2_ST_DAC_RATE_MASK; + switch(params_rate(params)) { + case 8000: + break; + case 11025: + tmp |= PCAP2_ST_DAC_RATE_11025; + break; + case 12000: + tmp |= PCAP2_ST_DAC_RATE_12000; + break; + case 16000: + tmp |= PCAP2_ST_DAC_RATE_16000; + break; + case 22050: + tmp |= PCAP2_ST_DAC_RATE_22050; + break; + case 24000: + tmp |= PCAP2_ST_DAC_RATE_24000; + break; + case 32000: + tmp |= PCAP2_ST_DAC_RATE_32000; + break; + case 44100: + tmp |= PCAP2_ST_DAC_RATE_44100; + break; + case 48000: + tmp |= PCAP2_ST_DAC_RATE_48000; + break; + default: + return -EINVAL; + } + tmp |= PCAP2_ST_DAC_RESET_DF; + pcap2_codec_write(codec, PCAP2_ST_DAC, tmp); + } + else { + tmp = pcap2_codec_read(codec, PCAP2_CODEC); + + tmp &= ~PCAP2_CODEC_RATE_MASK; + switch(params_rate(params)) { + case 8000: + break; + case 16000: + tmp |= PCAP2_CODEC_RATE_16000; + break; + default: + return -EINVAL; + } + tmp |= PCAP2_CODEC_RESET_DF; + pcap2_codec_write(codec, PCAP2_CODEC, tmp); + } + + return 0; +} + +static int pcap2_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_dapm_widget *w; + unsigned int tmp; + + if (codec_dai->id == PCAP2_STEREO_DAI) { + snd_soc_dapm_set_endpoint(codec, "ST_DAC", 0); + tmp = pcap2_codec_read(codec, PCAP2_ST_DAC); + tmp &= ~(PCAP2_ST_DAC_EN | PCAP2_ST_DAC_CLK_EN); + pcap2_codec_write(codec, PCAP2_ST_DAC, tmp); + } + else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dapm_set_endpoint(codec, "CDC_DAC", 0); + else + snd_soc_dapm_set_endpoint(codec, "CDC_ADC", 0); + list_for_each_entry(w, &codec->dapm_widgets, list) { + if ((!strcmp(w->name, "CDC_DAC") || !strcmp(w->name, "CDC_ADC")) && w->connected) + goto in_use; + } + tmp = pcap2_codec_read(codec, PCAP2_CODEC); + tmp &= ~(PCAP2_CODEC_EN | PCAP2_CODEC_CLK_EN); + pcap2_codec_write(codec, PCAP2_CODEC, tmp); + } +in_use: + snd_soc_dapm_sync_endpoints(codec); + + return 0; +} + +static int pcap2_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + + unsigned int tmp; + if (codec_dai->id == PCAP2_STEREO_DAI) { + /* ST_DAC */ + + tmp = pcap2_codec_read(codec, PCAP2_ST_DAC); + + tmp &= ~PCAP2_ST_DAC_CLKSEL_MASK; + switch (clk_id) { + case PCAP2_CLK_AP: + tmp |= PCAP2_ST_DAC_CLKSEL_AP; + break; + case PCAP2_CLK_BP: + break; + default: + return -ENODEV; + } + + tmp &= ~PCAP2_ST_DAC_CLK_MASK; + switch (freq) { + case 13000000: + break; +/* case 15M36: + tmp |= PCAP2_ST_DAC_CLK_15M36; + break; + case 16M8: + tmp |= PCAP2_ST_DAC_CLK_16M8; + break; + case 19M44: + tmp |= PCAP2_ST_DAC_CLK_19M44; + break; +*/ case 26000000: + tmp |= PCAP2_ST_DAC_CLK_26M; + break; +/* case EXT_MCLK: + tmp |= PCAP2_ST_DAC_CLK_MCLK; + break; + case FSYNC: + tmp |= PCAP2_ST_DAC_CLK_FSYNC; + break; + case BITCLK: + tmp |= PCAP2_ST_DAC_CLK_BITCLK; + break; +*/ default: + return -EINVAL; + } + pcap2_codec_write(codec, PCAP2_ST_DAC, tmp); + } + else { + /* MONO_DAC */ + tmp = pcap2_codec_read(codec, PCAP2_CODEC); + + tmp &= ~PCAP2_CODEC_CLKSEL_MASK; + switch (clk_id) { + case PCAP2_CLK_AP: + tmp |= PCAP2_CODEC_CLKSEL_AP; + break; + case PCAP2_CLK_BP: + break; + default: + return -ENODEV; + } + + tmp &= ~PCAP2_CODEC_CLK_MASK; + switch (freq) { + case 13000000: + break; +/* case 15M36: + tmp |= PCAP2_CODEC_CLK_15M36; + break; + case 16M8: + tmp |= PCAP2_CODEC_CLK_16M8; + break; + case 19M44: + tmp |= PCAP2_CODEC_CLK_19M44; + break; +*/ case 26000000: + tmp |= PCAP2_CODEC_CLK_26M; + break; + default: + return -EINVAL; + } + pcap2_codec_write(codec, PCAP2_CODEC, tmp); + } + return 0; +} + +static int pcap2_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int tmp = 0; + + if (codec_dai->id == PCAP2_STEREO_DAI) { + /* ST_DAC */ + + /* disable CODEC */ + pcap2_codec_write(codec, PCAP2_CODEC, 0); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + tmp |= 0x1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + tmp |= 0x4000; + break; +/* case SND_SOC_NET: + tmp |= 0x2000; + break; +*/ case SND_SOC_DAIFMT_DSP_B: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + break; + case SND_SOC_DAIFMT_NB_NF: + tmp |= 0x60000; + break; + case SND_SOC_DAIFMT_IB_NF: + tmp |= 0x40000; + break; + case SND_SOC_DAIFMT_NB_IF: + tmp |= 0x20000; + break; + } + /* set dai to AP */ + tmp |= 0x1000; + + /* set BCLK */ + tmp |= 0x18000; + + pcap2_codec_write(codec, PCAP2_ST_DAC, tmp); + } + else { + /* MONO_DAC */ + + /* disable ST_DAC */ + pcap2_codec_write(codec, PCAP2_ST_DAC, 0); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + tmp |= 0x2; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + break; + case SND_SOC_DAIFMT_NB_NF: + tmp |= 0x600; + break; + case SND_SOC_DAIFMT_IB_NF: + tmp |= 0x400; + break; + case SND_SOC_DAIFMT_NB_IF: + tmp |= 0x200; + break; + } + if (codec_dai->id == PCAP2_MONO_DAI) + /* set dai to AP */ + tmp |= 0x8000; + + tmp |= 0x5; /* IHF / OHF */ + + pcap2_codec_write(codec, PCAP2_CODEC, tmp); + } + return 0; +} + +static int pcap2_prepare(struct snd_pcm_substream *substream) +{ + + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int tmp; + /* FIXME enable clock only if codec is master */ + if (codec_dai->id == PCAP2_STEREO_DAI) { + snd_soc_dapm_set_endpoint(codec, "ST_DAC", 1); + snd_soc_dapm_set_endpoint(codec, "CDC_DAC", 0); + snd_soc_dapm_set_endpoint(codec, "CDC_ADC", 0); + tmp = pcap2_codec_read(codec, PCAP2_ST_DAC); + tmp |= (PCAP2_ST_DAC_EN | PCAP2_ST_DAC_CLK_EN); + pcap2_codec_write(codec, PCAP2_ST_DAC, tmp); + } + else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dapm_set_endpoint(codec, "CDC_DAC", 1); + else + snd_soc_dapm_set_endpoint(codec, "CDC_ADC", 1); + snd_soc_dapm_set_endpoint(codec, "ST_DAC", 0); + tmp = pcap2_codec_read(codec, PCAP2_CODEC); + tmp |= (PCAP2_CODEC_EN | PCAP2_CODEC_CLK_EN); + pcap2_codec_write(codec, PCAP2_CODEC, tmp); + } + snd_soc_dapm_sync_endpoints(codec); + mdelay(1); +#ifdef PCAP2_DEBUG + dump_registers(); +#endif + return 0; +} + +/* + * Define codec DAI. + */ +struct snd_soc_codec_dai pcap2_dai[] = { +{ + .name = "PCAP2 MONO", + .id = 0, + .playback = { + .stream_name = "CDC_DAC playback", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "CDC_DAC capture", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .prepare = pcap2_prepare, + .hw_params = pcap2_hw_params, + .hw_free = pcap2_hw_free, + }, + .dai_ops = { +// .digital_mute = pcap2_mute, + .set_fmt = pcap2_set_dai_fmt, + .set_sysclk = pcap2_set_dai_sysclk, + }, +}, +{ + .name = "PCAP2 STEREO", + .id = 1, + .playback = { + .stream_name = "ST_DAC playback", + .channels_min = 1, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { /* FIXME: PCAP support this?? */ + .stream_name = "ST_DAC capture", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .prepare = pcap2_prepare, + .hw_params = pcap2_hw_params, + .hw_free = pcap2_hw_free, + }, + .dai_ops = { +// .digital_mute = pcap2_mute, + .set_fmt = pcap2_set_dai_fmt, + .set_sysclk = pcap2_set_dai_sysclk, + }, +}, +{ + .name = "PCAP2 BP", + .id = 2, + .playback = { + .stream_name = "BP playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .prepare = pcap2_prepare, + .hw_params = pcap2_hw_params, + .hw_free = pcap2_hw_free, + }, + .dai_ops = { +// .digital_mute = pcap2_mute, + .set_fmt = pcap2_set_dai_fmt, + .set_sysclk = pcap2_set_dai_sysclk, + }, +}, +}; +EXPORT_SYMBOL_GPL(pcap2_dai); + +static int pcap2_codec_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + dbg("pcap2_codec_suspend"); + pcap2_codec_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int pcap2_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + dbg("pcap2_codec_resume"); + pcap2_codec_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + pcap2_codec_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the PCAP2 driver + * register the mixer and dsp interfaces with the kernel + */ +static int pcap2_codec_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + dbg("pcap2_codec_init"); + codec->name = "PCAP2 Audio"; + codec->owner = THIS_MODULE; + codec->read = pcap2_codec_read; + codec->write = pcap2_codec_write; + codec->dapm_event = pcap2_codec_dapm_event; + codec->dai = pcap2_dai; + codec->num_dai = ARRAY_SIZE(pcap2_dai); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + return ret; + } + /* power on device */ + pcap2_codec_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + /* set the update bits */ + + pcap2_codec_add_controls(codec); + pcap2_codec_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + } + + return ret; +} + +static int pcap2_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct pcap2_codec_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + info("PCAP2 Audio Codec %s", PCAP2_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + pcap2_codec_socdev = socdev; + + ret = pcap2_codec_init(socdev); + return ret; +} + +/* power down chip and remove */ +static int pcap2_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + if (codec->control_data) + pcap2_codec_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + kfree(codec); + + return 0; +} + +/* codec device ops */ +struct snd_soc_codec_device soc_codec_dev_pcap2 = { + .probe = pcap2_codec_probe, + .remove = pcap2_codec_remove, + .suspend = pcap2_codec_suspend, + .resume = pcap2_codec_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_pcap2); + +MODULE_DESCRIPTION("ASoC PCAP2 codec"); +MODULE_AUTHOR("Daniel Ribeiro"); +MODULE_LICENSE("GPL"); Index: linux-2.6.24/sound/soc/codecs/pcap2.h =================================================================== --- /dev/null +++ linux-2.6.24/sound/soc/codecs/pcap2.h @@ -0,0 +1,81 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _PCAP2_H +#define _PCAP2_H + +/* 16 bit reads/writes on pcap registers (ugly workaround) */ +#define SL (1 << 5) /* lower 16 bits */ +#define SM (1 << 6) /* mid 16 bits */ +#define SH (1 << 7) /* higher 16 bits */ + +/* PCAP2 register space */ +#define PCAP2_CODEC 0x0b +#define PCAP2_OUTPUT_AMP 0x0c +#define PCAP2_ST_DAC 0x0d +#define PCAP2_INPUT_AMP 0x1a + +#define PCAP2_MONO_DAI 0 +#define PCAP2_STEREO_DAI 1 +#define PCAP2_BP_DAI 2 + +#define PCAP2_CLK_BP 0 +#define PCAP2_CLK_AP 1 + +#define PCAP2_CODEC_EN 0x2000 +#define PCAP2_CODEC_CLK_EN 0x1000 +#define PCAP2_CODEC_RESET_DF 0x800 +#define PCAP2_CODEC_RATE_MASK 0x4000 +#define PCAP2_CODEC_RATE_8000 0x0 +#define PCAP2_CODEC_RATE_16000 0x4000 +#define PCAP2_CODEC_CLKSEL_MASK 0x10000 +#define PCAP2_CODEC_CLKSEL_AP 0x10000 +#define PCAP2_CODEC_CLKSEL_BP 0x0 +#define PCAP2_CODEC_CLK_MASK 0x1c0 +#define PCAP2_CODEC_CLK_13M 0x0 +#define PCAP2_CODEC_CLK_15M36 0x40 +#define PCAP2_CODEC_CLK_16M8 0x80 +#define PCAP2_CODEC_CLK_19M44 0xc0 +#define PCAP2_CODEC_CLK_26M 0x100 + +#define PCAP2_ST_DAC_EN 0x80 +#define PCAP2_ST_DAC_CLK_EN 0x20 +#define PCAP2_ST_DAC_RESET_DF 0x40 +#define PCAP2_ST_DAC_RATE_MASK 0xf00 +#define PCAP2_ST_DAC_RATE_8000 0x0 +#define PCAP2_ST_DAC_RATE_11025 0x100 +#define PCAP2_ST_DAC_RATE_12000 0x200 +#define PCAP2_ST_DAC_RATE_16000 0x300 +#define PCAP2_ST_DAC_RATE_22050 0x400 +#define PCAP2_ST_DAC_RATE_24000 0x500 +#define PCAP2_ST_DAC_RATE_32000 0x600 +#define PCAP2_ST_DAC_RATE_44100 0x700 +#define PCAP2_ST_DAC_RATE_48000 0x800 +#define PCAP2_ST_DAC_CLKSEL_MASK 0x80000 +#define PCAP2_ST_DAC_CLKSEL_AP 0x80000 +#define PCAP2_ST_DAC_CLKSEL_BP 0x0 +#define PCAP2_ST_DAC_CLK_MASK 0x1c +#define PCAP2_ST_DAC_CLK_13M 0x0 +#define PCAP2_ST_DAC_CLK_15M36 0x4 +#define PCAP2_ST_DAC_CLK_16M8 0x8 +#define PCAP2_ST_DAC_CLK_19M44 0xc +#define PCAP2_ST_DAC_CLK_26M 0x10 +#define PCAP2_ST_DAC_CLK_MCLK 0x14 +#define PCAP2_ST_DAC_CLK_FSYNC 0x18 +#define PCAP2_ST_DAC_CLK_BITCLK 0x1c + +#define PCAP2_INPUT_AMP_LOWPWR 0x80000 +#define PCAP2_INPUT_AMP_V2EN2 0x200000 + +#define PCAP2_OUTPUT_AMP_PGAR_EN 0x800 +#define PCAP2_OUTPUT_AMP_PGAL_EN 0x1000 +#define PCAP2_OUTPUT_AMP_CDC_SW 0x100 +#define PCAP2_OUTPUT_AMP_ST_DAC_SW 0x200 + +extern struct snd_soc_codec_dai pcap2_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_pcap2; + +#endif Index: linux-2.6.24/sound/soc/pxa/Kconfig =================================================================== --- linux-2.6.24.orig/sound/soc/pxa/Kconfig +++ linux-2.6.24/sound/soc/pxa/Kconfig @@ -57,3 +57,12 @@ help Say Y if you want to add support for SoC audio on Sharp Zaurus SL-C6000x models (Tosa). + +config SND_PXA2XX_SOC_EZX + tristate "SoC Audio support for EZX" + depends on SND_PXA2XX_SOC && PXA_EZX + select SND_PXA2XX_SOC_SSP + select SND_SOC_PCAP2 + help + Say Y if you want to add support for SoC audio on + Motorola EZX Phones (a780/e680). Index: linux-2.6.24/sound/soc/pxa/ezx.c =================================================================== --- /dev/null +++ linux-2.6.24/sound/soc/pxa/ezx.c @@ -0,0 +1,349 @@ +/* + * ezx.c - Machine specific code for EZX phones + * + * Copyright (C) 2007 Daniel Ribeiro + * + * 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 + +#include "../codecs/pcap2.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-ssp.h" + +#define GPIO_HW_ATTENUATE_A780 96 + +static struct snd_soc_codec *control_codec; + +static void ezx_ext_control(struct snd_soc_codec *codec) +{ + if (ezx_pcap_read_bit(pbit(PCAP_REG_PSTAT, PCAP_IRQ_A1))) + snd_soc_dapm_set_endpoint(codec, "Headset", 1); + else + snd_soc_dapm_set_endpoint(codec, "Headset", 0); + if (ezx_pcap_read_bit(pbit(PCAP_REG_PSTAT, PCAP_IRQ_MB2))) + snd_soc_dapm_set_endpoint(codec, "External Mic", 1); + else + snd_soc_dapm_set_endpoint(codec, "External Mic", 0); + + snd_soc_dapm_sync_endpoints(codec); +} + +static irqreturn_t jack_irq(int irq, void *data) +{ + ezx_ext_control(control_codec); + return IRQ_HANDLED; +} + + +/* + * Alsa operations + * Only implement the required operations for your platform. + * These operations are specific to the machine only. + */ + + /* + * Called by ALSA when a PCM substream is opened, private data can be allocated. + */ +static int ezx_machine_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->codec; + + /* check the jack status at stream startup */ + ezx_ext_control(codec); + return 0; +} + +/* + * Called by ALSA when the hardware params are set by application. This + * function can also be called multiple times and can allocate buffers + * (using snd_pcm_lib_* ). It's non-atomic. + */ +static int ezx_machine_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_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* set codec DAI configuration */ + if (codec_dai->id == PCAP2_STEREO_DAI) + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBM_CFM); + else + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if(ret < 0) + return ret; + + /* Turn on clock output on CLK_PIO */ + OSCC |= 0x8; + + /* set clock source */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, PCAP2_CLK_AP, + 13000000, SND_SOC_CLOCK_IN); + if(ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = cpu_dai->dai_ops.set_tristate(cpu_dai, 0); + if (ret < 0) + return ret; + + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai,PXA2XX_SSP_CLK_EXT, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +/* + * Free's resources allocated by hw_params, can be called multiple times + */ +static int ezx_machine_hw_free(struct snd_pcm_substream *substream) +{ + OSCC &= ~0x8; /* turn off clock output on CLK_PIO */ + + return 0; +} + +static int ezx_machine_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + + if (codec_dai->id == PCAP2_STEREO_DAI) { + /* override pxa2xx-ssp sample size for stereo/network mode */ + SSCR0_P(cpu_dai->id+1) &= ~(SSCR0_DSS | SSCR0_EDSS); + SSCR0_P(cpu_dai->id+1) |= (SSCR0_EDSS | SSCR0_DataSize(16)); + } + return 0; +} + +/* machine Alsa PCM operations */ +static struct snd_soc_ops ezx_ops = { + .startup = ezx_machine_startup, + .prepare = ezx_machine_prepare, + .hw_free = ezx_machine_hw_free, + .hw_params = ezx_machine_hw_params, +}; + +static int bp_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_codec_dai *codec_dai = rtd->dai->codec_dai; +// struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if(ret < 0) + return ret; + + /* set clock source */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, PCAP2_CLK_BP, + 13000000, SND_SOC_CLOCK_IN); + + return ret; +} + + + +/* machine dapm widgets */ +static const struct snd_soc_dapm_widget ezx_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headset", NULL), + SND_SOC_DAPM_SPK("Earpiece", NULL), + SND_SOC_DAPM_SPK("Loudspeaker", NULL), + SND_SOC_DAPM_MIC("Built-in Mic", NULL), + SND_SOC_DAPM_MIC("External Mic", NULL), +}; + +/* machine audio map (connections to the codec pins) */ +static const char *audio_map[][3] = { + { "Headset", NULL, "AR" }, + { "Headset", NULL, "AL" }, + { "Earpiece", NULL, "A1" }, + { "Loudspeaker", NULL, "A2" }, + + { "Built-in Mic", NULL, "A5" }, + { "External Mic", NULL, "A3" }, + + {NULL, NULL, NULL}, +}; + +/* + * Initialise the machine audio subsystem. + */ +static int ezx_machine_init(struct snd_soc_codec *codec) +{ + int i; + /* mark unused codec pins as NC */ +// snd_soc_dapm_set_endpoint(codec, "FIXME", 0); + control_codec = codec; + + /* Add ezx specific controls */ +// for (i = 0; i < ARRAY_SIZE(ezx_controls); i++) { +// if ((err = snd_ctl_add(codec->card, snd_soc_cnew(&ezx_controls[i], codec, NULL))) < 0) +// return err; +// } + + /* Add ezx specific widgets */ + for(i = 0; i < ARRAY_SIZE(ezx_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &ezx_dapm_widgets[i]); + } + /* Set up ezx specific audio path interconnects */ + for(i = 0; audio_map[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, audio_map[i][0], audio_map[i][1], audio_map[i][2]); + } + + /* synchronise subsystem */ + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_cpu_dai bp_dai = +{ + .name = "Baseband", + .id = 0, + .type = SND_SOC_DAI_PCM, + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { +// .startup = bp_startup, +// .shutdown = bp_shutdown, + .hw_params = bp_hw_params, +// .hw_free = bp_hw_free, + }, +}; + +/* template digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link ezx_dai[] = { +{ + .name = "PCAP2 STEREO", + .stream_name = "stereo playback", + .cpu_dai = &pxa_ssp_dai[PXA2XX_DAI_SSP3], + .codec_dai = &pcap2_dai[PCAP2_STEREO_DAI], + .init = ezx_machine_init, + .ops = &ezx_ops, +}, +{ + .name = "PCAP2 MONO", + .stream_name = "mono playback", + .cpu_dai = &pxa_ssp_dai[PXA2XX_DAI_SSP3], + .codec_dai = &pcap2_dai[PCAP2_MONO_DAI], +// .init = ezx_machine_init, /* the stereo call already registered our controls */ + .ops = &ezx_ops, +}, +{ + .name = "PCAP2 BP", + .stream_name = "BP Audio", + .cpu_dai = &bp_dai, + .codec_dai = &pcap2_dai[PCAP2_BP_DAI], +}, +}; + +/* template audio machine driver */ +static struct snd_soc_machine snd_soc_machine_ezx = { + .name = "Motorola EZX", +// .probe +// .remove +// .suspend_pre +// .resume_post + .dai_link = ezx_dai, + .num_links = ARRAY_SIZE(ezx_dai), +}; + +/* template audio subsystem */ +static struct snd_soc_device ezx_snd_devdata = { + .machine = &snd_soc_machine_ezx, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_pcap2, +}; + +static struct platform_device *ezx_snd_device; + +static int __init ezx_init(void) +{ + int ret; + ezx_snd_device = platform_device_alloc("soc-audio", -1); + if (!ezx_snd_device) + return -ENOMEM; + + platform_set_drvdata(ezx_snd_device, &ezx_snd_devdata); + ezx_snd_devdata.dev = &ezx_snd_device->dev; + ret = platform_device_add(ezx_snd_device); + + if (ret) + platform_device_put(ezx_snd_device); + /* configure gpio for ssp3 */ + pxa_gpio_mode(GPIO83_SFRM3_MD); /* SFRM */ + pxa_gpio_mode(GPIO81_STXD3_MD); /* TXD */ + pxa_gpio_mode(GPIO52_SCLK3_MD); /* SCLK */ + pxa_gpio_mode(GPIO89_SRXD3_MD); /* RXD */ + + /* configure gpio for ssp2 */ + pxa_gpio_mode(37 | GPIO_IN); /* SFRM */ + pxa_gpio_mode(38 | GPIO_IN); /* TXD */ + pxa_gpio_mode(22 | GPIO_IN); /* SCLK */ + pxa_gpio_mode(88 | GPIO_IN); /* RXD */ + + pxa_gpio_mode(GPIO_HW_ATTENUATE_A780 | GPIO_OUT); + pxa_gpio_set_value(GPIO_HW_ATTENUATE_A780, 1); + + /* request jack irq */ + request_irq(EZX_IRQ_HEADJACK, &jack_irq, IRQF_DISABLED, "headphone jack", NULL); + request_irq(EZX_IRQ_MIC, &jack_irq, IRQF_DISABLED, "mic jack", NULL); + + return ret; +} + +static void __exit ezx_exit(void) +{ + free_irq(EZX_IRQ_HEADJACK, NULL); + free_irq(EZX_IRQ_MIC, NULL); + platform_device_unregister(ezx_snd_device); +} + +module_init(ezx_init); +module_exit(ezx_exit); + Index: linux-2.6.24/sound/soc/codecs/Makefile =================================================================== --- linux-2.6.24.orig/sound/soc/codecs/Makefile +++ linux-2.6.24/sound/soc/codecs/Makefile @@ -4,6 +4,7 @@ snd-soc-wm8753-objs := wm8753.o snd-soc-wm9712-objs := wm9712.o snd-soc-cs4270-objs := cs4270.o +snd-soc-pcap2-objs := pcap2.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o @@ -11,3 +12,4 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_PCAP2) += snd-soc-pcap2.o Index: linux-2.6.24/sound/soc/codecs/Kconfig =================================================================== --- linux-2.6.24.orig/sound/soc/codecs/Kconfig +++ linux-2.6.24/sound/soc/codecs/Kconfig @@ -37,3 +37,6 @@ bool depends on SND_SOC_CS4270 +config SND_SOC_PCAP2 + tristate + depends on SND_SOC && EZX_PCAP Index: linux-2.6.24/sound/soc/pxa/Makefile =================================================================== --- linux-2.6.24.orig/sound/soc/pxa/Makefile +++ linux-2.6.24/sound/soc/pxa/Makefile @@ -14,9 +14,10 @@ snd-soc-poodle-objs := poodle.o snd-soc-tosa-objs := tosa.o snd-soc-spitz-objs := spitz.o +snd-soc-ezx-objs := ezx.o obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o - +obj-$(CONFIG_SND_PXA2XX_SOC_EZX) += snd-soc-ezx.o