Index: linux-2.6.21-moko/sound/soc/codecs/ak4535.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/ak4535.c @@ -0,0 +1,697 @@ +/* + * ak4535.c -- AK4535 ALSA Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on wm8753.c by Liam Girdwood + * + * 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 "ak4535.h" + +#define AUDIO_NAME "ak4535" +#define AK4535_VERSION "0.3" + +struct snd_soc_codec_device soc_codec_dev_ak4535; + +/* codec private data */ +struct ak4535_priv { + unsigned int sysclk; +}; + +/* + * ak4535 register cache + */ +static const u16 ak4535_reg[AK4535_CACHEREGNUM] = { + 0x0000, 0x0080, 0x0000, 0x0003, + 0x0002, 0x0000, 0x0011, 0x0001, + 0x0000, 0x0040, 0x0036, 0x0010, + 0x0000, 0x0000, 0x0057, 0x0000, +}; + +/* + * read ak4535 register cache + */ +static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write ak4535 register cache + */ +static inline void ak4535_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the AK4535 register space + */ +static int ak4535_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D8 AK4535 register offset + * D7...D0 register data + */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + ak4535_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"}; +static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"}; +static const char *ak4535_hp_out[] = {"Stereo", "Mono"}; +static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"}; +static const char *ak4535_mic_select[] = {"Internal", "External"}; + +static const struct soc_enum ak4535_enum[] = { + SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain), + SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out), + SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out), + SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp), + SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select), +}; + +static const struct snd_kcontrol_new ak4535_snd_controls[] = { + SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0), + SOC_ENUM("Mono 1 Output", ak4535_enum[1]), + SOC_ENUM("Mono 1 Gain", ak4535_enum[0]), + SOC_ENUM("Headphone Output", ak4535_enum[2]), + SOC_ENUM("Playback Deemphasis", ak4535_enum[3]), + SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0), + SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0), + SOC_ENUM("Mic Select", ak4535_enum[4]), + SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0), + SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0), + SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0), + SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0), + SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0), + SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0), + SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0), + SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1), + SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1), + SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0), + SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0), +}; + +/* add non dapm controls */ +static int ak4535_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ak4535_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ak4535_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Mono 1 Mixer */ +static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0), + SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0), +}; + +/* Stereo Mixer */ +static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0), + SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0), +}; + +/* Input Mixer */ +static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0), + SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ak4535_input_mux_control = + SOC_DAPM_ENUM("Input Select", ak4535_enum[0]); + +/* HP L switch */ +static const struct snd_kcontrol_new ak4535_hpl_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1); + +/* HP R switch */ +static const struct snd_kcontrol_new ak4535_hpr_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1); + +/* Speaker switch */ +static const struct snd_kcontrol_new ak4535_spk_control = + SOC_DAPM_SINGLE("Switch", AK4535_MODE2, 0, 0, 0); + +/* mono 2 switch */ +static const struct snd_kcontrol_new ak4535_mono2_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0); + +/* Line out switch */ +static const struct snd_kcontrol_new ak4535_line_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0); + +/* ak4535 dapm widgets */ +static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_stereo_mixer_controls[0], + ARRAY_SIZE(ak4535_stereo_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_mono1_mixer_controls[0], + ARRAY_SIZE(ak4535_mono1_mixer_controls)), + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_input_mixer_controls[0], + ARRAY_SIZE(ak4535_mono1_mixer_controls)), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &ak4535_input_mux_control), + SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0), + SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0, + &ak4535_mono2_control), + SND_SOC_DAPM_SWITCH("Speaker Enable", SND_SOC_NOPM, 0, 0, + &ak4535_spk_control), + SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0, + &ak4535_line_control), + SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpl_control), + SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpr_control), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPP"), + SND_SOC_DAPM_OUTPUT("SPN"), + SND_SOC_DAPM_OUTPUT("MOUT1"), + SND_SOC_DAPM_OUTPUT("MOUT2"), + SND_SOC_DAPM_OUTPUT("MICOUT"), + SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 1), + SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0), + SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0), + SND_SOC_DAPM_INPUT("MICIN"), + SND_SOC_DAPM_INPUT("MICEXT"), + SND_SOC_DAPM_INPUT("AUX"), + SND_SOC_DAPM_INPUT("MIN"), + SND_SOC_DAPM_INPUT("AIN"), +}; + +static const char *audio_map[][3] = { + /*stereo mixer */ + {"Stereo Mixer", "Playback Switch", "DAC"}, + {"Stereo Mixer", "Mic Sidetone Switch", "Mic"}, + {"Stereo Mixer", "Aux Bypass Switch", "AUX In"}, + + /* mono1 mixer */ + {"Mono1 Mixer", "Mic Sidetone Switch", "Mic"}, + {"Mono1 Mixer", "Mono Playback Switch", "DAC"}, + + /* mono2 mixer */ + {"Mono2 Mixer", "Mono Playback Switch", "Stereo Mixer"}, + + /* Mic */ + {"AIN", NULL, "Mic"}, + {"Input Mux", "Internal", "Mic Int Bias"}, + {"Input Mux", "External", "Mic Ext Bias"}, + {"Mic Int Bias", NULL, "MICIN"}, + {"Mic Ext Bias", NULL, "MICEXT"}, + {"MICOUT", NULL, "Input Mux"}, + + /* line out */ + {"LOUT", "Switch", "Line"}, + {"ROUT", "Switch", "Line Out Enable"}, + {"Line Out Enable", NULL, "Line Out"}, + {"Line Out", NULL, "Stereo Mixer"}, + + /* mono1 out */ + {"MOUT1", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono Mixer"}, + + /* left HP */ + {"HPL", "Switch", "Left HP Enable"}, + {"Left HP Enable", NULL, "HP L Amp"}, + {"HP L Amp", NULL, "Stereo Mixer"}, + + /* right HP */ + {"HPR", "Switch", "Right HP Enable"}, + {"Right HP Enable", NULL, "HP R Amp"}, + {"HP R Amp", NULL, "Stereo Mixer"}, + + /* speaker */ + {"SPP", "Switch", "Speaker Enable"}, + {"SPN", "Switch", "Speaker Enable"}, + {"Speaker Enable", NULL, "Spk Amp"}, + {"Spk Amp", NULL, "MIN"}, + + /* mono 2 */ + {"MOUT2", "Switch", "Mono 2 Enable"}, + {"Mono 2 Enable", NULL, "Stereo Mixer"}, + + /* Aux In */ + {"Aux In", NULL, "AUX"}, + + /* ADC */ + {"ADC", NULL, "Input Mixer"}, + {"Input Mixer", "Mic Capture Switch", "Mic"}, + {"Input Mixer", "Aux Capture Switch", "Aux In"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int ak4535_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(ak4535_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &ak4535_dapm_widgets[i]); + } + + /* set up audio path audio_mapnects */ + 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 ak4535_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; + struct ak4535_priv *ak4535 = codec->private_data; + + ak4535->sysclk = freq; + return 0; +} + +static int ak4535_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ak4535_priv *ak4535 = codec->private_data; + u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5); + int rate = params_rate(params), fs = 256; + + if (rate) + fs = ak4535->sysclk / rate; + + /* set fs */ + switch (fs) { + case 1024: + mode2 |= (0x2 << 5); + break; + case 512: + mode2 |= (0x1 << 5); + break; + case 256: + break; + } + + /* set rate */ + ak4535_write(codec, AK4535_MODE2, mode2); + return 0; +} + +static int ak4535_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 mode1 = 0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mode1 = 0x0002; + break; + case SND_SOC_DAIFMT_LEFT_J: + mode1 = 0x0001; + break; + default: + return -EINVAL; + } + + /* use 32 fs for BCLK to save power */ + mode1 |= 0x4; + + ak4535_write(codec, AK4535_MODE1, mode1); + return 0; +} + +static int ak4535_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf; + if (mute) + ak4535_write(codec, AK4535_DAC, mute_reg); + else + ak4535_write(codec, AK4535_DAC, mute_reg | 0x20); + return 0; +} + +static int ak4535_dapm_event(struct snd_soc_codec *codec, int event) +{ + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, clk and osc on, dac unmute, active */ + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, dac mute, inactive */ + ak4535_write(codec, AK4535_PM1, 0x80); + ak4535_write(codec, AK4535_PM2, 0x0); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, inactive */ + ak4535_write(codec, AK4535_PM1, 0x0); + ak4535_write(codec, AK4535_PM2, 0x80); + break; + } + codec->dapm_state = event; + return 0; +} + +#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +struct snd_soc_codec_dai ak4535_dai = { + .name = "AK4535", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = ak4535_hw_params, + }, + .dai_ops = { + .set_fmt = ak4535_set_dai_fmt, + .digital_mute = ak4535_mute, + .set_sysclk = ak4535_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(ak4535_dai); + +static int ak4535_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; + + ak4535_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int ak4535_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(ak4535_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + ak4535_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + ak4535_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the AK4535 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ak4535_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "AK4535"; + codec->owner = THIS_MODULE; + codec->read = ak4535_read_reg_cache; + codec->write = ak4535_write; + codec->dapm_event = ak4535_dapm_event; + codec->dai = &ak4535_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(ak4535_reg); + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(ak4535_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, ak4535_reg, + sizeof(u16) * ARRAY_SIZE(ak4535_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(ak4535_reg); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ak4535: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + ak4535_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + ak4535_add_controls(codec); + ak4535_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ak4535: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + + return ret; +} + +static struct snd_soc_device *ak4535_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +#define I2C_DRIVERID_AK4535 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver ak4535_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static int ak4535_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = ak4535_socdev; + struct ak4535_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + printk(KERN_ERR "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = ak4535_init(socdev); + if (ret < 0) { + printk(KERN_ERR "failed to initialise AK4535\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int ak4535_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec* codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int ak4535_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, ak4535_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver ak4535_i2c_driver = { + .driver = { + .name = "AK4535 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_AK4535, + .attach_adapter = ak4535_i2c_attach, + .detach_client = ak4535_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "AK4535", + .driver = &ak4535_i2c_driver, +}; +#endif + +static int ak4535_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct ak4535_setup_data *setup; + struct snd_soc_codec* codec; + struct ak4535_priv *ak4535; + int ret = 0; + + printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ak4535 = kzalloc(sizeof(struct ak4535_priv), GFP_KERNEL); + if (ak4535 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ak4535; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ak4535_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&ak4535_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int ak4535_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) + ak4535_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&ak4535_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak4535 = { + .probe = ak4535_probe, + .remove = ak4535_remove, + .suspend = ak4535_suspend, + .resume = ak4535_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4535); + +MODULE_DESCRIPTION("Soc AK4535 driver"); +MODULE_AUTHOR("Richard Purdie"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/ak4535.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/ak4535.h @@ -0,0 +1,46 @@ +/* + * ak4535.h -- AK4535 Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on wm8753.h + * + * 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 _AK4535_H +#define _AK4535_H + +/* AK4535 register space */ + +#define AK4535_PM1 0x0 +#define AK4535_PM2 0x1 +#define AK4535_SIG1 0x2 +#define AK4535_SIG2 0x3 +#define AK4535_MODE1 0x4 +#define AK4535_MODE2 0x5 +#define AK4535_DAC 0x6 +#define AK4535_MIC 0x7 +#define AK4535_TIMER 0x8 +#define AK4535_ALC1 0x9 +#define AK4535_ALC2 0xa +#define AK4535_PGA 0xb +#define AK4535_LATT 0xc +#define AK4535_RATT 0xd +#define AK4535_VOL 0xe +#define AK4535_STATUS 0xf + +#define AK4535_CACHEREGNUM 0x10 + +struct ak4535_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai ak4535_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak4535; + +#endif Index: linux-2.6.21-moko/sound/soc/codecs/uda1380.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/uda1380.c @@ -0,0 +1,745 @@ +/* + * uda1380.c - Philips UDA1380 ALSA SoC audio driver + * + * 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. + * + * Modified by Richard Purdie to fit into SoC + * codec model. + * + * Copyright (c) 2005 Giorgio Padrin + * Copyright 2005 Openedhand Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uda1380.h" + +#define UDA1380_VERSION "0.5" +#define AUDIO_NAME "uda1380" +/* + * Debug + */ + +#define UDA1380_DEBUG 0 + +#ifdef UDA1380_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +/* + * uda1380 register cache + */ +static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = { + 0x0502, 0x0000, 0x0000, 0x3f3f, + 0x0202, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0xff00, 0x0000, 0x4800, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x8000, 0x0002, 0x0000, +}; + +/* + * read uda1380 register cache + */ +static inline unsigned int uda1380_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == UDA1380_RESET) + return 0; + if (reg >= UDA1380_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write uda1380 register cache + */ +static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= UDA1380_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the UDA1380 register space + */ +static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + /* data is + * data[0] is register offset + * data[1] is MS byte + * data[2] is LS byte + */ + data[0] = reg; + data[1] = (value & 0xff00) >> 8; + data[2] = value & 0x00ff; + + uda1380_write_reg_cache (codec, reg, value); + + /* the interpolator & decimator regs must only be written when the + * codec DAI is active. + */ + if (!codec->active && (reg >= UDA1380_MVOL)) + return 0; + dbg("uda hw write %x val %x\n", reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) { + unsigned int val; + i2c_master_send(codec->control_data, data, 1); + i2c_master_recv(codec->control_data, data, 2); + val = (data[0]<<8) | data[1]; + if (val != value) { + dbg("READ BACK VAL %x\n", (data[0]<<8) | data[1]); + return -EIO; + } + return 0; + } else + return -EIO; +} + +#define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0) + +/* declarations of ALSA reg_elem_REAL controls */ +static const char *uda1380_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz", + "96kHz"}; +static const char *uda1380_input_sel[] = {"Line", "Mic"}; +static const char *uda1380_output_sel[] = {"Direct", "Mixer"}; +static const char *uda1380_spf_mode[] = {"Flat", "Minimum1", "Minimum2", + "Maximum"}; + +static const struct soc_enum uda1380_enum[] = { + SOC_ENUM_DOUBLE(UDA1380_DEEMP, 0, 8, 5, uda1380_deemp), + SOC_ENUM_SINGLE(UDA1380_ADC, 3, 2, uda1380_input_sel), + SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode), + SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel), /* R02_EN_AVC */ +}; + +static const struct snd_kcontrol_new uda1380_snd_controls[] = { + SOC_DOUBLE("Playback Volume", UDA1380_MVOL, 0, 8, 255, 1), + SOC_DOUBLE("Mixer Volume", UDA1380_MIXVOL, 0, 8, 255, 1), + SOC_ENUM("Sound Processing Filter Mode", uda1380_enum[2]), + SOC_DOUBLE("Treble Volume", UDA1380_MODE, 4, 12, 3, 0), + SOC_DOUBLE("Bass Volume", UDA1380_MODE, 0, 8, 15, 0), + SOC_ENUM("Playback De-emphasis", uda1380_enum[0]), + SOC_DOUBLE("Capture Volume", UDA1380_DEC, 0, 8, 127, 0), + SOC_DOUBLE("Line Capture Volume", UDA1380_PGA, 0, 8, 15, 0), + SOC_SINGLE("Mic Capture Volume", UDA1380_PGA, 8, 11, 0), + SOC_DOUBLE("Playback Switch", UDA1380_DEEMP, 3, 11, 1, 1), + SOC_SINGLE("Capture Switch", UDA1380_PGA, 15, 1, 0), + SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), + SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), + SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0), + SOC_SINGLE("Silence", UDA1380_MIXER, 7, 1, 0), + SOC_SINGLE("Silence Detection", UDA1380_MIXER, 6, 1, 0), +}; + +/* add non dapm controls */ +static int uda1380_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&uda1380_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Input mux */ +static const struct snd_kcontrol_new uda1380_input_mux_control = + SOC_DAPM_ENUM("Input Select", uda1380_enum[1]); + +/* Output mux */ +static const struct snd_kcontrol_new uda1380_output_mux_control = + SOC_DAPM_ENUM("Output Select", uda1380_enum[3]); + +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &uda1380_input_mux_control), + SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0, + &uda1380_output_mux_control), + SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0), + SND_SOC_DAPM_INPUT("VINM"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_INPUT("VINR"), + SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("VOUTLHP"), + SND_SOC_DAPM_OUTPUT("VOUTRHP"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), + SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0), + SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0), +}; + +static const char *audio_map[][3] = { + + /* output mux */ + {"HeadPhone Driver", NULL, "Output Mux"}, + {"VOUTR", NULL, "Output Mux"}, + {"VOUTL", NULL, "Output Mux"}, + + {"Analog Mixer", NULL, "VINR"}, + {"Analog Mixer", NULL, "VINL"}, + {"Analog Mixer", NULL, "DAC"}, + + {"Output Mux", "Direct", "DAC"}, + {"Output Mux", "Mixer", "Analog Mixer"}, + + /* headphone driver */ + {"VOUTLHP", NULL, "HeadPhone Driver"}, + {"VOUTRHP", NULL, "HeadPhone Driver"}, + + /* input mux */ + {"Left ADC", NULL, "Input Mux"}, + {"Input Mux", "Mic", "Mic LNA"}, + {"Input Mux", "Line", "Left PGA"}, + + /* right input */ + {"Right ADC", NULL, "Right PGA"}, + + /* inputs */ + {"Mic LNA", NULL, "VINM"}, + {"Left PGA", NULL, "VINL"}, + {"Right PGA", NULL, "VINR"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int uda1380_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(uda1380_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &uda1380_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 uda1380_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int iface; + /* set up DAI based upon fmt */ + + iface = uda1380_read_reg_cache (codec, UDA1380_IFACE); + iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK); + + /* FIXME: how to select I2S for DATAO and MSB for DATAI correctly? */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORI_I2S | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORI_LSB16 | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORI_MSB | R01_SFORO_I2S; + } + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) + iface |= R01_SIM; + + uda1380_write(codec, UDA1380_IFACE, iface); + + return 0; +} + +/* + * Flush reg cache + * We can only write the interpolator and decimator registers + * when the DAI is being clocked by the CPU DAI. It's up to the + * machine and cpu DAI driver to do this before we are called. + */ +static int uda1380_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int reg, reg_start, reg_end, clk; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg_start = UDA1380_MVOL; + reg_end = UDA1380_MIXER; + } else { + reg_start = UDA1380_DEC; + reg_end = UDA1380_AGC; + } + + /* FIXME disable DAC_CLK */ + clk = uda1380_read_reg_cache (codec, 00); + uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK); + + for (reg = reg_start; reg <= reg_end; reg++ ) { + dbg("reg %x val %x\n",reg, uda1380_read_reg_cache (codec, reg)); + uda1380_write(codec, reg, uda1380_read_reg_cache (codec, reg)); + } + + /* FIXME enable DAC_CLK */ + uda1380_write(codec, UDA1380_CLK, clk | R00_DAC_CLK); + + return 0; +} + +static int uda1380_pcm_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* set WSPLL power and divider if running from this clock */ + if (clk & R00_DAC_CLK) { + int rate = params_rate(params); + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + clk &= ~0x3; /* clear SEL_LOOP_DIV */ + switch (rate) { + case 6250 ... 12500: + clk |= 0x0; + break; + case 12501 ... 25000: + clk |= 0x1; + break; + case 25001 ... 50000: + clk |= 0x2; + break; + case 50001 ... 100000: + clk |= 0x3; + break; + } + uda1380_write(codec, UDA1380_PM, R02_PON_PLL | pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk |= R00_EN_DAC | R00_EN_INT; + else + clk |= R00_EN_ADC | R00_EN_DEC; + + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* shut down WSPLL power if running from this clock */ + if (clk & R00_DAC_CLK) { + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + uda1380_write(codec, UDA1380_PM, ~R02_PON_PLL & pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk &= ~(R00_EN_DAC | R00_EN_INT); + else + clk &= ~(R00_EN_ADC | R00_EN_DEC); + + uda1380_write(codec, UDA1380_CLK, clk); +} + +static int uda1380_mute(struct snd_soc_codec_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & 0xbfff; + + /* FIXME: mute(codec,0) is called when the magician clock is already + * set to WSPLL, but for some unknown reason writing to interpolator + * registers works only when clocked by SYSCLK */ + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk); + if (mute) + uda1380_write(codec, UDA1380_DEEMP, mute_reg | 0x4000); + else + uda1380_write(codec, UDA1380_DEEMP, mute_reg); + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static int uda1380_dapm_event(struct snd_soc_codec *codec, int event) +{ + int pm = uda1380_read_reg_cache(codec, UDA1380_PM); + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + /* enable internal bias */ + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS | pm); + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except internal bias */ + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, inactive */ + uda1380_write(codec, UDA1380_PM, 0x0); + break; + } + codec->dapm_state = event; + return 0; +} + +#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +struct snd_soc_codec_dai uda1380_dai[] = { +{ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{/* playback only - dual interface */ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{ /* capture only - dual interface*/ + .name = "UDA1380", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .set_fmt = uda1380_set_dai_fmt, + }, +}, +}; +EXPORT_SYMBOL_GPL(uda1380_dai); + +static int uda1380_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; + + uda1380_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int uda1380_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(uda1380_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + uda1380_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + uda1380_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the UDA1380 driver + * register the mixer and dsp interfaces with the kernel + */ +static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "UDA1380"; + codec->owner = THIS_MODULE; + codec->read = uda1380_read_reg_cache; + codec->write = uda1380_write; + codec->dapm_event = uda1380_dapm_event; + codec->dai = uda1380_dai; + codec->num_dai = ARRAY_SIZE(uda1380_dai); + codec->reg_cache_size = ARRAY_SIZE(uda1380_reg); + codec->reg_cache = + kcalloc(ARRAY_SIZE(uda1380_reg), sizeof(u16), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, uda1380_reg, + sizeof(u16) * ARRAY_SIZE(uda1380_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(uda1380_reg); + uda1380_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if(ret < 0) { + printk(KERN_ERR "uda1380: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + uda1380_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + /* set clock input */ + switch (dac_clk) { + case UDA1380_DAC_CLK_SYSCLK: + uda1380_write(codec, UDA1380_CLK, 0); + break; + case UDA1380_DAC_CLK_WSPLL: + uda1380_write(codec, UDA1380_CLK, R00_DAC_CLK); + break; + } + + /* uda1380 init */ + uda1380_add_controls(codec); + uda1380_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "uda1380: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *uda1380_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +#define I2C_DRIVERID_UDA1380 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver uda1380_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int uda1380_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = uda1380_socdev; + struct uda1380_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + printk(KERN_ERR "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = uda1380_init(socdev, setup->dac_clk); + if (ret < 0) { + printk(KERN_ERR "failed to initialise UDA1380\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int uda1380_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec* codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int uda1380_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, uda1380_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver uda1380_i2c_driver = { + .driver = { + .name = "UDA1380 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_UDA1380, + .attach_adapter = uda1380_i2c_attach, + .detach_client = uda1380_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "UDA1380", + .driver = &uda1380_i2c_driver, +}; +#endif + +static int uda1380_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct uda1380_setup_data *setup; + struct snd_soc_codec* codec; + int ret = 0; + + printk(KERN_INFO "UDA1380 Audio Codec %s", UDA1380_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); + + uda1380_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&uda1380_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int uda1380_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) + uda1380_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&uda1380_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_uda1380 = { + .probe = uda1380_probe, + .remove = uda1380_remove, + .suspend = uda1380_suspend, + .resume = uda1380_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); + +MODULE_AUTHOR("Giorgio Padrin"); +MODULE_DESCRIPTION("Audio support for codec Philips UDA1380"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/uda1380.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/uda1380.h @@ -0,0 +1,89 @@ +/* + * Audio support for Philips UDA1380 + * + * 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. + * + * Copyright (c) 2005 Giorgio Padrin + */ + +#ifndef _UDA1380_H +#define _UDA1380_H + +#define UDA1380_CLK 0x00 +#define UDA1380_IFACE 0x01 +#define UDA1380_PM 0x02 +#define UDA1380_AMIX 0x03 +#define UDA1380_HP 0x04 +#define UDA1380_MVOL 0x10 +#define UDA1380_MIXVOL 0x11 +#define UDA1380_MODE 0x12 +#define UDA1380_DEEMP 0x13 +#define UDA1380_MIXER 0x14 +#define UDA1380_INTSTAT 0x18 +#define UDA1380_DEC 0x20 +#define UDA1380_PGA 0x21 +#define UDA1380_ADC 0x22 +#define UDA1380_AGC 0x23 +#define UDA1380_DECSTAT 0x28 +#define UDA1380_RESET 0x7f + +#define UDA1380_CACHEREGNUM 0x24 + +/* Register flags */ +#define R00_EN_ADC 0x0800 +#define R00_EN_DEC 0x0400 +#define R00_EN_DAC 0x0200 +#define R00_EN_INT 0x0100 +#define R00_DAC_CLK 0x0010 +#define R01_SFORI_I2S 0x0000 +#define R01_SFORI_LSB16 0x0100 +#define R01_SFORI_LSB18 0x0200 +#define R01_SFORI_LSB20 0x0300 +#define R01_SFORI_MSB 0x0500 +#define R01_SFORI_MASK 0x0700 +#define R01_SFORO_I2S 0x0000 +#define R01_SFORO_LSB16 0x0001 +#define R01_SFORO_LSB18 0x0002 +#define R01_SFORO_LSB20 0x0003 +#define R01_SFORO_LSB24 0x0004 +#define R01_SFORO_MSB 0x0005 +#define R01_SFORO_MASK 0x0007 +#define R01_SEL_SOURCE 0x0040 +#define R01_SIM 0x0010 +#define R02_PON_PLL 0x8000 +#define R02_PON_HP 0x2000 +#define R02_PON_DAC 0x0400 +#define R02_PON_BIAS 0x0100 +#define R02_EN_AVC 0x0080 +#define R02_PON_AVC 0x0040 +#define R02_PON_LNA 0x0010 +#define R02_PON_PGAL 0x0008 +#define R02_PON_ADCL 0x0004 +#define R02_PON_PGAR 0x0002 +#define R02_PON_ADCR 0x0001 +#define R13_MTM 0x4000 +#define R14_SILENCE 0x0080 +#define R14_SDET_ON 0x0040 +#define R21_MT_ADC 0x8000 +#define R22_SEL_LNA 0x0008 +#define R22_SEL_MIC 0x0004 +#define R22_SKIP_DCFIL 0x0002 +#define R23_AGC_EN 0x0001 + +struct uda1380_setup_data { + unsigned short i2c_address; + int dac_clk; +#define UDA1380_DAC_CLK_SYSCLK 0 +#define UDA1380_DAC_CLK_WSPLL 1 +}; + +#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */ +#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */ +#define UDA1380_DAI_CAPTURE 2 /* capture DAI */ + +extern struct snd_soc_codec_dai uda1380_dai[3]; +extern struct snd_soc_codec_device soc_codec_dev_uda1380; + +#endif /* _UDA1380_H */ Index: linux-2.6.21-moko/sound/soc/codecs/wm8753.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8753.c @@ -0,0 +1,1782 @@ +/* + * wm8753.c -- WM8753 ALSA Soc Audio driver + * + * Copyright 2003 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Notes: + * The WM8753 is a low power, high quality stereo codec with integrated PCM + * codec designed for portable digital telephony applications. + * + * Dual DAI:- + * + * This driver support 2 DAI PCM's. This makes the default PCM available for + * HiFi audio (e.g. MP3, ogg) playback/capture and the other PCM available for + * voice. + * + * Please note that the voice PCM can be connected directly to a Bluetooth + * codec or GSM modem and thus cannot be read or written to, although it is + * available to be configured with snd_hw_params(), etc and kcontrols in the + * normal alsa manner. + * + * Fast DAI switching:- + * + * The driver can now fast switch between the DAI configurations via a + * an alsa kcontrol. This allows the PCM to remain open. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8753.h" + +#define AUDIO_NAME "wm8753" +#define WM8753_VERSION "0.16" + +/* + * Debug + */ + +#define WM8753_DEBUG 0 + +#ifdef WM8753_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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) + +static int caps_charge = 2000; +module_param(caps_charge, int, 0); +MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); + +static void wm8753_set_dai_mode(struct snd_soc_codec *codec, + unsigned int mode); + +/* codec private data */ +struct wm8753_priv { + unsigned int sysclk; + unsigned int pcmclk; +}; + +/* + * wm8753 register cache + * We can't read the WM8753 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8753_reg[] = { + 0x0008, 0x0000, 0x000a, 0x000a, + 0x0033, 0x0000, 0x0007, 0x00ff, + 0x00ff, 0x000f, 0x000f, 0x007b, + 0x0000, 0x0032, 0x0000, 0x00c3, + 0x00c3, 0x00c0, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0055, + 0x0005, 0x0050, 0x0055, 0x0050, + 0x0055, 0x0050, 0x0055, 0x0079, + 0x0079, 0x0079, 0x0079, 0x0079, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0097, 0x0097, 0x0000, 0x0004, + 0x0000, 0x0083, 0x0024, 0x01ba, + 0x0000, 0x0083, 0x0024, 0x01ba, + 0x0000, 0x0000 +}; + +/* + * read wm8753 register cache + */ +static inline unsigned int wm8753_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg < 1 || reg > (ARRAY_SIZE(wm8753_reg) + 1)) + return -1; + return cache[reg - 1]; +} + +/* + * write wm8753 register cache + */ +static inline void wm8753_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg < 1 || reg > 0x3f) + return; + cache[reg - 1] = value; +} + +/* + * write to the WM8753 register space + */ +static int wm8753_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8753_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8753_reset(c) wm8753_write(c, WM8753_RESET, 0) + +/* + * WM8753 Controls + */ +static const char *wm8753_base[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm8753_base_filter[] = + {"130Hz @ 48kHz", "200Hz @ 48kHz", "100Hz @ 16kHz", "400Hz @ 48kHz", + "100Hz @ 8kHz", "200Hz @ 8kHz"}; +static const char *wm8753_treble[] = {"8kHz", "4kHz"}; +static const char *wm8753_alc_func[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8753_ng_type[] = {"Constant PGA Gain", "Mute ADC Output"}; +static const char *wm8753_3d_func[] = {"Capture", "Playback"}; +static const char *wm8753_3d_uc[] = {"2.2kHz", "1.5kHz"}; +static const char *wm8753_3d_lc[] = {"200Hz", "500Hz"}; +static const char *wm8753_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz"}; +static const char *wm8753_mono_mix[] = {"Stereo", "Left", "Right", "Mono"}; +static const char *wm8753_dac_phase[] = {"Non Inverted", "Inverted"}; +static const char *wm8753_line_mix[] = {"Line 1 + 2", "Line 1 - 2", + "Line 1", "Line 2"}; +static const char *wm8753_mono_mux[] = {"Line Mix", "Rx Mix"}; +static const char *wm8753_right_mux[] = {"Line 2", "Rx Mix"}; +static const char *wm8753_left_mux[] = {"Line 1", "Rx Mix"}; +static const char *wm8753_rxmsel[] = {"RXP - RXN", "RXP + RXN", "RXP", "RXN"}; +static const char *wm8753_sidetone_mux[] = {"Left PGA", "Mic 1", "Mic 2", + "Right PGA"}; +static const char *wm8753_mono2_src[] = {"Inverted Mono 1", "Left", "Right", + "Left + Right"}; +static const char *wm8753_out3[] = {"VREF", "ROUT2", "Left + Right"}; +static const char *wm8753_out4[] = {"VREF", "Capture ST", "LOUT2"}; +static const char *wm8753_radcsel[] = {"PGA", "Line or RXP-RXN", "Sidetone"}; +static const char *wm8753_ladcsel[] = {"PGA", "Line or RXP-RXN", "Line"}; +static const char *wm8753_mono_adc[] = {"Stereo", "Analogue Mix Left", + "Analogue Mix Right", "Digital Mono Mix"}; +static const char *wm8753_adc_hp[] = {"3.4Hz @ 48kHz", "82Hz @ 16k", + "82Hz @ 8kHz", "170Hz @ 8kHz"}; +static const char *wm8753_adc_filter[] = {"HiFi", "Voice"}; +static const char *wm8753_mic_sel[] = {"Mic 1", "Mic 2", "Mic 3"}; +static const char *wm8753_dai_mode[] = {"DAI 0", "DAI 1", "DAI 2", "DAI 3"}; +static const char *wm8753_dat_sel[] = {"Stereo", "Left ADC", "Right ADC", + "Channel Swap"}; + +static const struct soc_enum wm8753_enum[] = { +SOC_ENUM_SINGLE(WM8753_BASS, 7, 2, wm8753_base), // 0 +SOC_ENUM_SINGLE(WM8753_BASS, 4, 6, wm8753_base_filter), // 1 +SOC_ENUM_SINGLE(WM8753_TREBLE, 6, 2, wm8753_treble), // 2 +SOC_ENUM_SINGLE(WM8753_ALC1, 7, 4, wm8753_alc_func), // 3 +SOC_ENUM_SINGLE(WM8753_NGATE, 1, 2, wm8753_ng_type), // 4 +SOC_ENUM_SINGLE(WM8753_3D, 7, 2, wm8753_3d_func), // 5 +SOC_ENUM_SINGLE(WM8753_3D, 6, 2, wm8753_3d_uc), // 6 +SOC_ENUM_SINGLE(WM8753_3D, 5, 2, wm8753_3d_lc), // 7 +SOC_ENUM_SINGLE(WM8753_DAC, 1, 4, wm8753_deemp), // 8 +SOC_ENUM_SINGLE(WM8753_DAC, 4, 4, wm8753_mono_mix), // 9 +SOC_ENUM_SINGLE(WM8753_DAC, 6, 2, wm8753_dac_phase), // 10 +SOC_ENUM_SINGLE(WM8753_INCTL1, 3, 4, wm8753_line_mix), // 11 +SOC_ENUM_SINGLE(WM8753_INCTL1, 2, 2, wm8753_mono_mux), // 12 +SOC_ENUM_SINGLE(WM8753_INCTL1, 1, 2, wm8753_right_mux), // 13 +SOC_ENUM_SINGLE(WM8753_INCTL1, 0, 2, wm8753_left_mux), // 14 +SOC_ENUM_SINGLE(WM8753_INCTL2, 6, 4, wm8753_rxmsel), // 15 +SOC_ENUM_SINGLE(WM8753_INCTL2, 4, 4, wm8753_sidetone_mux),// 16 +SOC_ENUM_SINGLE(WM8753_OUTCTL, 7, 4, wm8753_mono2_src), // 17 +SOC_ENUM_SINGLE(WM8753_OUTCTL, 0, 3, wm8753_out3), // 18 +SOC_ENUM_SINGLE(WM8753_ADCTL2, 7, 3, wm8753_out4), // 19 +SOC_ENUM_SINGLE(WM8753_ADCIN, 2, 3, wm8753_radcsel), // 20 +SOC_ENUM_SINGLE(WM8753_ADCIN, 0, 3, wm8753_ladcsel), // 21 +SOC_ENUM_SINGLE(WM8753_ADCIN, 4, 4, wm8753_mono_adc), // 22 +SOC_ENUM_SINGLE(WM8753_ADC, 2, 4, wm8753_adc_hp), // 23 +SOC_ENUM_SINGLE(WM8753_ADC, 4, 2, wm8753_adc_filter), // 24 +SOC_ENUM_SINGLE(WM8753_MICBIAS, 6, 3, wm8753_mic_sel), // 25 +SOC_ENUM_SINGLE(WM8753_IOCTL, 2, 4, wm8753_dai_mode), // 26 +SOC_ENUM_SINGLE(WM8753_ADC, 7, 4, wm8753_dat_sel), // 27 +}; + + +static int wm8753_get_dai(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL); + + ucontrol->value.integer.value[0] = (mode & 0xc) >> 2; + return 0; +} + +static int wm8753_set_dai(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL); + + if (((mode &0xc) >> 2) == ucontrol->value.integer.value[0]) + return 0; + + mode &= 0xfff3; + mode |= (ucontrol->value.integer.value[0] << 2); + + wm8753_write(codec, WM8753_IOCTL, mode); + wm8753_set_dai_mode(codec, ucontrol->value.integer.value[0]); + return 1; +} + +static const struct snd_kcontrol_new wm8753_snd_controls[] = { +SOC_DOUBLE_R("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0), + +SOC_DOUBLE_R("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0), + +SOC_DOUBLE_R("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V, 0, 127, 0), +SOC_DOUBLE_R("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0, 127, 0), + +SOC_SINGLE("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0), + +SOC_DOUBLE_R("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7, 1), +SOC_DOUBLE_R("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4, 7, 1), +SOC_DOUBLE_R("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7, 1), + +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7, 1, 0), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7, 1, 0), + +SOC_SINGLE("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1), +SOC_SINGLE("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1), +SOC_SINGLE("Mono Voice Playback Volume", WM8753_MOUTM2, 4, 7, 1), +SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0), + +SOC_ENUM("Bass Boost", wm8753_enum[0]), +SOC_ENUM("Bass Filter", wm8753_enum[1]), +SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1), +SOC_ENUM("Treble Cut-off", wm8753_enum[2]), + +SOC_DOUBLE("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1), +SOC_SINGLE("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1), + +SOC_DOUBLE_R("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0), +SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1), + +SOC_ENUM("Capture Filter Select", wm8753_enum[23]), +SOC_ENUM("Capture Filter Cut-off", wm8753_enum[24]), +SOC_SINGLE("Capture Filter Switch", WM8753_ADC, 0, 1, 1), + +SOC_SINGLE("ALC Capture Target Volume", WM8753_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", WM8753_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", wm8753_enum[3]), +SOC_SINGLE("ALC Capture ZC Switch", WM8753_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", WM8753_ALC2, 0, 15, 1), +SOC_SINGLE("ALC Capture Decay Time", WM8753_ALC3, 4, 15, 1), +SOC_SINGLE("ALC Capture Attack Time", WM8753_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", WM8753_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", wm8753_enum[4]), +SOC_SINGLE("ALC Capture NG Switch", WM8753_NGATE, 0, 1, 0), + +SOC_ENUM("3D Function", wm8753_enum[5]), +SOC_ENUM("3D Upper Cut-off", wm8753_enum[6]), +SOC_ENUM("3D Lower Cut-off", wm8753_enum[7]), +SOC_SINGLE("3D Volume", WM8753_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8753_3D, 0, 1, 0), + +SOC_SINGLE("Capture 6dB Attenuate", WM8753_ADCTL1, 2, 1, 0), +SOC_SINGLE("Playback 6dB Attenuate", WM8753_ADCTL1, 1, 1, 0), + +SOC_ENUM("De-emphasis", wm8753_enum[8]), +SOC_ENUM("Playback Mono Mix", wm8753_enum[9]), +SOC_ENUM("Playback Phase", wm8753_enum[10]), + +SOC_SINGLE("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0), +SOC_SINGLE("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0), + +SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai), + +SOC_ENUM("ADC Data Select", wm8753_enum[27]), +}; + +/* add non dapm controls */ +static int wm8753_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8753_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8753_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * _DAPM_ Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8753_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_LOUTM2, 7, 1, 0), +SOC_DAPM_SINGLE("Left Playback Switch", WM8753_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_LOUTM1, 7, 1, 0), +}; + +/* Right mixer */ +static const struct snd_kcontrol_new wm8753_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_ROUTM2, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8753_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_ROUTM1, 7, 1, 0), +}; + +/* Mono mixer */ +static const struct snd_kcontrol_new wm8753_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8753_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8753_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_MOUTM2, 3, 1, 0), +SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_MOUTM2, 7, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_MOUTM1, 7, 1, 0), +}; + +/* Mono 2 Mux */ +static const struct snd_kcontrol_new wm8753_mono2_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[17]); + +/* Out 3 Mux */ +static const struct snd_kcontrol_new wm8753_out3_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[18]); + +/* Out 4 Mux */ +static const struct snd_kcontrol_new wm8753_out4_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[19]); + +/* ADC Mono Mix */ +static const struct snd_kcontrol_new wm8753_adc_mono_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[22]); + +/* Record mixer */ +static const struct snd_kcontrol_new wm8753_record_mixer_controls[] = { +SOC_DAPM_SINGLE("Voice Capture Switch", WM8753_RECMIX2, 3, 1, 0), +SOC_DAPM_SINGLE("Left Capture Switch", WM8753_RECMIX1, 3, 1, 0), +SOC_DAPM_SINGLE("Right Capture Switch", WM8753_RECMIX1, 7, 1, 0), +}; + +/* Left ADC mux */ +static const struct snd_kcontrol_new wm8753_adc_left_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[21]); + +/* Right ADC mux */ +static const struct snd_kcontrol_new wm8753_adc_right_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[20]); + +/* MIC mux */ +static const struct snd_kcontrol_new wm8753_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[16]); + +/* ALC mixer */ +static const struct snd_kcontrol_new wm8753_alc_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Capture Switch", WM8753_INCTL2, 3, 1, 0), +SOC_DAPM_SINGLE("Mic2 Capture Switch", WM8753_INCTL2, 2, 1, 0), +SOC_DAPM_SINGLE("Mic1 Capture Switch", WM8753_INCTL2, 1, 1, 0), +SOC_DAPM_SINGLE("Rx Capture Switch", WM8753_INCTL2, 0, 1, 0), +}; + +/* Left Line mux */ +static const struct snd_kcontrol_new wm8753_line_left_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[14]); + +/* Right Line mux */ +static const struct snd_kcontrol_new wm8753_line_right_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[13]); + +/* Mono Line mux */ +static const struct snd_kcontrol_new wm8753_line_mono_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[12]); + +/* Line mux and mixer */ +static const struct snd_kcontrol_new wm8753_line_mux_mix_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[11]); + +/* Rx mux and mixer */ +static const struct snd_kcontrol_new wm8753_rx_mux_mix_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[15]); + +/* Mic Selector Mux */ +static const struct snd_kcontrol_new wm8753_mic_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[25]); + +static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8753_PWR1, 5, 0), +SND_SOC_DAPM_MIXER("Left Mixer", WM8753_PWR4, 0, 0, + &wm8753_left_mixer_controls[0], ARRAY_SIZE(wm8753_left_mixer_controls)), +SND_SOC_DAPM_PGA("Left Out 1", WM8753_PWR3, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Out 2", WM8753_PWR3, 6, 0, NULL, 0), +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", WM8753_PWR1, 3, 0), +SND_SOC_DAPM_OUTPUT("LOUT1"), +SND_SOC_DAPM_OUTPUT("LOUT2"), +SND_SOC_DAPM_MIXER("Right Mixer", WM8753_PWR4, 1, 0, + &wm8753_right_mixer_controls[0], ARRAY_SIZE(wm8753_right_mixer_controls)), +SND_SOC_DAPM_PGA("Right Out 1", WM8753_PWR3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Out 2", WM8753_PWR3, 5, 0, NULL, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", WM8753_PWR1, 2, 0), +SND_SOC_DAPM_OUTPUT("ROUT1"), +SND_SOC_DAPM_OUTPUT("ROUT2"), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8753_PWR4, 2, 0, + &wm8753_mono_mixer_controls[0], ARRAY_SIZE(wm8753_mono_mixer_controls)), +SND_SOC_DAPM_PGA("Mono Out 1", WM8753_PWR3, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out 2", WM8753_PWR3, 1, 0, NULL, 0), +SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", WM8753_PWR1, 4, 0), +SND_SOC_DAPM_OUTPUT("MONO1"), +SND_SOC_DAPM_MUX("Mono 2 Mux", SND_SOC_NOPM, 0, 0, &wm8753_mono2_controls), +SND_SOC_DAPM_OUTPUT("MONO2"), +SND_SOC_DAPM_MIXER("Out3 Left + Right", -1, 0, 0, NULL, 0), +SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out3_controls), +SND_SOC_DAPM_PGA("Out 3", WM8753_PWR3, 4, 0, NULL, 0), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_MUX("Out4 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out4_controls), +SND_SOC_DAPM_PGA("Out 4", WM8753_PWR3, 3, 0, NULL, 0), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_MIXER("Playback Mixer", WM8753_PWR4, 3, 0, + &wm8753_record_mixer_controls[0], + ARRAY_SIZE(wm8753_record_mixer_controls)), +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8753_PWR2, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8753_PWR2, 2, 0), +SND_SOC_DAPM_MUX("Capture Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8753_adc_mono_controls), +SND_SOC_DAPM_MUX("Capture Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8753_adc_mono_controls), +SND_SOC_DAPM_MUX("Capture Left Mux", SND_SOC_NOPM, 0, 0, + &wm8753_adc_left_controls), +SND_SOC_DAPM_MUX("Capture Right Mux", SND_SOC_NOPM, 0, 0, + &wm8753_adc_right_controls), +SND_SOC_DAPM_MUX("Mic Sidetone Mux", SND_SOC_NOPM, 0, 0, + &wm8753_mic_mux_controls), +SND_SOC_DAPM_PGA("Left Capture Volume", WM8753_PWR2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Capture Volume", WM8753_PWR2, 4, 0, NULL, 0), +SND_SOC_DAPM_MIXER("ALC Mixer", WM8753_PWR2, 6, 0, + &wm8753_alc_mixer_controls[0], ARRAY_SIZE(wm8753_alc_mixer_controls)), +SND_SOC_DAPM_MUX("Line Left Mux", SND_SOC_NOPM, 0, 0, + &wm8753_line_left_controls), +SND_SOC_DAPM_MUX("Line Right Mux", SND_SOC_NOPM, 0, 0, + &wm8753_line_right_controls), +SND_SOC_DAPM_MUX("Line Mono Mux", SND_SOC_NOPM, 0, 0, + &wm8753_line_mono_controls), +SND_SOC_DAPM_MUX("Line Mixer", WM8753_PWR2, 0, 0, + &wm8753_line_mux_mix_controls), +SND_SOC_DAPM_MUX("Rx Mixer", WM8753_PWR2, 1, 0, + &wm8753_rx_mux_mix_controls), +SND_SOC_DAPM_PGA("Mic 1 Volume", WM8753_PWR2, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mic 2 Volume", WM8753_PWR2, 7, 0, NULL, 0), +SND_SOC_DAPM_MUX("Mic Selection Mux", SND_SOC_NOPM, 0, 0, + &wm8753_mic_sel_mux_controls), +SND_SOC_DAPM_INPUT("LINE1"), +SND_SOC_DAPM_INPUT("LINE2"), +SND_SOC_DAPM_INPUT("RXP"), +SND_SOC_DAPM_INPUT("RXN"), +SND_SOC_DAPM_INPUT("ACIN"), +SND_SOC_DAPM_OUTPUT("ACOP"), +SND_SOC_DAPM_INPUT("MIC1N"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2N"), +SND_SOC_DAPM_INPUT("MIC2"), +SND_SOC_DAPM_VMID("VREF"), +}; + +static const char *audio_map[][3] = { + /* left mixer */ + {"Left Mixer", "Left Playback Switch", "Left DAC"}, + {"Left Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Left Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"}, + {"Left Mixer", "Bypass Playback Switch", "Line Left Mux"}, + + /* right mixer */ + {"Right Mixer", "Right Playback Switch", "Right DAC"}, + {"Right Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Right Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"}, + {"Right Mixer", "Bypass Playback Switch", "Line Right Mux"}, + + /* mono mixer */ + {"Mono Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"}, + {"Mono Mixer", "Bypass Playback Switch", "Line Mono Mux"}, + + /* left out */ + {"Left Out 1", NULL, "Left Mixer"}, + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out */ + {"Right Out 1", NULL, "Right Mixer"}, + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono 1 out */ + {"Mono Out 1", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out 1"}, + + /* mono 2 out */ + {"Mono 2 Mux", "Left + Right", "Out3 Left + Right"}, + {"Mono 2 Mux", "Inverted Mono 1", "MONO1"}, + {"Mono 2 Mux", "Left", "Left Mixer"}, + {"Mono 2 Mux", "Right", "Right Mixer"}, + {"Mono Out 2", NULL, "Mono 2 Mux"}, + {"MONO2", NULL, "Mono Out 2"}, + + /* out 3 */ + {"Out3 Left + Right", NULL, "Left Mixer"}, + {"Out3 Left + Right", NULL, "Right Mixer"}, + {"Out3 Mux", "VREF", "VREF"}, + {"Out3 Mux", "Left + Right", "Out3 Left + Right"}, + {"Out3 Mux", "ROUT2", "ROUT2"}, + {"Out 3", NULL, "Out3 Mux"}, + {"OUT3", NULL, "Out 3"}, + + /* out 4 */ + {"Out4 Mux", "VREF", "VREF"}, + {"Out4 Mux", "Capture ST", "Capture ST Mixer"}, + {"Out4 Mux", "LOUT2", "LOUT2"}, + {"Out 4", NULL, "Out4 Mux"}, + {"OUT4", NULL, "Out 4"}, + + /* record mixer */ + {"Playback Mixer", "Left Capture Switch", "Left Mixer"}, + {"Playback Mixer", "Voice Capture Switch", "Mono Mixer"}, + {"Playback Mixer", "Right Capture Switch", "Right Mixer"}, + + /* Mic/SideTone Mux */ + {"Mic Sidetone Mux", "Left PGA", "Left Capture Volume"}, + {"Mic Sidetone Mux", "Right PGA", "Right Capture Volume"}, + {"Mic Sidetone Mux", "Mic 1", "Mic 1 Volume"}, + {"Mic Sidetone Mux", "Mic 2", "Mic 2 Volume"}, + + /* Capture Left Mux */ + {"Capture Left Mux", "PGA", "Left Capture Volume"}, + {"Capture Left Mux", "Line or RXP-RXN", "Line Left Mux"}, + {"Capture Left Mux", "Line", "LINE1"}, + + /* Capture Right Mux */ + {"Capture Right Mux", "PGA", "Right Capture Volume"}, + {"Capture Right Mux", "Line or RXP-RXN", "Line Right Mux"}, + {"Capture Right Mux", "Sidetone", "Capture ST Mixer"}, + + /* Mono Capture mixer-mux */ + {"Capture Right Mixer", "Stereo", "Capture Right Mux"}, + {"Capture Left Mixer", "Analogue Mix Left", "Capture Left Mux"}, + {"Capture Left Mixer", "Analogue Mix Left", "Capture Right Mux"}, + {"Capture Right Mixer", "Analogue Mix Right", "Capture Left Mux"}, + {"Capture Right Mixer", "Analogue Mix Right", "Capture Right Mux"}, + {"Capture Left Mixer", "Digital Mono Mix", "Capture Left Mux"}, + {"Capture Left Mixer", "Digital Mono Mix", "Capture Right Mux"}, + {"Capture Right Mixer", "Digital Mono Mix", "Capture Left Mux"}, + {"Capture Right Mixer", "Digital Mono Mix", "Capture Right Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Capture Left Mixer"}, + {"Right ADC", NULL, "Capture Right Mixer"}, + + /* Left Capture Volume */ + {"Left Capture Volume", NULL, "ACIN"}, + + /* Right Capture Volume */ + {"Right Capture Volume", NULL, "Mic 2 Volume"}, + + /* ALC Mixer */ + {"ALC Mixer", "Line Capture Switch", "Line Mixer"}, + {"ALC Mixer", "Mic2 Capture Switch", "Mic 2 Volume"}, + {"ALC Mixer", "Mic1 Capture Switch", "Mic 1 Volume"}, + {"ALC Mixer", "Rx Capture Switch", "Rx Mixer"}, + + /* Line Left Mux */ + {"Line Left Mux", "Line 1", "LINE1"}, + {"Line Left Mux", "Rx Mix", "Rx Mixer"}, + + /* Line Right Mux */ + {"Line Right Mux", "Line 2", "LINE2"}, + {"Line Right Mux", "Rx Mix", "Rx Mixer"}, + + /* Line Mono Mux */ + {"Line Mono Mux", "Line Mix", "Line Mixer"}, + {"Line Mono Mux", "Rx Mix", "Rx Mixer"}, + + /* Line Mixer/Mux */ + {"Line Mixer", "Line 1 + 2", "LINE1"}, + {"Line Mixer", "Line 1 - 2", "LINE1"}, + {"Line Mixer", "Line 1 + 2", "LINE2"}, + {"Line Mixer", "Line 1 - 2", "LINE2"}, + {"Line Mixer", "Line 1", "LINE1"}, + {"Line Mixer", "Line 2", "LINE2"}, + + /* Rx Mixer/Mux */ + {"Rx Mixer", "RXP - RXN", "RXP"}, + {"Rx Mixer", "RXP + RXN", "RXP"}, + {"Rx Mixer", "RXP - RXN", "RXN"}, + {"Rx Mixer", "RXP + RXN", "RXN"}, + {"Rx Mixer", "RXP", "RXP"}, + {"Rx Mixer", "RXN", "RXN"}, + + /* Mic 1 Volume */ + {"Mic 1 Volume", NULL, "MIC1N"}, + {"Mic 1 Volume", NULL, "Mic Selection Mux"}, + + /* Mic 2 Volume */ + {"Mic 2 Volume", NULL, "MIC2N"}, + {"Mic 2 Volume", NULL, "MIC2"}, + + /* Mic Selector Mux */ + {"Mic Selection Mux", "Mic 1", "MIC1"}, + {"Mic Selection Mux", "Mic 2", "MIC2N"}, + {"Mic Selection Mux", "Mic 3", "MIC2"}, + + /* ACOP */ + {"ACOP", NULL, "ALC Mixer"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8753_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm8753_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &wm8753_dapm_widgets[i]); + + /* set up the WM8753 audio map */ + 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; +} + +/* PLL divisors */ +struct _pll_div { + u32 div2:1; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 22) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8753 N value outwith recommended range! N = %d\n",Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8753_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + u16 reg, enable; + int offset; + struct snd_soc_codec *codec = codec_dai->codec; + + if (pll_id < WM8753_PLL1 || pll_id > WM8753_PLL2) + return -ENODEV; + + if (pll_id == WM8753_PLL1) { + offset = 0; + enable = 0x10; + reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xffef; + } else { + offset = 4; + enable = 0x8; + reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfff7; + } + + if (!freq_in || !freq_out) { + /* disable PLL */ + wm8753_write(codec, WM8753_PLL1CTL1 + offset, 0x0026); + wm8753_write(codec, WM8753_CLOCK, reg); + return 0; + } else { + + u16 value = 0; + struct _pll_div pll_div; + + pll_factors(&pll_div, freq_out * 8, freq_in); + + /* set up N and K PLL divisor ratios */ + /* bits 8:5 = PLL_N, bits 3:0 = PLL_K[21:18] */ + value = (pll_div.n << 5) + ((pll_div.k & 0x3c0000) >> 18); + wm8753_write(codec, WM8753_PLL1CTL2 + offset, value); + + /* bits 8:0 = PLL_K[17:9] */ + value = (pll_div.k & 0x03fe00) >> 9; + wm8753_write(codec, WM8753_PLL1CTL3 + offset, value); + + /* bits 8:0 = PLL_K[8:0] */ + value = pll_div.k & 0x0001ff; + wm8753_write(codec, WM8753_PLL1CTL4 + offset, value); + + /* set PLL as input and enable */ + wm8753_write(codec, WM8753_PLL1CTL1 + offset, 0x0027 | + (pll_div.div2 << 3)); + wm8753_write(codec, WM8753_CLOCK, reg | enable); + } + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk (after PLL) clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 0x6, 0x0}, + {11289600, 8000, 0x16, 0x0}, + {18432000, 8000, 0x7, 0x0}, + {16934400, 8000, 0x17, 0x0}, + {12000000, 8000, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 0x18, 0x0}, + {16934400, 11025, 0x19, 0x0}, + {12000000, 11025, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 0xa, 0x0}, + {18432000, 16000, 0xb, 0x0}, + {12000000, 16000, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 0x1a, 0x0}, + {16934400, 22050, 0x1b, 0x0}, + {12000000, 22050, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 0xc, 0x0}, + {18432000, 32000, 0xd, 0x0}, + {12000000, 32000, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 0x10, 0x0}, + {16934400, 44100, 0x11, 0x0}, + {12000000, 44100, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 0x0, 0x0}, + {18432000, 48000, 0x1, 0x0}, + {12000000, 48000, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 0x1e, 0x0}, + {16934400, 88200, 0x1f, 0x0}, + {12000000, 88200, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 0xe, 0x0}, + {18432000, 96000, 0xf, 0x0}, + {12000000, 96000, 0xe, 0x1}, +}; + +static int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +/* + * Clock after PLL and dividers + */ +static int wm8753_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; + struct wm8753_priv *wm8753 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + if (clk_id == WM8753_MCLK) { + wm8753->sysclk = freq; + return 0; + } else if (clk_id == WM8753_PCMCLK) { + wm8753->pcmclk = freq; + return 0; + } + break; + } + return -EINVAL; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01ec; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + voice |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + voice |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + voice |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + voice |= 0x0013; + break; + default: + return -EINVAL; + } + + wm8753_write(codec, WM8753_PCM, voice); + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8753_pcm_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8753_priv *wm8753 = codec->private_data; + u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01f3; + u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x017f; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + voice |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + voice |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + voice |= 0x000c; + break; + } + + /* sample rate */ + if (params_rate(params) * 384 == wm8753->pcmclk) + srate |= 0x80; + wm8753_write(codec, WM8753_SRATE1, srate); + + wm8753_write(codec, WM8753_PCM, voice); + return 0; +} + +/* + * Set's PCM dai fmt and BCLK. + */ +static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 voice, ioctl; + + voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x011f; + ioctl = wm8753_read_reg_cache(codec, WM8753_IOCTL) & 0x015d; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ioctl |= 0x2; + case SND_SOC_DAIFMT_CBM_CFS: + voice |= 0x0040; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + voice |= 0x0080; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + voice &= ~0x0010; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + voice |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + voice |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + voice |= 0x0010; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8753_write(codec, WM8753_PCM, voice); + wm8753_write(codec, WM8753_IOCTL, ioctl); + return 0; +} + +static int wm8753_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8753_PCMDIV: + reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0x003f; + wm8753_write(codec, WM8753_CLOCK, reg | div); + break; + case WM8753_BCLKDIV: + reg = wm8753_read_reg_cache(codec, WM8753_SRATE2) & 0x01c7; + wm8753_write(codec, WM8753_SRATE2, reg | div); + break; + case WM8753_VXCLKDIV: + reg = wm8753_read_reg_cache(codec, WM8753_SRATE2) & 0x003f; + wm8753_write(codec, WM8753_SRATE2, reg | div); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * Set's HiFi DAC format. + */ +static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01e0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + hifi |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + hifi |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + hifi |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + hifi |= 0x0013; + break; + default: + return -EINVAL; + } + + wm8753_write(codec, WM8753_HIFI, hifi); + return 0; +} + +/* + * Set's I2S DAI format. + */ +static int wm8753_i2s_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 ioctl, hifi; + + hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x011f; + ioctl = wm8753_read_reg_cache(codec, WM8753_IOCTL) & 0x00ae; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ioctl |= 0x1; + case SND_SOC_DAIFMT_CBM_CFS: + hifi |= 0x0040; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + hifi |= 0x0080; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + hifi &= ~0x0010; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + hifi |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + hifi |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + hifi |= 0x0010; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8753_write(codec, WM8753_HIFI, hifi); + wm8753_write(codec, WM8753_IOCTL, ioctl); + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8753_i2s_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8753_priv *wm8753 = codec->private_data; + u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x01c0; + u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01f3; + int coeff; + + /* is digital filter coefficient valid ? */ + coeff = get_coeff(wm8753->sysclk, params_rate(params)); + if (coeff < 0) { + printk(KERN_ERR "wm8753 invalid MCLK or rate\n"); + return coeff; + } + wm8753_write(codec, WM8753_SRATE1, srate | (coeff_div[coeff].sr << 1) | + coeff_div[coeff].usb); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + hifi |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + hifi |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + hifi |= 0x000c; + break; + } + + wm8753_write(codec, WM8753_HIFI, hifi); + return 0; +} + +static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 clock; + + /* set clk source as pcmclk */ + clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb; + wm8753_write(codec, WM8753_CLOCK, clock); + + if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + return wm8753_pcm_set_dai_fmt(codec_dai, fmt); +} + +static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + return wm8753_i2s_set_dai_fmt(codec_dai, fmt); +} + +static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 clock; + + /* set clk source as pcmclk */ + clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb; + wm8753_write(codec, WM8753_CLOCK, clock); + + if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + return wm8753_i2s_set_dai_fmt(codec_dai, fmt); +} + +static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 clock; + + /* set clk source as mclk */ + clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb; + wm8753_write(codec, WM8753_CLOCK, clock | 0x4); + + if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + return wm8753_i2s_set_dai_fmt(codec_dai, fmt); +} + +static int wm8753_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8753_read_reg_cache(codec, WM8753_DAC) & 0xfff7; + + /* the digital mute covers the HiFi and Voice DAC's on the WM8753. + * make sure we check if they are not both active when we mute */ + if (mute && dai->id == 1) { + if (!wm8753_dai[WM8753_DAI_VOICE].playback.active || + !wm8753_dai[WM8753_DAI_HIFI].playback.active) + wm8753_write(codec, WM8753_DAC, mute_reg | 0x8); + } else { + if (mute) + wm8753_write(codec, WM8753_DAC, mute_reg | 0x8); + else + wm8753_write(codec, WM8753_DAC, mute_reg); + } + + return 0; +} + +static int wm8753_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 pwr_reg = wm8753_read_reg_cache(codec, WM8753_PWR1) & 0xfe3e; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* set vmid to 50k and unmute dac */ + wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00c0); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + /* set vmid to 5k for quick power up */ + wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x01c1); + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* mute dac and set vmid to 500k, enable VREF */ + wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x0141); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + wm8753_write(codec, WM8753_PWR1, 0x0001); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8753_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8753_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +/* + * The WM8753 supports upto 4 different and mutually exclusive DAI + * configurations. This gives 2 PCM's available for use, hifi and voice. + * NOTE: The Voice PCM cannot play or capture audio to the CPU as it's DAI + * is connected between the wm8753 and a BT codec or GSM modem. + * + * 1. Voice over PCM DAI - HIFI DAC over HIFI DAI + * 2. Voice over HIFI DAI - HIFI disabled + * 3. Voice disabled - HIFI over HIFI + * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture + */ +static const struct snd_soc_codec_dai wm8753_all_dai[] = { +/* DAI HiFi mode 1 */ +{ .name = "WM8753 HiFi", + .id = 1, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { /* dummy for fast DAI switching */ + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_i2s_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode1h_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI Voice mode 1 */ +{ .name = "WM8753 Voice", + .id = 1, + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_pcm_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode1v_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI HiFi mode 2 - dummy */ +{ .name = "WM8753 HiFi", + .id = 2, +}, +/* DAI Voice mode 2 */ +{ .name = "WM8753 Voice", + .id = 2, + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_pcm_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode2_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI HiFi mode 3 */ +{ .name = "WM8753 HiFi", + .id = 3, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_i2s_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode3_4_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI Voice mode 3 - dummy */ +{ .name = "WM8753 Voice", + .id = 3, +}, +/* DAI HiFi mode 4 */ +{ .name = "WM8753 HiFi", + .id = 4, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_i2s_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode3_4_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI Voice mode 4 - dummy */ +{ .name = "WM8753 Voice", + .id = 4, +}, +}; + +struct snd_soc_codec_dai wm8753_dai[2]; +EXPORT_SYMBOL_GPL(wm8753_dai); + +static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode) +{ + if (mode < 4) { + wm8753_dai[0] = wm8753_all_dai[mode << 1]; + wm8753_dai[1] = wm8753_all_dai[(mode << 1) + 1]; + } + wm8753_dai[0].codec = codec; + wm8753_dai[1].codec = codec; +} + +static void wm8753_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, delayed_work.work); + wm8753_dapm_event(codec, codec->dapm_state); +} + +static int wm8753_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; + + wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8753_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8753_reg); i++) { + if (i + 1 == WM8753_RESET) + continue; + data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* charge wm8753 caps */ + if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) { + wm8753_dapm_event(codec, SNDRV_CTL_POWER_D2); + codec->dapm_state = SNDRV_CTL_POWER_D0; + schedule_delayed_work(&codec->delayed_work, + msecs_to_jiffies(caps_charge)); + } + + return 0; +} + +/* + * initialise the WM8753 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8753_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8753"; + codec->owner = THIS_MODULE; + codec->read = wm8753_read_reg_cache; + codec->write = wm8753_write; + codec->dapm_event = wm8753_dapm_event; + codec->dai = wm8753_dai; + codec->num_dai = 2; + codec->reg_cache_size = ARRAY_SIZE(wm8753_reg); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8753_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, wm8753_reg, + sizeof(u16) * ARRAY_SIZE(wm8753_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8753_reg); + wm8753_set_dai_mode(codec, 0); + + wm8753_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8753: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps */ + wm8753_dapm_event(codec, SNDRV_CTL_POWER_D2); + codec->dapm_state = SNDRV_CTL_POWER_D3hot; + schedule_delayed_work(&codec->delayed_work, + msecs_to_jiffies(caps_charge)); + + /* set the update bits */ + reg = wm8753_read_reg_cache(codec, WM8753_LDAC); + wm8753_write(codec, WM8753_LDAC, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_RDAC); + wm8753_write(codec, WM8753_RDAC, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_LOUT1V); + wm8753_write(codec, WM8753_LOUT1V, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_ROUT1V); + wm8753_write(codec, WM8753_ROUT1V, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_LOUT2V); + wm8753_write(codec, WM8753_LOUT2V, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_ROUT2V); + wm8753_write(codec, WM8753_ROUT2V, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_LINVOL); + wm8753_write(codec, WM8753_LINVOL, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_RINVOL); + wm8753_write(codec, WM8753_RINVOL, reg | 0x0100); + + wm8753_add_controls(codec); + wm8753_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8753: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8753_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * WM8753 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +#define I2C_DRIVERID_WM8753 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8753_i2c_driver; +static struct i2c_client client_template; + +static int wm8753_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8753_socdev; + struct wm8753_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8753_init(socdev); + if (ret < 0) { + err("failed to initialise WM8753\n"); + goto err; + } + + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8753_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8753_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8753_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8753_i2c_driver = { + .driver = { + .name = "WM8753 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8753, + .attach_adapter = wm8753_i2c_attach, + .detach_client = wm8753_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8753", + .driver = &wm8753_i2c_driver, +}; +#endif + +static int wm8753_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8753_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8753_priv *wm8753; + int ret = 0; + + info("WM8753 Audio Codec %s", WM8753_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL); + if (wm8753 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8753; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8753_socdev = socdev; + INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work); + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8753_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* + * This function forces any delayed work to be queued and run. + */ +static int run_delayed_work(struct delayed_work *dwork) +{ + int ret; + + /* cancel any work waiting to be queued. */ + ret = cancel_delayed_work(dwork); + + /* if there was any work waiting then we run it now and + * wait for it's completion */ + if (ret) { + schedule_delayed_work(dwork, 0); + flush_scheduled_work(); + } + return ret; +} + +/* power down chip */ +static int wm8753_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) + wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + run_delayed_work(&codec->delayed_work); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8753_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8753 = { + .probe = wm8753_probe, + .remove = wm8753_remove, + .suspend = wm8753_suspend, + .resume = wm8753_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8753); + +MODULE_DESCRIPTION("ASoC WM8753 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8753.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8753.h @@ -0,0 +1,126 @@ +/* + * wm8753.h -- audio driver for WM8753 + * + * Copyright 2003 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8753_H +#define _WM8753_H + +/* WM8753 register space */ + +#define WM8753_DAC 0x01 +#define WM8753_ADC 0x02 +#define WM8753_PCM 0x03 +#define WM8753_HIFI 0x04 +#define WM8753_IOCTL 0x05 +#define WM8753_SRATE1 0x06 +#define WM8753_SRATE2 0x07 +#define WM8753_LDAC 0x08 +#define WM8753_RDAC 0x09 +#define WM8753_BASS 0x0a +#define WM8753_TREBLE 0x0b +#define WM8753_ALC1 0x0c +#define WM8753_ALC2 0x0d +#define WM8753_ALC3 0x0e +#define WM8753_NGATE 0x0f +#define WM8753_LADC 0x10 +#define WM8753_RADC 0x11 +#define WM8753_ADCTL1 0x12 +#define WM8753_3D 0x13 +#define WM8753_PWR1 0x14 +#define WM8753_PWR2 0x15 +#define WM8753_PWR3 0x16 +#define WM8753_PWR4 0x17 +#define WM8753_ID 0x18 +#define WM8753_INTPOL 0x19 +#define WM8753_INTEN 0x1a +#define WM8753_GPIO1 0x1b +#define WM8753_GPIO2 0x1c +#define WM8753_RESET 0x1f +#define WM8753_RECMIX1 0x20 +#define WM8753_RECMIX2 0x21 +#define WM8753_LOUTM1 0x22 +#define WM8753_LOUTM2 0x23 +#define WM8753_ROUTM1 0x24 +#define WM8753_ROUTM2 0x25 +#define WM8753_MOUTM1 0x26 +#define WM8753_MOUTM2 0x27 +#define WM8753_LOUT1V 0x28 +#define WM8753_ROUT1V 0x29 +#define WM8753_LOUT2V 0x2a +#define WM8753_ROUT2V 0x2b +#define WM8753_MOUTV 0x2c +#define WM8753_OUTCTL 0x2d +#define WM8753_ADCIN 0x2e +#define WM8753_INCTL1 0x2f +#define WM8753_INCTL2 0x30 +#define WM8753_LINVOL 0x31 +#define WM8753_RINVOL 0x32 +#define WM8753_MICBIAS 0x33 +#define WM8753_CLOCK 0x34 +#define WM8753_PLL1CTL1 0x35 +#define WM8753_PLL1CTL2 0x36 +#define WM8753_PLL1CTL3 0x37 +#define WM8753_PLL1CTL4 0x38 +#define WM8753_PLL2CTL1 0x39 +#define WM8753_PLL2CTL2 0x3a +#define WM8753_PLL2CTL3 0x3b +#define WM8753_PLL2CTL4 0x3c +#define WM8753_BIASCTL 0x3d +#define WM8753_ADCTL2 0x3f + +struct wm8753_setup_data { + unsigned short i2c_address; +}; + +#define WM8753_PLL1 0 +#define WM8753_PLL2 1 + +/* clock inputs */ +#define WM8753_MCLK 0 +#define WM8753_PCMCLK 1 + +/* clock divider id's */ +#define WM8753_PCMDIV 0 +#define WM8753_BCLKDIV 1 +#define WM8753_VXCLKDIV 2 + +/* PCM clock dividers */ +#define WM8753_PCM_DIV_1 (0 << 6) +#define WM8753_PCM_DIV_3 (2 << 6) +#define WM8753_PCM_DIV_5_5 (3 << 6) +#define WM8753_PCM_DIV_2 (4 << 6) +#define WM8753_PCM_DIV_4 (5 << 6) +#define WM8753_PCM_DIV_6 (6 << 6) +#define WM8753_PCM_DIV_8 (7 << 6) + +/* BCLK clock dividers */ +#define WM8753_BCLK_DIV_1 (0 << 3) +#define WM8753_BCLK_DIV_2 (1 << 3) +#define WM8753_BCLK_DIV_4 (2 << 3) +#define WM8753_BCLK_DIV_8 (3 << 3) +#define WM8753_BCLK_DIV_16 (4 << 3) + +/* VXCLK clock dividers */ +#define WM8753_VXCLK_DIV_1 (0 << 6) +#define WM8753_VXCLK_DIV_2 (1 << 6) +#define WM8753_VXCLK_DIV_4 (2 << 6) +#define WM8753_VXCLK_DIV_8 (3 << 6) +#define WM8753_VXCLK_DIV_16 (4 << 6) + +#define WM8753_DAI_HIFI 0 +#define WM8753_DAI_VOICE 1 + +extern struct snd_soc_codec_dai wm8753_dai[2]; +extern struct snd_soc_codec_device soc_codec_dev_wm8753; + +#endif Index: linux-2.6.21-moko/sound/soc/codecs/wm8772.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8772.c @@ -0,0 +1,603 @@ +/* + * wm8772.c -- WM8772 ALSA Soc Audio driver + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "wm8772.h" + +#define AUDIO_NAME "WM8772" +#define WM8772_VERSION "0.4" + +/* codec private data */ +struct wm8772_priv { + unsigned int adcclk; + unsigned int dacclk; +}; + +/* + * wm8772 register cache + * We can't read the WM8772 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8772_reg[] = { + 0x00ff, 0x00ff, 0x0120, 0x0000, /* 0 */ + 0x00ff, 0x00ff, 0x00ff, 0x00ff, /* 4 */ + 0x00ff, 0x0000, 0x0080, 0x0040, /* 8 */ + 0x0000 +}; + +/* + * read wm8772 register cache + */ +static inline unsigned int wm8772_read_reg_cache(struct snd_soc_codec * codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg > WM8772_CACHE_REGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8772 register cache + */ +static inline void wm8772_write_reg_cache(struct snd_soc_codec * codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg > WM8772_CACHE_REGNUM) + return; + cache[reg] = value; +} + +static int wm8772_write(struct snd_soc_codec * codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8772 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8772_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -1; +} + +#define wm8772_reset(c) wm8772_write(c, WM8772_RESET, 0) + +/* + * WM8772 Controls + */ +static const char *wm8772_zero_flag[] = {"All Ch", "Ch 1", "Ch 2", "Ch3"}; + +static const struct soc_enum wm8772_enum[] = { +SOC_ENUM_SINGLE(WM8772_DACCTRL, 0, 4, wm8772_zero_flag), +}; + +static const struct snd_kcontrol_new wm8772_snd_controls[] = { + +SOC_SINGLE("Left1 Playback Volume", WM8772_LDAC1VOL, 0, 255, 0), +SOC_SINGLE("Left2 Playback Volume", WM8772_LDAC2VOL, 0, 255, 0), +SOC_SINGLE("Left3 Playback Volume", WM8772_LDAC3VOL, 0, 255, 0), +SOC_SINGLE("Right1 Playback Volume", WM8772_RDAC1VOL, 0, 255, 0), +SOC_SINGLE("Right1 Playback Volume", WM8772_RDAC2VOL, 0, 255, 0), +SOC_SINGLE("Right1 Playback Volume", WM8772_RDAC3VOL, 0, 255, 0), +SOC_SINGLE("Master Playback Volume", WM8772_MDACVOL, 0, 255, 0), + +SOC_SINGLE("Playback Switch", WM8772_DACCH, 0, 1, 0), +SOC_SINGLE("Capture Switch", WM8772_ADCCTRL, 2, 1, 0), + +SOC_SINGLE("Demp1 Playback Switch", WM8772_DACCTRL, 6, 1, 0), +SOC_SINGLE("Demp2 Playback Switch", WM8772_DACCTRL, 7, 1, 0), +SOC_SINGLE("Demp3 Playback Switch", WM8772_DACCTRL, 8, 1, 0), + +SOC_SINGLE("Phase Invert 1 Switch", WM8772_IFACE, 6, 1, 0), +SOC_SINGLE("Phase Invert 2 Switch", WM8772_IFACE, 7, 1, 0), +SOC_SINGLE("Phase Invert 3 Switch", WM8772_IFACE, 8, 1, 0), + +SOC_SINGLE("Playback ZC Switch", WM8772_DACCTRL, 0, 1, 0), + +SOC_SINGLE("Capture High Pass Switch", WM8772_ADCCTRL, 3, 1, 0), +}; + +/* add non dapm controls */ +static int wm8772_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8772_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8772_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +static int wm8772_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; + struct wm8772_priv *wm8772 = codec->private_data; + + switch (freq) { + case 4096000: + case 5644800: + case 6144000: + case 8192000: + case 8467000: + case 9216000: + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + case 22579200: + case 24576000: + case 33868800: + case 36864000: + if (clk_id == WM8772_DACCLK) { + wm8772->dacclk = freq; + return 0; + } else if (clk_id == WM8772_ADCCLK) { + wm8772->adcclk = freq; + return 0; + } + } + return -EINVAL; +} + +static int wm8772_set_dac_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 diface = wm8772_read_reg_cache(codec, WM8772_IFACE) & 0x1f0; + u16 diface_ctrl = wm8772_read_reg_cache(codec, WM8772_DACRATE) & 0x1ef; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + diface_ctrl |= 0x0010; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + diface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + diface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + diface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + diface |= 0x0007; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + diface |= 0x0008; + break; + default: + return -EINVAL; + } + + wm8772_write(codec, WM8772_DACRATE, diface_ctrl); + wm8772_write(codec, WM8772_IFACE, diface); + return 0; +} + +static int wm8772_set_adc_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 aiface = 0; + u16 aiface_ctrl = wm8772_read_reg_cache(codec, WM8772_ADCCTRL) & 0x1cf; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aiface |= 0x0010; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + aiface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aiface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + aiface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + aiface |= 0x0003; + aiface_ctrl |= 0x0010; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aiface_ctrl |= 0x0020; + break; + default: + return -EINVAL; + } + + wm8772_write(codec, WM8772_ADCCTRL, aiface_ctrl); + wm8772_write(codec, WM8772_ADCRATE, aiface); + return 0; +} + +static int wm8772_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8772_priv *wm8772 = codec->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + u16 diface = wm8772_read_reg_cache(codec, WM8772_IFACE) & 0x1cf; + u16 diface_ctrl = wm8772_read_reg_cache(codec, WM8772_DACRATE) & 0x3f; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + diface |= 0x0010; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + diface |= 0x0020; + break; + case SNDRV_PCM_FORMAT_S32_LE: + diface |= 0x0030; + break; + } + + /* set rate */ + switch (wm8772->dacclk / params_rate(params)) { + case 768: + diface_ctrl |= (0x5 << 6); + break; + case 512: + diface_ctrl |= (0x4 << 6); + break; + case 384: + diface_ctrl |= (0x3 << 6); + break; + case 256: + diface_ctrl |= (0x2 << 6); + break; + case 192: + diface_ctrl |= (0x1 << 6); + break; + } + + wm8772_write(codec, WM8772_DACRATE, diface_ctrl); + wm8772_write(codec, WM8772_IFACE, diface); + + } else { + + u16 aiface = wm8772_read_reg_cache(codec, WM8772_ADCRATE) & 0x113; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + aiface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + aiface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + aiface |= 0x000c; + break; + } + + /* set rate */ + switch (wm8772->adcclk / params_rate(params)) { + case 768: + aiface |= (0x5 << 5); + break; + case 512: + aiface |= (0x4 << 5); + break; + case 384: + aiface |= (0x3 << 5); + break; + case 256: + aiface |= (0x2 << 5); + break; + } + + wm8772_write(codec, WM8772_ADCRATE, aiface); + } + + return 0; +} + +static int wm8772_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 master = wm8772_read_reg_cache(codec, WM8772_DACRATE) & 0xffe0; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, clk and osc on, dac unmute, active */ + wm8772_write(codec, WM8772_DACRATE, master); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, dac mute, inactive */ + wm8772_write(codec, WM8772_DACRATE, master | 0x0f); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + wm8772_write(codec, WM8772_DACRATE, master | 0x1f); + break; + } + codec->dapm_state = event; + return 0; +} + +struct snd_soc_codec_dai wm8772_dai[] = { +{ + .name = "WM8772", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 6, + }, + .ops = { + .hw_params = wm8772_hw_params, + }, + .dai_ops = { + .set_fmt = wm8772_set_dac_dai_fmt, + .set_sysclk = wm8772_set_dai_sysclk, + }, +}, +{ + .name = "WM8772", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .hw_params = wm8772_hw_params, + }, + .dai_ops = { + .set_fmt = wm8772_set_adc_dai_fmt, + .set_sysclk = wm8772_set_dai_sysclk, + }, +}, +}; +EXPORT_SYMBOL_GPL(wm8772_dai); + +static int wm8772_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; + + wm8772_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8772_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8772_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8772_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8772_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8772 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8772_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8772"; + codec->owner = THIS_MODULE; + codec->read = wm8772_read_reg_cache; + codec->write = wm8772_write; + codec->dapm_event = wm8772_dapm_event; + codec->dai = wm8772_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8772_reg); + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8772_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, wm8772_reg, + sizeof(u16) * ARRAY_SIZE(wm8772_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8772_reg); + + wm8772_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if(ret < 0) { + printk(KERN_ERR "wm8772: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8772_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* set the update bits */ + reg = wm8772_read_reg_cache(codec, WM8772_MDACVOL); + wm8772_write(codec, WM8772_MDACVOL, reg | 0x0100); + reg = wm8772_read_reg_cache(codec, WM8772_LDAC1VOL); + wm8772_write(codec, WM8772_LDAC1VOL, reg | 0x0100); + reg = wm8772_read_reg_cache(codec, WM8772_LDAC2VOL); + wm8772_write(codec, WM8772_LDAC2VOL, reg | 0x0100); + reg = wm8772_read_reg_cache(codec, WM8772_LDAC3VOL); + wm8772_write(codec, WM8772_LDAC3VOL, reg | 0x0100); + reg = wm8772_read_reg_cache(codec, WM8772_RDAC1VOL); + wm8772_write(codec, WM8772_RDAC1VOL, reg | 0x0100); + reg = wm8772_read_reg_cache(codec, WM8772_RDAC2VOL); + wm8772_write(codec, WM8772_RDAC2VOL, reg | 0x0100); + reg = wm8772_read_reg_cache(codec, WM8772_RDAC3VOL); + wm8772_write(codec, WM8772_RDAC3VOL, reg | 0x0100); + + wm8772_add_controls(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8772: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8772_socdev; + +static int wm8772_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8772_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8772_priv *wm8772; + int ret = 0; + + printk(KERN_INFO "WM8772 Audio Codec %s", WM8772_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8772 = kzalloc(sizeof(struct wm8772_priv), GFP_KERNEL); + if (wm8772 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8772; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8772_socdev = socdev; + + /* Add other interfaces here */ +#warning do SPI device probe here and then call wm8772_init() + + return ret; +} + +/* power down chip */ +static int wm8772_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) + wm8772_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + kfree(codec->private_data); + kfree(codec->reg_cache); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8772 = { + .probe = wm8772_probe, + .remove = wm8772_remove, + .suspend = wm8772_suspend, + .resume = wm8772_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8772); + +MODULE_DESCRIPTION("ASoC WM8772 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8772.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8772.h @@ -0,0 +1,46 @@ +/* + * wm8772.h -- audio driver for WM8772 + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8772_H +#define _WM8772_H + +/* WM8772 register space */ + +#define WM8772_LDAC1VOL 0x00 +#define WM8772_RDAC1VOL 0x01 +#define WM8772_DACCH 0x02 +#define WM8772_IFACE 0x03 +#define WM8772_LDAC2VOL 0x04 +#define WM8772_RDAC2VOL 0x05 +#define WM8772_LDAC3VOL 0x06 +#define WM8772_RDAC3VOL 0x07 +#define WM8772_MDACVOL 0x08 +#define WM8772_DACCTRL 0x09 +#define WM8772_DACRATE 0x0a +#define WM8772_ADCRATE 0x0b +#define WM8772_ADCCTRL 0x0c +#define WM8772_RESET 0x1f + +#define WM8772_CACHE_REGNUM 10 + +#define WM8772_DACCLK 0 +#define WM8772_ADCCLK 1 + +#define WM8753_DAI_DAC 0 +#define WM8753_DAI_ADC 1 + +extern struct snd_soc_codec_dai wm8772_dai[2]; +extern struct snd_soc_codec_device soc_codec_dev_wm8772; + +#endif Index: linux-2.6.21-moko/sound/soc/codecs/wm8971.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8971.c @@ -0,0 +1,971 @@ +/* + * wm8971.c -- WM8971 ALSA SoC Audio driver + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly + * + * Based on wm8753.c by Liam Girdwood + * + * 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 "wm8971.h" + +#define AUDIO_NAME "wm8971" +#define WM8971_VERSION "0.9" + +#undef WM8971_DEBUG + +#ifdef WM8971_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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 WM8971_REG_COUNT 43 + +static struct workqueue_struct *wm8971_workq = NULL; + +/* codec private data */ +struct wm8971_priv { + unsigned int sysclk; +}; + +/* + * wm8971 register cache + * We can't read the WM8971 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8971_reg[] = { + 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */ + 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */ + 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */ + 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */ + 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */ + 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */ + 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */ + 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */ + 0x0079, 0x0079, 0x0079, /* 40 */ +}; + +static inline unsigned int wm8971_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg < WM8971_REG_COUNT) + return cache[reg]; + + return -1; +} + +static inline void wm8971_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg < WM8971_REG_COUNT) + cache[reg] = value; +} + +static int wm8971_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8971_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8971_reset(c) wm8971_write(c, WM8971_RESET, 0) + +/* WM8971 Controls */ +static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" }; +static const char *wm8971_bass_filter[] = { "130Hz @ 48kHz", + "200Hz @ 48kHz" }; +static const char *wm8971_treble[] = { "8kHz", "4kHz" }; +static const char *wm8971_alc_func[] = { "Off", "Right", "Left", "Stereo" }; +static const char *wm8971_ng_type[] = { "Constant PGA Gain", + "Mute ADC Output" }; +static const char *wm8971_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8971_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static const char *wm8971_dac_phase[] = { "Non Inverted", "Inverted" }; +static const char *wm8971_lline_mux[] = {"Line", "NC", "NC", "PGA", + "Differential"}; +static const char *wm8971_rline_mux[] = {"Line", "Mic", "NC", "PGA", + "Differential"}; +static const char *wm8971_lpga_sel[] = {"Line", "NC", "NC", "Differential"}; +static const char *wm8971_rpga_sel[] = {"Line", "Mic", "NC", "Differential"}; +static const char *wm8971_adcpol[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; + +static const struct soc_enum wm8971_enum[] = { + SOC_ENUM_SINGLE(WM8971_BASS, 7, 2, wm8971_bass), /* 0 */ + SOC_ENUM_SINGLE(WM8971_BASS, 6, 2, wm8971_bass_filter), + SOC_ENUM_SINGLE(WM8971_TREBLE, 6, 2, wm8971_treble), + SOC_ENUM_SINGLE(WM8971_ALC1, 7, 4, wm8971_alc_func), + SOC_ENUM_SINGLE(WM8971_NGATE, 1, 2, wm8971_ng_type), /* 4 */ + SOC_ENUM_SINGLE(WM8971_ADCDAC, 1, 4, wm8971_deemp), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 4, 4, wm8971_mono_mux), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 1, 2, wm8971_dac_phase), + SOC_ENUM_SINGLE(WM8971_LOUTM1, 0, 5, wm8971_lline_mux), /* 8 */ + SOC_ENUM_SINGLE(WM8971_ROUTM1, 0, 5, wm8971_rline_mux), + SOC_ENUM_SINGLE(WM8971_LADCIN, 6, 4, wm8971_lpga_sel), + SOC_ENUM_SINGLE(WM8971_RADCIN, 6, 4, wm8971_rpga_sel), + SOC_ENUM_SINGLE(WM8971_ADCDAC, 5, 4, wm8971_adcpol), /* 12 */ + SOC_ENUM_SINGLE(WM8971_ADCIN, 6, 4, wm8971_mono_mux), +}; + +static const struct snd_kcontrol_new wm8971_snd_controls[] = { + SOC_DOUBLE_R("Capture Volume", WM8971_LINVOL, WM8971_RINVOL, 0, 63, 0), + SOC_DOUBLE_R("Capture ZC Switch", WM8971_LINVOL, WM8971_RINVOL, 6, 1, 0), + SOC_DOUBLE_R("Capture Switch", WM8971_LINVOL, WM8971_RINVOL, 7, 1, 1), + + SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8971_LOUT1V, + WM8971_ROUT1V, 7, 1, 0), + SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8971_LOUT2V, + WM8971_ROUT2V, 7, 1, 0), + SOC_SINGLE("Mono Playback ZC Switch", WM8971_MOUTV, 7, 1, 0), + + SOC_DOUBLE_R("PCM Volume", WM8971_LDAC, WM8971_RDAC, 0, 255, 0), + + SOC_DOUBLE_R("Bypass Left Playback Volume", WM8971_LOUTM1, + WM8971_LOUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Right Playback Volume", WM8971_ROUTM1, + WM8971_ROUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8971_MOUTM1, + WM8971_MOUTM2, 4, 7, 1), + + SOC_DOUBLE_R("Headphone Playback Volume", WM8971_LOUT1V, + WM8971_ROUT1V, 0, 127, 0), + SOC_DOUBLE_R("Speaker Playback Volume", WM8971_LOUT2V, + WM8971_ROUT2V, 0, 127, 0), + + SOC_ENUM("Bass Boost", wm8971_enum[0]), + SOC_ENUM("Bass Filter", wm8971_enum[1]), + SOC_SINGLE("Bass Volume", WM8971_BASS, 0, 7, 1), + + SOC_SINGLE("Treble Volume", WM8971_TREBLE, 0, 7, 0), + SOC_ENUM("Treble Cut-off", wm8971_enum[2]), + + SOC_SINGLE("Capture Filter Switch", WM8971_ADCDAC, 0, 1, 1), + + SOC_SINGLE("ALC Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Max Volume", WM8971_ALC1, 4, 7, 0), + + SOC_SINGLE("ALC Capture Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Capture Max Volume", WM8971_ALC1, 4, 7, 0), + SOC_ENUM("ALC Capture Function", wm8971_enum[3]), + SOC_SINGLE("ALC Capture ZC Switch", WM8971_ALC2, 7, 1, 0), + SOC_SINGLE("ALC Capture Hold Time", WM8971_ALC2, 0, 15, 0), + SOC_SINGLE("ALC Capture Decay Time", WM8971_ALC3, 4, 15, 0), + SOC_SINGLE("ALC Capture Attack Time", WM8971_ALC3, 0, 15, 0), + SOC_SINGLE("ALC Capture NG Threshold", WM8971_NGATE, 3, 31, 0), + SOC_ENUM("ALC Capture NG Type", wm8971_enum[4]), + SOC_SINGLE("ALC Capture NG Switch", WM8971_NGATE, 0, 1, 0), + + SOC_SINGLE("Capture 6dB Attenuate", WM8971_ADCDAC, 8, 1, 0), + SOC_SINGLE("Playback 6dB Attenuate", WM8971_ADCDAC, 7, 1, 0), + + SOC_ENUM("Playback De-emphasis", wm8971_enum[5]), + SOC_ENUM("Playback Function", wm8971_enum[6]), + SOC_ENUM("Playback Phase", wm8971_enum[7]), + + SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0), +}; + +/* add non-DAPM controls */ +static int wm8971_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8971_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8971_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * DAPM Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8971_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8971_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_LOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8971_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_ROUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8971_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_ROUTM2, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm8971_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_MOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_MOUTM2, 7, 1, 0), +}; + +/* Left Line Mux */ +static const struct snd_kcontrol_new wm8971_left_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[8]); + +/* Right Line Mux */ +static const struct snd_kcontrol_new wm8971_right_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[9]); + +/* Left PGA Mux */ +static const struct snd_kcontrol_new wm8971_left_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[10]); + +/* Right PGA Mux */ +static const struct snd_kcontrol_new wm8971_right_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[11]); + +/* Mono ADC Mux */ +static const struct snd_kcontrol_new wm8971_monomux_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[13]); + +static const struct snd_soc_dapm_widget wm8971_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_left_mixer_controls[0], + ARRAY_SIZE(wm8971_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_right_mixer_controls[0], + ARRAY_SIZE(wm8971_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8971_PWR2, 2, 0, + &wm8971_mono_mixer_controls[0], + ARRAY_SIZE(wm8971_mono_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8971_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8971_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8971_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8971_PWR2, 6, 0, NULL, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8971_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8971_PWR2, 8, 0), + SND_SOC_DAPM_PGA("Mono Out 1", WM8971_PWR2, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8971_PWR1, 1, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8971_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8971_PWR1, 3, 0), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8971_PWR1, 5, 0, + &wm8971_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8971_PWR1, 4, 0, + &wm8971_right_pga_controls), + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_right_line_controls), + + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("MONO"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("MIC"), +}; + +static const char *audio_map[][3] = { + /* left mixer */ + {"Left Mixer", "Playback Switch", "Left DAC"}, + {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Left Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* right mixer */ + {"Right Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Right Mixer", "Playback Switch", "Right DAC"}, + {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* left out 1 */ + {"Left Out 1", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + + /* left out 2 */ + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out 1 */ + {"Right Out 1", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + + /* right out 2 */ + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono mixer */ + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* mono out */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out"}, + + /* Left Line Mux */ + {"Left Line Mux", "Line", "LINPUT1"}, + {"Left Line Mux", "PGA", "Left PGA Mux"}, + {"Left Line Mux", "Differential", "Differential Mux"}, + + /* Right Line Mux */ + {"Right Line Mux", "Line", "RINPUT1"}, + {"Right Line Mux", "Mic", "MIC"}, + {"Right Line Mux", "PGA", "Right PGA Mux"}, + {"Right Line Mux", "Differential", "Differential Mux"}, + + /* Left PGA Mux */ + {"Left PGA Mux", "Line", "LINPUT1"}, + {"Left PGA Mux", "Differential", "Differential Mux"}, + + /* Right PGA Mux */ + {"Right PGA Mux", "Line", "RINPUT1"}, + {"Right PGA Mux", "Differential", "Differential Mux"}, + + /* Differential Mux */ + {"Differential Mux", "Line", "LINPUT1"}, + {"Differential Mux", "Line", "RINPUT1"}, + + /* Left ADC Mux */ + {"Left ADC Mux", "Stereo", "Left PGA Mux"}, + {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, + {"Left ADC Mux", "Digital Mono", "Left PGA Mux"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Stereo", "Right PGA Mux"}, + {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, + {"Right ADC Mux", "Digital Mono", "Right PGA Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8971_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm8971_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm8971_dapm_widgets[i]); + } + + /* set up audio path audio_mapnects */ + 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; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int wm8971_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; + struct wm8971_priv *wm8971 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8971->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8971_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + wm8971_write(codec, WM8971_IFACE, iface); + return 0; +} + +static int wm8971_pcm_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8971_priv *wm8971 = codec->private_data; + u16 iface = wm8971_read_reg_cache(codec, WM8971_IFACE) & 0x1f3; + u16 srate = wm8971_read_reg_cache(codec, WM8971_SRATE) & 0x1c0; + int coeff = get_coeff(wm8971->sysclk, params_rate(params)); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + wm8971_write(codec, WM8971_IFACE, iface); + if (coeff >= 0) + wm8971_write(codec, WM8971_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8971_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8971_read_reg_cache(codec, WM8971_ADCDAC) & 0xfff7; + + if (mute) + wm8971_write(codec, WM8971_ADCDAC, mute_reg | 0x8); + else + wm8971_write(codec, WM8971_ADCDAC, mute_reg); + return 0; +} + +static int wm8971_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 pwr_reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* set vmid to 50k and unmute dac */ + wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x00c1); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + /* set vmid to 5k for quick power up */ + wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x01c0); + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* mute dac and set vmid to 500k, enable VREF */ + wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x0140); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + wm8971_write(codec, WM8971_PWR1, 0x0001); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8971_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_codec_dai wm8971_dai = { + .name = "WM8971", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .ops = { + .hw_params = wm8971_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8971_mute, + .set_fmt = wm8971_set_dai_fmt, + .set_sysclk = wm8971_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(wm8971_dai); + +static void wm8971_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, delayed_work.work); + wm8971_dapm_event(codec, codec->dapm_state); +} + +static int wm8971_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; + + wm8971_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8971_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8971_reg); i++) { + if (i + 1 == WM8971_RESET) + continue; + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8971_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* charge wm8971 caps */ + if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) { + wm8971_dapm_event(codec, SNDRV_CTL_POWER_D2); + codec->dapm_state = SNDRV_CTL_POWER_D0; + queue_delayed_work(wm8971_workq, &codec->delayed_work, + msecs_to_jiffies(1000)); + } + + return 0; +} + +static int wm8971_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8971"; + codec->owner = THIS_MODULE; + codec->read = wm8971_read_reg_cache; + codec->write = wm8971_write; + codec->dapm_event = wm8971_dapm_event; + codec->dai = &wm8971_dai; + codec->reg_cache_size = ARRAY_SIZE(wm8971_reg); + codec->num_dai = 1; + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8971_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, wm8971_reg, + sizeof(u16) * ARRAY_SIZE(wm8971_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8971_reg); + + wm8971_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8971: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps */ + wm8971_dapm_event(codec, SNDRV_CTL_POWER_D2); + codec->dapm_state = SNDRV_CTL_POWER_D3hot; + queue_delayed_work(wm8971_workq, &codec->delayed_work, + msecs_to_jiffies(1000)); + + /* set the update bits */ + reg = wm8971_read_reg_cache(codec, WM8971_LDAC); + wm8971_write(codec, WM8971_LDAC, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_RDAC); + wm8971_write(codec, WM8971_RDAC, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LOUT1V); + wm8971_write(codec, WM8971_LOUT1V, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_ROUT1V); + wm8971_write(codec, WM8971_ROUT1V, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LOUT2V); + wm8971_write(codec, WM8971_LOUT2V, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_ROUT2V); + wm8971_write(codec, WM8971_ROUT2V, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LINVOL); + wm8971_write(codec, WM8971_LINVOL, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_RINVOL); + wm8971_write(codec, WM8971_RINVOL, reg | 0x0100); + + wm8971_add_controls(codec); + wm8971_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8971: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8971_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * WM8731 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +#define I2C_DRIVERID_WM8971 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8971_i2c_driver; +static struct i2c_client client_template; + +static int wm8971_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8971_socdev; + struct wm8971_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + + i2c_set_clientdata(i2c, codec); + + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8971_init(socdev); + if (ret < 0) { + err("failed to initialise WM8971\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8971_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec* codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8971_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8971_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8971_i2c_driver = { + .driver = { + .name = "WM8971 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8971, + .attach_adapter = wm8971_i2c_attach, + .detach_client = wm8971_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8971", + .driver = &wm8971_i2c_driver, +}; +#endif + +static int wm8971_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8971_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8971_priv *wm8971; + int ret = 0; + + info("WM8971 Audio Codec %s", WM8971_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8971 = kzalloc(sizeof(struct wm8971_priv), GFP_KERNEL); + if (wm8971 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8971; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8971_socdev = socdev; + + INIT_DELAYED_WORK(&codec->delayed_work, wm8971_work); + wm8971_workq = create_workqueue("wm8971"); + if (wm8971_workq == NULL) { + kfree(codec); + return -ENOMEM; + } +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8971_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + + return ret; +} + +/* power down chip */ +static int wm8971_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) + wm8971_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + if (wm8971_workq) + destroy_workqueue(wm8971_workq); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8971_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8971 = { + .probe = wm8971_probe, + .remove = wm8971_remove, + .suspend = wm8971_suspend, + .resume = wm8971_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8971); + +MODULE_DESCRIPTION("ASoC WM8971 driver"); +MODULE_AUTHOR("Lab126"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8971.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8971.h @@ -0,0 +1,63 @@ +/* + * wm8971.h -- audio driver for WM8971 + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly + * + * 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. + * + */ + +#ifndef _WM8971_H +#define _WM8971_H + +#define WM8971_LINVOL 0x00 +#define WM8971_RINVOL 0x01 +#define WM8971_LOUT1V 0x02 +#define WM8971_ROUT1V 0x03 +#define WM8971_ADCDAC 0x05 +#define WM8971_IFACE 0x07 +#define WM8971_SRATE 0x08 +#define WM8971_LDAC 0x0a +#define WM8971_RDAC 0x0b +#define WM8971_BASS 0x0c +#define WM8971_TREBLE 0x0d +#define WM8971_RESET 0x0f +#define WM8971_ALC1 0x11 +#define WM8971_ALC2 0x12 +#define WM8971_ALC3 0x13 +#define WM8971_NGATE 0x14 +#define WM8971_LADC 0x15 +#define WM8971_RADC 0x16 +#define WM8971_ADCTL1 0x17 +#define WM8971_ADCTL2 0x18 +#define WM8971_PWR1 0x19 +#define WM8971_PWR2 0x1a +#define WM8971_ADCTL3 0x1b +#define WM8971_ADCIN 0x1f +#define WM8971_LADCIN 0x20 +#define WM8971_RADCIN 0x21 +#define WM8971_LOUTM1 0x22 +#define WM8971_LOUTM2 0x23 +#define WM8971_ROUTM1 0x24 +#define WM8971_ROUTM2 0x25 +#define WM8971_MOUTM1 0x26 +#define WM8971_MOUTM2 0x27 +#define WM8971_LOUT2V 0x28 +#define WM8971_ROUT2V 0x29 +#define WM8971_MOUTV 0x2A + +#define WM8971_SYSCLK 0 + +struct wm8971_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8971_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8971; + +#endif Index: linux-2.6.21-moko/sound/soc/codecs/wm8974.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8974.c @@ -0,0 +1,873 @@ +/* + * wm8974.c -- WM8974 ALSA Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * + * 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 "wm8974.h" + +#define AUDIO_NAME "wm8974" +#define WM8974_VERSION "0.6" + +/* + * Debug + */ + +#define WM8974_DEBUG 0 + +#ifdef WM8974_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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) + +struct snd_soc_codec_device soc_codec_dev_wm8974; + +/* + * wm8974 register cache + * We can't read the WM8974 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8974_reg[WM8974_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0050, 0x0000, 0x0140, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00ff, + 0x0000, 0x0000, 0x0100, 0x00ff, + 0x0000, 0x0000, 0x012c, 0x002c, + 0x002c, 0x002c, 0x002c, 0x0000, + 0x0032, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0038, 0x000b, 0x0032, 0x0000, + 0x0008, 0x000c, 0x0093, 0x00e9, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0003, 0x0010, 0x0000, 0x0000, + 0x0000, 0x0002, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0039, 0x0000, + 0x0000, +}; + +/* + * read wm8974 register cache + */ +static inline unsigned int wm8974_read_reg_cache(struct snd_soc_codec * codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8974_RESET) + return 0; + if (reg >= WM8974_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8974 register cache + */ +static inline void wm8974_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8974_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8974 register space + */ +static int wm8974_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8974 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8974_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8974_reset(c) wm8974_write(c, WM8974_RESET, 0) + +static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" }; +static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8974_eqmode[] = {"Capture", "Playback" }; +static const char *wm8974_bw[] = {"Narrow", "Wide" }; +static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" }; +static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" }; +static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" }; +static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" }; +static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" }; +static const char *wm8974_alc[] = {"ALC", "Limiter" }; + +static const struct soc_enum wm8974_enum[] = { + SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */ + SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */ + SOC_ENUM_SINGLE(WM8974_DAC, 4, 4, wm8974_deemp), + SOC_ENUM_SINGLE(WM8974_EQ1, 8, 2, wm8974_eqmode), + + SOC_ENUM_SINGLE(WM8974_EQ1, 5, 4, wm8974_eq1), + SOC_ENUM_SINGLE(WM8974_EQ2, 8, 2, wm8974_bw), + SOC_ENUM_SINGLE(WM8974_EQ2, 5, 4, wm8974_eq2), + SOC_ENUM_SINGLE(WM8974_EQ3, 8, 2, wm8974_bw), + + SOC_ENUM_SINGLE(WM8974_EQ3, 5, 4, wm8974_eq3), + SOC_ENUM_SINGLE(WM8974_EQ4, 8, 2, wm8974_bw), + SOC_ENUM_SINGLE(WM8974_EQ4, 5, 4, wm8974_eq4), + SOC_ENUM_SINGLE(WM8974_EQ5, 8, 2, wm8974_bw), + + SOC_ENUM_SINGLE(WM8974_EQ5, 5, 4, wm8974_eq5), + SOC_ENUM_SINGLE(WM8974_ALC3, 8, 2, wm8974_alc), +}; + +static const struct snd_kcontrol_new wm8974_snd_controls[] = { + +SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0), + +SOC_ENUM("DAC Companding", wm8974_enum[1]), +SOC_ENUM("ADC Companding", wm8974_enum[0]), + +SOC_ENUM("Playback De-emphasis", wm8974_enum[2]), +SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0), + +SOC_SINGLE("PCM Volume", WM8974_DACVOL, 0, 127, 0), + +SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0), +SOC_SINGLE("ADC Inversion Switch", WM8974_COMP, 0, 1, 0), + +SOC_SINGLE("Capture Volume", WM8974_ADCVOL, 0, 127, 0), + +SOC_ENUM("Equaliser Function", wm8974_enum[3]), +SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]), +SOC_SINGLE("EQ1 Volume", WM8974_EQ1, 0, 31, 1), + +SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]), +SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]), +SOC_SINGLE("EQ2 Volume", WM8974_EQ2, 0, 31, 1), + +SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]), +SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]), +SOC_SINGLE("EQ3 Volume", WM8974_EQ3, 0, 31, 1), + +SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]), +SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]), +SOC_SINGLE("EQ4 Volume", WM8974_EQ4, 0, 31, 1), + +SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]), +SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]), +SOC_SINGLE("EQ5 Volume", WM8974_EQ5, 0, 31, 1), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8974_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8974_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8974_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8974_enum[13]), +SOC_SINGLE("ALC Capture Decay", WM8974_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8974_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE, 0, 7, 0), + +SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA, 7, 1, 0), +SOC_SINGLE("Capture PGA Volume", WM8974_INPPGA, 0, 63, 0), + +SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL, 7, 1, 0), +SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL, 6, 1, 1), +SOC_SINGLE("Speaker Playback Volume", WM8974_SPKVOL, 0, 63, 0), + +SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST, 8, 1, 0), +SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 0), +}; + +/* add non dapm controls */ +static int wm8974_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8974_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8974_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Speaker Output Mixer */ +static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 1), +}; + +/* Mono Output Mixer */ +static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 1), +}; + +/* AUX Input boost vol */ +static const struct snd_kcontrol_new wm8974_aux_boost_controls = +SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0); + +/* Mic Input boost vol */ +static const struct snd_kcontrol_new wm8974_mic_boost_controls = +SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0); + +/* Capture boost switch */ +static const struct snd_kcontrol_new wm8974_capture_boost_controls = +SOC_DAPM_SINGLE("Capture Boost Switch", WM8974_INPPGA, 6, 1, 0); + +/* Aux In to PGA */ +static const struct snd_kcontrol_new wm8974_aux_capture_boost_controls = +SOC_DAPM_SINGLE("Aux Capture Boost Switch", WM8974_INPPGA, 2, 1, 0); + +/* Mic P In to PGA */ +static const struct snd_kcontrol_new wm8974_micp_capture_boost_controls = +SOC_DAPM_SINGLE("Mic P Capture Boost Switch", WM8974_INPPGA, 0, 1, 0); + +/* Mic N In to PGA */ +static const struct snd_kcontrol_new wm8974_micn_capture_boost_controls = +SOC_DAPM_SINGLE("Mic N Capture Boost Switch", WM8974_INPPGA, 1, 1, 0); + +static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0, + &wm8974_speaker_mixer_controls[0], + ARRAY_SIZE(wm8974_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0, + &wm8974_mono_mixer_controls[0], + ARRAY_SIZE(wm8974_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER3, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mic PGA", WM8974_POWER2, 2, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Aux Boost", SND_SOC_NOPM, 0, 0, + &wm8974_aux_boost_controls, 1), +SND_SOC_DAPM_PGA("Mic Boost", SND_SOC_NOPM, 0, 0, + &wm8974_mic_boost_controls, 1), +SND_SOC_DAPM_SWITCH("Capture Boost", SND_SOC_NOPM, 0, 0, + &wm8974_capture_boost_controls), + +SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0, NULL, 0), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8974_POWER1, 4, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +}; + +static const char *audio_map[][3] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Boost Mixer */ + {"Boost Mixer", NULL, "ADC"}, + {"Capture Boost Switch", "Aux Capture Boost Switch", "AUX"}, + {"Aux Boost", "Aux Volume", "Boost Mixer"}, + {"Capture Boost", "Capture Switch", "Boost Mixer"}, + {"Mic Boost", "Mic Volume", "Boost Mixer"}, + + /* Inputs */ + {"MICP", NULL, "Mic Boost"}, + {"MICN", NULL, "Mic PGA"}, + {"Mic PGA", NULL, "Capture Boost"}, + {"AUX", NULL, "Aux Input"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8974_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm8974_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm8974_dapm_widgets[i]); + } + + /* set up audio path audio_mapnects */ + 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; +} + +struct pll_ { + unsigned int in_hz, out_hz; + unsigned int pre:4; /* prescale - 1 */ + unsigned int n:4; + unsigned int k; +}; + +struct pll_ pll[] = { + {12000000, 11289600, 0, 7, 0x86c220}, + {12000000, 12288000, 0, 8, 0x3126e8}, + {13000000, 11289600, 0, 6, 0xf28bd4}, + {13000000, 12288000, 0, 7, 0x8fd525}, + {12288000, 11289600, 0, 7, 0x59999a}, + {11289600, 12288000, 0, 8, 0x80dee9}, + /* liam - add more entries */ +}; + +static int wm8974_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int i; + u16 reg; + + if(freq_in == 0 || freq_out == 0) { + reg = wm8974_read_reg_cache(codec, WM8974_POWER1); + wm8974_write(codec, WM8974_POWER1, reg & 0x1df); + return 0; + } + + for(i = 0; i < ARRAY_SIZE(pll); i++) { + if (freq_in == pll[i].in_hz && freq_out == pll[i].out_hz) { + wm8974_write(codec, WM8974_PLLN, (pll[i].pre << 4) | pll[i].n); + wm8974_write(codec, WM8974_PLLK1, pll[i].k >> 18); + wm8974_write(codec, WM8974_PLLK1, (pll[i].k >> 9) && 0x1ff); + wm8974_write(codec, WM8974_PLLK1, pll[i].k && 0x1ff); + reg = wm8974_read_reg_cache(codec, WM8974_POWER1); + wm8974_write(codec, WM8974_POWER1, reg | 0x020); + return 0; + } + } + return -EINVAL; +} + +/* + * Configure WM8974 clock dividers. + */ +static int wm8974_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8974_OPCLKDIV: + reg = wm8974_read_reg_cache(codec, WM8974_GPIO & 0x1cf); + wm8974_write(codec, WM8974_GPIO, reg | div); + break; + case WM8974_MCLKDIV: + reg = wm8974_read_reg_cache(codec, WM8974_CLOCK & 0x1f); + wm8974_write(codec, WM8974_CLOCK, reg | div); + break; + case WM8974_ADCCLK: + reg = wm8974_read_reg_cache(codec, WM8974_ADC & 0x1f7); + wm8974_write(codec, WM8974_ADC, reg | div); + break; + case WM8974_DACCLK: + reg = wm8974_read_reg_cache(codec, WM8974_DAC & 0x1f7); + wm8974_write(codec, WM8974_DAC, reg | div); + break; + case WM8974_BCLKDIV: + reg = wm8974_read_reg_cache(codec, WM8974_CLOCK & 0x1e3); + wm8974_write(codec, WM8974_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8974_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + u16 clk = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x1fe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + wm8974_write(codec, WM8974_IFACE, iface); + wm8974_write(codec, WM8974_CLOCK, clk); + return 0; +} + +static int wm8974_pcm_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface = wm8974_read_reg_cache(codec, WM8974_ADD) & 0x19f; + u16 adn = wm8974_read_reg_cache(codec, WM8974_ADD) & 0x1f1; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0020; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0040; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x0060; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + adn |= 0x5 << 1; + break; + case SNDRV_PCM_RATE_11025: + adn |= 0x4 << 1; + break; + case SNDRV_PCM_RATE_16000: + adn |= 0x3 << 1; + break; + case SNDRV_PCM_RATE_22050: + adn |= 0x2 << 1; + break; + case SNDRV_PCM_RATE_32000: + adn |= 0x1 << 1; + break; + case SNDRV_PCM_RATE_44100: + break; + } + + wm8974_write(codec, WM8974_IFACE, iface); + wm8974_write(codec, WM8974_ADD, adn); + return 0; +} + +static int wm8974_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8974_read_reg_cache(codec, WM8974_DAC) & 0xffbf; + + if(mute) + wm8974_write(codec, WM8974_DAC, mute_reg | 0x40); + else + wm8974_write(codec, WM8974_DAC, mute_reg); + return 0; +} + +/* liam need to make this lower power with dapm */ +static int wm8974_dapm_event(struct snd_soc_codec *codec, int event) +{ + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, clk and osc on, dac unmute, active */ + wm8974_write(codec, WM8974_POWER1, 0x1ff); + wm8974_write(codec, WM8974_POWER2, 0x1ff); + wm8974_write(codec, WM8974_POWER3, 0x1ff); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, dac mute, inactive */ + + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + wm8974_write(codec, WM8974_POWER1, 0x0); + wm8974_write(codec, WM8974_POWER2, 0x0); + wm8974_write(codec, WM8974_POWER3, 0x0); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8974_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_codec_dai wm8974_dai = { + .name = "WM8974 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM8974_RATES, + .formats = WM8974_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = WM8974_RATES, + .formats = WM8974_FORMATS,}, + .ops = { + .hw_params = wm8974_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8974_mute, + .set_fmt = wm8974_set_dai_fmt, + .set_clkdiv = wm8974_set_dai_clkdiv, + .set_pll = wm8974_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8974_dai); + +static int wm8974_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; + + wm8974_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8974_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8974_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8974_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8974_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8974 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8974_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8974"; + codec->owner = THIS_MODULE; + codec->read = wm8974_read_reg_cache; + codec->write = wm8974_write; + codec->dapm_event = wm8974_dapm_event; + codec->dai = &wm8974_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8974_reg); + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8974_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, wm8974_reg, + sizeof(u16) * ARRAY_SIZE(wm8974_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8974_reg); + + wm8974_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if(ret < 0) { + printk(KERN_ERR "wm8974: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8974_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8974_add_controls(codec); + wm8974_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8974: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8974_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * WM8974 2 wire address is 0x1a + */ +#define I2C_DRIVERID_WM8974 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8974_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8974_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8974_socdev; + struct wm8974_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if(ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8974_init(socdev); + if(ret < 0) { + err("failed to initialise WM8974\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8974_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8974_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8974_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8974_i2c_driver = { + .driver = { + .name = "WM8974 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8974, + .attach_adapter = wm8974_i2c_attach, + .detach_client = wm8974_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8974", + .driver = &wm8974_i2c_driver, +}; +#endif + +static int wm8974_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8974_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + info("WM8974 Audio Codec %s", WM8974_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); + + wm8974_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8974_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8974_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) + wm8974_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8974_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8974 = { + .probe = wm8974_probe, + .remove = wm8974_remove, + .suspend = wm8974_suspend, + .resume = wm8974_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8974); + +MODULE_DESCRIPTION("ASoC WM8974 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8974.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8974.h @@ -0,0 +1,104 @@ +/* + * wm8974.h -- WM8974 Soc Audio driver + * + * 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 _WM8974_H +#define _WM8974_H + +/* WM8974 register space */ + +#define WM8974_RESET 0x0 +#define WM8974_POWER1 0x1 +#define WM8974_POWER2 0x2 +#define WM8974_POWER3 0x3 +#define WM8974_IFACE 0x4 +#define WM8974_COMP 0x5 +#define WM8974_CLOCK 0x6 +#define WM8974_ADD 0x7 +#define WM8974_GPIO 0x8 +#define WM8974_DAC 0xa +#define WM8974_DACVOL 0xb +#define WM8974_ADC 0xe +#define WM8974_ADCVOL 0xf +#define WM8974_EQ1 0x12 +#define WM8974_EQ2 0x13 +#define WM8974_EQ3 0x14 +#define WM8974_EQ4 0x15 +#define WM8974_EQ5 0x16 +#define WM8974_DACLIM1 0x18 +#define WM8974_DACLIM2 0x19 +#define WM8974_NOTCH1 0x1b +#define WM8974_NOTCH2 0x1c +#define WM8974_NOTCH3 0x1d +#define WM8974_NOTCH4 0x1e +#define WM8974_ALC1 0x20 +#define WM8974_ALC2 0x21 +#define WM8974_ALC3 0x22 +#define WM8974_NGATE 0x23 +#define WM8974_PLLN 0x24 +#define WM8974_PLLK1 0x25 +#define WM8974_PLLK2 0x26 +#define WM8974_PLLK3 0x27 +#define WM8974_ATTEN 0x28 +#define WM8974_INPUT 0x2c +#define WM8974_INPPGA 0x2d +#define WM8974_ADCBOOST 0x2f +#define WM8974_OUTPUT 0x31 +#define WM8974_SPKMIX 0x32 +#define WM8974_SPKVOL 0x36 +#define WM8974_MONOMIX 0x38 + +#define WM8974_CACHEREGNUM 57 + +/* Clock divider Id's */ +#define WM8974_OPCLKDIV 0 +#define WM8974_MCLKDIV 1 +#define WM8974_ADCCLK 2 +#define WM8974_DACCLK 3 +#define WM8974_BCLKDIV 4 + +/* DAC clock dividers */ +#define WM8974_DACCLK_F2 (1 << 3) +#define WM8974_DACCLK_F4 (0 << 3) + +/* ADC clock dividers */ +#define WM8974_ADCCLK_F2 (1 << 3) +#define WM8974_ADCCLK_F4 (0 << 3) + +/* PLL Out dividers */ +#define WM8974_OPCLKDIV_1 (0 << 4) +#define WM8974_OPCLKDIV_2 (1 << 4) +#define WM8974_OPCLKDIV_3 (2 << 4) +#define WM8974_OPCLKDIV_4 (3 << 4) + +/* BCLK clock dividers */ +#define WM8974_BCLKDIV_1 (0 << 2) +#define WM8974_BCLKDIV_2 (1 << 2) +#define WM8974_BCLKDIV_4 (2 << 2) +#define WM8974_BCLKDIV_8 (3 << 2) +#define WM8974_BCLKDIV_16 (4 << 2) +#define WM8974_BCLKDIV_32 (5 << 2) + +/* MCLK clock dividers */ +#define WM8974_MCLKDIV_1 (0 << 5) +#define WM8974_MCLKDIV_1_5 (1 << 5) +#define WM8974_MCLKDIV_2 (2 << 5) +#define WM8974_MCLKDIV_3 (3 << 5) +#define WM8974_MCLKDIV_4 (4 << 5) +#define WM8974_MCLKDIV_6 (5 << 5) +#define WM8974_MCLKDIV_8 (6 << 5) +#define WM8974_MCLKDIV_12 (7 << 5) + + +struct wm8974_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8974_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8974; + +#endif Index: linux-2.6.21-moko/sound/soc/codecs/wm9713.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm9713.c @@ -0,0 +1,1220 @@ +/* + * wm9713.c -- ALSA Soc WM9713 codec support + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 4th Feb 2006 Initial version. + * + * Features:- + * + * o Support for AC97 Codec, Voice DAC and Aux DAC + * o Support for DAPM + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm9713.h" + +#define WM9713_VERSION "0.12" + +struct wm9713_priv { + u32 pll_in; /* PLL input frequency */ + u32 pll_out; /* PLL output frequency */ +}; + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg); +static int ac97_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val); + +/* + * WM9713 register cache + * Reg 0x3c bit 15 is used by touch driver. + */ +static const u16 wm9713_reg[] = { + 0x6174, 0x8080, 0x8080, 0x8080, // 6 + 0xc880, 0xe808, 0xe808, 0x0808, // e + 0x00da, 0x8000, 0xd600, 0xaaa0, // 16 + 0xaaa0, 0xaaa0, 0x0000, 0x0000, // 1e + 0x0f0f, 0x0040, 0x0000, 0x7f00, // 26 + 0x0405, 0x0410, 0xbb80, 0xbb80, // 2e + 0x0000, 0xbb80, 0x0000, 0x4523, // 36 + 0x0000, 0x2000, 0x7eff, 0xffff, // 3e + 0x0000, 0x0000, 0x0080, 0x0000, // 46 + 0x0000, 0x0000, 0xfffe, 0xffff, // 4e + 0x0000, 0x0000, 0x0000, 0xfffe, // 56 + 0x4000, 0x0000, 0x0000, 0x0000, // 5e + 0xb032, 0x3e00, 0x0000, 0x0000, // 66 + 0x0000, 0x0000, 0x0000, 0x0000, // 6e + 0x0000, 0x0000, 0x0000, 0x0006, // 76 + 0x0001, 0x0000, 0x574d, 0x4c13, // 7e + 0x0000, 0x0000, 0x0000 // virtual hp & mic mixers +}; + +/* virtual HP mixers regs */ +#define HPL_MIXER 0x80 +#define HPR_MIXER 0x82 +#define MICB_MUX 0x82 + +static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"}; +static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"}; +static const char *wm9713_rec_src[] = + {"Mic 1", "Mic 2", "Line", "Mono In", "Headphone", "Speaker", + "Mono Out", "Zh"}; +static const char *wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"}; +static const char *wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"}; +static const char *wm9713_mono_pga[] = {"Vmid", "Zh", "Mono", "Inv", + "Mono Vmid", "Inv Vmid"}; +static const char *wm9713_spk_pga[] = + {"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid", + "Speaker Vmid", "Inv Vmid"}; +static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone", + "Headphone Vmid"}; +static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"}; +static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"}; +static const char *wm9713_dac_inv[] = + {"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone", + "Headphone Mono", "NC", "Vmid"}; +static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"}; +static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"}; +static const char *wm9713_micb_select[] = {"MPB", "MPA"}; + +static const struct soc_enum wm9713_enum[] = { +SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), /* record mux mono 2 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src), /* record mux left 3 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src), /* record mux right 4*/ +SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */ +SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */ +SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */ +SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */ +SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */ +SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */ +}; + +static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = { +SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1), +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE,15, 7, 1, 1), +SOC_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1), +SOC_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1), +SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1), +SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1), + +SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0), +SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1), + +SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1), +SOC_ENUM("Capture Volume Steps", wm9713_enum[5]), +SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0), +SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0), + +SOC_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1), +SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0), +SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0), + +SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), +SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), +SOC_SINGLE("ALC Decay Time ", AC97_CODEC_CLASS_REV, 4, 15, 0), +SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0), +SOC_ENUM("ALC Function", wm9713_enum[6]), +SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0), +SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0), +SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0), +SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0), +SOC_ENUM("ALC NG Type", wm9713_enum[17]), +SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0), + +SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0), +SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0), + +SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1), +SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0), +SOC_SINGLE("Out4 Playback Volume", AC97_MASTER_MONO, 8, 63, 1), + +SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1), +SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0), +SOC_SINGLE("Out3 Playback Volume", AC97_MASTER_MONO, 0, 63, 1), + +SOC_SINGLE("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1), +SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0), +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1), + +SOC_SINGLE("PC Beep Playback Headphone Volume", AC97_AUX, 12, 7, 1), +SOC_SINGLE("PC Beep Playback Speaker Volume", AC97_AUX, 8, 7, 1), +SOC_SINGLE("PC Beep Playback Mono Volume", AC97_AUX, 4, 7, 1), + +SOC_SINGLE("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1), +SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1), +SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1), + +SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1), +SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1), +SOC_SINGLE("Aux Playback Mono Volume", AC97_REC_SEL, 4, 7, 1), + +SOC_ENUM("Bass Control", wm9713_enum[16]), +SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1), +SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1), +SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0), +SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1), +SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1), + +SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0), +SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0), +SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1), +}; + +/* add non dapm controls */ +static int wm9713_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm9713_snd_ac97_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm9713_snd_ac97_controls[i],codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* We have to create a fake left and right HP mixers because + * the codec only has a single control that is shared by both channels. + * This makes it impossible to determine the audio path using the current + * register map, thus we add a new (virtual) register to help determine the + * audio route within the device. + */ +static int mixer_event (struct snd_soc_dapm_widget *w, int event) +{ + u16 l, r, beep, tone, phone, rec, pcm, aux; + + l = ac97_read(w->codec, HPL_MIXER); + r = ac97_read(w->codec, HPR_MIXER); + beep = ac97_read(w->codec, AC97_PC_BEEP); + tone = ac97_read(w->codec, AC97_MASTER_TONE); + phone = ac97_read(w->codec, AC97_PHONE); + rec = ac97_read(w->codec, AC97_REC_SEL); + pcm = ac97_read(w->codec, AC97_PCM); + aux = ac97_read(w->codec, AC97_AUX); + + if (event & SND_SOC_DAPM_PRE_REG) + return 0; + if (l & 0x1 || r & 0x1) + ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff); + else + ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000); + + if (l & 0x2 || r & 0x2) + ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff); + else + ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000); + + if (l & 0x4 || r & 0x4) + ac97_write(w->codec, AC97_PHONE, phone & 0x7fff); + else + ac97_write(w->codec, AC97_PHONE, phone | 0x8000); + + if (l & 0x8 || r & 0x8) + ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff); + else + ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000); + + if (l & 0x10 || r & 0x10) + ac97_write(w->codec, AC97_PCM, pcm & 0x7fff); + else + ac97_write(w->codec, AC97_PCM, pcm | 0x8000); + + if (l & 0x20 || r & 0x20) + ac97_write(w->codec, AC97_AUX, aux & 0x7fff); + else + ac97_write(w->codec, AC97_AUX, aux | 0x8000); + + return 0; +} + +/* Left Headphone Mixers */ +static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", HPL_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0), +}; + +/* Right Headphone Mixers */ +static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", HPR_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0), +}; + +/* headphone capture mux */ +static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[1]); + +/* headphone mic mux */ +static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[0]); + +/* Speaker Mixer */ +static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 11, 1, 1), +SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1), +SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1), +SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1), +SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 7, 1, 1), +SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1), +SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1), +SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1), +SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1), +SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1), +SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1), +}; + +/* mono mic mux */ +static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[2]); + +/* mono output mux */ +static const struct snd_kcontrol_new wm9713_mono_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[7]); + +/* speaker left output mux */ +static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[8]); + +/* speaker right output mux */ +static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[9]); + +/* headphone left output mux */ +static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[10]); + +/* headphone right output mux */ +static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[11]); + +/* Out3 mux */ +static const struct snd_kcontrol_new wm9713_out3_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[12]); + +/* Out4 mux */ +static const struct snd_kcontrol_new wm9713_out4_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[13]); + +/* DAC inv mux 1 */ +static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[14]); + +/* DAC inv mux 2 */ +static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[15]); + +/* Capture source left */ +static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[3]); + +/* Capture source right */ +static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[4]); + +/* mic source */ +static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[18]); + +/* mic source B virtual control */ +static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[19]); + +static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = { +SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_rec_mux_controls), +SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_mic_mux_controls), +SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0, + &wm9713_mono_mic_mux_controls), +SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_mono_mux_controls), +SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_spkl_mux_controls), +SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_spkr_mux_controls), +SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hpl_out_mux_controls), +SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hpr_out_mux_controls), +SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0, + &wm9713_out3_mux_controls), +SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0, + &wm9713_out4_mux_controls), +SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0, + &wm9713_dac_inv1_mux_controls), +SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0, + &wm9713_dac_inv2_mux_controls), +SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0, + &wm9713_rec_srcl_mux_controls), +SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0, + &wm9713_rec_srcr_mux_controls), +SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0, + &wm9713_mic_sel_mux_controls ), +SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0, + &wm9713_micb_sel_mux_controls ), +SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1, + &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls), + mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1, + &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls), + mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1, + &wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)), +SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1, + &wm9713_speaker_mixer_controls[0], + ARRAY_SIZE(wm9713_speaker_mixer_controls)), +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1), +SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1), +SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1), +SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_EXTENDED_MID, 5, 1), +SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_EXTENDED_MID, 4, 1), +SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0), +SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1), +SND_SOC_DAPM_OUTPUT("MONO"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_OUTPUT("SPKL"), +SND_SOC_DAPM_OUTPUT("SPKR"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_INPUT("LINEL"), +SND_SOC_DAPM_INPUT("LINER"), +SND_SOC_DAPM_INPUT("MONOIN"), +SND_SOC_DAPM_INPUT("PCBEEP"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2A"), +SND_SOC_DAPM_INPUT("MIC2B"), +SND_SOC_DAPM_VMID("VMID"), +}; + +static const char *audio_map[][3] = { + /* left HP mixer */ + {"Left HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Left HP Mixer", "Bypass Playback Switch", "Left Line In"}, + {"Left HP Mixer", "PCM Playback Switch", "Left DAC"}, + {"Left HP Mixer", "MonoIn Playback Switch", "Mono In"}, + {"Left HP Mixer", NULL, "Capture Headphone Mux"}, + + /* right HP mixer */ + {"Right HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Right HP Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Right HP Mixer", "Bypass Playback Switch", "Right Line In"}, + {"Right HP Mixer", "PCM Playback Switch", "Right DAC"}, + {"Right HP Mixer", "MonoIn Playback Switch", "Mono In"}, + {"Right HP Mixer", NULL, "Capture Headphone Mux"}, + + /* virtual mixer - mixes left & right channels for spk and mono */ + {"AC97 Mixer", NULL, "Left DAC"}, + {"AC97 Mixer", NULL, "Right DAC"}, + {"Line Mixer", NULL, "Right Line In"}, + {"Line Mixer", NULL, "Left Line In"}, + {"HP Mixer", NULL, "Left HP Mixer"}, + {"HP Mixer", NULL, "Right HP Mixer"}, + {"Capture Mixer", NULL, "Left Capture Source"}, + {"Capture Mixer", NULL, "Right Capture Source"}, + + /* speaker mixer */ + {"Speaker Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Speaker Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Speaker Mixer", "Bypass Playback Switch", "Line Mixer"}, + {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Speaker Mixer", "MonoIn Playback Switch", "Mono In"}, + + /* mono mixer */ + {"Mono Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Mono Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Mono Mixer", "Bypass Playback Switch", "Line Mixer"}, + {"Mono Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Mono Mixer", NULL, "Capture Mono Mux"}, + + /* DAC inv mux 1 */ + {"DAC Inv Mux 1", "Mono", "Mono Mixer"}, + {"DAC Inv Mux 1", "Speaker", "Speaker Mixer"}, + {"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"}, + {"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"}, + {"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"}, + + /* DAC inv mux 2 */ + {"DAC Inv Mux 2", "Mono", "Mono Mixer"}, + {"DAC Inv Mux 2", "Speaker", "Speaker Mixer"}, + {"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"}, + {"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"}, + {"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"}, + + /* headphone left mux */ + {"Left Headphone Out Mux", "Headphone", "Left HP Mixer"}, + + /* headphone right mux */ + {"Right Headphone Out Mux", "Headphone", "Right HP Mixer"}, + + /* speaker left mux */ + {"Left Speaker Out Mux", "Headphone", "Left HP Mixer"}, + {"Left Speaker Out Mux", "Speaker", "Speaker Mixer"}, + {"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"}, + + /* speaker right mux */ + {"Right Speaker Out Mux", "Headphone", "Right HP Mixer"}, + {"Right Speaker Out Mux", "Speaker", "Speaker Mixer"}, + {"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"}, + + /* mono mux */ + {"Mono Out Mux", "Mono", "Mono Mixer"}, + {"Mono Out Mux", "Inv", "DAC Inv Mux 1"}, + + /* out 3 mux */ + {"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"}, + + /* out 4 mux */ + {"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"}, + + /* output pga */ + {"HPL", NULL, "Left Headphone"}, + {"Left Headphone", NULL, "Left Headphone Out Mux"}, + {"HPR", NULL, "Right Headphone"}, + {"Right Headphone", NULL, "Right Headphone Out Mux"}, + {"OUT3", NULL, "Out 3"}, + {"Out 3", NULL, "Out 3 Mux"}, + {"OUT4", NULL, "Out 4"}, + {"Out 4", NULL, "Out 4 Mux"}, + {"SPKL", NULL, "Left Speaker"}, + {"Left Speaker", NULL, "Left Speaker Out Mux"}, + {"SPKR", NULL, "Right Speaker"}, + {"Right Speaker", NULL, "Right Speaker Out Mux"}, + {"MONO", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono Out Mux"}, + + /* input pga */ + {"Left Line In", NULL, "LINEL"}, + {"Right Line In", NULL, "LINER"}, + {"Mono In", NULL, "MONOIN"}, + {"Mic A PGA", NULL, "Mic A Pre Amp"}, + {"Mic B PGA", NULL, "Mic B Pre Amp"}, + + /* left capture select */ + {"Left Capture Source", "Mic 1", "Mic A Pre Amp"}, + {"Left Capture Source", "Mic 2", "Mic B Pre Amp"}, + {"Left Capture Source", "Line", "LINEL"}, + {"Left Capture Source", "Mono In", "MONOIN"}, + {"Left Capture Source", "Headphone", "Left HP Mixer"}, + {"Left Capture Source", "Speaker", "Speaker Mixer"}, + {"Left Capture Source", "Mono Out", "Mono Mixer"}, + + /* right capture select */ + {"Right Capture Source", "Mic 1", "Mic A Pre Amp"}, + {"Right Capture Source", "Mic 2", "Mic B Pre Amp"}, + {"Right Capture Source", "Line", "LINER"}, + {"Right Capture Source", "Mono In", "MONOIN"}, + {"Right Capture Source", "Headphone", "Right HP Mixer"}, + {"Right Capture Source", "Speaker", "Speaker Mixer"}, + {"Right Capture Source", "Mono Out", "Mono Mixer"}, + + /* left ADC */ + {"Left ADC", NULL, "Left Capture Source"}, + + /* right ADC */ + {"Right ADC", NULL, "Right Capture Source"}, + + /* mic */ + {"Mic A Pre Amp", NULL, "Mic A Source"}, + {"Mic A Source", "Mic 1", "MIC1"}, + {"Mic A Source", "Mic 2 A", "MIC2A"}, + {"Mic A Source", "Mic 2 B", "Mic B Source"}, + {"Mic B Pre Amp", "MPB", "Mic B Source"}, + {"Mic B Source", NULL, "MIC2B"}, + + /* headphone capture */ + {"Capture Headphone Mux", "Stereo", "Capture Mixer"}, + {"Capture Headphone Mux", "Left", "Left Capture Source"}, + {"Capture Headphone Mux", "Right", "Right Capture Source"}, + + /* mono capture */ + {"Capture Mono Mux", "Stereo", "Capture Mixer"}, + {"Capture Mono Mux", "Left", "Left Capture Source"}, + {"Capture Mono Mux", "Right", "Right Capture Source"}, + + {NULL, NULL, NULL}, +}; + +static int wm9713_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm9713_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm9713_dapm_widgets[i]); + } + + /* set up audio path audio_mapnects */ + 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 unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || + reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 || + reg == AC97_CD) + return soc_ac97_ops.read(codec->ac97, reg); + else { + reg = reg >> 1; + + if (reg > (ARRAY_SIZE(wm9713_reg))) + return -EIO; + + return cache[reg]; + } +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + if (reg < 0x7c) + soc_ac97_ops.write(codec->ac97, reg, val); + reg = reg >> 1; + if (reg <= (ARRAY_SIZE(wm9713_reg))) + cache[reg] = val; + + return 0; +} + +struct pll_ { + unsigned int in_hz; + unsigned int lf:1; /* allows low frequency use */ + unsigned int sdm:1; /* allows fraction n div */ + unsigned int divsel:1; /* enables input clock div */ + unsigned int divctl:1; /* input clock divider */ + unsigned int n:4; + unsigned int k; +}; + +struct pll_ pll[] = { + {13000000, 0, 1, 0, 0, 7, 0x23f488}, + {2048000, 1, 0, 0, 0, 12, 0x0}, + {4096000, 1, 0, 0, 0, 6, 0x0}, + {12288000, 0, 0, 0, 0, 8, 0x0}, + /* liam - add more entries */ +}; + +static int wm9713_set_pll(struct snd_soc_codec *codec, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct wm9713_priv *wm9713 = codec->private_data; + int i; + u16 reg, reg2; + + /* turn PLL off ? */ + if (freq_in == 0 || freq_out == 0) { + /* disable PLL power and select ext source */ + reg = ac97_read(codec, AC97_HANDSET_RATE); + ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080); + reg = ac97_read(codec, AC97_EXTENDED_MID); + ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200); + wm9713->pll_out = 0; + return 0; + } + + for (i = 0; i < ARRAY_SIZE(pll); i++) { + if (pll[i].in_hz == freq_in) + goto found; + } + return -EINVAL; + +found: + if (pll[i].sdm == 0) { + reg = (pll[i].n << 12) | (pll[i].lf << 11) | + (pll[i].divsel << 9) | (pll[i].divctl << 8); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + } else { + /* write the fractional k to the reg 0x46 pages */ + reg2 = (pll[i].n << 12) | (pll[i].lf << 11) | (pll[i].sdm << 10) | + (pll[i].divsel << 9) | (pll[i].divctl << 8); + + reg = reg2 | (0x5 << 4) | (pll[i].k >> 20); /* K [21:20] */ + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + reg = reg2 | (0x4 << 4) | ((pll[i].k >> 16) & 0xf); /* K [19:16] */ + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + reg = reg2 | (0x3 << 4) | ((pll[i].k >> 12) & 0xf); /* K [15:12] */ + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + reg = reg2 | (0x2 << 4) | ((pll[i].k >> 8) & 0xf); /* K [11:8] */ + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + reg = reg2 | (0x1 << 4) | ((pll[i].k >> 4) & 0xf); /* K [7:4] */ + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + reg = reg2 | (0x0 << 4) | (pll[i].k & 0xf); /* K [3:0] */ + ac97_write(codec, AC97_LINE1_LEVEL, reg); + } + + /* turn PLL on and select as source */ + reg = ac97_read(codec, AC97_EXTENDED_MID); + ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff); + reg = ac97_read(codec, AC97_HANDSET_RATE); + ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f); + wm9713->pll_out = freq_out; + wm9713->pll_in = freq_in; + + /* wait 10ms AC97 link frames for the link to stabilise */ + schedule_timeout_interruptible(msecs_to_jiffies(10)); + return 0; +} + +static int wm9713_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + return wm9713_set_pll(codec, pll_id, freq_in, freq_out); +} + +/* + * Tristate the PCM DAI lines, tristate can be disabled by calling + * wm9713_set_dai_fmt() + */ +static int wm9713_set_dai_tristate(struct snd_soc_codec_dai *codec_dai, + int tristate) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0x9fff; + + if (tristate) + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); + + return 0; +} + +/* + * Configure WM9713 clock dividers. + * Voice DAC needs 256 FS + */ +static int wm9713_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM9713_PCMCLK_DIV: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xf0ff; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_CLKA_MULT: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffd; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_CLKB_MULT: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffb; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_HIFI_DIV: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0x8fff; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_PCMBCLK_DIV: + reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xf1ff; + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +}; + +static int wm9713_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 gpio = ac97_read(codec, AC97_GPIO_CFG) & 0xffe2; + u16 reg = 0x8000; + + /* clock masters */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK){ + case SND_SOC_DAIFMT_CBM_CFM: + reg |= 0x4000; + gpio |= 0x0008; + break; + case SND_SOC_DAIFMT_CBM_CFS: + reg |= 0x6000; + gpio |= 0x000c; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg |= 0x0200; + gpio |= 0x000d; + break; + case SND_SOC_DAIFMT_CBS_CFM: + gpio |= 0x0009; + break; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + reg |= 0x00c0; + break; + case SND_SOC_DAIFMT_IB_NF: + reg |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + reg |= 0x0040; + break; + } + + /* DAI format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + reg |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + reg |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + reg |= 0x0043; + break; + } + + ac97_write(codec, AC97_GPIO_CFG, gpio); + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); + return 0; +} + +static int wm9713_pcm_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + reg |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + reg |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + reg |= 0x000c; + break; + } + + /* enable PCM interface in master mode */ + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); + return 0; +} + +static void wm9713_voiceshutdown(snd_pcm_substream_t *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 status; + + /* Gracefully shut down the voice interface. */ + status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; + ac97_write(codec,AC97_HANDSET_RATE,0x0280); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + ac97_write(codec,AC97_HANDSET_RATE,0x0F80); + ac97_write(codec,AC97_EXTENDED_MID,status); +} + +static int ac97_hifi_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int reg; + u16 vra; + + vra = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return ac97_write(codec, reg, runtime->rate); +} + +static int ac97_aux_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 vra, xsle; + + vra = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); + xsle = ac97_read(codec, AC97_PCI_SID); + ac97_write(codec, AC97_PCI_SID, xsle | 0x8000); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + + return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate); +} + +#define WM9713_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM9713_PCM_FORMATS \ + (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_LE) + +struct snd_soc_codec_dai wm9713_dai[] = { +{ + .name = "AC97 HiFi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .prepare = ac97_hifi_prepare,}, + }, + { + .name = "AC97 Aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9713_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .prepare = ac97_aux_prepare,}, + }, + { + .name = "WM9713 Voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9713_RATES, + .formats = WM9713_PCM_FORMATS,}, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = WM9713_PCM_FORMATS,}, + .ops = { + .hw_params = wm9713_pcm_hw_params, + .shutdown = wm9713_voiceshutdown,}, + .dai_ops = { + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll, + .set_fmt = wm9713_set_dai_fmt, + .set_tristate = wm9713_set_dai_tristate, + }, + }, +}; +EXPORT_SYMBOL_GPL(wm9713_dai); + +int wm9713_reset(struct snd_soc_codec *codec, int try_warm) +{ + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (!(ac97_read(codec, 0) & 0x8000)) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + if (ac97_read(codec, 0) & 0x8000) + return -EIO; + return 0; +} +EXPORT_SYMBOL_GPL(wm9713_reset); + +static int wm9713_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 reg; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* enable thermal shutdown */ + reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff; + ac97_write(codec, AC97_EXTENDED_MID, reg); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* enable master bias and vmid */ + reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff; + ac97_write(codec, AC97_EXTENDED_MID, reg); + ac97_write(codec, AC97_POWERDOWN, 0x0000); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* disable everything including AC link */ + ac97_write(codec, AC97_EXTENDED_MID, 0xffff); + ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); + ac97_write(codec, AC97_POWERDOWN, 0xffff); + break; + } + codec->dapm_state = event; + return 0; +} + +static int wm9713_soc_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; + + wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm9713_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm9713_priv *wm9713 = codec->private_data; + int i, ret; + u16 *cache = codec->reg_cache; + + if ((ret = wm9713_reset(codec, 1)) < 0){ + printk(KERN_ERR "could not reset AC97 codec\n"); + return ret; + } + + wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* do we need to re-start the PLL ? */ + if (wm9713->pll_out) + wm9713_set_pll(codec, 0, wm9713->pll_in, wm9713->pll_out); + + /* only synchronise the codec if warm reset failed */ + if (ret == 0) { + for (i = 2; i < ARRAY_SIZE(wm9713_reg) << 1; i+=2) { + if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID || + i == AC97_EXTENDED_MSTATUS || i > 0x66) + continue; + soc_ac97_ops.write(codec->ac97, i, cache[i>>1]); + } + } + + if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) + wm9713_dapm_event(codec, SNDRV_CTL_POWER_D0); + + return ret; +} + +static int wm9713_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0, reg; + + printk(KERN_INFO "WM9713/WM9714 SoC Audio Codec %s\n", WM9713_VERSION); + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->codec == NULL) + return -ENOMEM; + codec = socdev->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm9713_reg), GFP_KERNEL); + if (codec->reg_cache == NULL){ + ret = -ENOMEM; + goto cache_err; + } + memcpy(codec->reg_cache, wm9713_reg, + sizeof(u16) * ARRAY_SIZE(wm9713_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm9713_reg); + codec->reg_cache_step = 2; + + codec->private_data = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL); + if (codec->private_data == NULL) { + ret = -ENOMEM; + goto priv_err; + } + + codec->name = "WM9713"; + codec->owner = THIS_MODULE; + codec->dai = wm9713_dai; + codec->num_dai = ARRAY_SIZE(wm9713_dai); + codec->write = ac97_write; + codec->read = ac97_read; + codec->dapm_event = wm9713_dapm_event; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) + goto codec_err; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + /* do a cold reset for the controller and then try + * a warm reset followed by an optional cold reset for codec */ + wm9713_reset(codec, 0); + ret = wm9713_reset(codec, 1); + if (ret < 0) { + printk(KERN_ERR "AC97 link error\n"); + goto reset_err; + } + + wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* unmute the adc - move to kcontrol */ + reg = ac97_read(codec, AC97_CD) & 0x7fff; + ac97_write(codec, AC97_CD, reg); + + wm9713_add_controls(codec); + wm9713_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) + goto reset_err; + return 0; + +reset_err: + snd_soc_free_pcms(socdev); + +pcm_err: + snd_soc_free_ac97_codec(codec); + +codec_err: + kfree(codec->private_data); + +priv_err: + kfree(codec->reg_cache); + +cache_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int wm9713_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->private_data); + kfree(codec->reg_cache); + kfree(codec->dai); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm9713 = { + .probe = wm9713_soc_probe, + .remove = wm9713_soc_remove, + .suspend = wm9713_soc_suspend, + .resume = wm9713_soc_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm9713); + +MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm9713.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm9713.h @@ -0,0 +1,51 @@ +/* + * wm9713.h -- WM9713 Soc Audio driver + */ + +#ifndef _WM9713_H +#define _WM9713_H + +/* clock inputs */ +#define WM9713_CLKA_PIN 0 +#define WM9713_CLKB_PIN 1 + +/* clock divider ID's */ +#define WM9713_PCMCLK_DIV 0 +#define WM9713_CLKA_MULT 1 +#define WM9713_CLKB_MULT 2 +#define WM9713_HIFI_DIV 3 +#define WM9713_PCMBCLK_DIV 4 + +/* PCM clk div */ +#define WM9713_PCMDIV(x) ((x - 1) << 8) + +/* HiFi Div */ +#define WM9713_HIFIDIV(x) ((x - 1) << 12) + +/* MCLK clock mulitipliers */ +#define WM9713_CLKA_X1 (0 << 1) +#define WM9713_CLKA_X2 (1 << 1) +#define WM9713_CLKB_X1 (0 << 2) +#define WM9713_CLKB_X2 (1 << 2) + +/* MCLK clock MUX */ +#define WM9713_CLK_MUX_A (0 << 0) +#define WM9713_CLK_MUX_B (1 << 0) + +/* Voice DAI BCLK divider */ +#define WM9713_PCMBCLK_DIV_1 (0 << 9) +#define WM9713_PCMBCLK_DIV_2 (1 << 9) +#define WM9713_PCMBCLK_DIV_4 (2 << 9) +#define WM9713_PCMBCLK_DIV_8 (3 << 9) +#define WM9713_PCMBCLK_DIV_16 (4 << 9) + +#define WM9713_DAI_AC97_HIFI 0 +#define WM9713_DAI_AC97_AUX 1 +#define WM9713_DAI_PCM_VOICE 2 + +extern struct snd_soc_codec_device soc_codec_dev_wm9713; +extern struct snd_soc_codec_dai wm9713_dai[3]; + +int wm9713_reset(struct snd_soc_codec *codec, int try_warm); + +#endif Index: linux-2.6.21-moko/sound/soc/pxa/mainstone.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/mainstone.c @@ -0,0 +1,127 @@ +/* + * mainstone.c -- SoC audio for Mainstone + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Mainstone audio amplifier code taken from arch/arm/mach-pxa/mainstone.c + * Copyright: MontaVista Software Inc. + * + * 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. + * + * Revision history + * 30th Oct 2005 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/ac97.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-ac97.h" + +static struct snd_soc_machine mainstone; +static long mst_audio_suspend_mask; + +static int mainstone_suspend(struct platform_device *pdev, pm_message_t state) +{ + mst_audio_suspend_mask = MST_MSCWR2; + MST_MSCWR2 |= MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_resume(struct platform_device *pdev) +{ + MST_MSCWR2 &= mst_audio_suspend_mask | ~MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_probe(struct platform_device *pdev) +{ + MST_MSCWR2 &= ~MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_remove(struct platform_device *pdev) +{ + MST_MSCWR2 |= MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static struct snd_soc_machine_config codecs[] = { +{ + .name = "AC97", + .sname = "AC97 HiFi", + .iface = &pxa_ac97_interface[0], +}, +{ + .name = "AC97 Aux", + .sname = "AC97 Aux", + .iface = &pxa_ac97_interface[1], +}, +}; + +static struct snd_soc_machine mainstone = { + .name = "Mainstone", + .probe = mainstone_probe, + .remove = mainstone_remove, + .suspend_pre = mainstone_suspend, + .resume_post = mainstone_resume, + .config = codecs, + .nconfigs = ARRAY_SIZE(codecs), +}; + +static struct snd_soc_device mainstone_snd_devdata = { + .machine = &mainstone, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_ac97, +}; + +static struct platform_device *mainstone_snd_device; + +static int __init mainstone_init(void) +{ + int ret; + + mainstone_snd_device = platform_device_alloc("soc-audio", -1); + if (!mainstone_snd_device) + return -ENOMEM; + + platform_set_drvdata(mainstone_snd_device, &mainstone_snd_devdata); + mainstone_snd_devdata.dev = &mainstone_snd_device->dev; + ret = platform_device_add(mainstone_snd_device); + + if (ret) + platform_device_put(mainstone_snd_device); + + return ret; +} + +static void __exit mainstone_exit(void) +{ + platform_device_unregister(mainstone_snd_device); +} + +module_init(mainstone_init); +module_exit(mainstone_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC Mainstone"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/pxa/mainstone_baseband.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/mainstone_baseband.c @@ -0,0 +1,212 @@ +/* + * mainstone_baseband.c + * Mainstone Example Baseband modem -- ALSA Soc Audio Layer + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 15th Apr 2006 Initial version. + * + * This is example code to demonstrate connecting a baseband modem to the PCM + * DAI on the WM9713 codec on the Intel Mainstone platform. It is by no means + * complete as it requires code to control the modem. + * + * The architecture consists of the WM9713 AC97 DAI connected to the PXA27x + * AC97 controller and the WM9713 PCM DAI connected to the basebands DAI. The + * baseband is controlled via a serial port. Audio is routed between the PXA27x + * and the baseband via internal WM9713 analog paths. + * + * This driver is not the baseband modem driver. This driver only calls + * functions from the Baseband driver to set up it's PCM DAI. + * + * It's intended to use this driver as follows:- + * + * 1. open() WM9713 PCM audio device. + * 2. open() serial device (for AT commands). + * 3. configure PCM audio device (rate etc) - sets up WM9713 PCM DAI, + * this will also set up the baseband PCM DAI (via calling baseband driver). + * 4. send any further AT commands to set up baseband. + * 5. configure codec audio mixer paths. + * 6. open(), configure and read/write AC97 audio device - to Tx/Rx voice + * + * The PCM audio device is opened but IO is never performed on it as the IO is + * directly between the codec and the baseband (and not the CPU). + * + * TODO: + * o Implement callbacks + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/wm9713.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-ac97.h" +#include "pxa2xx-ssp.h" + +static struct snd_soc_machine mainstone; + +/* Do specific baseband PCM voice startup here */ +static int baseband_startup(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* Do specific baseband PCM voice shutdown here */ +static void baseband_shutdown (struct snd_pcm_substream *substream) +{ +} + +/* Do specific baseband modem PCM voice hw params init here */ +static int baseband_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return 0; +} + +/* Do specific baseband modem PCM voice hw params free here */ +static int baseband_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * Baseband Processor DAI + */ +static struct snd_soc_cpu_dai baseband_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 = baseband_startup, + .shutdown = baseband_shutdown, + .hw_params = baseband_hw_params, + .hw_free = baseband_hw_free, + }, +}; + +/* PM */ +static int mainstone_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int mainstone_resume(struct platform_device *pdev) +{ + return 0; +} + +static int mainstone_probe(struct platform_device *pdev) +{ + return 0; +} + +static int mainstone_remove(struct platform_device *pdev) +{ + return 0; +} + +static int mainstone_wm9713_init(struct snd_soc_codec *codec) +{ + return 0; +} + +/* the physical audio connections between the WM9713, Baseband and pxa2xx */ +static struct snd_soc_dai_link mainstone_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI], + .codec_dai = &wm9713_dai[WM9713_DAI_AC97_HIFI], + .init = mainstone_wm9713_init, +}, +{ + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX], + .codec_dai = &wm9713_dai[WM9713_DAI_AC97_AUX], +}, +{ + .name = "Baseband", + .stream_name = "Voice", + .cpu_dai = &baseband_dai, + .codec_dai = &wm9713_dai[WM9713_DAI_PCM_VOICE], +}, +}; + +static struct snd_soc_machine mainstone = { + .name = "Mainstone", + .probe = mainstone_probe, + .remove = mainstone_remove, + .suspend_pre = mainstone_suspend, + .resume_post = mainstone_resume, + .dai_link = mainstone_dai, + .num_links = ARRAY_SIZE(mainstone_dai), +}; + +static struct snd_soc_device mainstone_snd_ac97_devdata = { + .machine = &mainstone, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm9713, +}; + +static struct platform_device *mainstone_snd_ac97_device; + +static int __init mainstone_init(void) +{ + int ret; + + mainstone_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!mainstone_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(mainstone_snd_ac97_device, &mainstone_snd_ac97_devdata); + mainstone_snd_ac97_devdata.dev = &mainstone_snd_ac97_device->dev; + + if((ret = platform_device_add(mainstone_snd_ac97_device)) != 0) + platform_device_put(mainstone_snd_ac97_device); + + return ret; +} + +static void __exit mainstone_exit(void) +{ + platform_device_unregister(mainstone_snd_ac97_device); +} + +module_init(mainstone_init); +module_exit(mainstone_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("Mainstone Example Baseband PCM Interface"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/pxa/mainstone_bluetooth.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/mainstone_bluetooth.c @@ -0,0 +1,371 @@ +/* + * mainstone_bluetooth.c + * Mainstone Example Bluetooth -- ALSA Soc Audio Layer + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 15th May 2006 Initial version. + * + * This is example code to demonstrate connecting a bluetooth codec to the PCM + * DAI on the WM8753 codec on the Intel Mainstone platform. It is by no means + * complete as it requires code to control the BT codec. + * + * The architecture consists of the WM8753 HIFI DAI connected to the PXA27x + * I2S controller and the WM8753 PCM DAI connected to the bluetooth DAI. The + * bluetooth codec and wm8753 are controlled via I2C. Audio is routed between + * the PXA27x and the bluetooth via internal WM8753 analog paths. + * + * This example supports the following audio input/outputs. + * + * o Board mounted Mic and Speaker (spk has amplifier) + * o Headphones via jack socket + * o BT source and sink + * + * This driver is not the bluetooth codec driver. This driver only calls + * functions from the Bluetooth driver to set up it's PCM DAI. + * + * It's intended to use the driver as follows:- + * + * 1. open() WM8753 PCM audio device. + * 2. configure PCM audio device (rate etc) - sets up WM8753 PCM DAI, + * this should also set up the BT codec DAI (via calling bt driver). + * 3. configure codec audio mixer paths. + * 4. open(), configure and read/write HIFI audio device - to Tx/Rx voice + * + * The PCM audio device is opened but IO is never performed on it as the IO is + * directly between the codec and the BT codec (and not the CPU). + * + * TODO: + * o Implement callbacks + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/wm8753.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-ssp.h" + +static struct snd_soc_machine mainstone; + +/* Do specific bluetooth PCM startup here */ +static int bt_startup(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* Do specific bluetooth PCM shutdown here */ +static void bt_shutdown (struct snd_pcm_substream *substream) +{ +} + +/* Do pecific bluetooth PCM hw params init here */ +static int bt_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return 0; +} + +/* Do specific bluetooth PCM hw params free here */ +static int bt_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +#define BT_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100) + +/* + * BT Codec DAI + */ +static struct snd_soc_cpu_dai bt_dai = +{ .name = "Bluetooth", + .id = 0, + .type = SND_SOC_DAI_PCM, + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = BT_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = BT_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .startup = bt_startup, + .shutdown = bt_shutdown, + .hw_params = bt_hw_params, + .hw_free = bt_hw_free, + }, +}; + +/* PM */ +static int mainstone_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int mainstone_resume(struct platform_device *pdev) +{ + return 0; +} + +static int mainstone_probe(struct platform_device *pdev) +{ + return 0; +} + +static int mainstone_remove(struct platform_device *pdev) +{ + return 0; +} + +/* + * Machine audio functions. + * + * The machine now has 3 extra audio controls. + * + * Jack function: Sets function (device plugged into Jack) to nothing (Off) + * or Headphones. + * + * Mic function: Set the on board Mic to On or Off + * Spk function: Set the on board Spk to On or Off + * + * example: BT playback (of far end) and capture (of near end) + * Set Mic and Speaker to On, open BT alsa interface as above and set up + * internal audio paths. + */ + +static int machine_jack_func = 0; +static int machine_spk_func = 0; +static int machine_mic_func = 0; + +static int machine_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = machine_jack_func; + return 0; +} + +static int machine_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + machine_jack_func = ucontrol->value.integer.value[0]; + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", machine_jack_func); + return 0; +} + +static int machine_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = machine_spk_func; + return 0; +} + +static int machine_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (machine_spk_func == ucontrol->value.integer.value[0]) + return 0; + + machine_spk_func = ucontrol->value.integer.value[0]; + snd_soc_dapm_set_endpoint(codec, "Spk", machine_spk_func); + return 1; +} + +static int machine_get_mic(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = machine_spk_func; + return 0; +} + +static int machine_set_mic(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (machine_spk_func == ucontrol->value.integer.value[0]) + return 0; + + machine_spk_func = ucontrol->value.integer.value[0]; + snd_soc_dapm_set_endpoint(codec, "Mic", machine_mic_func); + return 1; +} + +/* turns on board speaker amp on/off */ +static int machine_amp_event(struct snd_soc_dapm_widget *w, int event) +{ +#if 0 + if (SND_SOC_DAPM_EVENT_ON(event)) + /* on */ + else + /* off */ +#endif + return 0; +} + +/* machine dapm widgets */ +static const struct snd_soc_dapm_widget machine_dapm_widgets[] = { +SND_SOC_DAPM_HP("Headphone Jack", NULL), +SND_SOC_DAPM_SPK("Spk", machine_amp_event), +SND_SOC_DAPM_MIC("Mic", NULL), +}; + +/* machine connections to the codec pins */ +static const char* audio_map[][3] = { + + /* headphone connected to LOUT1, ROUT1 */ + {"Headphone Jack", NULL, "LOUT"}, + {"Headphone Jack", NULL, "ROUT"}, + + /* speaker connected to LOUT2, ROUT2 */ + {"Spk", NULL, "ROUT2"}, + {"Spk", NULL, "LOUT2"}, + + /* mic is connected to MIC1 (via Mic Bias) */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic"}, + + {NULL, NULL, NULL}, +}; + +static const char* jack_function[] = {"Off", "Headphone"}; +static const char* spk_function[] = {"Off", "On"}; +static const char* mic_function[] = {"Off", "On"}; +static const struct soc_enum machine_ctl_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, mic_function), +}; + +static const struct snd_kcontrol_new wm8753_machine_controls[] = { + SOC_ENUM_EXT("Jack Function", machine_ctl_enum[0], machine_get_jack, machine_set_jack), + SOC_ENUM_EXT("Speaker Function", machine_ctl_enum[1], machine_get_spk, machine_set_spk), + SOC_ENUM_EXT("Mic Function", machine_ctl_enum[2], machine_get_mic, machine_set_mic), +}; + +static int mainstone_wm8753_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* not used on this machine - e.g. will never be powered up */ + snd_soc_dapm_set_endpoint(codec, "OUT3", 0); + snd_soc_dapm_set_endpoint(codec, "OUT4", 0); + snd_soc_dapm_set_endpoint(codec, "MONO2", 0); + snd_soc_dapm_set_endpoint(codec, "MONO1", 0); + snd_soc_dapm_set_endpoint(codec, "LINE1", 0); + snd_soc_dapm_set_endpoint(codec, "LINE2", 0); + snd_soc_dapm_set_endpoint(codec, "RXP", 0); + snd_soc_dapm_set_endpoint(codec, "RXN", 0); + snd_soc_dapm_set_endpoint(codec, "MIC2", 0); + + /* Add machine specific controls */ + for (i = 0; i < ARRAY_SIZE(wm8753_machine_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8753_machine_controls[i],codec, NULL))) < 0) + return err; + } + + /* Add machine specific widgets */ + for(i = 0; i < ARRAY_SIZE(machine_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &machine_dapm_widgets[i]); + } + + /* Set up machine specific audio path audio_mapnects */ + 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_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_dai_link mainstone_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_HIFI], + .init = mainstone_wm8753_init, +}, +{ /* Voice via BT */ + .name = "Bluetooth", + .stream_name = "Voice", + .cpu_dai = &bt_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_VOICE], +}, +}; + +static struct snd_soc_machine mainstone = { + .name = "Mainstone", + .probe = mainstone_probe, + .remove = mainstone_remove, + .suspend_pre = mainstone_suspend, + .resume_post = mainstone_resume, + .dai_link = mainstone_dai, + .num_links = ARRAY_SIZE(mainstone_dai), +}; + +static struct snd_soc_device mainstone_snd_wm8753_devdata = { + .machine = &mainstone, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm8753, +}; + +static struct platform_device *mainstone_snd_wm8753_device; + +static int __init mainstone_init(void) +{ + int ret; + + mainstone_snd_wm8753_device = platform_device_alloc("soc-audio", -1); + if (!mainstone_snd_wm8753_device) + return -ENOMEM; + + platform_set_drvdata(mainstone_snd_wm8753_device, &mainstone_snd_wm8753_devdata); + mainstone_snd_wm8753_devdata.dev = &mainstone_snd_wm8753_device->dev; + + if((ret = platform_device_add(mainstone_snd_wm8753_device)) != 0) + platform_device_put(mainstone_snd_wm8753_device); + + return ret; +} + +static void __exit mainstone_exit(void) +{ + platform_device_unregister(mainstone_snd_wm8753_device); +} + +module_init(mainstone_init); +module_exit(mainstone_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("Mainstone Example Bluetooth PCM Interface"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/pxa/mainstone_wm8731.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/mainstone_wm8731.c @@ -0,0 +1,203 @@ +/* + * mainstone.c -- SoC audio for Mainstone + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Mainstone audio amplifier code taken from arch/arm/mach-pxa/mainstone.c + * Copyright: MontaVista Software Inc. + * + * 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. + * + * Revision history + * 5th June 2006 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/wm8731.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" + +static struct snd_soc_machine mainstone; + +static int mainstone_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; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.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 = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8731_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops mainstone_ops = { + .hw_params = mainstone_hw_params, +}; + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const char* intercon[][3] = { + + /* 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"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +/* + * Logic for a wm8731 as connected on a Endrelia ETI-B1 board. + */ +static int mainstone_wm8731_init(struct snd_soc_codec *codec) +{ + int i; + + + /* Add specific widgets */ + for(i = 0; i < ARRAY_SIZE(dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &dapm_widgets[i]); + } + + /* Set up specific audio path interconnects */ + for(i = 0; intercon[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, intercon[i][0], intercon[i][1], intercon[i][2]); + } + + /* not connected */ + snd_soc_dapm_set_endpoint(codec, "RLINEIN", 0); + snd_soc_dapm_set_endpoint(codec, "LLINEIN", 0); + + /* always connected */ + snd_soc_dapm_set_endpoint(codec, "Int Mic", 1); + snd_soc_dapm_set_endpoint(codec, "Ext Spk", 1); + + snd_soc_dapm_sync_endpoints(codec); + + return 0; +} + +static struct snd_soc_dai_link mainstone_dai[] = { +{ + .name = "WM8731", + .stream_name = "WM8731 HiFi", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &wm8731_dai, + .init = mainstone_wm8731_init, + .ops = &mainstone_ops, + }, +}; + +static struct snd_soc_machine mainstone = { + .name = "Mainstone", + .dai_link = mainstone_dai, + .num_links = ARRAY_SIZE(mainstone_dai), +}; + +static struct wm8731_setup_data corgi_wm8731_setup = { + .i2c_address = 0x1b, +}; + +static struct snd_soc_device mainstone_snd_devdata = { + .machine = &mainstone, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm8731, + .codec_data = &corgi_wm8731_setup, +}; + +static struct platform_device *mainstone_snd_device; + +static int __init mainstone_init(void) +{ + int ret; + + mainstone_snd_device = platform_device_alloc("soc-audio", -1); + if (!mainstone_snd_device) + return -ENOMEM; + + platform_set_drvdata(mainstone_snd_device, &mainstone_snd_devdata); + mainstone_snd_devdata.dev = &mainstone_snd_device->dev; + ret = platform_device_add(mainstone_snd_device); + + if (ret) + platform_device_put(mainstone_snd_device); + + return ret; +} + +static void __exit mainstone_exit(void) +{ + platform_device_unregister(mainstone_snd_device); +} + +module_init(mainstone_init); +module_exit(mainstone_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM8731 Mainstone"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/pxa/mainstone_wm8753.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/mainstone_wm8753.c @@ -0,0 +1,547 @@ +/* + * mainstone.c -- SoC audio for Mainstone + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Mainstone audio amplifier code taken from arch/arm/mach-pxa/mainstone.c + * Copyright: MontaVista Software Inc. + * + * 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. + * + * Revision history + * 30th Oct 2005 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/wm8753.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-ssp.h" + +/* + * SSP GPIO's + */ +#define GPIO26_SSP1RX_MD (26 | GPIO_ALT_FN_1_IN) +#define GPIO25_SSP1TX_MD (25 | GPIO_ALT_FN_2_OUT) +#define GPIO23_SSP1CLKS_MD (23 | GPIO_ALT_FN_2_IN) +#define GPIO24_SSP1FRMS_MD (24 | GPIO_ALT_FN_2_IN) +#define GPIO23_SSP1CLKM_MD (23 | GPIO_ALT_FN_2_OUT) +#define GPIO24_SSP1FRMM_MD (24 | GPIO_ALT_FN_2_OUT) +#define GPIO53_SSP1SYSCLK_MD (53 | GPIO_ALT_FN_2_OUT) + +#define GPIO11_SSP2RX_MD (11 | GPIO_ALT_FN_2_IN) +#define GPIO13_SSP2TX_MD (13 | GPIO_ALT_FN_1_OUT) +#define GPIO22_SSP2CLKS_MD (22 | GPIO_ALT_FN_3_IN) +#define GPIO88_SSP2FRMS_MD (88 | GPIO_ALT_FN_3_IN) +#define GPIO22_SSP2CLKM_MD (22 | GPIO_ALT_FN_3_OUT) +#define GPIO88_SSP2FRMM_MD (88 | GPIO_ALT_FN_3_OUT) +#define GPIO22_SSP2SYSCLK_MD (22 | GPIO_ALT_FN_2_OUT) + +#define GPIO82_SSP3RX_MD (82 | GPIO_ALT_FN_1_IN) +#define GPIO81_SSP3TX_MD (81 | GPIO_ALT_FN_1_OUT) +#define GPIO84_SSP3CLKS_MD (84 | GPIO_ALT_FN_1_IN) +#define GPIO83_SSP3FRMS_MD (83 | GPIO_ALT_FN_1_IN) +#define GPIO84_SSP3CLKM_MD (84 | GPIO_ALT_FN_1_OUT) +#define GPIO83_SSP3FRMM_MD (83 | GPIO_ALT_FN_1_OUT) +#define GPIO45_SSP3SYSCLK_MD (45 | GPIO_ALT_FN_3_OUT) + +#if 0 +static struct pxa2xx_gpio ssp_gpios[3][4] = { + {{ /* SSP1 SND_SOC_DAIFMT_CBM_CFM */ + .rx = GPIO26_SSP1RX_MD, + .tx = GPIO25_SSP1TX_MD, + .clk = (23 | GPIO_ALT_FN_2_IN), + .frm = (24 | GPIO_ALT_FN_2_IN), + .sys = GPIO53_SSP1SYSCLK_MD, + }, + { /* SSP1 SND_SOC_DAIFMT_CBS_CFS */ + .rx = GPIO26_SSP1RX_MD, + .tx = GPIO25_SSP1TX_MD, + .clk = (23 | GPIO_ALT_FN_2_OUT), + .frm = (24 | GPIO_ALT_FN_2_OUT), + .sys = GPIO53_SSP1SYSCLK_MD, + }, + { /* SSP1 SND_SOC_DAIFMT_CBS_CFM */ + .rx = GPIO26_SSP1RX_MD, + .tx = GPIO25_SSP1TX_MD, + .clk = (23 | GPIO_ALT_FN_2_OUT), + .frm = (24 | GPIO_ALT_FN_2_IN), + .sys = GPIO53_SSP1SYSCLK_MD, + }, + { /* SSP1 SND_SOC_DAIFMT_CBM_CFS */ + .rx = GPIO26_SSP1RX_MD, + .tx = GPIO25_SSP1TX_MD, + .clk = (23 | GPIO_ALT_FN_2_IN), + .frm = (24 | GPIO_ALT_FN_2_OUT), + .sys = GPIO53_SSP1SYSCLK_MD, + }}, + {{ /* SSP2 SND_SOC_DAIFMT_CBM_CFM */ + .rx = GPIO11_SSP2RX_MD, + .tx = GPIO13_SSP2TX_MD, + .clk = (22 | GPIO_ALT_FN_3_IN), + .frm = (88 | GPIO_ALT_FN_3_IN), + .sys = GPIO22_SSP2SYSCLK_MD, + }, + { /* SSP2 SND_SOC_DAIFMT_CBS_CFS */ + .rx = GPIO11_SSP2RX_MD, + .tx = GPIO13_SSP2TX_MD, + .clk = (22 | GPIO_ALT_FN_3_OUT), + .frm = (88 | GPIO_ALT_FN_3_OUT), + .sys = GPIO22_SSP2SYSCLK_MD, + }, + { /* SSP2 SND_SOC_DAIFMT_CBS_CFM */ + .rx = GPIO11_SSP2RX_MD, + .tx = GPIO13_SSP2TX_MD, + .clk = (22 | GPIO_ALT_FN_3_OUT), + .frm = (88 | GPIO_ALT_FN_3_IN), + .sys = GPIO22_SSP2SYSCLK_MD, + }, + { /* SSP2 SND_SOC_DAIFMT_CBM_CFS */ + .rx = GPIO11_SSP2RX_MD, + .tx = GPIO13_SSP2TX_MD, + .clk = (22 | GPIO_ALT_FN_3_IN), + .frm = (88 | GPIO_ALT_FN_3_OUT), + .sys = GPIO22_SSP2SYSCLK_MD, + }}, + {{ /* SSP3 SND_SOC_DAIFMT_CBM_CFM */ + .rx = GPIO82_SSP3RX_MD, + .tx = GPIO81_SSP3TX_MD, + .clk = (84 | GPIO_ALT_FN_3_IN), + .frm = (83 | GPIO_ALT_FN_3_IN), + .sys = GPIO45_SSP3SYSCLK_MD, + }, + { /* SSP3 SND_SOC_DAIFMT_CBS_CFS */ + .rx = GPIO82_SSP3RX_MD, + .tx = GPIO81_SSP3TX_MD, + .clk = (84 | GPIO_ALT_FN_3_OUT), + .frm = (83 | GPIO_ALT_FN_3_OUT), + .sys = GPIO45_SSP3SYSCLK_MD, + }, + { /* SSP3 SND_SOC_DAIFMT_CBS_CFM */ + .rx = GPIO82_SSP3RX_MD, + .tx = GPIO81_SSP3TX_MD, + .clk = (84 | GPIO_ALT_FN_3_OUT), + .frm = (83 | GPIO_ALT_FN_3_IN), + .sys = GPIO45_SSP3SYSCLK_MD, + }, + { /* SSP3 SND_SOC_DAIFMT_CBM_CFS */ + .rx = GPIO82_SSP3RX_MD, + .tx = GPIO81_SSP3TX_MD, + .clk = (84 | GPIO_ALT_FN_3_IN), + .frm = (83 | GPIO_ALT_FN_3_OUT), + .sys = GPIO45_SSP3SYSCLK_MD, + }}, +}; +#endif + +static struct snd_soc_machine mainstone; + +static int mainstone_hifi_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; + unsigned int pll_out = 0, bclk = 0, fmt = 0; + int ret = 0; + + /* + * The WM8753 is far better at generating accurate audio clocks than the + * pxa2xx I2S controller, so we will use it as master when we can. + * i.e all rates except 8k and 16k as BCLK must be 64 * rate when the + * pxa27x or pxa25x is slave. Note this restriction does not apply to SSP + * I2S emulation mode. + */ + switch (params_rate(params)) { + case 8000: + case 16000: + fmt = SND_SOC_DAIFMT_CBS_CFS; + pll_out = 12288000; + break; + case 48000: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_4; + pll_out = 12288000; + break; + case 96000: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_2; + pll_out = 12288000; + break; + case 11025: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_16; + pll_out = 11289600; + break; + case 22050: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_8; + pll_out = 11289600; + break; + case 44100: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_4; + pll_out = 11289600; + break; + case 88200: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_2; + pll_out = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | fmt); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | fmt); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_MCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk); + if (ret < 0) + return ret; + + /* codec PLL input is 13 MHz */ + ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, 13000000, pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int mainstone_hifi_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; + + /* disable the PLL */ + return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, 0, 0); +} + +/* + * Mainstone WM8753 HiFi DAI opserations. + */ +static struct snd_soc_ops mainstone_hifi_ops = { + .hw_params = mainstone_hifi_hw_params, + .hw_free = mainstone_hifi_hw_free, +}; + +static int mainstone_voice_startup(struct snd_pcm_substream *substream) +{ + /* enable USB on the go MUX so we can use SSPFRM2 */ + MST_MSCWR2 |= MST_MSCWR2_USB_OTG_SEL; + MST_MSCWR2 &= ~MST_MSCWR2_USB_OTG_RST; + + return 0; +} + +static void mainstone_voice_shutdown(struct snd_pcm_substream *substream) +{ +// struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* disable USB on the go MUX so we can use ttyS0 */ + MST_MSCWR2 &= ~MST_MSCWR2_USB_OTG_SEL; + MST_MSCWR2 |= MST_MSCWR2_USB_OTG_RST; + + /* liam may need to tristate DAI */ +} + +static int mainstone_voice_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; + unsigned int pll_out = 0, bclk = 0, pcmdiv = 0; + int ret = 0; + + /* + * The WM8753 is far better at generating accurate audio clocks than the + * pxa2xx SSP controller, so we will use it as master when we can. + */ + switch (params_rate(params)) { + case 8000: + pll_out = 12288000; + pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 256kHz */ + break; + case 16000: + pll_out = 12288000; + pcmdiv = WM8753_PCM_DIV_3; /* 4.096 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 512kHz */ + break; + case 48000: + pll_out = 12288000; + pcmdiv = WM8753_PCM_DIV_1; /* 12.288 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 1.536 MHz */ + break; + case 11025: + pll_out = 11289600; + pcmdiv = WM8753_PCM_DIV_4; /* 11.2896 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 352.8 kHz */ + break; + case 22050: + pll_out = 11289600; + pcmdiv = WM8753_PCM_DIV_2; /* 11.2896 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 705.6 kHz */ + break; + case 44100: + pll_out = 11289600; + pcmdiv = WM8753_PCM_DIV_1; /* 11.2896 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 1.4112 MHz */ + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_PCMCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the SSP system clock as input (unused) */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_SSP_CLK_PLL, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_VXCLKDIV, bclk); + if (ret < 0) + return ret; + + /* set codec PCM division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv); + if (ret < 0) + return ret; + + /* codec PLL input is 13 MHz */ + ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, 13000000, pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int mainstone_voice_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; + + /* disable the PLL */ + return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, 0, 0); +} + +static struct snd_soc_ops mainstone_voice_ops = { + .startup = mainstone_voice_startup, + .shutdown = mainstone_voice_shutdown, + .hw_params = mainstone_voice_hw_params, + .hw_free = mainstone_voice_hw_free, +}; + +static long mst_audio_suspend_mask; + +static int mainstone_suspend(struct platform_device *pdev, pm_message_t state) +{ + mst_audio_suspend_mask = MST_MSCWR2; + MST_MSCWR2 |= MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_resume(struct platform_device *pdev) +{ + MST_MSCWR2 &= mst_audio_suspend_mask | ~MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_probe(struct platform_device *pdev) +{ + MST_MSCWR2 &= ~MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_remove(struct platform_device *pdev) +{ + MST_MSCWR2 |= MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +/* example machine audio_mapnections */ +static const char* audio_map[][3] = { + + /* mic is connected to mic1 - with bias */ + {"MIC1", NULL, "Mic Bias"}, + {"MIC1N", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic1 Jack"}, + {"Mic Bias", NULL, "Mic1 Jack"}, + + {"ACIN", NULL, "ACOP"}, + {NULL, NULL, NULL}, +}; + +/* headphone detect support on my board */ +static const char * hp_pol[] = {"Headphone", "Speaker"}; +static const struct soc_enum wm8753_enum = + SOC_ENUM_SINGLE(WM8753_OUTCTL, 1, 2, hp_pol); + +static const struct snd_kcontrol_new wm8753_mainstone_controls[] = { + SOC_SINGLE("Headphone Detect Switch", WM8753_OUTCTL, 6, 1, 0), + SOC_ENUM("Headphone Detect Polarity", wm8753_enum), +}; + +/* + * This is an example machine initialisation for a wm8753 connected to a + * Mainstone II. It is missing logic to detect hp/mic insertions and logic + * to re-route the audio in such an event. + */ +static int mainstone_wm8753_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* set up mainstone codec pins */ + snd_soc_dapm_set_endpoint(codec, "RXP", 0); + snd_soc_dapm_set_endpoint(codec, "RXN", 0); + snd_soc_dapm_set_endpoint(codec, "MIC2", 0); + + /* add mainstone specific controls */ + for (i = 0; i < ARRAY_SIZE(wm8753_mainstone_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8753_mainstone_controls[i],codec, NULL))) < 0) + return err; + } + + /* set up mainstone specific audio path audio_mapnects */ + 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_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_dai_link mainstone_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_HIFI], + .init = mainstone_wm8753_init, + .ops = &mainstone_hifi_ops, +}, +{ /* Voice via BT */ + .name = "Bluetooth", + .stream_name = "Voice", + .cpu_dai = &pxa_ssp_dai[1], + .codec_dai = &wm8753_dai[WM8753_DAI_VOICE], + .ops = &mainstone_voice_ops, +}, +}; + +static struct snd_soc_machine mainstone = { + .name = "Mainstone", + .probe = mainstone_probe, + .remove = mainstone_remove, + .suspend_pre = mainstone_suspend, + .resume_post = mainstone_resume, + .dai_link = mainstone_dai, + .num_links = ARRAY_SIZE(mainstone_dai), +}; + +static struct wm8753_setup_data mainstone_wm8753_setup = { + .i2c_address = 0x1a, +}; + +static struct snd_soc_device mainstone_snd_devdata = { + .machine = &mainstone, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm8753, + .codec_data = &mainstone_wm8753_setup, +}; + +static struct platform_device *mainstone_snd_device; + +static int __init mainstone_init(void) +{ + int ret; + + mainstone_snd_device = platform_device_alloc("soc-audio", -1); + if (!mainstone_snd_device) + return -ENOMEM; + + platform_set_drvdata(mainstone_snd_device, &mainstone_snd_devdata); + mainstone_snd_devdata.dev = &mainstone_snd_device->dev; + ret = platform_device_add(mainstone_snd_device); + + if (ret) + platform_device_put(mainstone_snd_device); + + /* SSP port 2 slave */ + pxa_gpio_mode(GPIO11_SSP2RX_MD); + pxa_gpio_mode(GPIO13_SSP2TX_MD); + pxa_gpio_mode(GPIO22_SSP2CLKS_MD); + pxa_gpio_mode(GPIO88_SSP2FRMS_MD); + + return ret; +} + +static void __exit mainstone_exit(void) +{ + platform_device_unregister(mainstone_snd_device); +} + +module_init(mainstone_init); +module_exit(mainstone_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM8753 Mainstone"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/pxa/mainstone_wm8974.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/mainstone_wm8974.c @@ -0,0 +1,104 @@ +/* + * mainstone.c -- SoC audio for Mainstone + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Mainstone audio amplifier code taken from arch/arm/mach-pxa/mainstone.c + * Copyright: MontaVista Software Inc. + * + * 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. + * + * Revision history + * 30th Oct 2005 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/wm8974.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" + +static struct snd_soc_machine mainstone; + +static int mainstone_wm8974_init(struct snd_soc_codec *codec) +{ + return 0; +} + +static struct snd_soc_dai_link mainstone_dai[] = { +{ + .name = "WM8974", + .stream_name = "WM8974 HiFi", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &wm8974_dai, + .init = mainstone_wm8974_init, +}, +}; + +static struct snd_soc_machine mainstone = { + .name = "Mainstone", + .dai_link = mainstone_dai, + .num_links = ARRAY_SIZE(mainstone_dai), +}; + +static struct wm8974_setup_data mainstone_wm8974_setup = { + .i2c_address = 0x1a, +}; + +static struct snd_soc_device mainstone_snd_devdata = { + .machine = &mainstone, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm8974, + .codec_data = &mainstone_wm8974_setup, +}; + +static struct platform_device *mainstone_snd_device; + +static int __init mainstone_init(void) +{ + int ret; + + mainstone_snd_device = platform_device_alloc("soc-audio", -1); + if (!mainstone_snd_device) + return -ENOMEM; + + platform_set_drvdata(mainstone_snd_device, &mainstone_snd_devdata); + mainstone_snd_devdata.dev = &mainstone_snd_device->dev; + ret = platform_device_add(mainstone_snd_device); + + if (ret) + platform_device_put(mainstone_snd_device); + + return ret; +} + +static void __exit mainstone_exit(void) +{ + platform_device_unregister(mainstone_snd_device); +} + +module_init(mainstone_init); +module_exit(mainstone_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC Mainstone"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/pxa/mainstone_wm9712.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/mainstone_wm9712.c @@ -0,0 +1,172 @@ +/* + * mainstone.c -- SoC audio for Mainstone + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Mainstone audio amplifier code taken from arch/arm/mach-pxa/mainstone.c + * Copyright: MontaVista Software Inc. + * + * 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. + * + * Revision history + * 29th Jan 2006 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/wm9712.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-ac97.h" + +static struct snd_soc_machine mainstone; +static long mst_audio_suspend_mask; + +static int mainstone_suspend(struct platform_device *pdev, pm_message_t state) +{ + mst_audio_suspend_mask = MST_MSCWR2; + MST_MSCWR2 |= MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_resume(struct platform_device *pdev) +{ + MST_MSCWR2 &= mst_audio_suspend_mask | ~MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_probe(struct platform_device *pdev) +{ + MST_MSCWR2 &= ~MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_remove(struct platform_device *pdev) +{ + MST_MSCWR2 |= MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +/* mainstone machine dapm widgets */ +static const struct snd_soc_dapm_widget mainstone_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic (Internal)", NULL), +}; + +/* example machine interconnections */ +static const char* intercon[][3] = { + + /* mic is connected to mic1 - with bias */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic (Internal)"}, + + {NULL, NULL, NULL}, +}; + +/* + * This is an example machine initialisation for a wm8753 connected to a + * Mainstone II. It is missing logic to detect hp/mic insertions and logic + * to re-route the audio in such an event. + */ +static int mainstone_wm9712_init(struct snd_soc_codec *codec) +{ + int i; + + /* set up mainstone codec pins */ + snd_soc_dapm_set_endpoint(codec, "RXP", 0); + snd_soc_dapm_set_endpoint(codec, "RXN", 0); + //snd_soc_dapm_set_endpoint(codec, "MIC2", 0); + + /* Add mainstone specific widgets */ + for(i = 0; i < ARRAY_SIZE(mainstone_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &mainstone_dapm_widgets[i]); + } + + /* set up mainstone specific audio path interconnects */ + for(i = 0; intercon[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, intercon[i][0], intercon[i][1], intercon[i][2]); + } + + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_dai_link mainstone_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI], + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI], + .init = mainstone_wm9712_init, +}, +{ + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX], + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX], +}, +}; + +static struct snd_soc_machine mainstone = { + .name = "Mainstone", + .probe = mainstone_probe, + .remove = mainstone_remove, + .suspend_pre = mainstone_suspend, + .resume_post = mainstone_resume, + .dai_link = mainstone_dai, + .num_links = ARRAY_SIZE(mainstone_dai), +}; + +static struct snd_soc_device mainstone_snd_ac97_devdata = { + .machine = &mainstone, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm9712, +}; + +static struct platform_device *mainstone_snd_ac97_device; + +static int __init mainstone_init(void) +{ + int ret; + + mainstone_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!mainstone_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(mainstone_snd_ac97_device, &mainstone_snd_ac97_devdata); + mainstone_snd_ac97_devdata.dev = &mainstone_snd_ac97_device->dev; + + if((ret = platform_device_add(mainstone_snd_ac97_device)) != 0) + platform_device_put(mainstone_snd_ac97_device); + + return ret; +} + +static void __exit mainstone_exit(void) +{ + platform_device_unregister(mainstone_snd_ac97_device); +} + +module_init(mainstone_init); +module_exit(mainstone_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM9712 Mainstone"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/pxa/mainstone_wm9713.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/mainstone_wm9713.c @@ -0,0 +1,318 @@ +/* + * mainstone.c -- SoC audio for Mainstone + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Mainstone audio amplifier code taken from arch/arm/mach-pxa/mainstone.c + * Copyright: MontaVista Software Inc. + * + * 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. + * + * Revision history + * 29th Jan 2006 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/wm9713.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-ac97.h" +#include "pxa2xx-ssp.h" + +#define GPIO11_SSP2RX_MD (11 | GPIO_ALT_FN_2_IN) +#define GPIO13_SSP2TX_MD (13 | GPIO_ALT_FN_1_OUT) +#define GPIO22_SSP2CLKS_MD (22 | GPIO_ALT_FN_3_IN) +#define GPIO88_SSP2FRMS_MD (88 | GPIO_ALT_FN_3_IN) +#define GPIO22_SSP2CLKM_MD (22 | GPIO_ALT_FN_3_OUT) +#define GPIO88_SSP2FRMM_MD (88 | GPIO_ALT_FN_3_OUT) +#define GPIO22_SSP2SYSCLK_MD (22 | GPIO_ALT_FN_2_OUT) + +static struct snd_soc_machine mainstone; + +static int mainstone_voice_startup(struct snd_pcm_substream *substream) +{ + /* enable USB on the go MUX so we can use SSPFRM2 */ + MST_MSCWR2 |= MST_MSCWR2_USB_OTG_SEL; + MST_MSCWR2 &= ~MST_MSCWR2_USB_OTG_RST; + return 0; +} + +static void mainstone_voice_shutdown(struct snd_pcm_substream *substream) +{ + /* disable USB on the go MUX so we can use ttyS0 */ + MST_MSCWR2 &= ~MST_MSCWR2_USB_OTG_SEL; + MST_MSCWR2 |= MST_MSCWR2_USB_OTG_RST; +} + +static int mainstone_voice_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; + unsigned int bclk = 0, pcmdiv = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + pcmdiv = WM9713_PCMDIV(12); /* 2.048 MHz */ + bclk = WM9713_PCMBCLK_DIV_16; /* 128kHz */ + break; + case 16000: + pcmdiv = WM9713_PCMDIV(6); /* 4.096 MHz */ + bclk = WM9713_PCMBCLK_DIV_16; /* 256kHz */ + break; + case 48000: + pcmdiv = WM9713_PCMDIV(2); /* 12.288 MHz */ + bclk = WM9713_PCMBCLK_DIV_16; /* 512kHz */ + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the SSP system clock as input (unused) */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_SSP_CLK_PLL, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM9713_PCMBCLK_DIV, bclk); + if (ret < 0) + return ret; + + /* set codec PCM division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM9713_PCMCLK_DIV, pcmdiv); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops mainstone_voice_ops = { + .startup = mainstone_voice_startup, + .shutdown = mainstone_voice_shutdown, + .hw_params = mainstone_voice_hw_params, +}; + +static int test = 0; +static int get_test(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = test; + return 0; +} + +static int set_test(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + test = ucontrol->value.integer.value[0]; + if(test) { + + } else { + + } + return 0; +} + +static long mst_audio_suspend_mask; + +static int mainstone_suspend(struct platform_device *pdev, pm_message_t state) +{ + mst_audio_suspend_mask = MST_MSCWR2; + MST_MSCWR2 |= MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_resume(struct platform_device *pdev) +{ + MST_MSCWR2 &= mst_audio_suspend_mask | ~MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_probe(struct platform_device *pdev) +{ + MST_MSCWR2 &= ~MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static int mainstone_remove(struct platform_device *pdev) +{ + MST_MSCWR2 |= MST_MSCWR2_AC97_SPKROFF; + return 0; +} + +static const char* test_function[] = {"Off", "On"}; +static const struct soc_enum mainstone_enum[] = { + SOC_ENUM_SINGLE_EXT(2, test_function), +}; + +static const struct snd_kcontrol_new mainstone_controls[] = { + SOC_ENUM_EXT("ATest Function", mainstone_enum[0], get_test, set_test), +}; + +/* mainstone machine dapm widgets */ +static const struct snd_soc_dapm_widget mainstone_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic 1", NULL), + SND_SOC_DAPM_MIC("Mic 2", NULL), + SND_SOC_DAPM_MIC("Mic 3", NULL), +}; + +/* example machine audio_mapnections */ +static const char* audio_map[][3] = { + + /* mic is connected to mic1 - with bias */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic 1"}, + /* mic is connected to mic2A - with bias */ + {"MIC2A", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic 2"}, + /* mic is connected to mic2B - with bias */ + {"MIC2B", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic 3"}, + + {NULL, NULL, NULL}, +}; + +/* + * This is an example machine initialisation for a wm9713 connected to a + * Mainstone II. It is missing logic to detect hp/mic insertions and logic + * to re-route the audio in such an event. + */ +static int mainstone_wm9713_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* set up mainstone codec pins */ + snd_soc_dapm_set_endpoint(codec, "RXP", 0); + snd_soc_dapm_set_endpoint(codec, "RXN", 0); + //snd_soc_dapm_set_endpoint(codec, "MIC2", 0); + + /* Add test specific controls */ + for (i = 0; i < ARRAY_SIZE(mainstone_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&mainstone_controls[i],codec, NULL))) < 0) + return err; + } + + /* Add mainstone specific widgets */ + for(i = 0; i < ARRAY_SIZE(mainstone_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &mainstone_dapm_widgets[i]); + } + + /* set up mainstone specific audio path audio_mapnects */ + 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_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_dai_link mainstone_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI], + .codec_dai = &wm9713_dai[WM9713_DAI_AC97_HIFI], + .init = mainstone_wm9713_init, +}, +{ + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX], + .codec_dai = &wm9713_dai[WM9713_DAI_AC97_AUX], +}, +{ + .name = "WM9713", + .stream_name = "WM9713 Voice", + .cpu_dai = &pxa_ssp_dai[PXA2XX_DAI_SSP2], + .codec_dai = &wm9713_dai[WM9713_DAI_PCM_VOICE], + .ops = &mainstone_voice_ops, +}, +}; + +static struct snd_soc_machine mainstone = { + .name = "Mainstone", + .probe = mainstone_probe, + .remove = mainstone_remove, + .suspend_pre = mainstone_suspend, + .resume_post = mainstone_resume, + .dai_link = mainstone_dai, + .num_links = ARRAY_SIZE(mainstone_dai), +}; + +static struct snd_soc_device mainstone_snd_ac97_devdata = { + .machine = &mainstone, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm9713, +}; + +static struct platform_device *mainstone_snd_ac97_device; + +static int __init mainstone_init(void) +{ + int ret; + + mainstone_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!mainstone_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(mainstone_snd_ac97_device, &mainstone_snd_ac97_devdata); + mainstone_snd_ac97_devdata.dev = &mainstone_snd_ac97_device->dev; + + if((ret = platform_device_add(mainstone_snd_ac97_device)) != 0) + platform_device_put(mainstone_snd_ac97_device); + + /* SSP port 2 slave */ + pxa_gpio_mode(GPIO11_SSP2RX_MD); + pxa_gpio_mode(GPIO13_SSP2TX_MD); + pxa_gpio_mode(GPIO22_SSP2CLKS_MD); + pxa_gpio_mode(GPIO88_SSP2FRMS_MD); + + return ret; +} + +static void __exit mainstone_exit(void) +{ + platform_device_unregister(mainstone_snd_ac97_device); +} + +module_init(mainstone_init); +module_exit(mainstone_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM9713 Mainstone"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/pxa/pxa2xx-ssp.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/pxa2xx-ssp.c @@ -0,0 +1,666 @@ +/* + * pxa2xx-ssp.c -- ALSA Soc Audio Layer + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 12th Aug 2005 Initial version. + * + * TODO: + * o Test network mode for > 16bit sample size + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "pxa2xx-pcm.h" +#include "pxa2xx-ssp.h" + +#define PXA_SSP_DEBUG 0 + +#if PXA_SSP_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +/* + * SSP audio private data + */ +struct ssp_priv { + unsigned int sysclk; +}; + +static struct ssp_priv ssp_clk[3]; +static struct ssp_dev ssp[3]; +#ifdef CONFIG_PM +static struct ssp_state ssp_state[3]; +#endif + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp1_pcm_mono_out = { + .name = "SSP1 PCM Mono out", + .dev_addr = __PREG(SSDR_P1), + .drcmr = &DRCMRTXSSDR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp1_pcm_mono_in = { + .name = "SSP1 PCM Mono in", + .dev_addr = __PREG(SSDR_P1), + .drcmr = &DRCMRRXSSDR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp1_pcm_stereo_out = { + .name = "SSP1 PCM Stereo out", + .dev_addr = __PREG(SSDR_P1), + .drcmr = &DRCMRTXSSDR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp1_pcm_stereo_in = { + .name = "SSP1 PCM Stereo in", + .dev_addr = __PREG(SSDR_P1), + .drcmr = &DRCMRRXSSDR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp2_pcm_mono_out = { + .name = "SSP2 PCM Mono out", + .dev_addr = __PREG(SSDR_P2), + .drcmr = &DRCMRTXSS2DR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp2_pcm_mono_in = { + .name = "SSP2 PCM Mono in", + .dev_addr = __PREG(SSDR_P2), + .drcmr = &DRCMRRXSS2DR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp2_pcm_stereo_out = { + .name = "SSP2 PCM Stereo out", + .dev_addr = __PREG(SSDR_P2), + .drcmr = &DRCMRTXSS2DR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp2_pcm_stereo_in = { + .name = "SSP2 PCM Stereo in", + .dev_addr = __PREG(SSDR_P2), + .drcmr = &DRCMRRXSS2DR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp3_pcm_mono_out = { + .name = "SSP3 PCM Mono out", + .dev_addr = __PREG(SSDR_P3), + .drcmr = &DRCMRTXSS3DR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp3_pcm_mono_in = { + .name = "SSP3 PCM Mono in", + .dev_addr = __PREG(SSDR_P3), + .drcmr = &DRCMRRXSS3DR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp3_pcm_stereo_out = { + .name = "SSP3 PCM Stereo out", + .dev_addr = __PREG(SSDR_P3), + .drcmr = &DRCMRTXSS3DR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp3_pcm_stereo_in = { + .name = "SSP3 PCM Stereo in", + .dev_addr = __PREG(SSDR_P3), + .drcmr = &DRCMRRXSS3DR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params *ssp_dma_params[3][4] = { + {&pxa2xx_ssp1_pcm_mono_out, &pxa2xx_ssp1_pcm_mono_in, + &pxa2xx_ssp1_pcm_stereo_out,&pxa2xx_ssp1_pcm_stereo_in,}, + {&pxa2xx_ssp2_pcm_mono_out, &pxa2xx_ssp2_pcm_mono_in, + &pxa2xx_ssp2_pcm_stereo_out, &pxa2xx_ssp2_pcm_stereo_in,}, + {&pxa2xx_ssp3_pcm_mono_out, &pxa2xx_ssp3_pcm_mono_in, + &pxa2xx_ssp3_pcm_stereo_out,&pxa2xx_ssp3_pcm_stereo_in,}, +}; + +static int pxa2xx_ssp_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + + if (!rtd->dai->cpu_dai->active) { + ret = ssp_init (&ssp[cpu_dai->id], cpu_dai->id + 1, + SSP_NO_IRQ); + if (ret < 0) + return ret; + ssp_disable(&ssp[cpu_dai->id]); + } + return ret; +} + +static void pxa2xx_ssp_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + + if (!cpu_dai->active) { + ssp_disable(&ssp[cpu_dai->id]); + ssp_exit(&ssp[cpu_dai->id]); + } +} + +#if defined (CONFIG_PXA27x) +static int cken[3] = {CKEN23_SSP1, CKEN3_SSP2, CKEN4_SSP3}; +#else +static int cken[3] = {CKEN3_SSP, CKEN9_NSSP, CKEN10_ASSP}; +#endif + +#ifdef CONFIG_PM + +static int pxa2xx_ssp_suspend(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ + if (!dai->active) + return 0; + + ssp_save_state(&ssp[dai->id], &ssp_state[dai->id]); + pxa_set_cken(cken[dai->id], 0); + return 0; +} + +static int pxa2xx_ssp_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ + if (!dai->active) + return 0; + + pxa_set_cken(cken[dai->id], 1); + ssp_restore_state(&ssp[dai->id], &ssp_state[dai->id]); + ssp_enable(&ssp[dai->id]); + + return 0; +} + +#else +#define pxa2xx_ssp_suspend NULL +#define pxa2xx_ssp_resume NULL +#endif + +/* + * Set the SSP ports SYSCLK. + */ +static int pxa2xx_ssp_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + int port = cpu_dai->id + 1; + u32 sscr0 = SSCR0_P(port) & + ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ADC); + + dbg("pxa2xx_ssp_set_dai_sysclk id: %d, clk_id %d, freq %d", + cpu_dai->id, clk_id, freq); + + switch (clk_id) { + case PXA2XX_SSP_CLK_NET_PLL: + sscr0 |= SSCR0_MOD; + case PXA2XX_SSP_CLK_PLL: + /* Internal PLL is fixed on pxa25x and pxa27x */ +#ifdef CONFIG_PXA27x + ssp_clk[cpu_dai->id].sysclk = 13000000; +#else + ssp_clk[cpu_dai->id].sysclk = 1843200; +#endif + break; + case PXA2XX_SSP_CLK_EXT: + ssp_clk[cpu_dai->id].sysclk = freq; + sscr0 |= SSCR0_ECS; + break; + case PXA2XX_SSP_CLK_NET: + ssp_clk[cpu_dai->id].sysclk = freq; + sscr0 |= SSCR0_NCS | SSCR0_MOD; + break; + case PXA2XX_SSP_CLK_AUDIO: + ssp_clk[cpu_dai->id].sysclk = 0; + SSCR0_P(port) |= SSCR0_SerClkDiv(1); + sscr0 |= SSCR0_ADC; + break; + default: + return -ENODEV; + } + + /* the SSP CKEN clock must be disabled when changing SSP clock mode */ + pxa_set_cken(cken[cpu_dai->id], 0); + SSCR0_P(port) |= sscr0; + pxa_set_cken(cken[cpu_dai->id], 1); + return 0; +} + +/* + * Set the SSP clock dividers. + */ +static int pxa2xx_ssp_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai, + int div_id, int div) +{ + int port = cpu_dai->id + 1; + + switch (div_id) { + case PXA2XX_SSP_AUDIO_DIV_ACDS: + SSACD_P(port) &= ~ 0x7; + SSACD_P(port) |= SSACD_ACDS(div); + break; + case PXA2XX_SSP_AUDIO_DIV_SCDB: + SSACD_P(port) &= ~0x8; + if (div == PXA2XX_SSP_CLK_SCDB_1) + SSACD_P(port) |= SSACD_SCDB; + break; + case PXA2XX_SSP_DIV_SCR: + SSCR0_P(port) &= ~SSCR0_SCR; + SSCR0_P(port) |= SSCR0_SerClkDiv(div); + break; + default: + return -ENODEV; + } + + return 0; +} + +/* + * Configure the PLL frequency pxa27x and (afaik - pxa320 only) + */ +static int pxa2xx_ssp_set_dai_pll(struct snd_soc_cpu_dai *cpu_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + int port = cpu_dai->id + 1; + + SSACD_P(port) &= ~0x70; + switch (freq_out) { + case 5622000: + break; + case 11345000: + SSACD_P(port) |= (0x1 << 4); + break; + case 12235000: + SSACD_P(port) |= (0x2 << 4); + break; + case 14857000: + SSACD_P(port) |= (0x3 << 4); + break; + case 32842000: + SSACD_P(port) |= (0x4 << 4); + break; + case 48000000: + SSACD_P(port) |= (0x5 << 4); + break; + } + return 0; +} + +/* + * Set the active slots in TDM/Network mode + */ +static int pxa2xx_ssp_set_dai_tdm_slot(struct snd_soc_cpu_dai *cpu_dai, + unsigned int mask, int slots) +{ + int port = cpu_dai->id + 1; + + SSCR0_P(port) &= ~SSCR0_SlotsPerFrm(7); + + /* set number of active slots */ + SSCR0_P(port) |= SSCR0_SlotsPerFrm(slots); + + /* set active slot mask */ + SSTSA_P(port) = mask; + SSRSA_P(port) = mask; + return 0; +} + +/* + * Tristate the SSP DAI lines + */ +static int pxa2xx_ssp_set_dai_tristate(struct snd_soc_cpu_dai *cpu_dai, + int tristate) +{ + int port = cpu_dai->id + 1; + + if (tristate) + SSCR1_P(port) &= ~SSCR1_TTE; + else + SSCR1_P(port) |= SSCR1_TTE; + + return 0; +} + +/* + * Set up the SSP DAI format. + * The SSP Port must be inactive before calling this function as the + * physical interface format is changed. + */ +static int pxa2xx_ssp_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, + unsigned int fmt) +{ + int port = cpu_dai->id + 1; + + /* reset port settings */ + SSCR0_P(port) = 0; + SSCR1_P(port) = 0; + SSPSP_P(port) = 0; + + /* NOTE: I2S emulation is still very much work in progress here */ + + /* FIXME: this is what wince uses for msb */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_MSB) { + SSCR0_P(port) = SSCR0_EDSS | SSCR0_TISSP | SSCR0_DataSize(16); + goto master; + } + + /* check for I2S emulation mode - handle it separately */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) { + /* 8.4.11 */ + + /* Only SSCR0[NCS] or SSCR0[ECS] bit fields settings are optional */ + SSCR0_P(port) = SSCR0_EDSS | SSCR0_PSP | SSCR0_DataSize(16); + + /* set FIFO thresholds */ + SSCR1_P(port) = SSCR1_RxTresh(14) | SSCR1_TxTresh(1); + + /* normal: */ + /* all bit fields must be cleared except: FSRT = 1 and + * SFRMWDTH = 16, DMYSTART=0,1) */ + SSPSP_P(port) = SSPSP_FSRT | SSPSP_SFRMWDTH(16) | SSPSP_DMYSTRT(0); + goto master; + } + + SSCR0_P(port) |= SSCR0_PSP; + SSCR1_P(port) = SSCR1_RxTresh(14) | SSCR1_TxTresh(1) | + SSCR1_TRAIL | SSCR1_RWOT; + +master: + switch(fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + SSCR1_P(port) |= (SSCR1_SCLKDIR | SSCR1_SFRMDIR); + break; + case SND_SOC_DAIFMT_CBM_CFS: + SSCR1_P(port) |= SSCR1_SCLKDIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + SSCR1_P(port) |= SSCR1_SFRMDIR; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + SSPSP_P(port) |= SSPSP_SFRMP | SSPSP_FSRT; + break; + case SND_SOC_DAIFMT_IB_IF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + SSPSP_P(port) |= SSPSP_DMYSTRT(1); + case SND_SOC_DAIFMT_DSP_B: + SSPSP_P(port) |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_MSB: + /* handled above */ + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set the SSP audio DMA parameters and sample size. + * Can be called multiple times by oss emulation. + */ +static int pxa2xx_ssp_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_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int dma = 0, chn = params_channels(params); + int port = cpu_dai->id + 1; + + /* select correct DMA params */ + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + dma = 1; /* capture DMA offset is 1,3 */ + if (chn == 2) + dma += 2; /* stereo DMA offset is 2, mono is 0 */ + cpu_dai->dma_data = ssp_dma_params[cpu_dai->id][dma]; + + dbg("pxa2xx_ssp_hw_params: dma %d", dma); + + /* we can only change the settings if the port is not in use */ + if (SSCR0_P(port) & SSCR0_SSE) + return 0; + + /* clear selected SSP bits */ + SSCR0_P(port) &= ~(SSCR0_DSS | SSCR0_EDSS); + + /* bit size */ + switch(params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + SSCR0_P(port) |= SSCR0_DataSize(16); + break; + case SNDRV_PCM_FORMAT_S24_LE: + SSCR0_P(port) |=(SSCR0_EDSS | SSCR0_DataSize(8)); + /* we must be in network mode (2 slots) for 24 bit stereo */ + break; + case SNDRV_PCM_FORMAT_S32_LE: + SSCR0_P(port) |= (SSCR0_EDSS | SSCR0_DataSize(16)); + /* we must be in network mode (2 slots) for 32 bit stereo */ + break; + } + + dbg("SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x SSPSP 0x%08x SSSR 0x%08x SSACD 0x%08x", + SSCR0_P(port), SSCR1_P(port), + SSTO_P(port), SSPSP_P(port), + SSSR_P(port), SSACD_P(port)); + + return 0; +} + +static int pxa2xx_ssp_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + int port = cpu_dai->id + 1; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + ssp_enable(&ssp[cpu_dai->id]); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SSCR1_P(port) |= SSCR1_TSRE; + else + SSCR1_P(port) |= SSCR1_RSRE; + SSSR_P(port) |= SSSR_P(port); + break; + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SSCR1_P(port) |= SSCR1_TSRE; + else + SSCR1_P(port) |= SSCR1_RSRE; + ssp_enable(&ssp[cpu_dai->id]); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SSCR1_P(port) &= ~SSCR1_TSRE; + else + SSCR1_P(port) &= ~SSCR1_RSRE; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + ssp_disable(&ssp[cpu_dai->id]); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SSCR1_P(port) &= ~SSCR1_TSRE; + else + SSCR1_P(port) &= ~SSCR1_RSRE; + break; + + default: + ret = -EINVAL; + } + + dbg("SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x SSPSP 0x%08x SSSR 0x%08x", + SSCR0_P(port), SSCR1_P(port), + SSTO_P(port), SSPSP_P(port), + SSSR_P(port)); + + return ret; +} + +#define PXA2XX_SSP_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define PXA2XX_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_cpu_dai pxa_ssp_dai[] = { + { .name = "pxa2xx-ssp1", + .id = 0, + .type = SND_SOC_DAI_PCM, + .suspend = pxa2xx_ssp_suspend, + .resume = pxa2xx_ssp_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .ops = { + .startup = pxa2xx_ssp_startup, + .shutdown = pxa2xx_ssp_shutdown, + .trigger = pxa2xx_ssp_trigger, + .hw_params = pxa2xx_ssp_hw_params,}, + .dai_ops = { + .set_sysclk = pxa2xx_ssp_set_dai_sysclk, + .set_clkdiv = pxa2xx_ssp_set_dai_clkdiv, + .set_pll = pxa2xx_ssp_set_dai_pll, + .set_fmt = pxa2xx_ssp_set_dai_fmt, + .set_tdm_slot = pxa2xx_ssp_set_dai_tdm_slot, + .set_tristate = pxa2xx_ssp_set_dai_tristate, + }, + }, + { .name = "pxa2xx-ssp2", + .id = 1, + .type = SND_SOC_DAI_PCM, + .suspend = pxa2xx_ssp_suspend, + .resume = pxa2xx_ssp_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .ops = { + .startup = pxa2xx_ssp_startup, + .shutdown = pxa2xx_ssp_shutdown, + .trigger = pxa2xx_ssp_trigger, + .hw_params = pxa2xx_ssp_hw_params,}, + .dai_ops = { + .set_sysclk = pxa2xx_ssp_set_dai_sysclk, + .set_clkdiv = pxa2xx_ssp_set_dai_clkdiv, + .set_pll = pxa2xx_ssp_set_dai_pll, + .set_fmt = pxa2xx_ssp_set_dai_fmt, + .set_tdm_slot = pxa2xx_ssp_set_dai_tdm_slot, + .set_tristate = pxa2xx_ssp_set_dai_tristate, + }, + }, + { .name = "pxa2xx-ssp3", + .id = 2, + .type = SND_SOC_DAI_PCM, + .suspend = pxa2xx_ssp_suspend, + .resume = pxa2xx_ssp_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .ops = { + .startup = pxa2xx_ssp_startup, + .shutdown = pxa2xx_ssp_shutdown, + .trigger = pxa2xx_ssp_trigger, + .hw_params = pxa2xx_ssp_hw_params,}, + .dai_ops = { + .set_sysclk = pxa2xx_ssp_set_dai_sysclk, + .set_clkdiv = pxa2xx_ssp_set_dai_clkdiv, + .set_pll = pxa2xx_ssp_set_dai_pll, + .set_fmt = pxa2xx_ssp_set_dai_fmt, + .set_tdm_slot = pxa2xx_ssp_set_dai_tdm_slot, + .set_tristate = pxa2xx_ssp_set_dai_tristate, + }, + }, +}; +EXPORT_SYMBOL_GPL(pxa_ssp_dai); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("pxa2xx SSP/PCM SoC Interface"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/imx/imx-ssi.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/imx-ssi.c @@ -0,0 +1,591 @@ +/* + * imx-ssi.c -- SSI driver for Freescale IMX + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Based on mxc-alsa-mc13783 (C) 2006 Freescale. + * + * 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. + * + * Revision history + * 29th Aug 2006 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-ssi.h" +#include "imx31-pcm.h" + +static struct mxc_pcm_dma_params imx_ssi1_pcm_stereo_out = { + .name = "SSI1 PCM Stereo out", + .params = { + .bd_number = 1, + .transfer_type = emi_2_per, + .watermark_level = SDMA_TXFIFO_WATERMARK, + .word_size = TRANSFER_16BIT, // maybe add this in setup func + .per_address = SSI1_STX0, + .event_id = DMA_REQ_SSI1_TX1, + .peripheral_type = SSI, + }, +}; + +static struct mxc_pcm_dma_params imx_ssi1_pcm_stereo_in = { + .name = "SSI1 PCM Stereo in", + .params = { + .bd_number = 1, + .transfer_type = per_2_emi, + .watermark_level = SDMA_RXFIFO_WATERMARK, + .word_size = TRANSFER_16BIT, // maybe add this in setup func + .per_address = SSI1_SRX0, + .event_id = DMA_REQ_SSI1_RX1, + .peripheral_type = SSI, + }, +}; + +static struct mxc_pcm_dma_params imx_ssi2_pcm_stereo_out = { + .name = "SSI2 PCM Stereo out", + .params = { + .bd_number = 1, + .transfer_type = per_2_emi, + .watermark_level = SDMA_TXFIFO_WATERMARK, + .word_size = TRANSFER_16BIT, // maybe add this in setup func + .per_address = SSI2_STX0, + .event_id = DMA_REQ_SSI2_TX1, + .peripheral_type = SSI, + }, +}; + +static struct mxc_pcm_dma_params imx_ssi2_pcm_stereo_in = { + .name = "SSI2 PCM Stereo in", + .params = { + .bd_number = 1, + .transfer_type = per_2_emi, + .watermark_level = SDMA_RXFIFO_WATERMARK, + .word_size = TRANSFER_16BIT, // maybe add this in setup func + .per_address = SSI2_SRX0, + .event_id = DMA_REQ_SSI2_RX1, + .peripheral_type = SSI, + }, +}; + +/* + * SSI system clock configuration. + */ +static int imx_ssi_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + u32 scr; + + if (cpu_dai->id == IMX_DAI_SSI1) + scr = __raw_readw(SSI1_SCR); + else + scr = __raw_readw(SSI2_SCR); + + switch (clk_id) { + case IMX_SSP_SYS_CLK: + if (dir == SND_SOC_CLOCK_OUT) + scr |= SSI_SCR_SYS_CLK_EN; + else + scr &= ~SSI_SCR_SYS_CLK_EN; + break; + default: + return -EINVAL; + } + + if (cpu_dai->id == IMX_DAI_SSI1) + __raw_writew(scr, SSI1_SCR); + else + __raw_writew(scr, SSI2_SCR); + + return 0; +} + +/* + * SSI Clock dividers + */ +static int imx_ssi_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai, + int div_id, int div) +{ + u32 stccr; + + if (cpu_dai->id == IMX_DAI_SSI1) + stccr = __raw_readw(SSI1_STCCR); + else + stccr = __raw_readw(SSI2_STCCR); + + switch (div_id) { + case IMX_SSI_DIV_2: + stccr &= ~SSI_STCCR_DIV2; + stccr |= div; + break; + case IMX_SSI_DIV_PSR: + stccr &= ~SSI_STCCR_PSR; + stccr |= div; + break; + case IMX_SSI_DIV_PM: + stccr &= ~0xff; + stccr |= SSI_STCCR_PM(div); + break; + default: + return -EINVAL; + } + + if (cpu_dai->id == IMX_DAI_SSI1) + __raw_writew(stccr, SSI1_STCCR); + else + __raw_writew(stccr, SSI2_STCCR); + + return 0; +} + +/* + * SSI Network Mode or TDM slots configuration. + */ +static int imx_ssi_set_dai_tdm_slot(struct snd_soc_cpu_dai *cpu_dai, + unsigned int mask, int slots) +{ + u32 stmsk, srmsk, scr, stccr; + + if (cpu_dai->id == IMX_DAI_SSI1) { + stmsk = __raw_readw(SSI1_STMSK); + srmsk = __raw_readw(SSI1_SRMSK); + scr = __raw_readw(SSI1_SCR); + stccr = __raw_readw(SSI1_STCCR); + } else { + stmsk = __raw_readw(SSI2_STMSK); + srmsk = __raw_readw(SSI2_SRMSK); + scr = __raw_readw(SSI2_SCR); + stccr = __raw_readw(SSI2_STCCR); + } + + stmsk = srmsk = mask; + scr |= SSI_SCR_NET; + stccr &= ~0x1f00; + stccr |= SSI_STCCR_DC(slots); + + if (cpu_dai->id == IMX_DAI_SSI1) { + __raw_writew(stmsk, SSI1_STMSK); + __raw_writew(srmsk, SSI1_SRMSK); + __raw_writew(scr, SSI1_SCR); + __raw_writew(stccr, SSI1_STCCR); + } else { + __raw_writew(stmsk, SSI2_STMSK); + __raw_writew(srmsk, SSI2_SRMSK); + __raw_writew(scr, SSI2_SCR); + __raw_writew(stccr, SSI2_STCCR); + } + + return 0; +} + +/* + * SSI DAI format configuration. + */ +static int imx_ssi_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, + unsigned int fmt) +{ + u32 stcr = 0, srcr = 0; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + stcr |= SSI_STCR_TSCKP | SSI_STCR_TFSI | + SSI_STCR_TEFS | SSI_STCR_TXBIT0; + srcr |= SSI_SRCR_RSCKP | SSI_SRCR_RFSI | + SSI_SRCR_REFS | SSI_SRCR_RXBIT0; + break; + case SND_SOC_DAIFMT_LEFT_J: + stcr |= SSI_STCR_TSCKP | SSI_STCR_TFSI | SSI_STCR_TXBIT0; + srcr |= SSI_SRCR_RSCKP | SSI_SRCR_RFSI | SSI_SRCR_RXBIT0; + break; + case SND_SOC_DAIFMT_DSP_B: + stcr |= SSI_STCR_TEFS; // data 1 bit after sync + srcr |= SSI_SRCR_REFS; // data 1 bit after sync + case SND_SOC_DAIFMT_DSP_A: + stcr |= SSI_STCR_TFSL; // frame is 1 bclk long + srcr |= SSI_SRCR_RFSL; // frame is 1 bclk long + + /* DAI clock inversion */ + switch(fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + stcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP; + srcr |= SSI_SRCR_RFSI | SSI_SRCR_RSCKP; + break; + case SND_SOC_DAIFMT_IB_NF: + stcr |= SSI_STCR_TSCKP; + srcr |= SSI_SRCR_RSCKP; + break; + case SND_SOC_DAIFMT_NB_IF: + stcr |= SSI_STCR_TFSI; + srcr |= SSI_SRCR_RFSI; + break; + } + break; + } + + /* DAI clock master masks */ + switch(fmt & SND_SOC_DAIFMT_CLOCK_MASK){ + case SND_SOC_DAIFMT_CBM_CFM: + stcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR; + srcr |= SSI_SRCR_RFDIR | SSI_SRCR_RXDIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + stcr |= SSI_STCR_TFDIR; + srcr |= SSI_SRCR_RFDIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + stcr |= SSI_STCR_TXDIR; + srcr |= SSI_SRCR_RXDIR; + break; + } + + /* async */ + //if (rtd->cpu_dai->flags & SND_SOC_DAI_ASYNC) + // SSI1_SCR |= SSI_SCR_SYN; + + if (cpu_dai->id == IMX_DAI_SSI1) { + __raw_writew(stcr, SSI1_STCR); + __raw_writew(0, SSI1_STCCR); + __raw_writew(srcr, SSI1_SRCR); + __raw_writew(0, SSI1_SRCCR); + } else { + __raw_writew(stcr, SSI2_STCR); + __raw_writew(0, SSI2_STCCR); + __raw_writew(srcr, SSI2_SRCR); + __raw_writew(0, SSI2_SRCCR); + } + + return 0; +} + +static int imx_ssi_set_dai_tristate(struct snd_soc_cpu_dai *cpu_dai, + int tristate) +{ + // via GPIO ?? + return 0; +} + +static int imx_ssi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + + if (cpu_dai->id == IMX_DAI_SSI1) + mxc_clks_enable(SSI1_BAUD); + else + mxc_clks_enable(SSI2_BAUD); + return 0; +} + +static int imx_ssi_hw_tx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + u32 stccr, stcr; + + if (cpu_dai->id == IMX_DAI_SSI1) { + stccr = __raw_readw(SSI1_STCCR) & 0x600ff; + stcr = __raw_readw(SSI1_STCR); + } else { + stccr = __raw_readw(SSI2_STCCR) & 0x600ff; + stcr = __raw_readw(SSI2_STCR); + } + + /* DAI data (word) size */ + switch(params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + stccr |= SSI_STCCR_WL(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + stccr |= SSI_STCCR_WL(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + stccr |= SSI_STCCR_WL(24); + break; + } + + /* TDM - todo, only fifo 0 atm */ + stcr |= SSI_STCR_TFEN0; + stccr |= SSI_STCCR_DC(params_channels(params)); + + if (cpu_dai->id == IMX_DAI_SSI1) { + __raw_writew(stcr, SSI1_STCR); + __raw_writew(stccr, SSI1_STCCR); + } else { + __raw_writew(stcr, SSI2_STCR); + __raw_writew(stccr, SSI2_STCCR); + } + + return 0; +} + +static int imx_ssi_hw_rx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + u32 srccr, srcr; + + if (cpu_dai->id == IMX_DAI_SSI1) { + srccr = __raw_readw(SSI1_SRCCR) & 0x600ff; + srcr = __raw_readw(SSI1_SRCR); + } else { + srccr = __raw_readw(SSI2_SRCCR) & 0x600ff; + srcr = __raw_readw(SSI2_SRCR); + } + + /* DAI data (word) size */ + switch(params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + srccr |= SSI_SRCCR_WL(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + srccr |= SSI_SRCCR_WL(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + srccr |= SSI_SRCCR_WL(24); + break; + } + + /* TDM - todo, only fifo 0 atm */ + srcr |= SSI_SRCR_RFEN0; + srccr |= SSI_SRCCR_DC(params_channels(params)); + + if (cpu_dai->id == IMX_DAI_SSI1) { + __raw_writew(srcr, SSI1_SRCR); + __raw_writew(srccr, SSI1_SRCCR); + } else { + __raw_writew(srcr, SSI2_SRCR); + __raw_writew(srccr, SSI2_SRCCR); + } + return 0; +} + +static int imx_ssi_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_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (cpu_dai->id == IMX_DAI_SSI1) + cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out; + else + cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out; + return imx_ssi_hw_tx_params(substream, params); + } else { + if (cpu_dai->id == IMX_DAI_SSI1) + cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in; + else + cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in; + return imx_ssi_hw_rx_params(substream, params); + } +} + +static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + u32 scr, sier; + + if (cpu_dai->id == IMX_DAI_SSI1) { + scr = __raw_readw(SSI1_SCR) & 0x600ff; + sier = __raw_readw(SSI1_SIER); + } else { + scr = __raw_readw(SSI2_SCR) & 0x600ff; + sier = __raw_readw(SSI2_SIER); + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + scr |= SSI_SCR_TE; + sier |= SSI_SIER_TDMAE; + } else { + scr |= SSI_SCR_RE; + sier |= SSI_SIER_RDMAE; + } + scr |= SSI_SCR_SSIEN; + break; + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + scr |= SSI_SCR_TE; + else + scr |= SSI_SCR_RE; + scr |= SSI_SCR_SSIEN; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sier |= SSI_SIER_TDMAE; + else + sier |= SSI_SIER_RDMAE; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + scr &= ~SSI_SCR_SSIEN; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + scr &= ~SSI_SCR_TE; + else + scr &= ~SSI_SCR_RE; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sier &= ~SSI_SIER_TDMAE; + else + sier &= ~SSI_SIER_TDMAE; + break; + default: + return -EINVAL; + } + + if (cpu_dai->id == IMX_DAI_SSI1) { + __raw_writew(scr, SSI1_SCR); + __raw_writew(sier, SSI1_SIER); + } else { + __raw_writew(scr, SSI2_SCR); + __raw_writew(sier, SSI2_SIER); + } + + return 0; +} + +static void imx_ssi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + + /* shutdown SSI */ + if (!cpu_dai->active) { + if(cpu_dai->id == IMX_DAI_SSI1) { + __raw_writew(__raw_readw(SSI1_SCR) & ~SSI_SCR_SSIEN, SSI1_SCR); + mxc_clks_disable(SSI1_BAUD); + } else { + __raw_writew(__raw_readw(SSI2_SCR) & ~SSI_SCR_SSIEN, SSI2_SCR); + mxc_clks_disable(SSI2_BAUD); + } + } +} + +#ifdef CONFIG_PM +static int imx_ssi_suspend(struct platform_device *dev, + struct snd_soc_cpu_dai *dai) +{ + if(!dai->active) + return 0; + + // do we need to disable any clocks + + return 0; +} + +static int imx_ssi_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ + if(!dai->active) + return 0; + + // do we need to enable any clocks + return 0; +} + +#else +#define imx_ssi_suspend NULL +#define imx_ssi_resume NULL +#endif + +#define IMX_SSI_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 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define IMX_SSI_BITS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_cpu_dai imx_ssi_pcm_dai[] = { +{ + .name = "imx-i2s-1", + .id = IMX_DAI_SSI1, + .type = SND_SOC_DAI_I2S, + .suspend = imx_ssi_suspend, + .resume = imx_ssi_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .formats = IMX_SSI_BITS, + .rates = IMX_SSI_RATES,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .formats = IMX_SSI_BITS, + .rates = IMX_SSI_RATES,}, + .ops = { + .startup = imx_ssi_startup, + .shutdown = imx_ssi_shutdown, + .trigger = imx_ssi_trigger, + .hw_params = imx_ssi_hw_params,}, + .dai_ops = { + .set_sysclk = imx_ssi_set_dai_sysclk, + .set_clkdiv = imx_ssi_set_dai_clkdiv, + .set_fmt = imx_ssi_set_dai_fmt, + .set_tdm_slot = imx_ssi_set_dai_tdm_slot, + .set_tristate = imx_ssi_set_dai_tristate, + }, +}, +{ + .name = "imx-i2s-2", + .id = IMX_DAI_SSI2, + .type = SND_SOC_DAI_I2S, + .suspend = imx_ssi_suspend, + .resume = imx_ssi_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .formats = IMX_SSI_BITS, + .rates = IMX_SSI_RATES,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .formats = IMX_SSI_BITS, + .rates = IMX_SSI_RATES,}, + .ops = { + .startup = imx_ssi_startup, + .shutdown = imx_ssi_shutdown, + .trigger = imx_ssi_trigger, + .hw_params = imx_ssi_hw_params,}, + .dai_ops = { + .set_sysclk = imx_ssi_set_dai_sysclk, + .set_clkdiv = imx_ssi_set_dai_clkdiv, + .set_fmt = imx_ssi_set_dai_fmt, + .set_tdm_slot = imx_ssi_set_dai_tdm_slot, + .set_tristate = imx_ssi_set_dai_tristate, + }, +},}; +EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("i.MX ASoC I2S driver"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/imx/Kconfig =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/Kconfig @@ -0,0 +1,31 @@ +menu "SoC Audio for the Freescale i.MX" + +config SND_MXC_SOC + tristate "SoC Audio for the Freescale i.MX CPU" + depends on ARCH_MXC && SND + select SND_PCM + help + Say Y or M if you want to add support for codecs attached to + the MXC AC97, I2S or SSP interface. You will also need + to select the audio interfaces to support below. + +config SND_MXC_AC97 + tristate + select SND_AC97_CODEC + +config SND_MXC_SOC_AC97 + tristate + select AC97_BUS + +config SND_MXC_SOC_SSI + tristate + +config SND_SOC_MX31ADS_WM8753 + tristate "SoC Audio support for MX31 - WM8753" + depends on SND_MXC_SOC && ARCH_MX3 + select SND_MXC_SOC_SSI + help + Say Y if you want to add support for SoC audio on MX31ADS + with the WM8753. + +endmenu Index: linux-2.6.21-moko/sound/soc/imx/Makefile =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/Makefile @@ -0,0 +1,18 @@ +# i.MX Platform Support +snd-soc-imx21-objs := imx21-pcm.o +snd-soc-imx31-objs := imx31-pcm.o +snd-soc-imx-ac97-objs := imx-ac97.o +snd-soc-imx-ssi-objs := imx-ssi.o + +obj-$(CONFIG_SND_MXC_SOC) += snd-soc-imx31.o +obj-$(CONFIG_SND_MXC_SOC_AC97) += snd-soc-imx-ac97.o +obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-imx-ssi.o + +# i.MX Machine Support +snd-soc-mx31ads-wm8753-objs := mx31ads_wm8753.o +obj-$(CONFIG_SND_SOC_MX31ADS_WM8753) += snd-soc-mx31ads-wm8753.o +snd-soc-mx21ads-wm8753-objs := mx21ads_wm8753.o +obj-$(CONFIG_SND_SOC_MX21ADS_WM8753) += snd-soc-mx21ads-wm8753.o +snd-soc-mx21ads-wm8731-objs := mx21ads_wm8731.o +obj-$(CONFIG_SND_SOC_MX21ADS_WM8731) += snd-soc-mx21ads-wm8731.o + Index: linux-2.6.21-moko/sound/soc/codecs/wm8711.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8711.c @@ -0,0 +1,715 @@ +/* + * wm8711.c -- WM8711 ALSA SoC Audio driver + * + * Copyright 2006 Wolfson Microelectronics + * + * Author: Mike Arthur + * + * Based on wm8731.c by Richard Purdie + * + * 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 "wm8711.h" + +#define AUDIO_NAME "wm8711" +#define WM8711_VERSION "0.3" + +/* + * Debug + */ + +#define WM8711_DEBUG 0 + +#ifdef WM8711_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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) + +struct snd_soc_codec_device soc_codec_dev_wm8711; + +/* codec private data */ +struct wm8711_priv { + unsigned int sysclk; +}; + +/* + * wm8711 register cache + * We can't read the WM8711 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u16 wm8711_reg[WM8711_CACHEREGNUM] = { + 0x0079, 0x0079, 0x000a, 0x0008, + 0x009f, 0x000a, 0x0000, 0x0000 +}; + +/* + * read wm8711 register cache + */ +static inline unsigned int wm8711_read_reg_cache(struct snd_soc_codec * codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8711_RESET) + return 0; + if (reg >= WM8711_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8711 register cache + */ +static inline void wm8711_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8711_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8711 register space + */ +static int wm8711_write(struct snd_soc_codec * codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8711_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8711_reset(c) wm8711_write(c, WM8711_RESET, 0) + +static const struct snd_kcontrol_new wm8711_snd_controls[] = { + +SOC_DOUBLE_R("Master Playback Volume", WM8711_LOUT1V, WM8711_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Master Playback ZC Switch", WM8711_LOUT1V, WM8711_ROUT1V, + 7, 1, 0), + +}; + +/* add non dapm controls */ +static int wm8711_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8711_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8711_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Output Mixer */ +static const snd_kcontrol_new_t wm8711_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8711_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", WM8711_APANA, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8711_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", WM8711_PWR, 4, 1, + &wm8711_output_mixer_controls[0], + ARRAY_SIZE(wm8711_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8711_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +}; + +static const char *intercon[][3] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8711_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm8711_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm8711_dapm_widgets[i]); + } + + /* set up audio path interconnects */ + for(i = 0; intercon[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, intercon[i][0], intercon[i][1], + intercon[i][2]); + } + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return 0; +} + +static int wm8711_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8711_priv *wm8711 = codec->private_data; + u16 iface = wm8711_read_reg_cache(codec, WM8711_IFACE) & 0xfffc; + int i = get_coeff(wm8711->sysclk, params_rate(params)); + u16 srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + wm8711_write(codec, WM8711_SRATE, srate); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + } + + wm8711_write(codec, WM8711_IFACE, iface); + return 0; +} + +static int wm8711_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* set active */ + wm8711_write(codec, WM8711_ACTIVE, 0x0001); + return 0; +} + +static void wm8711_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* deactivate */ + if (!codec->active) { + udelay(50); + wm8711_write(codec, WM8711_ACTIVE, 0x0); + } +} + +static int wm8711_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8711_read_reg_cache(codec, WM8711_APDIGI) & 0xfff7; + + if (mute) + wm8711_write(codec, WM8711_APDIGI, mute_reg | 0x8); + else + wm8711_write(codec, WM8711_APDIGI, mute_reg); + + return 0; +} + +static int wm8711_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; + struct wm8711_priv *wm8711 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8711->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8711_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + wm8711_write(codec, WM8711_IFACE, iface); + return 0; +} + + +static int wm8711_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 reg = wm8711_read_reg_cache(codec, WM8711_PWR) & 0xff7f; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, osc on, dac unmute */ + wm8711_write(codec, WM8711_PWR, reg); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, */ + wm8711_write(codec, WM8711_PWR, reg | 0x0040); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + wm8711_write(codec, WM8711_ACTIVE, 0x0); + wm8711_write(codec, WM8711_PWR, 0xffff); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8711_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 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + +#define WM8711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_codec_dai wm8711_dai = { + .name = "WM8711", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8711_RATES, + .formats = WM8711_FORMATS,}, + .ops = { + .prepare = wm8711_pcm_prepare, + .hw_params = wm8711_hw_params, + .shutdown = wm8711_shutdown, + }, + .dai_ops = { + .digital_mute = wm8711_mute, + .set_sysclk = wm8711_set_dai_sysclk, + .set_fmt = wm8711_set_dai_fmt, + }, +}; +EXPORT_SYMBOL_GPL(wm8711_dai); + +static int wm8711_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; + + wm8711_write(codec, WM8711_ACTIVE, 0x0); + wm8711_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8711_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8711_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8711_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8711_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8711 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8711_init(struct snd_soc_device* socdev) +{ + struct snd_soc_codec* codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8711"; + codec->owner = THIS_MODULE; + codec->read = wm8711_read_reg_cache; + codec->write = wm8711_write; + codec->dapm_event = wm8711_dapm_event; + codec->dai = &wm8711_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8711_reg); + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8711_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, wm8711_reg, + sizeof(u16) * ARRAY_SIZE(wm8711_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8711_reg); + + wm8711_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8711: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8711_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* set the update bits */ + reg = wm8711_read_reg_cache(codec, WM8711_LOUT1V); + wm8711_write(codec, WM8711_LOUT1V, reg | 0x0100); + reg = wm8711_read_reg_cache(codec, WM8711_ROUT1V); + wm8711_write(codec, WM8711_ROUT1V, reg | 0x0100); + + wm8711_add_controls(codec); + wm8711_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8711: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8711_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * WM8711 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +#define I2C_DRIVERID_WM8711 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8711_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8711_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8711_socdev; + struct wm8711_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + + i2c_set_clientdata(i2c, codec); + + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8711_init(socdev); + if (ret < 0) { + err("failed to initialise WM8711\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; + +} + +static int wm8711_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec* codec = i2c_get_clientdata(client); + + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8711_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8711_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8711_i2c_driver = { + .driver = { + .name = "WM8711 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8711, + .attach_adapter = wm8711_i2c_attach, + .detach_client = wm8711_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8711", + .driver = &wm8711_i2c_driver, +}; +#endif + +static int wm8711_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8711_setup_data *setup; + struct snd_soc_codec* codec; + struct wm8711_priv *wm8711; + int ret = 0; + + info("WM8711 Audio Codec %s", WM8711_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8711 = kzalloc(sizeof(struct wm8711_priv), GFP_KERNEL); + if (wm8711 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8711; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8711_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8711_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8711_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) + wm8711_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8711_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8711 = { + .probe = wm8711_probe, + .remove = wm8711_remove, + .suspend = wm8711_suspend, + .resume = wm8711_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8711); + +MODULE_DESCRIPTION("ASoC WM8711 driver"); +MODULE_AUTHOR("Mike Arthur"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8711.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8711.h @@ -0,0 +1,42 @@ +/* + * wm8711.h -- WM8711 Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics + * + * Author: Mike Arthur + * + * Based on wm8731.h + * + * 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 _WM8711_H +#define _WM8711_H + +/* WM8711 register space */ + +#define WM8711_LOUT1V 0x02 +#define WM8711_ROUT1V 0x03 +#define WM8711_APANA 0x04 +#define WM8711_APDIGI 0x05 +#define WM8711_PWR 0x06 +#define WM8711_IFACE 0x07 +#define WM8711_SRATE 0x08 +#define WM8711_ACTIVE 0x09 +#define WM8711_RESET 0x0f + +#define WM8711_CACHEREGNUM 8 + +#define WM8711_SYSCLK 0 +#define WM8711_DAI 0 + +struct wm8711_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8711_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8711; + +#endif Index: linux-2.6.21-moko/sound/soc/codecs/wm8980.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8980.c @@ -0,0 +1,923 @@ +/* + * wm8980.c -- WM8980 ALSA Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics PLC. + * + * Authors: + * Mike Arthur + * + * 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 "wm8980.h" + +#define AUDIO_NAME "wm8980" +#define WM8980_VERSION "0.3" + +/* + * Debug + */ + +#define WM8980_DEBUG 0 + +#ifdef WM8980_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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) + +struct snd_soc_codec_device soc_codec_dev_wm8980; + +/* + * wm8980 register cache + * We can't read the WM8980 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8980_reg[WM8980_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0050, 0x0000, 0x0140, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00ff, + 0x00ff, 0x0000, 0x0100, 0x00ff, + 0x00ff, 0x0000, 0x012c, 0x002c, + 0x002c, 0x002c, 0x002c, 0x0000, + 0x0032, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0038, 0x000b, 0x0032, 0x0000, + 0x0008, 0x000c, 0x0093, 0x00e9, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0033, 0x0010, 0x0010, 0x0100, + 0x0100, 0x0002, 0x0001, 0x0001, + 0x0039, 0x0039, 0x0039, 0x0039, + 0x0001, 0x0001, +}; + +/* + * read wm8980 register cache + */ +static inline unsigned int wm8980_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8980_RESET) + return 0; + if (reg >= WM8980_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8980 register cache + */ +static inline void wm8980_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8980_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8980 register space + */ +static int wm8980_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8980 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8980_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -1; +} + +#define wm8980_reset(c) wm8980_write(c, WM8980_RESET, 0) + +static const char *wm8980_companding[] = {"Off", "NC", "u-law", "A-law" }; +static const char *wm8980_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8980_eqmode[] = {"Capture", "Playback" }; +static const char *wm8980_bw[] = {"Narrow", "Wide" }; +static const char *wm8980_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" }; +static const char *wm8980_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" }; +static const char *wm8980_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" }; +static const char *wm8980_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" }; +static const char *wm8980_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" }; +static const char *wm8980_alc[] = + {"ALC both on", "ALC left only", "ALC right only", "Limiter" }; + +static const struct soc_enum wm8980_enum[] = { + SOC_ENUM_SINGLE(WM8980_COMP, 1, 4, wm8980_companding), /* adc */ + SOC_ENUM_SINGLE(WM8980_COMP, 3, 4, wm8980_companding), /* dac */ + SOC_ENUM_SINGLE(WM8980_DAC, 4, 4, wm8980_deemp), + SOC_ENUM_SINGLE(WM8980_EQ1, 8, 2, wm8980_eqmode), + + SOC_ENUM_SINGLE(WM8980_EQ1, 5, 4, wm8980_eq1), + SOC_ENUM_SINGLE(WM8980_EQ2, 8, 2, wm8980_bw), + SOC_ENUM_SINGLE(WM8980_EQ2, 5, 4, wm8980_eq2), + SOC_ENUM_SINGLE(WM8980_EQ3, 8, 2, wm8980_bw), + + SOC_ENUM_SINGLE(WM8980_EQ3, 5, 4, wm8980_eq3), + SOC_ENUM_SINGLE(WM8980_EQ4, 8, 2, wm8980_bw), + SOC_ENUM_SINGLE(WM8980_EQ4, 5, 4, wm8980_eq4), + SOC_ENUM_SINGLE(WM8980_EQ5, 8, 2, wm8980_bw), + + SOC_ENUM_SINGLE(WM8980_EQ5, 5, 4, wm8980_eq5), + SOC_ENUM_SINGLE(WM8980_ALC3, 8, 2, wm8980_alc), +}; + +static const struct snd_kcontrol_new wm8980_snd_controls[] = { +SOC_SINGLE("Digital Loopback Switch", WM8980_COMP, 0, 1, 0), + +SOC_ENUM("ADC Companding", wm8980_enum[0]), +SOC_ENUM("DAC Companding", wm8980_enum[1]), + +SOC_SINGLE("Jack Detection Enable", WM8980_JACK1, 6, 1, 0), + +SOC_SINGLE("DAC Right Inversion Switch", WM8980_DAC, 1, 1, 0), +SOC_SINGLE("DAC Left Inversion Switch", WM8980_DAC, 0, 1, 0), + +SOC_SINGLE("Left Playback Volume", WM8980_DACVOLL, 0, 127, 0), +SOC_SINGLE("Right Playback Volume", WM8980_DACVOLR, 0, 127, 0), + +SOC_SINGLE("High Pass Filter Switch", WM8980_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Filter Switch", WM8980_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8980_ADC, 4, 7, 0), +SOC_SINGLE("Right ADC Inversion Switch", WM8980_ADC, 1, 1, 0), +SOC_SINGLE("Left ADC Inversion Switch", WM8980_ADC, 0, 1, 0), + +SOC_SINGLE("Left Capture Volume", WM8980_ADCVOLL, 0, 127, 0), +SOC_SINGLE("Right Capture Volume", WM8980_ADCVOLR, 0, 127, 0), + +SOC_ENUM("Equaliser Function", wm8980_enum[3]), +SOC_ENUM("EQ1 Cut Off", wm8980_enum[4]), +SOC_SINGLE("EQ1 Volume", WM8980_EQ1, 0, 31, 1), + +SOC_ENUM("Equaliser EQ2 Bandwith", wm8980_enum[5]), +SOC_ENUM("EQ2 Cut Off", wm8980_enum[6]), +SOC_SINGLE("EQ2 Volume", WM8980_EQ2, 0, 31, 1), + +SOC_ENUM("Equaliser EQ3 Bandwith", wm8980_enum[7]), +SOC_ENUM("EQ3 Cut Off", wm8980_enum[8]), +SOC_SINGLE("EQ3 Volume", WM8980_EQ3, 0, 31, 1), + +SOC_ENUM("Equaliser EQ4 Bandwith", wm8980_enum[9]), +SOC_ENUM("EQ4 Cut Off", wm8980_enum[10]), +SOC_SINGLE("EQ4 Volume", WM8980_EQ4, 0, 31, 1), + +SOC_ENUM("Equaliser EQ5 Bandwith", wm8980_enum[11]), +SOC_ENUM("EQ5 Cut Off", wm8980_enum[12]), +SOC_SINGLE("EQ5 Volume", WM8980_EQ5, 0, 31, 1), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8980_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8980_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8980_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8980_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8980_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8980_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8980_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8980_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8980_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8980_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8980_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8980_enum[13]), +SOC_SINGLE("ALC Capture Decay", WM8980_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8980_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8980_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8980_NGATE, 0, 7, 0), + +SOC_SINGLE("Left Capture PGA ZC Switch", WM8980_INPPGAL, 7, 1, 0), +SOC_SINGLE("Left Capture PGA Volume", WM8980_INPPGAL, 0, 63, 0), + +SOC_SINGLE("Right Capture PGA ZC Switch", WM8980_INPPGAR, 7, 1, 0), +SOC_SINGLE("Right Capture PGA Volume", WM8980_INPPGAR, 0, 63, 0), + +SOC_SINGLE("Left Headphone Playback ZC Switch", WM8980_HPVOLL, 7, 1, 0), +SOC_SINGLE("Left Headphone Playback Switch", WM8980_HPVOLL, 6, 1, 1), +SOC_SINGLE("Left Headphone Playback Volume", WM8980_HPVOLL, 0, 63, 0), + +SOC_SINGLE("Right Headphone Playback ZC Switch", WM8980_HPVOLR, 7, 1, 0), +SOC_SINGLE("Right Headphone Playback Switch", WM8980_HPVOLR, 6, 1, 1), +SOC_SINGLE("Right Headphone Playback Volume", WM8980_HPVOLR, 0, 63, 0), + +SOC_SINGLE("Left Speaker Playback ZC Switch", WM8980_SPKVOLL, 7, 1, 0), +SOC_SINGLE("Left Speaker Playback Switch", WM8980_SPKVOLL, 6, 1, 1), +SOC_SINGLE("Left Speaker Playback Volume", WM8980_SPKVOLL, 0, 63, 0), + +SOC_SINGLE("Right Speaker Playback ZC Switch", WM8980_SPKVOLR, 7, 1, 0), +SOC_SINGLE("Right Speaker Playback Switch", WM8980_SPKVOLR, 6, 1, 1), +SOC_SINGLE("Right Speaker Playback Volume", WM8980_SPKVOLR, 0, 63, 0), + +SOC_DOUBLE_R("Capture Boost(+20dB)", WM8980_ADCBOOSTL, WM8980_ADCBOOSTR, + 8, 1, 0), +}; + +/* add non dapm controls */ +static int wm8980_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8980_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8980_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Left Output Mixer */ +static const snd_kcontrol_new_t wm8980_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Right PCM Playback Switch", WM8980_OUTPUT, 6, 1, 1), +SOC_DAPM_SINGLE("Left PCM Playback Switch", WM8980_MIXL, 0, 1, 1), +SOC_DAPM_SINGLE("Line Bypass Switch", WM8980_MIXL, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8980_MIXL, 5, 1, 0), +}; + +/* Right Output Mixer */ +static const snd_kcontrol_new_t wm8980_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left PCM Playback Switch", WM8980_OUTPUT, 5, 1, 1), +SOC_DAPM_SINGLE("Right PCM Playback Switch", WM8980_MIXR, 0, 1, 1), +SOC_DAPM_SINGLE("Line Bypass Switch", WM8980_MIXR, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8980_MIXR, 5, 1, 0), +}; + +/* Left AUX Input boost vol */ +static const snd_kcontrol_new_t wm8980_laux_boost_controls = +SOC_DAPM_SINGLE("Left Aux Volume", WM8980_ADCBOOSTL, 0, 3, 0); + +/* Right AUX Input boost vol */ +static const snd_kcontrol_new_t wm8980_raux_boost_controls = +SOC_DAPM_SINGLE("Right Aux Volume", WM8980_ADCBOOSTR, 0, 3, 0); + +/* Left Input boost vol */ +static const snd_kcontrol_new_t wm8980_lmic_boost_controls = +SOC_DAPM_SINGLE("Left Input Volume", WM8980_ADCBOOSTL, 4, 3, 0); + +/* Right Input boost vol */ +static const snd_kcontrol_new_t wm8980_rmic_boost_controls = +SOC_DAPM_SINGLE("Right Input Volume", WM8980_ADCBOOSTR, 4, 3, 0); + +/* Left Aux In to PGA */ +static const snd_kcontrol_new_t wm8980_laux_capture_boost_controls = +SOC_DAPM_SINGLE("Left Capture Switch", WM8980_ADCBOOSTL, 8, 1, 0); + +/* Right Aux In to PGA */ +static const snd_kcontrol_new_t wm8980_raux_capture_boost_controls = +SOC_DAPM_SINGLE("Right Capture Switch", WM8980_ADCBOOSTR, 8, 1, 0); + +/* Left Input P In to PGA */ +static const snd_kcontrol_new_t wm8980_lmicp_capture_boost_controls = +SOC_DAPM_SINGLE("Left Input P Capture Boost Switch", WM8980_INPUT, 0, 1, 0); + +/* Right Input P In to PGA */ +static const snd_kcontrol_new_t wm8980_rmicp_capture_boost_controls = +SOC_DAPM_SINGLE("Right Input P Capture Boost Switch", WM8980_INPUT, 4, 1, 0); + +/* Left Input N In to PGA */ +static const snd_kcontrol_new_t wm8980_lmicn_capture_boost_controls = +SOC_DAPM_SINGLE("Left Input N Capture Boost Switch", WM8980_INPUT, 1, 1, 0); + +/* Right Input N In to PGA */ +static const snd_kcontrol_new_t wm8980_rmicn_capture_boost_controls = +SOC_DAPM_SINGLE("Right Input N Capture Boost Switch", WM8980_INPUT, 5, 1, 0); + +// TODO Widgets +static const struct snd_soc_dapm_widget wm8980_dapm_widgets[] = { +#if 0 +//SND_SOC_DAPM_MUTE("Mono Mute", WM8980_MONOMIX, 6, 0), +//SND_SOC_DAPM_MUTE("Speaker Mute", WM8980_SPKMIX, 6, 0), + +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8980_POWER3, 2, 0, + &wm8980_speaker_mixer_controls[0], + ARRAY_SIZE(wm8980_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8980_POWER3, 3, 0, + &wm8980_mono_mixer_controls[0], + ARRAY_SIZE(wm8980_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8980_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8980_POWER3, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8980_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8980_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8980_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8980_POWER3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mic PGA", WM8980_POWER2, 2, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Aux Boost", SND_SOC_NOPM, 0, 0, + &wm8980_aux_boost_controls, 1), +SND_SOC_DAPM_PGA("Mic Boost", SND_SOC_NOPM, 0, 0, + &wm8980_mic_boost_controls, 1), +SND_SOC_DAPM_SWITCH("Capture Boost", SND_SOC_NOPM, 0, 0, + &wm8980_capture_boost_controls), + +SND_SOC_DAPM_MIXER("Boost Mixer", WM8980_POWER2, 4, 0, NULL, 0), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8980_POWER1, 4, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +#endif +}; + +static const char *audio_map[][3] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Boost Mixer */ + {"Boost Mixer", NULL, "ADC"}, + {"Capture Boost Switch", "Aux Capture Boost Switch", "AUX"}, + {"Aux Boost", "Aux Volume", "Boost Mixer"}, + {"Capture Boost", "Capture Switch", "Boost Mixer"}, + {"Mic Boost", "Mic Volume", "Boost Mixer"}, + + /* Inputs */ + {"MICP", NULL, "Mic Boost"}, + {"MICN", NULL, "Mic PGA"}, + {"Mic PGA", NULL, "Capture Boost"}, + {"AUX", NULL, "Aux Input"}, + + /* */ + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8980_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm8980_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm8980_dapm_widgets[i]); + } + + /* set up audio path map */ + 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; +} + +struct pll_ { + unsigned int in_hz, out_hz; + unsigned int pre:4; /* prescale - 1 */ + unsigned int n:4; + unsigned int k; +}; + +struct pll_ pll[] = { + {12000000, 11289600, 0, 7, 0x86c220}, + {12000000, 12288000, 0, 8, 0x3126e8}, + {13000000, 11289600, 0, 6, 0xf28bd4}, + {13000000, 12288000, 0, 7, 0x8fd525}, + {12288000, 11289600, 0, 7, 0x59999a}, + {11289600, 12288000, 0, 8, 0x80dee9}, + /* TODO: liam - add more entries */ +}; + +static int wm8980_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int i; + u16 reg; + + if(freq_in == 0 || freq_out == 0) { + reg = wm8980_read_reg_cache(codec, WM8980_POWER1); + wm8980_write(codec, WM8980_POWER1, reg & 0x1df); + return 0; + } + + for(i = 0; i < ARRAY_SIZE(pll); i++) { + if (freq_in == pll[i].in_hz && freq_out == pll[i].out_hz) { + wm8980_write(codec, WM8980_PLLN, (pll[i].pre << 4) | pll[i].n); + wm8980_write(codec, WM8980_PLLK1, pll[i].k >> 18); + wm8980_write(codec, WM8980_PLLK1, (pll[i].k >> 9) && 0x1ff); + wm8980_write(codec, WM8980_PLLK1, pll[i].k && 0x1ff); + reg = wm8980_read_reg_cache(codec, WM8980_POWER1); + wm8980_write(codec, WM8980_POWER1, reg | 0x020); + return 0; + } + } + return -EINVAL; +} + +static int wm8980_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = wm8980_read_reg_cache(codec, WM8980_IFACE) & 0x3; + u16 clk = wm8980_read_reg_cache(codec, WM8980_CLOCK) & 0xfffe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + wm8980_write(codec, WM8980_IFACE, iface); + return 0; +} + +static int wm8980_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface = wm8980_read_reg_cache(codec, WM8980_IFACE) & 0xff9f; + u16 adn = wm8980_read_reg_cache(codec, WM8980_ADD) & 0x1f1; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0020; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0040; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + adn |= 0x5 << 1; + break; + case SNDRV_PCM_RATE_11025: + adn |= 0x4 << 1; + break; + case SNDRV_PCM_RATE_16000: + adn |= 0x3 << 1; + break; + case SNDRV_PCM_RATE_22050: + adn |= 0x2 << 1; + break; + case SNDRV_PCM_RATE_32000: + adn |= 0x1 << 1; + break; + } + + /* set iface */ + wm8980_write(codec, WM8980_IFACE, iface); + wm8980_write(codec, WM8980_ADD, adn); + return 0; +} + +static int wm8980_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8980_MCLKDIV: + reg = wm8980_read_reg_cache(codec, WM8980_CLOCK) & 0x11f; + wm8980_write(codec, WM8980_CLOCK, reg | div); + break; + case WM8980_BCLKDIV: + reg = wm8980_read_reg_cache(codec, WM8980_CLOCK) & 0x1c7; + wm8980_write(codec, WM8980_CLOCK, reg | div); + break; + case WM8980_OPCLKDIV: + reg = wm8980_read_reg_cache(codec, WM8980_GPIO) & 0x1cf; + wm8980_write(codec, WM8980_GPIO, reg | div); + break; + case WM8980_DACOSR: + reg = wm8980_read_reg_cache(codec, WM8980_DAC) & 0x1f7; + wm8980_write(codec, WM8980_DAC, reg | div); + break; + case WM8980_ADCOSR: + reg = wm8980_read_reg_cache(codec, WM8980_ADC) & 0x1f7; + wm8980_write(codec, WM8980_ADC, reg | div); + break; + case WM8980_MCLKSEL: + reg = wm8980_read_reg_cache(codec, WM8980_CLOCK) & 0x0ff; + wm8980_write(codec, WM8980_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8980_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8980_read_reg_cache(codec, WM8980_DAC) & 0xffbf; + + if(mute) + wm8980_write(codec, WM8980_DAC, mute_reg | 0x40); + else + wm8980_write(codec, WM8980_DAC, mute_reg); + + return 0; +} + +/* TODO: liam need to make this lower power with dapm */ +static int wm8980_dapm_event(struct snd_soc_codec *codec, int event) +{ + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, clk and osc on, dac unmute, active */ + wm8980_write(codec, WM8980_POWER1, 0x1ff); + wm8980_write(codec, WM8980_POWER2, 0x1ff); + wm8980_write(codec, WM8980_POWER3, 0x1ff); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, dac mute, inactive */ + + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + wm8980_write(codec, WM8980_POWER1, 0x0); + wm8980_write(codec, WM8980_POWER2, 0x0); + wm8980_write(codec, WM8980_POWER3, 0x0); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8980_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) + +#define WM8980_FORMATS \ + (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_3LE | SNDRV_PCM_FORMAT_S24_LE) + +struct snd_soc_codec_dai wm8980_dai = { + .name = "WM8980 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8980_RATES, + .formats = WM8980_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8980_RATES, + .formats = WM8980_FORMATS,}, + .ops = { + .hw_params = wm8980_hw_params, + }, + .dai_ops = { + .digital_mute = wm8980_mute, + .set_fmt = wm8980_set_dai_fmt, + .set_clkdiv = wm8980_set_dai_clkdiv, + .set_pll = wm8980_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8980_dai); + +static int wm8980_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; + + wm8980_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8980_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8980_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8980_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8980_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8980 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8980_init(struct snd_soc_device* socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8980"; + codec->owner = THIS_MODULE; + codec->read = wm8980_read_reg_cache; + codec->write = wm8980_write; + codec->dapm_event = wm8980_dapm_event; + codec->dai = &wm8980_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8980_reg); + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8980_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, wm8980_reg, + sizeof(u16) * ARRAY_SIZE(wm8980_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8980_reg); + + wm8980_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if(ret < 0) { + printk(KERN_ERR "wm8980: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8980_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8980_add_controls(codec); + wm8980_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8980: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8980_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * WM8980 2 wire address is 0x1a + */ +#define I2C_DRIVERID_WM8980 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8980_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8980_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8980_socdev; + struct wm8980_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + + i2c_set_clientdata(i2c, codec); + + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if(ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8980_init(socdev); + if(ret < 0) { + err("failed to initialise WM8980\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; + +} + +static int wm8980_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8980_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8980_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8980_i2c_driver = { + .driver = { + .name = "WM8980 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8980, + .attach_adapter = wm8980_i2c_attach, + .detach_client = wm8980_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8980", + .driver = &wm8980_i2c_driver, +}; +#endif + +static int wm8980_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8980_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + info("WM8980 Audio Codec %s", WM8980_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); + + wm8980_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8980_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8980_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) + wm8980_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8980_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8980 = { + .probe = wm8980_probe, + .remove = wm8980_remove, + .suspend = wm8980_suspend, + .resume = wm8980_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8980); + +MODULE_DESCRIPTION("ASoC WM8980 driver"); +MODULE_AUTHOR("Mike Arthur"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8980.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8980.h @@ -0,0 +1,116 @@ +/* + * wm8980.h -- WM8980 Soc Audio driver + * + * 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 _WM8980_H +#define _WM8980_H + +/* WM8980 register space */ + +#define WM8980_RESET 0x0 +#define WM8980_POWER1 0x1 +#define WM8980_POWER2 0x2 +#define WM8980_POWER3 0x3 +#define WM8980_IFACE 0x4 +#define WM8980_COMP 0x5 +#define WM8980_CLOCK 0x6 +#define WM8980_ADD 0x7 +#define WM8980_GPIO 0x8 +#define WM8980_JACK1 0x9 +#define WM8980_DAC 0xa +#define WM8980_DACVOLL 0xb +#define WM8980_DACVOLR 0xc +#define WM8980_JACK2 0xd +#define WM8980_ADC 0xe +#define WM8980_ADCVOLL 0xf +#define WM8980_ADCVOLR 0x10 +#define WM8980_EQ1 0x12 +#define WM8980_EQ2 0x13 +#define WM8980_EQ3 0x14 +#define WM8980_EQ4 0x15 +#define WM8980_EQ5 0x16 +#define WM8980_DACLIM1 0x18 +#define WM8980_DACLIM2 0x19 +#define WM8980_NOTCH1 0x1b +#define WM8980_NOTCH2 0x1c +#define WM8980_NOTCH3 0x1d +#define WM8980_NOTCH4 0x1e +#define WM8980_ALC1 0x20 +#define WM8980_ALC2 0x21 +#define WM8980_ALC3 0x22 +#define WM8980_NGATE 0x23 +#define WM8980_PLLN 0x24 +#define WM8980_PLLK1 0x25 +#define WM8980_PLLK2 0x26 +#define WM8980_PLLK3 0x27 +#define WM8980_VIDEO 0x28 +#define WM8980_3D 0x29 +#define WM8980_BEEP 0x2b +#define WM8980_INPUT 0x2c +#define WM8980_INPPGAL 0x2d +#define WM8980_INPPGAR 0x2e +#define WM8980_ADCBOOSTL 0x2f +#define WM8980_ADCBOOSTR 0x30 +#define WM8980_OUTPUT 0x31 +#define WM8980_MIXL 0x32 +#define WM8980_MIXR 0x33 +#define WM8980_HPVOLL 0x34 +#define WM8980_HPVOLR 0x35 +#define WM8980_SPKVOLL 0x36 +#define WM8980_SPKVOLR 0x37 +#define WM8980_OUT3MIX 0x38 +#define WM8980_MONOMIX 0x39 + +#define WM8980_CACHEREGNUM 58 + +/* + * WM8980 Clock dividers + */ +#define WM8980_MCLKDIV 0 +#define WM8980_BCLKDIV 1 +#define WM8980_OPCLKDIV 2 +#define WM8980_DACOSR 3 +#define WM8980_ADCOSR 4 +#define WM8980_MCLKSEL 5 + +#define WM8980_MCLK_MCLK (0 << 8) +#define WM8980_MCLK_PLL (1 << 8) + +#define WM8980_MCLK_DIV_1 (0 << 5) +#define WM8980_MCLK_DIV_1_5 (1 << 5) +#define WM8980_MCLK_DIV_2 (2 << 5) +#define WM8980_MCLK_DIV_3 (3 << 5) +#define WM8980_MCLK_DIV_4 (4 << 5) +#define WM8980_MCLK_DIV_5_5 (5 << 5) +#define WM8980_MCLK_DIV_6 (6 << 5) + +#define WM8980_BCLK_DIV_1 (0 << 2) +#define WM8980_BCLK_DIV_2 (1 << 2) +#define WM8980_BCLK_DIV_4 (2 << 2) +#define WM8980_BCLK_DIV_8 (3 << 2) +#define WM8980_BCLK_DIV_16 (4 << 2) +#define WM8980_BCLK_DIV_32 (5 << 2) + +#define WM8980_DACOSR_64 (0 << 3) +#define WM8980_DACOSR_128 (1 << 3) + +#define WM8980_ADCOSR_64 (0 << 3) +#define WM8980_ADCOSR_128 (1 << 3) + +#define WM8980_OPCLK_DIV_1 (0 << 4) +#define WM8980_OPCLK_DIV_2 (1 << 4) +#define WM8980_OPCLK_DIV_3 (2 << 4) +#define WM8980_OPCLK_DIV_4 (3 << 4) + +struct wm8980_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8980_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8980; + +#endif Index: linux-2.6.21-moko/sound/soc/codecs/wm8510.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8510.c @@ -0,0 +1,860 @@ +/* + * wm8510.c -- WM8510 ALSA Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * + * 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 "wm8510.h" + +#define AUDIO_NAME "wm8510" +#define WM8510_VERSION "0.6" + +/* + * Debug + */ + +#define WM8510_DEBUG 0 + +#ifdef WM8510_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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) + +struct snd_soc_codec_device soc_codec_dev_wm8510; + +/* + * wm8510 register cache + * We can't read the WM8510 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8510_reg[WM8510_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0050, 0x0000, 0x0140, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00ff, + 0x0000, 0x0000, 0x0100, 0x00ff, + 0x0000, 0x0000, 0x012c, 0x002c, + 0x002c, 0x002c, 0x002c, 0x0000, + 0x0032, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0038, 0x000b, 0x0032, 0x0000, + 0x0008, 0x000c, 0x0093, 0x00e9, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0003, 0x0010, 0x0000, 0x0000, + 0x0000, 0x0002, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0039, 0x0000, + 0x0000, +}; + +/* + * read wm8510 register cache + */ +static inline unsigned int wm8510_read_reg_cache(struct snd_soc_codec * codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8510_RESET) + return 0; + if (reg >= WM8510_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8510 register cache + */ +static inline void wm8510_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8510_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8510 register space + */ +static int wm8510_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8510 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8510_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8510_reset(c) wm8510_write(c, WM8510_RESET, 0) + +static const char *wm8510_companding[] = {"Off", "NC", "u-law", "A-law" }; +static const char *wm8510_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8510_alc[] = {"ALC", "Limiter" }; + +static const struct soc_enum wm8510_enum[] = { + SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */ + SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */ + SOC_ENUM_SINGLE(WM8510_DAC, 4, 4, wm8510_deemp), + SOC_ENUM_SINGLE(WM8510_ALC3, 8, 2, wm8510_alc), +}; + +static const struct snd_kcontrol_new wm8510_snd_controls[] = { + +SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0), + +SOC_ENUM("DAC Companding", wm8510_enum[1]), +SOC_ENUM("ADC Companding", wm8510_enum[0]), + +SOC_ENUM("Playback De-emphasis", wm8510_enum[2]), +SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0), + +SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), + +SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), +SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), + +SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8510_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8510_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8510_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8510_enum[3]), +SOC_SINGLE("ALC Capture Decay", WM8510_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8510_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE, 0, 7, 0), + +SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA, 7, 1, 0), +SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA, 0, 63, 0), + +SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL, 7, 1, 0), +SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL, 6, 1, 1), +SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL, 0, 63, 0), + +SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0), +SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 0), +}; + +/* add non dapm controls */ +static int wm8510_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8510_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8510_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Speaker Output Mixer */ +static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 1), +}; + +/* Mono Output Mixer */ +static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 1), +}; + +/* AUX Input boost vol */ +static const struct snd_kcontrol_new wm8510_aux_boost_controls = +SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0); + +/* Mic Input boost vol */ +static const struct snd_kcontrol_new wm8510_mic_boost_controls = +SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0); + +/* Capture boost switch */ +static const struct snd_kcontrol_new wm8510_capture_boost_controls = +SOC_DAPM_SINGLE("Capture Boost Switch", WM8510_INPPGA, 6, 1, 0); + +/* Aux In to PGA */ +static const struct snd_kcontrol_new wm8510_aux_capture_boost_controls = +SOC_DAPM_SINGLE("Aux Capture Boost Switch", WM8510_INPPGA, 2, 1, 0); + +/* Mic P In to PGA */ +static const struct snd_kcontrol_new wm8510_micp_capture_boost_controls = +SOC_DAPM_SINGLE("Mic P Capture Boost Switch", WM8510_INPPGA, 0, 1, 0); + +/* Mic N In to PGA */ +static const struct snd_kcontrol_new wm8510_micn_capture_boost_controls = +SOC_DAPM_SINGLE("Mic N Capture Boost Switch", WM8510_INPPGA, 1, 1, 0); + +static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0, + &wm8510_speaker_mixer_controls[0], + ARRAY_SIZE(wm8510_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0, + &wm8510_mono_mixer_controls[0], + ARRAY_SIZE(wm8510_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER3, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mic PGA", WM8510_POWER2, 2, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Aux Boost", SND_SOC_NOPM, 0, 0, + &wm8510_aux_boost_controls, 1), +SND_SOC_DAPM_PGA("Mic Boost", SND_SOC_NOPM, 0, 0, + &wm8510_mic_boost_controls, 1), +SND_SOC_DAPM_SWITCH("Capture Boost", SND_SOC_NOPM, 0, 0, + &wm8510_capture_boost_controls), + +SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0, NULL, 0), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +}; + +static const char *audio_map[][3] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Boost Mixer */ + {"Boost Mixer", NULL, "ADC"}, + {"Capture Boost Switch", "Aux Capture Boost Switch", "AUX"}, + {"Aux Boost", "Aux Volume", "Boost Mixer"}, + {"Capture Boost", "Capture Switch", "Boost Mixer"}, + {"Mic Boost", "Mic Volume", "Boost Mixer"}, + + /* Inputs */ + {"MICP", NULL, "Mic Boost"}, + {"MICN", NULL, "Mic PGA"}, + {"Mic PGA", NULL, "Capture Boost"}, + {"AUX", NULL, "Aux Input"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8510_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm8510_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm8510_dapm_widgets[i]); + } + + /* set up audio path audio_mapnects */ + 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; +} + +struct pll_ { + unsigned int pre_div:4; /* prescale - 1 */ + unsigned int n:4; + unsigned int k; +}; + +static struct pll_ pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div.pre_div = 1; + Ndiv = target / source; + } else + pll_div.pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8510 N value outwith recommended range! N = %d\n",Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +static int wm8510_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + if(freq_in == 0 || freq_out == 0) { + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg & 0x1df); + return 0; + } + + pll_factors(freq_out*8, freq_in); + + wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); + wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18); + wm8510_write(codec, WM8510_PLLK1, (pll_div.k >> 9) && 0x1ff); + wm8510_write(codec, WM8510_PLLK1, pll_div.k && 0x1ff); + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg | 0x020); + return 0; + +} + +/* + * Configure WM8510 clock dividers. + */ +static int wm8510_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8510_OPCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_GPIO & 0x1cf); + wm8510_write(codec, WM8510_GPIO, reg | div); + break; + case WM8510_MCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK & 0x1f); + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + case WM8510_ADCCLK: + reg = wm8510_read_reg_cache(codec, WM8510_ADC & 0x1f7); + wm8510_write(codec, WM8510_ADC, reg | div); + break; + case WM8510_DACCLK: + reg = wm8510_read_reg_cache(codec, WM8510_DAC & 0x1f7); + wm8510_write(codec, WM8510_DAC, reg | div); + break; + case WM8510_BCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK & 0x1e3); + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8510_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + u16 clk = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1fe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_CLOCK, clk); + return 0; +} + +static int wm8510_pcm_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x19f; + u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0020; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0040; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x0060; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + adn |= 0x5 << 1; + break; + case SNDRV_PCM_RATE_11025: + adn |= 0x4 << 1; + break; + case SNDRV_PCM_RATE_16000: + adn |= 0x3 << 1; + break; + case SNDRV_PCM_RATE_22050: + adn |= 0x2 << 1; + break; + case SNDRV_PCM_RATE_32000: + adn |= 0x1 << 1; + break; + case SNDRV_PCM_RATE_44100: + break; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_ADD, adn); + return 0; +} + +static int wm8510_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0xffbf; + + if(mute) + wm8510_write(codec, WM8510_DAC, mute_reg | 0x40); + else + wm8510_write(codec, WM8510_DAC, mute_reg); + return 0; +} + +/* liam need to make this lower power with dapm */ +static int wm8510_dapm_event(struct snd_soc_codec *codec, int event) +{ + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, clk and osc on, dac unmute, active */ + wm8510_write(codec, WM8510_POWER1, 0x1ff); + wm8510_write(codec, WM8510_POWER2, 0x1ff); + wm8510_write(codec, WM8510_POWER3, 0x1ff); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, dac mute, inactive */ + + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + wm8510_write(codec, WM8510_POWER1, 0x0); + wm8510_write(codec, WM8510_POWER2, 0x0); + wm8510_write(codec, WM8510_POWER3, 0x0); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_codec_dai wm8510_dai = { + .name = "WM8510 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .ops = { + .hw_params = wm8510_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8510_mute, + .set_fmt = wm8510_set_dai_fmt, + .set_clkdiv = wm8510_set_dai_clkdiv, + .set_pll = wm8510_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8510_dai); + +static int wm8510_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; + + wm8510_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8510_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8510_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8510_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8510_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8510 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8510_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8510"; + codec->owner = THIS_MODULE; + codec->read = wm8510_read_reg_cache; + codec->write = wm8510_write; + codec->dapm_event = wm8510_dapm_event; + codec->dai = &wm8510_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8510_reg); + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8510_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, wm8510_reg, + sizeof(u16) * ARRAY_SIZE(wm8510_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8510_reg); + + wm8510_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if(ret < 0) { + printk(KERN_ERR "wm8510: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8510_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8510_add_controls(codec); + wm8510_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8510: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8510_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * WM8510 2 wire address is 0x1a + */ +#define I2C_DRIVERID_WM8510 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8510_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8510_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8510_socdev; + struct wm8510_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if(ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8510_init(socdev); + if(ret < 0) { + err("failed to initialise WM8510\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8510_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8510_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8510_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8510_i2c_driver = { + .driver = { + .name = "WM8510 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8510, + .attach_adapter = wm8510_i2c_attach, + .detach_client = wm8510_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8510", + .driver = &wm8510_i2c_driver, +}; +#endif + +static int wm8510_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8510_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + info("WM8510 Audio Codec %s", WM8510_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); + + wm8510_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8510_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8510_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) + wm8510_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8510_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8510 = { + .probe = wm8510_probe, + .remove = wm8510_remove, + .suspend = wm8510_suspend, + .resume = wm8510_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8510); + +MODULE_DESCRIPTION("ASoC WM8510 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8510.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8510.h @@ -0,0 +1,103 @@ +/* + * wm8510.h -- WM8510 Soc Audio driver + * + * 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 _WM8510_H +#define _WM8510_H + +/* WM8510 register space */ + +#define WM8510_RESET 0x0 +#define WM8510_POWER1 0x1 +#define WM8510_POWER2 0x2 +#define WM8510_POWER3 0x3 +#define WM8510_IFACE 0x4 +#define WM8510_COMP 0x5 +#define WM8510_CLOCK 0x6 +#define WM8510_ADD 0x7 +#define WM8510_GPIO 0x8 +#define WM8510_DAC 0xa +#define WM8510_DACVOL 0xb +#define WM8510_ADC 0xe +#define WM8510_ADCVOL 0xf +#define WM8510_EQ1 0x12 +#define WM8510_EQ2 0x13 +#define WM8510_EQ3 0x14 +#define WM8510_EQ4 0x15 +#define WM8510_EQ5 0x16 +#define WM8510_DACLIM1 0x18 +#define WM8510_DACLIM2 0x19 +#define WM8510_NOTCH1 0x1b +#define WM8510_NOTCH2 0x1c +#define WM8510_NOTCH3 0x1d +#define WM8510_NOTCH4 0x1e +#define WM8510_ALC1 0x20 +#define WM8510_ALC2 0x21 +#define WM8510_ALC3 0x22 +#define WM8510_NGATE 0x23 +#define WM8510_PLLN 0x24 +#define WM8510_PLLK1 0x25 +#define WM8510_PLLK2 0x26 +#define WM8510_PLLK3 0x27 +#define WM8510_ATTEN 0x28 +#define WM8510_INPUT 0x2c +#define WM8510_INPPGA 0x2d +#define WM8510_ADCBOOST 0x2f +#define WM8510_OUTPUT 0x31 +#define WM8510_SPKMIX 0x32 +#define WM8510_SPKVOL 0x36 +#define WM8510_MONOMIX 0x38 + +#define WM8510_CACHEREGNUM 57 + +/* Clock divider Id's */ +#define WM8510_OPCLKDIV 0 +#define WM8510_MCLKDIV 1 +#define WM8510_ADCCLK 2 +#define WM8510_DACCLK 3 +#define WM8510_BCLKDIV 4 + +/* DAC clock dividers */ +#define WM8510_DACCLK_F2 (1 << 3) +#define WM8510_DACCLK_F4 (0 << 3) + +/* ADC clock dividers */ +#define WM8510_ADCCLK_F2 (1 << 3) +#define WM8510_ADCCLK_F4 (0 << 3) + +/* PLL Out dividers */ +#define WM8510_OPCLKDIV_1 (0 << 4) +#define WM8510_OPCLKDIV_2 (1 << 4) +#define WM8510_OPCLKDIV_3 (2 << 4) +#define WM8510_OPCLKDIV_4 (3 << 4) + +/* BCLK clock dividers */ +#define WM8510_BCLKDIV_1 (0 << 2) +#define WM8510_BCLKDIV_2 (1 << 2) +#define WM8510_BCLKDIV_4 (2 << 2) +#define WM8510_BCLKDIV_8 (3 << 2) +#define WM8510_BCLKDIV_16 (4 << 2) +#define WM8510_BCLKDIV_32 (5 << 2) + +/* MCLK clock dividers */ +#define WM8510_MCLKDIV_1 (0 << 5) +#define WM8510_MCLKDIV_1_5 (1 << 5) +#define WM8510_MCLKDIV_2 (2 << 5) +#define WM8510_MCLKDIV_3 (3 << 5) +#define WM8510_MCLKDIV_4 (4 << 5) +#define WM8510_MCLKDIV_6 (5 << 5) +#define WM8510_MCLKDIV_8 (6 << 5) +#define WM8510_MCLKDIV_12 (7 << 5) + +struct wm8510_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8510_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8510; + +#endif Index: linux-2.6.21-moko/sound/soc/imx/imx-ac97.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/imx-ac97.c @@ -0,0 +1,222 @@ +/* + * imx-ssi.c -- SSI driver for Freescale IMX + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Based on mxc-alsa-mc13783 (C) 2006 Freescale. + * + * 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. + * + * Revision history + * 29th Aug 2006 Initial version. + * + */ + + +static imx_pcm_dma_params_t imx_ssi1_pcm_stereo_out = { + .name = "SSI1 PCM Stereo out", + .params = { + .bd_number = 1, + .transfer_type = emi_2_per, + .watermark_level = SDMA_TXFIFO_WATERMARK, + .word_size = TRANSFER_16BIT, // maybe add this in setup func + .per_address = SSI1_STX0, + .event_id = DMA_REQ_SSI1_TX1, + .peripheral_type = SSI, + }, +}; + +static imx_pcm_dma_params_t imx_ssi1_pcm_stereo_in = { + .name = "SSI1 PCM Stereo in", + .params = { + .bd_number = 1, + .transfer_type = per_2_emi, + .watermark_level = SDMA_RXFIFO_WATERMARK, + .word_size = TRANSFER_16BIT, // maybe add this in setup func + .per_address = SSI1_SRX0, + .event_id = DMA_REQ_SSI1_RX1, + .peripheral_type = SSI, + }, +}; + +static imx_pcm_dma_params_t imx_ssi2_pcm_stereo_out = { + .name = "SSI2 PCM Stereo out", + .params = { + .bd_number = 1, + .transfer_type = per_2_emi, + .watermark_level = SDMA_TXFIFO_WATERMARK, + .word_size = TRANSFER_16BIT, // maybe add this in setup func + .per_address = SSI2_STX0, + .event_id = DMA_REQ_SSI2_TX1, + .peripheral_type = SSI, + }, +}; + +static imx_pcm_dma_params_t imx_ssi2_pcm_stereo_in = { + .name = "SSI2 PCM Stereo in", + .params = { + .bd_number = 1, + .transfer_type = per_2_emi, + .watermark_level = SDMA_RXFIFO_WATERMARK, + .word_size = TRANSFER_16BIT, // maybe add this in setup func + .per_address = SSI2_SRX0, + .event_id = DMA_REQ_SSI2_RX1, + .peripheral_type = SSI, + }, +}; + +static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ +} + +static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) +{ +} + +static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97) +{ +} + +static void imx_ssi_ac97_cold_reset(struct snd_ac97 *ac97) +{ +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = imx_ssi_ac97_read, + .write = imx_ssi_ac97_write, + .warm_reset = imx_ssi_ac97_warm_reset, + .reset = imx_ssi_ac97_cold_reset, +}; + + +static intimx_ssi1_ac97_probe(struct platform_device *pdev) +{ + int ret; + + + return ret; +} + +static void imx_ssi1_ac97_remove(struct platform_device *pdev) +{ + /* shutdown SSI */ + if(rtd->cpu_dai->id == 0) + SSI1_SCR &= ~SSI_SCR_SSIEN; + else + SSI2_SCR &= ~SSI_SCR_SSIEN; + } + +} + +static int imx_ssi1_ac97_prepare(struct snd_pcm_substream *substream) +{ + // set vra +} + +static int imx_ssi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + if (!rtd->cpu_dai->active) { + + } + + return 0; +} + +static int imx_ssi1_trigger(struct snd_pcm_substream *substream, int cmd) +{ + + return ret; +} + +static void imx_ssi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + +} + +#ifdef CONFIG_PM +static int imx_ssi_suspend(struct platform_device *dev, + struct snd_soc_cpu_dai *dai) +{ + if(!dai->active) + return 0; + + + return 0; +} + +static int imx_ssi_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ + if(!dai->active) + return 0; + + return 0; +} + +#else +#define imx_ssi_suspend NULL +#define imx_ssi_resume NULL +#endif + +#define IMX_AC97_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) + +struct snd_soc_cpu_dai imx_ssi_ac97_dai = { + .name = "imx-ac97-1", + .id = 0, + .type = SND_SOC_DAI_AC97, + .suspend = imx_ssi_suspend, + .resume = imx_ssi_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = IMX_AC97_RATES,}, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = IMX_AC97_RATES,}, + .ops = { + .probe = imx_ac97_probe, + .remove = imx_ac97_shutdown, + .trigger = imx_ssi_trigger, + .prepare = imx_ssi_ac97_prepare,}, +}, +{ + .name = "imx-ac97-2", + .id = 1, + .type = SND_SOC_DAI_AC97, + .suspend = imx_ssi_suspend, + .resume = imx_ssi_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = IMX_AC97_RATES,}, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = IMX_AC97_RATES,}, + .ops = { + .probe = imx_ac97_probe, + .remove = imx_ac97_shutdown, + .trigger = imx_ssi_trigger, + .prepare = imx_ssi_ac97_prepare,}, +}; + +EXPORT_SYMBOL_GPL(imx_ssi_ac97_dai); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("i.MX ASoC AC97 driver"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8976.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8976.c @@ -0,0 +1,885 @@ +/* + * wm8976.c -- WM8976 ALSA Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics PLC. + * + * 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 "wm8976.h" + +#define AUDIO_NAME "wm8976" +#define WM8976_VERSION "0.4" + +/* + * Debug + */ + +#define WM8976_DEBUG 0 + +#ifdef WM8976_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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) + +struct snd_soc_codec_device soc_codec_dev_wm8976; + +/* + * wm8976 register cache + * We can't read the WM8976 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8976_reg[WM8976_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0050, 0x0000, 0x0140, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00ff, + 0x00ff, 0x0000, 0x0100, 0x00ff, + 0x00ff, 0x0000, 0x012c, 0x002c, + 0x002c, 0x002c, 0x002c, 0x0000, + 0x0032, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0038, 0x000b, 0x0032, 0x0000, + 0x0008, 0x000c, 0x0093, 0x00e9, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0033, 0x0010, 0x0010, 0x0100, + 0x0100, 0x0002, 0x0001, 0x0001, + 0x0039, 0x0039, 0x0039, 0x0039, + 0x0001, 0x0001, +}; + +/* + * read wm8976 register cache + */ +static inline unsigned int wm8976_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8976_RESET) + return 0; + if (reg >= WM8976_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8976 register cache + */ +static inline void wm8976_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8976_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8976 register space + */ +static int wm8976_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8976 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8976_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -1; +} + +#define wm8976_reset(c) wm8976_write(c, WM8976_RESET, 0) + +static const char *wm8976_companding[] = {"Off", "NC", "u-law", "A-law" }; +static const char *wm8976_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8976_eqmode[] = {"Capture", "Playback" }; +static const char *wm8976_bw[] = {"Narrow", "Wide" }; +static const char *wm8976_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" }; +static const char *wm8976_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" }; +static const char *wm8976_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" }; +static const char *wm8976_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" }; +static const char *wm8976_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" }; +static const char *wm8976_alc[] = + {"ALC both on", "ALC left only", "ALC right only", "Limiter" }; + +static const struct soc_enum wm8976_enum[] = { + SOC_ENUM_SINGLE(WM8976_COMP, 1, 4, wm8976_companding), /* adc */ + SOC_ENUM_SINGLE(WM8976_COMP, 3, 4, wm8976_companding), /* dac */ + SOC_ENUM_SINGLE(WM8976_DAC, 4, 4, wm8976_deemp), + SOC_ENUM_SINGLE(WM8976_EQ1, 8, 2, wm8976_eqmode), + + SOC_ENUM_SINGLE(WM8976_EQ1, 5, 4, wm8976_eq1), + SOC_ENUM_SINGLE(WM8976_EQ2, 8, 2, wm8976_bw), + SOC_ENUM_SINGLE(WM8976_EQ2, 5, 4, wm8976_eq2), + SOC_ENUM_SINGLE(WM8976_EQ3, 8, 2, wm8976_bw), + + SOC_ENUM_SINGLE(WM8976_EQ3, 5, 4, wm8976_eq3), + SOC_ENUM_SINGLE(WM8976_EQ4, 8, 2, wm8976_bw), + SOC_ENUM_SINGLE(WM8976_EQ4, 5, 4, wm8976_eq4), + SOC_ENUM_SINGLE(WM8976_EQ5, 8, 2, wm8976_bw), + + SOC_ENUM_SINGLE(WM8976_EQ5, 5, 4, wm8976_eq5), + SOC_ENUM_SINGLE(WM8976_ALC3, 8, 2, wm8976_alc), +}; + +static const struct snd_kcontrol_new wm8976_snd_controls[] = { +SOC_SINGLE("Digital Loopback Switch", WM8976_COMP, 0, 1, 0), + +SOC_ENUM("ADC Companding", wm8976_enum[0]), +SOC_ENUM("DAC Companding", wm8976_enum[1]), + +SOC_SINGLE("Jack Detection Enable", WM8976_JACK1, 6, 1, 0), + +SOC_DOUBLE("DAC Inversion Switch", WM8976_DAC, 0, 1, 1, 0), + +SOC_DOUBLE_R("Headphone Playback Volume", WM8976_DACVOLL, WM8976_DACVOLR, 0, 127, 0), + +SOC_SINGLE("High Pass Filter Switch", WM8976_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Filter Switch", WM8976_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8976_ADC, 4, 7, 0), + +SOC_DOUBLE("ADC Inversion Switch", WM8976_ADC, 0, 1, 1, 0), + +SOC_SINGLE("Capture Volume", WM8976_ADCVOL, 0, 127, 0), + +SOC_ENUM("Equaliser Function", wm8976_enum[3]), +SOC_ENUM("EQ1 Cut Off", wm8976_enum[4]), +SOC_SINGLE("EQ1 Volume", WM8976_EQ1, 0, 31, 1), + +SOC_ENUM("Equaliser EQ2 Bandwith", wm8976_enum[5]), +SOC_ENUM("EQ2 Cut Off", wm8976_enum[6]), +SOC_SINGLE("EQ2 Volume", WM8976_EQ2, 0, 31, 1), + +SOC_ENUM("Equaliser EQ3 Bandwith", wm8976_enum[7]), +SOC_ENUM("EQ3 Cut Off", wm8976_enum[8]), +SOC_SINGLE("EQ3 Volume", WM8976_EQ3, 0, 31, 1), + +SOC_ENUM("Equaliser EQ4 Bandwith", wm8976_enum[9]), +SOC_ENUM("EQ4 Cut Off", wm8976_enum[10]), +SOC_SINGLE("EQ4 Volume", WM8976_EQ4, 0, 31, 1), + +SOC_ENUM("Equaliser EQ5 Bandwith", wm8976_enum[11]), +SOC_ENUM("EQ5 Cut Off", wm8976_enum[12]), +SOC_SINGLE("EQ5 Volume", WM8976_EQ5, 0, 31, 1), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8976_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8976_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8976_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8976_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8976_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8976_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8976_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8976_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8976_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8976_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8976_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8976_enum[13]), +SOC_SINGLE("ALC Capture Decay", WM8976_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8976_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8976_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8976_NGATE, 0, 7, 0), + +SOC_SINGLE("Capture PGA ZC Switch", WM8976_INPPGA, 7, 1, 0), +SOC_SINGLE("Capture PGA Volume", WM8976_INPPGA, 0, 63, 0), + +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8976_HPVOLL, WM8976_HPVOLR, 7, 1, 0), +SOC_DOUBLE_R("Headphone Playback Switch", WM8976_HPVOLL, WM8976_HPVOLR, 6, 1, 1), +SOC_DOUBLE_R("Headphone Playback Volume", WM8976_HPVOLL, WM8976_HPVOLR, 0, 63, 0), + +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8976_SPKVOLL, WM8976_SPKVOLR, 7, 1, 0), +SOC_DOUBLE_R("Speaker Playback Switch", WM8976_SPKVOLL, WM8976_SPKVOLR, 6, 1, 1), +SOC_DOUBLE_R("Speaker Playback Volume", WM8976_SPKVOLL, WM8976_SPKVOLR, 0, 63, 0), + +SOC_SINGLE("Capture Boost(+20dB)", WM8976_ADCBOOST, 8, 1, 0), +}; + +/* add non dapm controls */ +static int wm8976_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8976_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8976_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Left Output Mixer */ +static const snd_kcontrol_new_t wm8976_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Right PCM Playback Switch", WM8976_OUTPUT, 6, 1, 1), +SOC_DAPM_SINGLE("Left PCM Playback Switch", WM8976_MIXL, 0, 1, 1), +SOC_DAPM_SINGLE("Line Bypass Switch", WM8976_MIXL, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8976_MIXL, 5, 1, 0), +}; + +/* Right Output Mixer */ +static const snd_kcontrol_new_t wm8976_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left PCM Playback Switch", WM8976_OUTPUT, 5, 1, 1), +SOC_DAPM_SINGLE("Right PCM Playback Switch", WM8976_MIXR, 0, 1, 1), +SOC_DAPM_SINGLE("Line Bypass Switch", WM8976_MIXR, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8976_MIXR, 5, 1, 0), +}; + +/* Left AUX Input boost vol */ +static const snd_kcontrol_new_t wm8976_laux_boost_controls = +SOC_DAPM_SINGLE("Aux Volume", WM8976_ADCBOOST, 0, 3, 0); + +/* Left Input boost vol */ +static const snd_kcontrol_new_t wm8976_lmic_boost_controls = +SOC_DAPM_SINGLE("Input Volume", WM8976_ADCBOOST, 4, 3, 0); + +/* Left Aux In to PGA */ +static const snd_kcontrol_new_t wm8976_laux_capture_boost_controls = +SOC_DAPM_SINGLE("Capture Switch", WM8976_ADCBOOST, 8, 1, 0); + +/* Left Input P In to PGA */ +static const snd_kcontrol_new_t wm8976_lmicp_capture_boost_controls = +SOC_DAPM_SINGLE("Input P Capture Boost Switch", WM8976_INPUT, 0, 1, 0); + +/* Left Input N In to PGA */ +static const snd_kcontrol_new_t wm8976_lmicn_capture_boost_controls = +SOC_DAPM_SINGLE("Input N Capture Boost Switch", WM8976_INPUT, 1, 1, 0); + +// TODO Widgets +static const struct snd_soc_dapm_widget wm8976_dapm_widgets[] = { +#if 0 +//SND_SOC_DAPM_MUTE("Mono Mute", WM8976_MONOMIX, 6, 0), +//SND_SOC_DAPM_MUTE("Speaker Mute", WM8976_SPKMIX, 6, 0), + +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8976_POWER3, 2, 0, + &wm8976_speaker_mixer_controls[0], + ARRAY_SIZE(wm8976_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8976_POWER3, 3, 0, + &wm8976_mono_mixer_controls[0], + ARRAY_SIZE(wm8976_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8976_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8976_POWER3, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8976_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8976_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8976_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8976_POWER3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mic PGA", WM8976_POWER2, 2, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Aux Boost", SND_SOC_NOPM, 0, 0, + &wm8976_aux_boost_controls, 1), +SND_SOC_DAPM_PGA("Mic Boost", SND_SOC_NOPM, 0, 0, + &wm8976_mic_boost_controls, 1), +SND_SOC_DAPM_SWITCH("Capture Boost", SND_SOC_NOPM, 0, 0, + &wm8976_capture_boost_controls), + +SND_SOC_DAPM_MIXER("Boost Mixer", WM8976_POWER2, 4, 0, NULL, 0), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8976_POWER1, 4, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +#endif +}; + +static const char *audio_map[][3] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Boost Mixer */ + {"Boost Mixer", NULL, "ADC"}, + {"Capture Boost Switch", "Aux Capture Boost Switch", "AUX"}, + {"Aux Boost", "Aux Volume", "Boost Mixer"}, + {"Capture Boost", "Capture Switch", "Boost Mixer"}, + {"Mic Boost", "Mic Volume", "Boost Mixer"}, + + /* Inputs */ + {"MICP", NULL, "Mic Boost"}, + {"MICN", NULL, "Mic PGA"}, + {"Mic PGA", NULL, "Capture Boost"}, + {"AUX", NULL, "Aux Input"}, + + /* */ + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8976_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm8976_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm8976_dapm_widgets[i]); + } + + /* set up audio path map */ + 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; +} + +struct pll_ { + unsigned int in_hz, out_hz; + unsigned int pre:4; /* prescale - 1 */ + unsigned int n:4; + unsigned int k; +}; + +struct pll_ pll[] = { + {12000000, 11289600, 0, 7, 0x86c220}, + {12000000, 12288000, 0, 8, 0x3126e8}, + {13000000, 11289600, 0, 6, 0xf28bd4}, + {13000000, 12288000, 0, 7, 0x8fd525}, + {12288000, 11289600, 0, 7, 0x59999a}, + {11289600, 12288000, 0, 8, 0x80dee9}, + /* TODO: liam - add more entries */ +}; + +static int wm8976_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int i; + u16 reg; + + if(freq_in == 0 || freq_out == 0) { + reg = wm8976_read_reg_cache(codec, WM8976_POWER1); + wm8976_write(codec, WM8976_POWER1, reg & 0x1df); + return 0; + } + + for(i = 0; i < ARRAY_SIZE(pll); i++) { + if (freq_in == pll[i].in_hz && freq_out == pll[i].out_hz) { + wm8976_write(codec, WM8976_PLLN, (pll[i].pre << 4) | pll[i].n); + wm8976_write(codec, WM8976_PLLK1, pll[i].k >> 18); + wm8976_write(codec, WM8976_PLLK1, (pll[i].k >> 9) && 0x1ff); + wm8976_write(codec, WM8976_PLLK1, pll[i].k && 0x1ff); + reg = wm8976_read_reg_cache(codec, WM8976_POWER1); + wm8976_write(codec, WM8976_POWER1, reg | 0x020); + return 0; + } + } + return -EINVAL; +} + +static int wm8976_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = wm8976_read_reg_cache(codec, WM8976_IFACE) & 0x3; + u16 clk = wm8976_read_reg_cache(codec, WM8976_CLOCK) & 0xfffe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + wm8976_write(codec, WM8976_IFACE, iface); + return 0; +} + +static int wm8976_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface = wm8976_read_reg_cache(codec, WM8976_IFACE) & 0xff9f; + u16 adn = wm8976_read_reg_cache(codec, WM8976_ADD) & 0x1f1; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0020; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0040; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + adn |= 0x5 << 1; + break; + case SNDRV_PCM_RATE_11025: + adn |= 0x4 << 1; + break; + case SNDRV_PCM_RATE_16000: + adn |= 0x3 << 1; + break; + case SNDRV_PCM_RATE_22050: + adn |= 0x2 << 1; + break; + case SNDRV_PCM_RATE_32000: + adn |= 0x1 << 1; + break; + } + + /* set iface */ + wm8976_write(codec, WM8976_IFACE, iface); + wm8976_write(codec, WM8976_ADD, adn); + return 0; +} + +static int wm8976_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8976_MCLKDIV: + reg = wm8976_read_reg_cache(codec, WM8976_CLOCK) & 0x11f; + wm8976_write(codec, WM8976_CLOCK, reg | div); + break; + case WM8976_BCLKDIV: + reg = wm8976_read_reg_cache(codec, WM8976_CLOCK) & 0x1c7; + wm8976_write(codec, WM8976_CLOCK, reg | div); + break; + case WM8976_OPCLKDIV: + reg = wm8976_read_reg_cache(codec, WM8976_GPIO) & 0x1cf; + wm8976_write(codec, WM8976_GPIO, reg | div); + break; + case WM8976_DACOSR: + reg = wm8976_read_reg_cache(codec, WM8976_DAC) & 0x1f7; + wm8976_write(codec, WM8976_DAC, reg | div); + break; + case WM8976_ADCOSR: + reg = wm8976_read_reg_cache(codec, WM8976_ADC) & 0x1f7; + wm8976_write(codec, WM8976_ADC, reg | div); + break; + case WM8976_MCLKSEL: + reg = wm8976_read_reg_cache(codec, WM8976_CLOCK) & 0x0ff; + wm8976_write(codec, WM8976_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8976_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8976_read_reg_cache(codec, WM8976_DAC) & 0xffbf; + + if(mute) + wm8976_write(codec, WM8976_DAC, mute_reg | 0x40); + else + wm8976_write(codec, WM8976_DAC, mute_reg); + + return 0; +} + +/* TODO: liam need to make this lower power with dapm */ +static int wm8976_dapm_event(struct snd_soc_codec *codec, int event) +{ + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, clk and osc on, dac unmute, active */ + wm8976_write(codec, WM8976_POWER1, 0x1ff); + wm8976_write(codec, WM8976_POWER2, 0x1ff); + wm8976_write(codec, WM8976_POWER3, 0x1ff); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, dac mute, inactive */ + + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + wm8976_write(codec, WM8976_POWER1, 0x0); + wm8976_write(codec, WM8976_POWER2, 0x0); + wm8976_write(codec, WM8976_POWER3, 0x0); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8976_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) + +#define WM8976_FORMATS \ + (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_3LE | SNDRV_PCM_FORMAT_S24_LE) + +struct snd_soc_codec_dai wm8976_dai = { + .name = "WM8976 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8976_RATES, + .formats = WM8976_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = WM8976_RATES, + .formats = WM8976_FORMATS,}, + .ops = { + .hw_params = wm8976_hw_params, + }, + .dai_ops = { + .digital_mute = wm8976_mute, + .set_fmt = wm8976_set_dai_fmt, + .set_clkdiv = wm8976_set_dai_clkdiv, + .set_pll = wm8976_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8976_dai); + +static int wm8976_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; + + wm8976_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8976_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8976_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8976_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8976_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8976 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8976_init(struct snd_soc_device* socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8976"; + codec->owner = THIS_MODULE; + codec->read = wm8976_read_reg_cache; + codec->write = wm8976_write; + codec->dapm_event = wm8976_dapm_event; + codec->dai = &wm8976_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8976_reg); + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8976_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, wm8976_reg, + sizeof(u16) * ARRAY_SIZE(wm8976_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8976_reg); + + wm8976_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if(ret < 0) { + printk(KERN_ERR "wm8976: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8976_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8976_add_controls(codec); + wm8976_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8976: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8976_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * WM8976 2 wire address is 0x1a + */ +#define I2C_DRIVERID_WM8976 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8976_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8976_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8976_socdev; + struct wm8976_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + + i2c_set_clientdata(i2c, codec); + + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if(ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8976_init(socdev); + if(ret < 0) { + err("failed to initialise WM8976\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; + +} + +static int wm8976_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8976_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8976_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8976_i2c_driver = { + .driver = { + .name = "WM8976 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8976, + .attach_adapter = wm8976_i2c_attach, + .detach_client = wm8976_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8976", + .driver = &wm8976_i2c_driver, +}; +#endif + +static int wm8976_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8976_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + info("WM8976 Audio Codec %s", WM8976_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); + + wm8976_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8976_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8976_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) + wm8976_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8976_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8976 = { + .probe = wm8976_probe, + .remove = wm8976_remove, + .suspend = wm8976_suspend, + .resume = wm8976_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8976); + +MODULE_DESCRIPTION("ASoC WM8976 driver"); +MODULE_AUTHOR("Graeme Gregory"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8976.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8976.h @@ -0,0 +1,112 @@ +/* + * wm8976.h -- WM8976 Soc Audio driver + * + * 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 _WM8976_H +#define _WM8976_H + +/* WM8976 register space */ + +#define WM8976_RESET 0x0 +#define WM8976_POWER1 0x1 +#define WM8976_POWER2 0x2 +#define WM8976_POWER3 0x3 +#define WM8976_IFACE 0x4 +#define WM8976_COMP 0x5 +#define WM8976_CLOCK 0x6 +#define WM8976_ADD 0x7 +#define WM8976_GPIO 0x8 +#define WM8976_JACK1 0x9 +#define WM8976_DAC 0xa +#define WM8976_DACVOLL 0xb +#define WM8976_DACVOLR 0xc +#define WM8976_JACK2 0xd +#define WM8976_ADC 0xe +#define WM8976_ADCVOL 0xf +#define WM8976_EQ1 0x12 +#define WM8976_EQ2 0x13 +#define WM8976_EQ3 0x14 +#define WM8976_EQ4 0x15 +#define WM8976_EQ5 0x16 +#define WM8976_DACLIM1 0x18 +#define WM8976_DACLIM2 0x19 +#define WM8976_NOTCH1 0x1b +#define WM8976_NOTCH2 0x1c +#define WM8976_NOTCH3 0x1d +#define WM8976_NOTCH4 0x1e +#define WM8976_ALC1 0x20 +#define WM8976_ALC2 0x21 +#define WM8976_ALC3 0x22 +#define WM8976_NGATE 0x23 +#define WM8976_PLLN 0x24 +#define WM8976_PLLK1 0x25 +#define WM8976_PLLK2 0x26 +#define WM8976_PLLK3 0x27 +#define WM8976_3D 0x29 +#define WM8976_BEEP 0x2b +#define WM8976_INPUT 0x2c +#define WM8976_INPPGA 0x2d +#define WM8976_ADCBOOST 0x2f +#define WM8976_OUTPUT 0x31 +#define WM8976_MIXL 0x32 +#define WM8976_MIXR 0x33 +#define WM8976_HPVOLL 0x34 +#define WM8976_HPVOLR 0x35 +#define WM8976_SPKVOLL 0x36 +#define WM8976_SPKVOLR 0x37 +#define WM8976_OUT3MIX 0x38 +#define WM8976_MONOMIX 0x39 + +#define WM8976_CACHEREGNUM 58 + +/* + * WM8976 Clock dividers + */ +#define WM8976_MCLKDIV 0 +#define WM8976_BCLKDIV 1 +#define WM8976_OPCLKDIV 2 +#define WM8976_DACOSR 3 +#define WM8976_ADCOSR 4 +#define WM8976_MCLKSEL 5 + +#define WM8976_MCLK_MCLK (0 << 8) +#define WM8976_MCLK_PLL (1 << 8) + +#define WM8976_MCLK_DIV_1 (0 << 5) +#define WM8976_MCLK_DIV_1_5 (1 << 5) +#define WM8976_MCLK_DIV_2 (2 << 5) +#define WM8976_MCLK_DIV_3 (3 << 5) +#define WM8976_MCLK_DIV_4 (4 << 5) +#define WM8976_MCLK_DIV_5_5 (5 << 5) +#define WM8976_MCLK_DIV_6 (6 << 5) + +#define WM8976_BCLK_DIV_1 (0 << 2) +#define WM8976_BCLK_DIV_2 (1 << 2) +#define WM8976_BCLK_DIV_4 (2 << 2) +#define WM8976_BCLK_DIV_8 (3 << 2) +#define WM8976_BCLK_DIV_16 (4 << 2) +#define WM8976_BCLK_DIV_32 (5 << 2) + +#define WM8976_DACOSR_64 (0 << 3) +#define WM8976_DACOSR_128 (1 << 3) + +#define WM8976_ADCOSR_64 (0 << 3) +#define WM8976_ADCOSR_128 (1 << 3) + +#define WM8976_OPCLK_DIV_1 (0 << 4) +#define WM8976_OPCLK_DIV_2 (1 << 4) +#define WM8976_OPCLK_DIV_3 (2 << 4) +#define WM8976_OPCLK_DIV_4 (3 << 4) + +struct wm8976_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8976_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8976; + +#endif Index: linux-2.6.21-moko/sound/soc/imx/imx21-pcm.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/imx21-pcm.c @@ -0,0 +1,454 @@ +/* + * linux/sound/arm/mxc-pcm.c -- ALSA SoC interface for the Freescale i.MX CPU's + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Based on pxa2xx-pcm.c by Nicolas Pitre, (C) 2004 MontaVista Software, Inc. + * and on mxc-alsa-mc13783 (C) 2006 Freescale. + * + * 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. + * + * Revision history + * 29th Aug 2006 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-pcm.h" + +/* debug */ +#define IMX_DEBUG 0 +#if IMX_DEBUG +#define dbg(format, arg...) printk(format, ## arg) +#else +#define dbg(format, arg...) +#endif + +static const struct snd_pcm_hardware mxc_pcm_hardware = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .buffer_bytes_max = 32 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 8 * 1024, + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +struct mxc_runtime_data { + int dma_ch; + struct mxc_pcm_dma_param *dma_params; +}; + +/*! + * This function stops the current dma transfert for playback + * and clears the dma pointers. + * + * @param substream pointer to the structure of the current stream. + * + */ +static void audio_stop_dma(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + unsigned int dma_size = frames_to_bytes(runtime, runtime->period_size); + unsigned int offset dma_size * s->periods; + unsigned long flags; + + spin_lock_irqsave(&prtd->dma_lock, flags); + + dbg("MXC : audio_stop_dma active = 0\n"); + prtd->active = 0; + prtd->period = 0; + prtd->periods = 0; + + /* this stops the dma channel and clears the buffer ptrs */ + mxc_dma_stop(prtd->dma_wchannel); + if(substream == SNDRV_PCM_STREAM_PLAYBACK) + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_TO_DEVICE); + else + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); + + spin_unlock_irqrestore(&prtd->dma_lock, flags); +} + +/*! + * This function is called whenever a new audio block needs to be + * transferred to mc13783. The function receives the address and the size + * of the new block and start a new DMA transfer. + * + * @param substream pointer to the structure of the current stream. + * + */ +static int dma_new_period(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + unsigned int dma_size; + unsigned int offset; + int ret=0; + dma_request_t sdma_request; + + if (prtd->active){ + memset(&sdma_request, 0, sizeof(dma_request_t)); + dma_size = frames_to_bytes(runtime, runtime->period_size); + dbg("s->period (%x) runtime->periods (%d)\n", + s->period,runtime->periods); + dbg("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * prtd->period; + snd_assert(dma_size <= DMA_BUF_SIZE, ); + if(substream == SNDRV_PCM_STREAM_PLAYBACK) + sdma_request.sourceAddr = (char*)(dma_map_single(NULL, + runtime->dma_area + offset, dma_size, DMA_TO_DEVICE)); + else + sdma_request.destAddr = (char*)(dma_map_single(NULL, + runtime->dma_area + offset, dma_size, DMA_FROM_DEVICE)); + sdma_request.count = dma_size; + + dbg("MXC: Start DMA offset (%d) size (%d)\n", offset, + runtime->dma_bytes); + + mxc_dma_set_config(prtd->dma_wchannel, &sdma_request, 0); + if((ret = mxc_dma_start(prtd->dma_wchannel)) < 0) { + dbg("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", ret); + return err; + } + prtd->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + prtd->period++; + prtd->period %= runtime->periods; + } + return ret; +} + + +/*! + * This is a callback which will be called + * when a TX transfer finishes. The call occurs + * in interrupt context. + * + * @param dat pointer to the structure of the current stream. + * + */ +static void audio_dma_irq(void *data) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + struct mxc_runtime_data *prtd; + unsigned int dma_size; + unsigned int previous_period; + unsigned int offset; + + substream = data; + runtime = substream->runtime; + prtd = runtime->private_data; + previous_period = prtd->periods; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * previous_period; + + prtd->tx_spin = 0; + prtd->periods++; + prtd->periods %= runtime->periods; + + /* + * Give back to the CPU the access to the non cached memory + */ + if(substream == SNDRV_PCM_STREAM_PLAYBACK) + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_TO_DEVICE); + else + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (prtd->active) + snd_pcm_period_elapsed(substream); + + /* + * Trig next DMA transfer + */ + dma_new_period(substream); +} + +/*! + * This function configures the hardware to allow audio + * playback operations. It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int +snd_mxc_prepare(struct snd_pcm_substream *substream) +{ + struct mxc_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0; + prtd->period = 0; + prtd->periods = 0; + + dma_channel_params params; + int channel = 0; // passed in ? + + if ((ret = mxc_request_dma(&channel, "ALSA TX SDMA") < 0)){ + dbg("error requesting a write dma channel\n"); + return ret; + } + + /* configure DMA params */ + memset(¶ms, 0, sizeof(dma_channel_params)); + params.bd_number = 1; + params.arg = s; + params.callback = callback; + params.transfer_type = emi_2_per; + params.watermark_level = SDMA_TXFIFO_WATERMARK; + params.word_size = TRANSFER_16BIT; + //dbg(KERN_ERR "activating connection SSI1 - SDMA\n"); + params.per_address = SSI1_BASE_ADDR; + params.event_id = DMA_REQ_SSI1_TX1; + params.peripheral_type = SSI; + + /* set up chn with params */ + mxc_dma_setup_channel(channel, ¶ms); + s->dma_wchannel = channel; + + return ret; +} + +static int mxc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + if((ret=snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return ret; + runtime->dma_addr = virt_to_phys(runtime->dma_area); + + return ret; +} + +static int mxc_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int mxc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct mxc_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->tx_spin = 0; + /* requested stream startup */ + prtd->active = 1; + ret = dma_new_period(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + /* requested stream shutdown */ + ret = audio_stop_dma(substream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + prtd->active = 0; + prtd->periods = 0; + break; + case SNDRV_PCM_TRIGGER_RESUME: + prtd->active = 1; + prtd->tx_spin = 0; + ret = dma_new_period(substream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + prtd->active = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + prtd->active = 1; + if (prtd->old_offset) { + prtd->tx_spin = 0; + ret = dma_new_period(substream); + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t mxc_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + unsigned int offset = 0; + + /* tx_spin value is used here to check if a transfert is active */ + if (prtd->tx_spin){ + offset = (runtime->period_size * (prtd->periods)) + + (runtime->period_size >> 1); + if (offset >= runtime->buffer_size) + offset = runtime->period_size >> 1; + } else { + offset = (runtime->period_size * (s->periods)); + if (offset >= runtime->buffer_size) + offset = 0; + } + + return offset; +} + + +static int mxc_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd; + int ret; + + snd_soc_set_runtime_hwparams(substream, &mxc_pcm_hardware); + + if ((err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS)) < 0) + return err; + if ((err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &hw_playback_rates)) < 0) + return err; + msleep(10); // liam - why + + /* setup DMA controller for playback */ + if((err = configure_write_channel(&mxc_mc13783->s[SNDRV_PCM_STREAM_PLAYBACK], + audio_dma_irq)) < 0 ) + return err; + + if((prtd = kzalloc(sizeof(struct mxc_runtime_data), GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto out; + } + + runtime->private_data = prtd; + return 0; + + err1: + kfree(prtd); + out: + return ret; +} + +static int mxc_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + +// mxc_mc13783_t *chip; + audio_stream_t *s; + device_data_t* device; + int ssi; + + //chip = snd_pcm_substream_chip(substream); + s = &chip->s[substream->pstr->stream]; + device = &s->stream_device; + ssi = device->ssi; + + //disable_stereodac(); + + ssi_transmit_enable(ssi, false); + ssi_interrupt_disable(ssi, ssi_tx_dma_interrupt_enable); + ssi_tx_fifo_enable(ssi, ssi_fifo_0, false); + ssi_enable(ssi, false); + + chip->s[substream->pstr->stream].stream = NULL; + + return 0; +} + +static int +mxc_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +struct snd_pcm_ops mxc_pcm_ops = { + .open = mxc_pcm_open, + .close = mxc_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = mxc_pcm_hw_params, + .hw_free = mxc_pcm_hw_free, + .prepare = mxc_pcm_prepare, + .trigger = mxc_pcm_trigger, + .pointer = mxc_pcm_pointer, + .mmap = mxc_pcm_mmap, +}; + +static u64 mxc_pcm_dmamask = 0xffffffff; + +int mxc_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &mxc_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = mxc_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = mxc_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +struct snd_soc_platform mxc_soc_platform = { + .name = "mxc-audio", + .pcm_ops = &mxc_pcm_ops, + .pcm_new = mxc_pcm_new, + .pcm_free = mxc_pcm_free_dma_buffers, +}; + +EXPORT_SYMBOL_GPL(mxc_soc_platform); + +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("Freescale i.MX PCM DMA module"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/imx/imx21-pcm.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/imx21-pcm.h @@ -0,0 +1,237 @@ +/* + * mxc-pcm.h :- ASoC platform header for Freescale i.MX + * + * 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 _MXC_PCM_H +#define _MXC_PCM_H + +struct { + char *name; /* stream identifier */ + dma_channel_params dma_params; +} mxc_pcm_dma_param; + +extern struct snd_soc_cpu_dai mxc_ssi_dai[3]; + +/* platform data */ +extern struct snd_soc_platform mxc_soc_platform; +extern struct snd_ac97_bus_ops mxc_ac97_ops; + +/* temp until imx-regs.h is up2date */ +#define SSI1_STX0 __REG(IMX_SSI1_BASE + 0x00) +#define SSI1_STX0_PHYS __PHYS_REG(IMX_SSI1_BASE + 0x00) +#define SSI1_STX1 __REG(IMX_SSI1_BASE + 0x04) +#define SSI1_STX1_PHYS __PHYS_REG(IMX_SSI1_BASE + 0x04) +#define SSI1_SRX0 __REG(IMX_SSI1_BASE + 0x08) +#define SSI1_SRX0_PHYS __PHYS_REG(IMX_SSI1_BASE + 0x08) +#define SSI1_SRX1 __REG(IMX_SSI1_BASE + 0x0c) +#define SSI1_SRX1_PHYS __PHYS_REG(IMX_SSI1_BASE + 0x0c) +#define SSI1_SCR __REG(IMX_SSI1_BASE + 0x10) +#define SSI1_SISR __REG(IMX_SSI1_BASE + 0x14) +#define SSI1_SIER __REG(IMX_SSI1_BASE + 0x18) +#define SSI1_STCR __REG(IMX_SSI1_BASE + 0x1c) +#define SSI1_SRCR __REG(IMX_SSI1_BASE + 0x20) +#define SSI1_STCCR __REG(IMX_SSI1_BASE + 0x24) +#define SSI1_SRCCR __REG(IMX_SSI1_BASE + 0x28) +#define SSI1_SFCSR __REG(IMX_SSI1_BASE + 0x2c) +#define SSI1_STR __REG(IMX_SSI1_BASE + 0x30) +#define SSI1_SOR __REG(IMX_SSI1_BASE + 0x34) +#define SSI1_SACNT __REG(IMX_SSI1_BASE + 0x38) +#define SSI1_SACADD __REG(IMX_SSI1_BASE + 0x3c) +#define SSI1_SACDAT __REG(IMX_SSI1_BASE + 0x40) +#define SSI1_SATAG __REG(IMX_SSI1_BASE + 0x44) +#define SSI1_STMSK __REG(IMX_SSI1_BASE + 0x48) +#define SSI1_SRMSK __REG(IMX_SSI1_BASE + 0x4c) + +#define SSI2_STX0 __REG(IMX_SSI2_BASE + 0x00) +#define SSI2_STX0_PHYS __PHYS_REG(IMX_SSI2_BASE + 0x00) +#define SSI2_STX1 __REG(IMX_SSI2_BASE + 0x04) +#define SSI2_STX1_PHYS __PHYS_REG(IMX_SSI2_BASE + 0x04) +#define SSI2_SRX0 __REG(IMX_SSI2_BASE + 0x08) +#define SSI2_SRX0_PHYS __PHYS_REG(IMX_SSI2_BASE + 0x08) +#define SSI2_SRX1 __REG(IMX_SSI2_BASE + 0x0c) +#define SSI2_SRX1_PHYS __PHYS_REG(IMX_SSI2_BASE + 0x0c) +#define SSI2_SCR __REG(IMX_SSI2_BASE + 0x10) +#define SSI2_SISR __REG(IMX_SSI2_BASE + 0x14) +#define SSI2_SIER __REG(IMX_SSI2_BASE + 0x18) +#define SSI2_STCR __REG(IMX_SSI2_BASE + 0x1c) +#define SSI2_SRCR __REG(IMX_SSI2_BASE + 0x20) +#define SSI2_STCCR __REG(IMX_SSI2_BASE + 0x24) +#define SSI2_SRCCR __REG(IMX_SSI2_BASE + 0x28) +#define SSI2_SFCSR __REG(IMX_SSI2_BASE + 0x2c) +#define SSI2_STR __REG(IMX_SSI2_BASE + 0x30) +#define SSI2_SOR __REG(IMX_SSI2_BASE + 0x34) +#define SSI2_SACNT __REG(IMX_SSI2_BASE + 0x38) +#define SSI2_SACADD __REG(IMX_SSI2_BASE + 0x3c) +#define SSI2_SACDAT __REG(IMX_SSI2_BASE + 0x40) +#define SSI2_SATAG __REG(IMX_SSI2_BASE + 0x44) +#define SSI2_STMSK __REG(IMX_SSI2_BASE + 0x48) +#define SSI2_SRMSK __REG(IMX_SSI2_BASE + 0x4c) + +#define SSI_SCR_CLK_IST (1 << 9) +#define SSI_SCR_TCH_EN (1 << 8) +#define SSI_SCR_SYS_CLK_EN (1 << 7) +#define SSI_SCR_I2S_MODE_NORM (0 << 5) +#define SSI_SCR_I2S_MODE_MSTR (1 << 5) +#define SSI_SCR_I2S_MODE_SLAVE (2 << 5) +#define SSI_SCR_SYN (1 << 4) +#define SSI_SCR_NET (1 << 3) +#define SSI_SCR_RE (1 << 2) +#define SSI_SCR_TE (1 << 1) +#define SSI_SCR_SSIEN (1 << 0) + +#define SSI_SISR_CMDAU (1 << 18) +#define SSI_SISR_CMDDU (1 << 17) +#define SSI_SISR_RXT (1 << 16) +#define SSI_SISR_RDR1 (1 << 15) +#define SSI_SISR_RDR0 (1 << 14) +#define SSI_SISR_TDE1 (1 << 13) +#define SSI_SISR_TDE0 (1 << 12) +#define SSI_SISR_ROE1 (1 << 11) +#define SSI_SISR_ROE0 (1 << 10) +#define SSI_SISR_TUE1 (1 << 9) +#define SSI_SISR_TUE0 (1 << 8) +#define SSI_SISR_TFS (1 << 7) +#define SSI_SISR_RFS (1 << 6) +#define SSI_SISR_TLS (1 << 5) +#define SSI_SISR_RLS (1 << 4) +#define SSI_SISR_RFF1 (1 << 3) +#define SSI_SISR_RFF0 (1 << 2) +#define SSI_SISR_TFE1 (1 << 1) +#define SSI_SISR_TFE0 (1 << 0) + +#define SSI_SIER_RDMAE (1 << 22) +#define SSI_SIER_RIE (1 << 21) +#define SSI_SIER_TDMAE (1 << 20) +#define SSI_SIER_TIE (1 << 19) +#define SSI_SIER_CMDAU_EN (1 << 18) +#define SSI_SIER_CMDDU_EN (1 << 17) +#define SSI_SIER_RXT_EN (1 << 16) +#define SSI_SIER_RDR1_EN (1 << 15) +#define SSI_SIER_RDR0_EN (1 << 14) +#define SSI_SIER_TDE1_EN (1 << 13) +#define SSI_SIER_TDE0_EN (1 << 12) +#define SSI_SIER_ROE1_EN (1 << 11) +#define SSI_SIER_ROE0_EN (1 << 10) +#define SSI_SIER_TUE1_EN (1 << 9) +#define SSI_SIER_TUE0_EN (1 << 8) +#define SSI_SIER_TFS_EN (1 << 7) +#define SSI_SIER_RFS_EN (1 << 6) +#define SSI_SIER_TLS_EN (1 << 5) +#define SSI_SIER_RLS_EN (1 << 4) +#define SSI_SIER_RFF1_EN (1 << 3) +#define SSI_SIER_RFF0_EN (1 << 2) +#define SSI_SIER_TFE1_EN (1 << 1) +#define SSI_SIER_TFE0_EN (1 << 0) + +#define SSI_STCR_TXBIT0 (1 << 9) +#define SSI_STCR_TFEN1 (1 << 8) +#define SSI_STCR_TFEN0 (1 << 7) +#define SSI_STCR_TFDIR (1 << 6) +#define SSI_STCR_TXDIR (1 << 5) +#define SSI_STCR_TSHFD (1 << 4) +#define SSI_STCR_TSCKP (1 << 3) +#define SSI_STCR_TFSI (1 << 2) +#define SSI_STCR_TFSL (1 << 1) +#define SSI_STCR_TEFS (1 << 0) + +#define SSI_SRCR_RXBIT0 (1 << 9) +#define SSI_SRCR_RFEN1 (1 << 8) +#define SSI_SRCR_RFEN0 (1 << 7) +#define SSI_SRCR_RFDIR (1 << 6) +#define SSI_SRCR_RXDIR (1 << 5) +#define SSI_SRCR_RSHFD (1 << 4) +#define SSI_SRCR_RSCKP (1 << 3) +#define SSI_SRCR_RFSI (1 << 2) +#define SSI_SRCR_RFSL (1 << 1) +#define SSI_SRCR_REFS (1 << 0) + +#define SSI_STCCR_DIV2 (1 << 18) +#define SSI_STCCR_PSR (1 << 15) +#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_STCCR_PM(x) (((x) & 0xff) << 0) + +#define SSI_SRCCR_DIV2 (1 << 18) +#define SSI_SRCCR_PSR (1 << 15) +#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0) + + +#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28) +#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24) +#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20) +#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16) +#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12) +#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8) +#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4) +#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0) + +#define SSI_STR_TEST (1 << 15) +#define SSI_STR_RCK2TCK (1 << 14) +#define SSI_STR_RFS2TFS (1 << 13) +#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8) +#define SSI_STR_TXD2RXD (1 << 7) +#define SSI_STR_TCK2RCK (1 << 6) +#define SSI_STR_TFS2RFS (1 << 5) +#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0) + +#define SSI_SOR_CLKOFF (1 << 6) +#define SSI_SOR_RX_CLR (1 << 5) +#define SSI_SOR_TX_CLR (1 << 4) +#define SSI_SOR_INIT (1 << 3) +#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1) +#define SSI_SOR_SYNRST (1 << 0) + +#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5) +#define SSI_SACNT_WR (x << 4) +#define SSI_SACNT_RD (x << 3) +#define SSI_SACNT_TIF (x << 2) +#define SSI_SACNT_FV (x << 1) +#define SSI_SACNT_A97EN (x << 0) + + +/* AUDMUX registers */ +#define AUDMUX_HPCR1 __REG(IMX_AUDMUX_BASE + 0x00) +#define AUDMUX_HPCR2 __REG(IMX_AUDMUX_BASE + 0x04) +#define AUDMUX_HPCR3 __REG(IMX_AUDMUX_BASE + 0x08) +#define AUDMUX_PPCR1 __REG(IMX_AUDMUX_BASE + 0x10) +#define AUDMUX_PPCR2 __REG(IMX_AUDMUX_BASE + 0x14) +#define AUDMUX_PPCR3 __REG(IMX_AUDMUX_BASE + 0x18) + +#define AUDMUX_HPCR_TFSDIR (1 << 31) +#define AUDMUX_HPCR_TCLKDIR (1 << 30) +#define AUDMUX_HPCR_TFCSEL_TX (0 << 26) +#define AUDMUX_HPCR_TFCSEL_RX (8 << 26) +#define AUDMUX_HPCR_TFCSEL(x) (((x) & 0x7) << 26) +#define AUDMUX_HPCR_RFSDIR (1 << 25) +#define AUDMUX_HPCR_RCLKDIR (1 << 24) +#define AUDMUX_HPCR_RFCSEL_TX (0 << 20) +#define AUDMUX_HPCR_RFCSEL_RX (8 << 20) +#define AUDMUX_HPCR_RFCSEL(x) (((x) & 0x7) << 20) +#define AUDMUX_HPCR_RXDSEL(x) (((x) & 0x7) << 13) +#define AUDMUX_HPCR_SYN (1 << 12) +#define AUDMUX_HPCR_TXRXEN (1 << 10) +#define AUDMUX_HPCR_INMEN (1 << 8) +#define AUDMUX_HPCR_INMMASK(x) (((x) & 0xff) << 0) + +#define AUDMUX_PPCR_TFSDIR (1 << 31) +#define AUDMUX_PPCR_TCLKDIR (1 << 30) +#define AUDMUX_PPCR_TFCSEL_TX (0 << 26) +#define AUDMUX_PPCR_TFCSEL_RX (8 << 26) +#define AUDMUX_PPCR_TFCSEL(x) (((x) & 0x7) << 26) +#define AUDMUX_PPCR_RFSDIR (1 << 25) +#define AUDMUX_PPCR_RCLKDIR (1 << 24) +#define AUDMUX_PPCR_RFCSEL_TX (0 << 20) +#define AUDMUX_PPCR_RFCSEL_RX (8 << 20) +#define AUDMUX_PPCR_RFCSEL(x) (((x) & 0x7) << 20) +#define AUDMUX_PPCR_RXDSEL(x) (((x) & 0x7) << 13) +#define AUDMUX_PPCR_SYN (1 << 12) +#define AUDMUX_PPCR_TXRXEN (1 << 10) + + +#endif Index: linux-2.6.21-moko/sound/soc/imx/imx31-pcm.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/imx31-pcm.c @@ -0,0 +1,417 @@ +/* + * linux/sound/arm/mxc-pcm.c -- ALSA SoC interface for the Freescale i.MX CPU's + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Based on pxa2xx-pcm.c by Nicolas Pitre, (C) 2004 MontaVista Software, Inc. + * and on mxc-alsa-mc13783 (C) 2006 Freescale. + * + * 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. + * + * Revision history + * 29th Aug 2006 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx31-pcm.h" + +/* debug */ +#define IMX_DEBUG 0 +#if IMX_DEBUG +#define dbg(format, arg...) printk(format, ## arg) +#else +#define dbg(format, arg...) +#endif + +static const struct snd_pcm_hardware mxc_pcm_hardware = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .buffer_bytes_max = 32 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 8 * 1024, + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +struct mxc_runtime_data { + int dma_ch; + struct mxc_pcm_dma_param *dma_params; + spinlock_t dma_lock; + int active, period, periods; + int dma_wchannel; + int tx_spin, rx_spin; + int old_offset; +}; + +/*! + * This function stops the current dma transfer for playback + * and clears the dma pointers. + * + * @param substream pointer to the structure of the current stream. + * + */ +static void audio_stop_dma(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + unsigned int dma_size = frames_to_bytes(runtime, runtime->period_size); + unsigned int offset = dma_size * runtime->periods; + unsigned long flags; + + spin_lock_irqsave(&prtd->dma_lock, flags); + + dbg("MXC : audio_stop_dma active = 0\n"); + prtd->active = 0; + prtd->period = 0; + prtd->periods = 0; + + /* this stops the dma channel and clears the buffer ptrs */ + mxc_dma_stop(prtd->dma_wchannel); + if(substream == SNDRV_PCM_STREAM_PLAYBACK) + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_TO_DEVICE); + else + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); + + spin_unlock_irqrestore(&prtd->dma_lock, flags); +} + +/*! + * This function is called whenever a new audio block needs to be + * transferred to mc13783. The function receives the address and the size + * of the new block and start a new DMA transfer. + * + * @param substream pointer to the structure of the current stream. + * + */ +static int dma_new_period(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + unsigned int dma_size; + unsigned int offset; + int ret = 0; + dma_request_t sdma_request; + + if (prtd->active){ + memset(&sdma_request, 0, sizeof(dma_request_t)); + dma_size = frames_to_bytes(runtime, runtime->period_size); + dbg("s->period (%x) runtime->periods (%d)\n", + s->period,runtime->periods); + dbg("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * prtd->period; +// snd_assert(dma_size <= DMA_BUF_SIZE, ); + if(substream == SNDRV_PCM_STREAM_PLAYBACK) + sdma_request.sourceAddr = (char*)(dma_map_single(NULL, + runtime->dma_area + offset, dma_size, DMA_TO_DEVICE)); + else + sdma_request.destAddr = (char*)(dma_map_single(NULL, + runtime->dma_area + offset, dma_size, DMA_FROM_DEVICE)); + sdma_request.count = dma_size; + + dbg("MXC: Start DMA offset (%d) size (%d)\n", offset, + runtime->dma_bytes); + + mxc_dma_set_config(prtd->dma_wchannel, &sdma_request, 0); + if((ret = mxc_dma_start(prtd->dma_wchannel)) < 0) { + dbg("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", ret); + return ret; + } + prtd->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + prtd->period++; + prtd->period %= runtime->periods; + } + return ret; +} + + +/*! + * This is a callback which will be called + * when a TX transfer finishes. The call occurs + * in interrupt context. + * + * @param dat pointer to the structure of the current stream. + * + */ +static void audio_dma_irq(void *data) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + struct mxc_runtime_data *prtd; + unsigned int dma_size; + unsigned int previous_period; + unsigned int offset; + + substream = data; + runtime = substream->runtime; + prtd = runtime->private_data; + previous_period = prtd->periods; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * previous_period; + + prtd->tx_spin = 0; + prtd->periods++; + prtd->periods %= runtime->periods; + + /* + * Give back to the CPU the access to the non cached memory + */ + if(substream == SNDRV_PCM_STREAM_PLAYBACK) + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_TO_DEVICE); + else + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (prtd->active) + snd_pcm_period_elapsed(substream); + + /* + * Trig next DMA transfer + */ + dma_new_period(substream); +} + +/*! + * This function configures the hardware to allow audio + * playback operations. It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int +mxc_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + dma_channel_params *params = rtd->dai->cpu_dai->dma_data; + int ret = 0, channel = 0; // passed in ?; + + prtd->period = 0; + prtd->periods = 0; + + if(substream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = mxc_request_dma(&channel, "ALSA TX SDMA"); + if (ret < 0) { + dbg("error requesting a write dma channel\n"); + return ret; + } + + } else { + ret = mxc_request_dma(&channel, "ALSA RX SDMA"); + if (ret < 0) { + dbg("error requesting a read dma channel\n"); + return ret; + } + } + + /* set up chn with params */ + params->callback = audio_dma_irq; + mxc_dma_setup_channel(channel, params); + prtd->dma_wchannel = channel; + + return ret; +} + +static int mxc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if(ret < 0) + return ret; + runtime->dma_addr = virt_to_phys(runtime->dma_area); + + return ret; +} + +static int mxc_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int mxc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct mxc_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->tx_spin = 0; + /* requested stream startup */ + prtd->active = 1; + ret = dma_new_period(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + /* requested stream shutdown */ + audio_stop_dma(substream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + prtd->active = 0; + prtd->periods = 0; + break; + case SNDRV_PCM_TRIGGER_RESUME: + prtd->active = 1; + prtd->tx_spin = 0; + ret = dma_new_period(substream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + prtd->active = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + prtd->active = 1; + if (prtd->old_offset) { + prtd->tx_spin = 0; + ret = dma_new_period(substream); + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t mxc_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + unsigned int offset = 0; + + /* tx_spin value is used here to check if a transfert is active */ + if (prtd->tx_spin){ + offset = (runtime->period_size * (prtd->periods)) + + (runtime->period_size >> 1); + if (offset >= runtime->buffer_size) + offset = runtime->period_size >> 1; + } else { + offset = (runtime->period_size * (prtd->periods)); + if (offset >= runtime->buffer_size) + offset = 0; + } + + return offset; +} + + +static int mxc_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd; + int ret; + + snd_soc_set_runtime_hwparams(substream, &mxc_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + //ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + // &hw_playback_rates); + //if (ret < 0) + // return ret; + + prtd = kzalloc(sizeof(struct mxc_runtime_data), GFP_KERNEL); + if(prtd == NULL) + return -ENOMEM; + + runtime->private_data = prtd; + return 0; +} + +static int mxc_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + + kfree(prtd); + return 0; +} + +static int +mxc_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +struct snd_pcm_ops mxc_pcm_ops = { + .open = mxc_pcm_open, + .close = mxc_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = mxc_pcm_hw_params, + .hw_free = mxc_pcm_hw_free, + .prepare = mxc_pcm_prepare, + .trigger = mxc_pcm_trigger, + .pointer = mxc_pcm_pointer, + .mmap = mxc_pcm_mmap, +}; + +static u64 mxc_pcm_dmamask = 0xffffffff; + +int mxc_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &mxc_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + return ret; +} + +struct snd_soc_platform mxc_soc_platform = { + .name = "mxc-audio", + .pcm_ops = &mxc_pcm_ops, + .pcm_new = mxc_pcm_new, +}; + +EXPORT_SYMBOL_GPL(mxc_soc_platform); + +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("Freescale i.MX31 PCM DMA module"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/imx/imx31-pcm.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/imx31-pcm.h @@ -0,0 +1,241 @@ +/* + * mxc-pcm.h :- ASoC platform header for Freescale i.MX + * + * 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 _MXC_PCM_H +#define _MXC_PCM_H + +#include + +/* temp until imx-regs.h is up2date */ +#define SSI1_STX0 (SSI1_BASE_ADDR + 0x00) +#define SSI1_STX0_PHYS __PHYS_REG(SSI1_BASE_ADDR + 0x00) +#define SSI1_STX1 (SSI1_BASE_ADDR + 0x04) +#define SSI1_STX1_PHYS __PHYS_REG(SSI1_BASE_ADDR + 0x04) +#define SSI1_SRX0 (SSI1_BASE_ADDR + 0x08) +#define SSI1_SRX0_PHYS __PHYS_REG(SSI1_BASE_ADDR + 0x08) +#define SSI1_SRX1 (SSI1_BASE_ADDR + 0x0c) +#define SSI1_SRX1_PHYS __PHYS_REG(SSI1_BASE_ADDR + 0x0c) +#define SSI1_SCR (SSI1_BASE_ADDR + 0x10) +#define SSI1_SISR (SSI1_BASE_ADDR + 0x14) +#define SSI1_SIER (SSI1_BASE_ADDR + 0x18) +#define SSI1_STCR (SSI1_BASE_ADDR + 0x1c) +#define SSI1_SRCR (SSI1_BASE_ADDR + 0x20) +#define SSI1_STCCR (SSI1_BASE_ADDR + 0x24) +#define SSI1_SRCCR (SSI1_BASE_ADDR + 0x28) +#define SSI1_SFCSR (SSI1_BASE_ADDR + 0x2c) +#define SSI1_STR (SSI1_BASE_ADDR + 0x30) +#define SSI1_SOR (SSI1_BASE_ADDR + 0x34) +#define SSI1_SACNT (SSI1_BASE_ADDR + 0x38) +#define SSI1_SACADD (SSI1_BASE_ADDR + 0x3c) +#define SSI1_SACDAT (SSI1_BASE_ADDR + 0x40) +#define SSI1_SATAG (SSI1_BASE_ADDR + 0x44) +#define SSI1_STMSK (SSI1_BASE_ADDR + 0x48) +#define SSI1_SRMSK (SSI1_BASE_ADDR + 0x4c) + +#define SSI2_STX0 (SSI2_BASE_ADDR + 0x00) +#define SSI2_STX0_PHYS __PHYS_REG(SSI2_BASE_ADDR + 0x00) +#define SSI2_STX1 (SSI2_BASE_ADDR + 0x04) +#define SSI2_STX1_PHYS __PHYS_REG(SSI2_BASE_ADDR + 0x04) +#define SSI2_SRX0 (SSI2_BASE_ADDR + 0x08) +#define SSI2_SRX0_PHYS __PHYS_REG(SSI2_BASE_ADDR + 0x08) +#define SSI2_SRX1 (SSI2_BASE_ADDR + 0x0c) +#define SSI2_SRX1_PHYS __PHYS_REG(SSI2_BASE_ADDR + 0x0c) +#define SSI2_SCR (SSI2_BASE_ADDR + 0x10) +#define SSI2_SISR (SSI2_BASE_ADDR + 0x14) +#define SSI2_SIER (SSI2_BASE_ADDR + 0x18) +#define SSI2_STCR (SSI2_BASE_ADDR + 0x1c) +#define SSI2_SRCR (SSI2_BASE_ADDR + 0x20) +#define SSI2_STCCR (SSI2_BASE_ADDR + 0x24) +#define SSI2_SRCCR (SSI2_BASE_ADDR + 0x28) +#define SSI2_SFCSR (SSI2_BASE_ADDR + 0x2c) +#define SSI2_STR (SSI2_BASE_ADDR + 0x30) +#define SSI2_SOR (SSI2_BASE_ADDR + 0x34) +#define SSI2_SACNT (SSI2_BASE_ADDR + 0x38) +#define SSI2_SACADD (SSI2_BASE_ADDR + 0x3c) +#define SSI2_SACDAT (SSI2_BASE_ADDR + 0x40) +#define SSI2_SATAG (SSI2_BASE_ADDR + 0x44) +#define SSI2_STMSK (SSI2_BASE_ADDR + 0x48) +#define SSI2_SRMSK (SSI2_BASE_ADDR + 0x4c) + +#define SSI_SCR_CLK_IST (1 << 9) +#define SSI_SCR_TCH_EN (1 << 8) +#define SSI_SCR_SYS_CLK_EN (1 << 7) +#define SSI_SCR_I2S_MODE_NORM (0 << 5) +#define SSI_SCR_I2S_MODE_MSTR (1 << 5) +#define SSI_SCR_I2S_MODE_SLAVE (2 << 5) +#define SSI_SCR_SYN (1 << 4) +#define SSI_SCR_NET (1 << 3) +#define SSI_SCR_RE (1 << 2) +#define SSI_SCR_TE (1 << 1) +#define SSI_SCR_SSIEN (1 << 0) + +#define SSI_SISR_CMDAU (1 << 18) +#define SSI_SISR_CMDDU (1 << 17) +#define SSI_SISR_RXT (1 << 16) +#define SSI_SISR_RDR1 (1 << 15) +#define SSI_SISR_RDR0 (1 << 14) +#define SSI_SISR_TDE1 (1 << 13) +#define SSI_SISR_TDE0 (1 << 12) +#define SSI_SISR_ROE1 (1 << 11) +#define SSI_SISR_ROE0 (1 << 10) +#define SSI_SISR_TUE1 (1 << 9) +#define SSI_SISR_TUE0 (1 << 8) +#define SSI_SISR_TFS (1 << 7) +#define SSI_SISR_RFS (1 << 6) +#define SSI_SISR_TLS (1 << 5) +#define SSI_SISR_RLS (1 << 4) +#define SSI_SISR_RFF1 (1 << 3) +#define SSI_SISR_RFF0 (1 << 2) +#define SSI_SISR_TFE1 (1 << 1) +#define SSI_SISR_TFE0 (1 << 0) + +#define SSI_SIER_RDMAE (1 << 22) +#define SSI_SIER_RIE (1 << 21) +#define SSI_SIER_TDMAE (1 << 20) +#define SSI_SIER_TIE (1 << 19) +#define SSI_SIER_CMDAU_EN (1 << 18) +#define SSI_SIER_CMDDU_EN (1 << 17) +#define SSI_SIER_RXT_EN (1 << 16) +#define SSI_SIER_RDR1_EN (1 << 15) +#define SSI_SIER_RDR0_EN (1 << 14) +#define SSI_SIER_TDE1_EN (1 << 13) +#define SSI_SIER_TDE0_EN (1 << 12) +#define SSI_SIER_ROE1_EN (1 << 11) +#define SSI_SIER_ROE0_EN (1 << 10) +#define SSI_SIER_TUE1_EN (1 << 9) +#define SSI_SIER_TUE0_EN (1 << 8) +#define SSI_SIER_TFS_EN (1 << 7) +#define SSI_SIER_RFS_EN (1 << 6) +#define SSI_SIER_TLS_EN (1 << 5) +#define SSI_SIER_RLS_EN (1 << 4) +#define SSI_SIER_RFF1_EN (1 << 3) +#define SSI_SIER_RFF0_EN (1 << 2) +#define SSI_SIER_TFE1_EN (1 << 1) +#define SSI_SIER_TFE0_EN (1 << 0) + +#define SSI_STCR_TXBIT0 (1 << 9) +#define SSI_STCR_TFEN1 (1 << 8) +#define SSI_STCR_TFEN0 (1 << 7) +#define SSI_STCR_TFDIR (1 << 6) +#define SSI_STCR_TXDIR (1 << 5) +#define SSI_STCR_TSHFD (1 << 4) +#define SSI_STCR_TSCKP (1 << 3) +#define SSI_STCR_TFSI (1 << 2) +#define SSI_STCR_TFSL (1 << 1) +#define SSI_STCR_TEFS (1 << 0) + +#define SSI_SRCR_RXBIT0 (1 << 9) +#define SSI_SRCR_RFEN1 (1 << 8) +#define SSI_SRCR_RFEN0 (1 << 7) +#define SSI_SRCR_RFDIR (1 << 6) +#define SSI_SRCR_RXDIR (1 << 5) +#define SSI_SRCR_RSHFD (1 << 4) +#define SSI_SRCR_RSCKP (1 << 3) +#define SSI_SRCR_RFSI (1 << 2) +#define SSI_SRCR_RFSL (1 << 1) +#define SSI_SRCR_REFS (1 << 0) + +#define SSI_STCCR_DIV2 (1 << 18) +#define SSI_STCCR_PSR (1 << 15) +#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_STCCR_PM(x) (((x) & 0xff) << 0) + +#define SSI_SRCCR_DIV2 (1 << 18) +#define SSI_SRCCR_PSR (1 << 15) +#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0) + + +#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28) +#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24) +#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20) +#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16) +#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12) +#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8) +#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4) +#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0) + +#define SSI_STR_TEST (1 << 15) +#define SSI_STR_RCK2TCK (1 << 14) +#define SSI_STR_RFS2TFS (1 << 13) +#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8) +#define SSI_STR_TXD2RXD (1 << 7) +#define SSI_STR_TCK2RCK (1 << 6) +#define SSI_STR_TFS2RFS (1 << 5) +#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0) + +#define SSI_SOR_CLKOFF (1 << 6) +#define SSI_SOR_RX_CLR (1 << 5) +#define SSI_SOR_TX_CLR (1 << 4) +#define SSI_SOR_INIT (1 << 3) +#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1) +#define SSI_SOR_SYNRST (1 << 0) + +#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5) +#define SSI_SACNT_WR (x << 4) +#define SSI_SACNT_RD (x << 3) +#define SSI_SACNT_TIF (x << 2) +#define SSI_SACNT_FV (x << 1) +#define SSI_SACNT_AC97EN (x << 0) + + +/* AUDMUX registers */ +#define AUDMUX_HPCR1 (IMX_AUDMUX_BASE + 0x00) +#define AUDMUX_HPCR2 (IMX_AUDMUX_BASE + 0x04) +#define AUDMUX_HPCR3 (IMX_AUDMUX_BASE + 0x08) +#define AUDMUX_PPCR1 (IMX_AUDMUX_BASE + 0x10) +#define AUDMUX_PPCR2 (IMX_AUDMUX_BASE + 0x14) +#define AUDMUX_PPCR3 (IMX_AUDMUX_BASE + 0x18) + +#define AUDMUX_HPCR_TFSDIR (1 << 31) +#define AUDMUX_HPCR_TCLKDIR (1 << 30) +#define AUDMUX_HPCR_TFCSEL_TX (0 << 26) +#define AUDMUX_HPCR_TFCSEL_RX (8 << 26) +#define AUDMUX_HPCR_TFCSEL(x) (((x) & 0x7) << 26) +#define AUDMUX_HPCR_RFSDIR (1 << 25) +#define AUDMUX_HPCR_RCLKDIR (1 << 24) +#define AUDMUX_HPCR_RFCSEL_TX (0 << 20) +#define AUDMUX_HPCR_RFCSEL_RX (8 << 20) +#define AUDMUX_HPCR_RFCSEL(x) (((x) & 0x7) << 20) +#define AUDMUX_HPCR_RXDSEL(x) (((x) & 0x7) << 13) +#define AUDMUX_HPCR_SYN (1 << 12) +#define AUDMUX_HPCR_TXRXEN (1 << 10) +#define AUDMUX_HPCR_INMEN (1 << 8) +#define AUDMUX_HPCR_INMMASK(x) (((x) & 0xff) << 0) + +#define AUDMUX_PPCR_TFSDIR (1 << 31) +#define AUDMUX_PPCR_TCLKDIR (1 << 30) +#define AUDMUX_PPCR_TFCSEL_TX (0 << 26) +#define AUDMUX_PPCR_TFCSEL_RX (8 << 26) +#define AUDMUX_PPCR_TFCSEL(x) (((x) & 0x7) << 26) +#define AUDMUX_PPCR_RFSDIR (1 << 25) +#define AUDMUX_PPCR_RCLKDIR (1 << 24) +#define AUDMUX_PPCR_RFCSEL_TX (0 << 20) +#define AUDMUX_PPCR_RFCSEL_RX (8 << 20) +#define AUDMUX_PPCR_RFCSEL(x) (((x) & 0x7) << 20) +#define AUDMUX_PPCR_RXDSEL(x) (((x) & 0x7) << 13) +#define AUDMUX_PPCR_SYN (1 << 12) +#define AUDMUX_PPCR_TXRXEN (1 << 10) + +#define SDMA_TXFIFO_WATERMARK 0x4 +#define SDMA_RXFIFO_WATERMARK 0x6 + +struct mxc_pcm_dma_params { + char *name; /* stream identifier */ + dma_channel_params params; +}; + +extern struct snd_soc_cpu_dai mxc_ssi_dai[3]; + +/* platform data */ +extern struct snd_soc_platform mxc_soc_platform; +extern struct snd_ac97_bus_ops mxc_ac97_ops; + +#endif Index: linux-2.6.21-moko/sound/soc/pxa/magician.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/magician.c @@ -0,0 +1,563 @@ +/* + * SoC audio for HTC Magician + * + * Copyright (c) 2006 Philipp Zabel + * + * based on spitz.c, + * 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 +#include +#include +#include "../codecs/uda1380.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-ssp.h" + +#define MAGICIAN_HP_OFF 0 +#define MAGICIAN_HEADSET 1 +#define MAGICIAN_HP 2 + +#define MAGICIAN_SPK_ON 0 +#define MAGICIAN_SPK_OFF 1 + +#define MAGICIAN_MIC 0 +#define MAGICIAN_MIC_EXT 1 +#define MAGICIAN_BT_HS 2 + +/* + * SSP GPIO's + */ +#define GPIO26_SSP1RX_MD (26 | GPIO_ALT_FN_1_IN) +#define GPIO25_SSP1TX_MD (25 | GPIO_ALT_FN_2_OUT) +#define GPIO23_SSP1CLKS_MD (23 | GPIO_ALT_FN_2_IN) +#define GPIO24_SSP1FRMS_MD (24 | GPIO_ALT_FN_2_IN) +#define GPIO23_SSP1CLKM_MD (23 | GPIO_ALT_FN_2_OUT) +#define GPIO24_SSP1FRMM_MD (24 | GPIO_ALT_FN_2_OUT) +#define GPIO53_SSP1SYSCLK_MD (53 | GPIO_ALT_FN_2_OUT) + +static int magician_jack_func = MAGICIAN_HP_OFF; +static int magician_spk_func = MAGICIAN_SPK_ON; +static int magician_in_sel = MAGICIAN_MIC; + +extern struct platform_device magician_cpld; + +static void magician_ext_control(struct snd_soc_codec *codec) +{ + if (magician_spk_func == MAGICIAN_SPK_ON) + snd_soc_dapm_set_endpoint(codec, "Speaker", 1); + else + snd_soc_dapm_set_endpoint(codec, "Speaker", 0); + + /* set up jack connection */ + switch (magician_jack_func) { + case MAGICIAN_HP: + /* enable and unmute hp jack, disable mic bias */ + snd_soc_dapm_set_endpoint(codec, "Mic Jack", 0); + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1); + break; + case MAGICIAN_HEADSET: + /* enable mic jack and bias, mute hp */ + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1); + snd_soc_dapm_set_endpoint(codec, "Mic Jack", 1); + break; + case MAGICIAN_HP_OFF: + /* jack removed, everything off */ + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); + snd_soc_dapm_set_endpoint(codec, "Mic Jack", 0); + break; + } +#if 0 + /* fixme pH5, can we detect and config the correct Mic type ? */ + switch(magician_in_sel) { + case MAGICIAN_IN_MIC: + snd_soc_dapm_set_endpoint(codec, "Mic Jack", 1); + break; + case MAGICIAN_IN_MIC_EXT: + snd_soc_dapm_set_endpoint(codec, "Mic Jack", 1); + break; + case MAGICIAN_IN_BT_HS: + snd_soc_dapm_set_endpoint(codec, "Mic Jack", 0); + break; + } +#endif + snd_soc_dapm_sync_endpoints(codec); +} + +static int magician_startup(snd_pcm_substream_t *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 */ + magician_ext_control(codec); + + return 0; +} + +/* + * Magician uses SSP port for playback. + */ +static int magician_playback_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; + unsigned int acps, acds, div4; + int ret = 0; + + /* + * Rate = SSPSCLK / (word size(16)) + * SSPSCLK = (ACPS / ACDS) / SSPSCLKDIV(div4 or div1) + */ + switch (params_rate(params)) { + case 8000: + acps = 32842000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_32; /* wrong - 32 bits/sample */ + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + case 11025: + acps = 5622000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_8; /* 16 bits/sample, 1 slot */ + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + case 22050: + acps = 5622000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_4; + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + case 44100: + acps = 11345000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_4; + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + case 48000: + acps = 12235000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_4; + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set audio clock as clock source */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_SSP_CLK_AUDIO, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set the SSP audio system clock ACDS divider */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, + PXA2XX_SSP_AUDIO_DIV_ACDS, acds); + if (ret < 0) + return ret; + + /* set the SSP audio system clock SCDB divider4 */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, + PXA2XX_SSP_AUDIO_DIV_SCDB, div4); + if (ret < 0) + return ret; + + /* set SSP audio pll clock */ + ret = cpu_dai->dai_ops.set_pll(cpu_dai, 0, 0, acps); + if (ret < 0) + return ret; + + return 0; +} + +/* + * We have to enable the SSP port early so the UDA1380 can flush + * it's register cache. The UDA1380 can only write it's interpolator and + * decimator registers when the link is running. + */ +static int magician_playback_prepare(struct snd_pcm_substream *substream) +{ + /* enable SSP clock - is this needed ? */ + SSCR0_P(1) |= SSCR0_SSE; + + /* FIXME: ENABLE I2S */ + SACR0 |= SACR0_BCKD; + SACR0 |= SACR0_ENB; + pxa_set_cken(CKEN8_I2S, 1); + + return 0; +} + +static int magician_playback_hw_free(struct snd_pcm_substream *substream) +{ + /* FIXME: DISABLE I2S */ + SACR0 &= ~SACR0_ENB; + SACR0 &= ~SACR0_BCKD; + pxa_set_cken(CKEN8_I2S, 0); + return 0; +} + +/* + * Magician uses I2S for capture. + */ +static int magician_capture_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_MSB | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, + SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the I2S system clock as output */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +/* + * We have to enable the I2S port early so the UDA1380 can flush + * it's register cache. The UDA1380 can only write it's interpolator and + * decimator registers when the link is running. + */ +static int magician_capture_prepare(struct snd_pcm_substream *substream) +{ + SACR0 |= SACR0_ENB; + return 0; +} + +static struct snd_soc_ops magician_capture_ops = { + .startup = magician_startup, + .hw_params = magician_capture_hw_params, + .prepare = magician_capture_prepare, +}; + +static struct snd_soc_ops magician_playback_ops = { + .startup = magician_startup, + .hw_params = magician_playback_hw_params, + .prepare = magician_playback_prepare, + .hw_free = magician_playback_hw_free, +}; + +static int magician_get_jack(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + ucontrol->value.integer.value[0] = magician_jack_func; + return 0; +} + +static int magician_set_jack(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (magician_jack_func == ucontrol->value.integer.value[0]) + return 0; + + magician_jack_func = ucontrol->value.integer.value[0]; + magician_ext_control(codec); + return 1; +} + +static int magician_get_spk(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + ucontrol->value.integer.value[0] = magician_spk_func; + return 0; +} + +static int magician_set_spk(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (magician_spk_func == ucontrol->value.integer.value[0]) + return 0; + + magician_spk_func = ucontrol->value.integer.value[0]; + magician_ext_control(codec); + return 1; +} + +static int magician_get_input(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + ucontrol->value.integer.value[0] = magician_in_sel; + return 0; +} + +static int magician_set_input(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (magician_in_sel == ucontrol->value.integer.value[0]) + return 0; + + magician_in_sel = ucontrol->value.integer.value[0]; + + switch (magician_in_sel) { + case MAGICIAN_MIC: + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_IN_SEL0); + magician_egpio_enable(&magician_cpld, + EGPIO_NR_MAGICIAN_IN_SEL1); + break; + case MAGICIAN_MIC_EXT: + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_IN_SEL0); + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_IN_SEL1); + } + + return 1; +} + +static int magician_spk_power(struct snd_soc_dapm_widget *w, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + magician_egpio_enable(&magician_cpld, + EGPIO_NR_MAGICIAN_SPK_POWER); + else + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_SPK_POWER); + return 0; +} + +static int magician_hp_power(struct snd_soc_dapm_widget *w, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + magician_egpio_enable(&magician_cpld, + EGPIO_NR_MAGICIAN_EP_POWER); + else + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_EP_POWER); + return 0; +} + +static int magician_mic_bias(struct snd_soc_dapm_widget *w, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + magician_egpio_enable(&magician_cpld, + EGPIO_NR_MAGICIAN_MIC_POWER); + else + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_MIC_POWER); + return 0; +} + +/* magician machine dapm widgets */ +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", magician_hp_power), + SND_SOC_DAPM_MIC("Mic Jack", magician_mic_bias), + SND_SOC_DAPM_SPK("Speaker", magician_spk_power), +}; + +/* magician machine audio_map */ +static const char *audio_map[][3] = { + + /* headphone connected to VOUTLHP, VOUTRHP */ + {"Headphone Jack", NULL, "VOUTLHP"}, + {"Headphone Jack", NULL, "VOUTRHP"}, + + /* ext speaker connected to VOUTL, VOUTR */ + {"Speaker", NULL, "VOUTL"}, + {"Speaker", NULL, "VOUTR"}, + + /* mic is connected to VINM */ + {"VINM", NULL, "Mic Jack"}, + + /* line is connected to VINL, VINR */ + {"VINL", NULL, "Line Jack"}, + {"VINR", NULL, "Line Jack"}, + + {NULL, NULL, NULL}, +}; + +static const char *jack_function[] = { "Off", "Headset", "Headphone" }; +static const char *spk_function[] = { "On", "Off" }; +static const char *input_select[] = { "Internal Mic", "External Mic" }; +static const struct soc_enum magician_enum[] = { + SOC_ENUM_SINGLE_EXT(4, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, input_select), +}; + +static const struct snd_kcontrol_new uda1380_magician_controls[] = { + SOC_ENUM_EXT("Jack Function", magician_enum[0], magician_get_jack, + magician_set_jack), + SOC_ENUM_EXT("Speaker Function", magician_enum[1], magician_get_spk, + magician_set_spk), + SOC_ENUM_EXT("Input Select", magician_enum[2], magician_get_input, + magician_set_input), +}; + +/* + * Logic for a uda1380 as connected on a HTC Magician + */ +static int magician_uda1380_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* NC codec pins */ + snd_soc_dapm_set_endpoint(codec, "VOUTLHP", 0); + snd_soc_dapm_set_endpoint(codec, "VOUTRHP", 0); + + /* Add magician specific controls */ + for (i = 0; i < ARRAY_SIZE(uda1380_magician_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&uda1380_magician_controls[i], + codec, NULL))) < 0) + return err; + } + + /* Add magician specific widgets */ + for (i = 0; i < ARRAY_SIZE(uda1380_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &uda1380_dapm_widgets[i]); + } + + /* Set up magician 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]); + } + + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +/* magician digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link magician_dai[] = { +{ + .name = "uda1380", + .stream_name = "UDA1380 Playback", + .cpu_dai = &pxa_ssp_dai[0], + .codec_dai = &uda1380_dai[UDA1380_DAI_PLAYBACK], + .init = magician_uda1380_init, + .ops = &magician_playback_ops, +}, +{ + .name = "uda1380", + .stream_name = "UDA1380 Capture", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &uda1380_dai[UDA1380_DAI_CAPTURE], + .ops = &magician_capture_ops, +} +}; + +/* magician audio machine driver */ +static struct snd_soc_machine snd_soc_machine_magician = { + .name = "Magician", + .dai_link = magician_dai, + .num_links = ARRAY_SIZE(magician_dai), +}; + +/* magician audio private data */ +static struct uda1380_setup_data magician_uda1380_setup = { + .i2c_address = 0x18, + .dac_clk = UDA1380_DAC_CLK_WSPLL, +}; + +/* magician audio subsystem */ +static struct snd_soc_device magician_snd_devdata = { + .machine = &snd_soc_machine_magician, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_uda1380, + .codec_data = &magician_uda1380_setup, +}; + +static struct platform_device *magician_snd_device; + +static int __init magician_init(void) +{ + int ret; + + if (!machine_is_magician()) + return -ENODEV; + + magician_egpio_enable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_POWER); + + /* we may need to have the clock running here - pH5 */ + magician_egpio_enable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_RESET); + udelay(5); + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_RESET); + + magician_snd_device = platform_device_alloc("soc-audio", -1); + if (!magician_snd_device) + return -ENOMEM; + + platform_set_drvdata(magician_snd_device, &magician_snd_devdata); + magician_snd_devdata.dev = &magician_snd_device->dev; + ret = platform_device_add(magician_snd_device); + + if (ret) + platform_device_put(magician_snd_device); + + pxa_gpio_mode(GPIO53_SSP1SYSCLK_MD); + pxa_gpio_mode(GPIO26_SSP1RX_MD); + pxa_gpio_mode(GPIO25_SSP1TX_MD); + pxa_gpio_mode(GPIO23_SSP1CLKM_MD); + pxa_gpio_mode(GPIO24_SSP1FRMM_MD); + + return ret; +} + +static void __exit magician_exit(void) +{ + platform_device_unregister(magician_snd_device); + + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_SPK_POWER); + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_EP_POWER); + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_MIC_POWER); + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_POWER); +} + +module_init(magician_init); +module_exit(magician_exit); + +MODULE_AUTHOR("Philipp Zabel"); +MODULE_DESCRIPTION("ALSA SoC Magician"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/templates/template-ac97.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/templates/template-ac97.c @@ -0,0 +1,270 @@ +/* + * ltemplate-ac97.c -- AC97 support for the xxx chip. + * + * 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 "template-pcm.h" + +#define AC97_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AC97_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +/* DAI description of AC97 controllers capabilities */ +static struct snd_soc_dai_mode template_ac97_modes[] = { + { + .pcmfmt = SNDRV_PCM_FMTBIT_S16_LE, + .pcmrate = AC97_RATES, + .pcmdir = AC97_DIR, + }, +}; + +/* AC97 controlller reads codec register */ +static unsigned short template_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ +} + +/* AC97 controller writes to codec register */ +static void template_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ +} + +/* AC97 controller asserts a warm reset */ +static void template_ac97_warm_reset(struct snd_ac97 *ac97) +{ +} + +/* AC97 controller asserts a cold reset */ +static void template_ac97_cold_reset(struct snd_ac97 *ac97) +{ +} + +/* AC97 controller operations */ +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = template_ac97_read, + .write = template_ac97_write, + .warm_reset = template_ac97_warm_reset, + .reset = template_ac97_cold_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +/* DMA structure describing platform specific AC97 DMA for each logical DAI */ +static struct template_pcm_dma_params template_ac97_pcm_stereo_out = { + .name = "AC97 PCM Stereo out", + .dev_addr = __PREG(PCDR), +}; + +static struct template_pcm_dma_params template_ac97_pcm_stereo_in = { + .name = "AC97 PCM Stereo in", + .dev_addr = __PREG(PCDR), +}; + +static struct template_pcm_dma_params template_ac97_pcm_aux_mono_out = { + .name = "AC97 Aux PCM (Slot 5) Mono out", + .dev_addr = __PREG(MODR), +}; + +static struct template_pcm_dma_params template_ac97_pcm_aux_mono_in = { + .name = "AC97 Aux PCM (Slot 5) Mono in", + .dev_addr = __PREG(MODR), +}; + +static struct template_pcm_dma_params template_ac97_pcm_mic_mono_in = { + .name = "AC97 Mic PCM (Slot 6) Mono in", + .dev_addr = __PREG(MCDR), +}; + +#ifdef CONFIG_PM +/* suspend the AC97 controller */ +static int template_ac97_suspend(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ +} + +/* resume the AC97 controller */ +static int template_ac97_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ +} + +#else +#define template_ac97_suspend NULL +#define template_ac97_resume NULL +#endif + +/* + * Probe initialises the AC97 controller. e.g. + * request any IRQ's + * configure GPIO's + * enable any clocks + */ +static int template_ac97_probe(struct platform_device *pdev) +{ +} + +/* + * Free's resources setup in probe() + */ +static void template_ac97_remove(struct platform_device *pdev) +{ +} + +/* + * Alsa operations + * Only implement the required operations for your platform. + * These operations are specific to the AC97 controller and DAI only. + */ + + /* + * Called by ALSA when a PCM substream is opened, private data can be allocated. + */ +static int template_ac97_startup(struct snd_pcm_substream *substream) +{ +} + +/* + * Called by ALSA when a PCM substream is closed. Private data can be + * freed here. + */ +static int template_ac97_shutdown(struct snd_pcm_substream *substream) +{ +} + +/* + * Called by ALSA when the PCM substream is prepared, can set format, sample + * rate, etc. This function is non atomic and can be called multiple times, + * it can refer to the runtime info. + */ +static int template_ac97_prepare(struct snd_pcm_substream *substream) +{ +} + +/* + * 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 template_ac97_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ +} + +/* + * Free's resources allocated by hw_params, can be called multiple times + */ +static int template_ac97_hw_free(struct snd_pcm_substream *substream) +{ +} + +/* + * Starts (Triggers) audio playback or capture. + * Usually only needed for DMA + */ +static int template_ac97_trigger(struct snd_pcm_substream *substream, int cmd) +{ +} + +/* + * Define each AC97 slot grouping as a DAI. + * + * e.g. (below) + * Slots 3 & 4 = HiFi DAI + * Slot 5 = Aux playback + * Slot 7 = Mic Capture + * + * This gives 3 logical DAI's on the 1 physical AC97 DAI. + * + */ +struct snd_soc_cpu_dai template_ac97_dai[] = { +{ + .name = "template-ac97-HiFi", + .id = 0, + .type = SND_SOC_DAI_AC97, + /* DAI driver operations - only needed on 1st logical DAI in AC97 */ + .probe = template_ac97_probe, + .remove = template_ac97_remove, + .suspend = template_ac97_suspend, + .resume = template_ac97_resume, + /* playback and capture stream info */ + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2,}, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2,}, + /* alsa PCM operations */ + .ops = { + .startup = template_ac97_startup, + .shutdown = template_ac97_shutdown, + .prepare = template_ac97_prepare, + .trigger = template_ac97_trigger, + .hw_params = template_ac97_hw_params, + .hw_free = template_ac97_hw_free,}, + /* DAI capabilities */ + .caps = { + .num_modes = ARRAY_SIZE(template_ac97_modes), + .mode = template_ac97_modes,}, +}, +/* AC97 AUX playback - not supported on all controllers */ +{ + .name = "template-ac97-aux", + .id = 1, + .type = SND_SOC_DAI_AC97, + .playback = { + .stream_name = "AC97 Aux Playback", + .channels_min = 1, + .channels_max = 1,}, + .capture = { + .stream_name = "AC97 Aux Capture", + .channels_min = 1, + .channels_max = 1,}, + .ops = { + .hw_params = template_ac97_hw_aux_params,}, + .caps = { + .num_modes = ARRAY_SIZE(template_ac97_modes), + .mode = template_ac97_modes,}, +}, +/* AC97 Mic capture - not supported on all controllers */ +{ + .name = "template-ac97-mic", + .id = 2, + .type = SND_SOC_DAI_AC97, + .capture = { + .stream_name = "AC97 Mic Capture", + .channels_min = 1, + .channels_max = 1,}, + .ops = { + .hw_params = template_ac97_hw_mic_params,}, + .caps = { + .num_modes = ARRAY_SIZE(template_ac97_modes), + .mode = template_ac97_modes,},}, +}; +EXPORT_SYMBOL_GPL(template_ac97_dai); + Index: linux-2.6.21-moko/sound/soc/templates/template-codec.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/templates/template-codec.c @@ -0,0 +1,784 @@ +/* + * template-codec.c -- Template Codec Audio driver + * + * 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 "template-codec.h" + +#define AUDIO_NAME "template-codec" +#define TEMPLATE_VERSION "0.1" + +/* + * Debug + */ + +#define TEMPLATE_DEBUG 0 + +#ifdef TEMPLATE_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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) + +struct snd_soc_codec_device soc_codec_dev_template_codec; + +/* + * template_codec register cache + */ +static const u16 template_codec_reg[TEMPLATE_CACHEREGNUM] = { + 0x0097, 0x0097, 0x0079, 0x0079, + 0x000a, 0x0008, 0x009f, 0x000a, + 0x0000, 0x0000 +}; + +/* Codec DAI can support these hardware formats */ +#define TEMPLATE_DAIFMT \ + (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_RIGHT_J | \ + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_IB_NF | \ + SND_SOC_DAIFMT_IB_IF) + +/* Codec DAI supports direction */ +#define TEMPLATE_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +/* Codec DAI supports rates */ +#define TEMPLATE_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 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +/* Codec DAI supports PCM word sizes */ +#define TEMPLATE_HIFI_BITS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +/* + * Description of supported codec DAI supported modes. + */ +static struct snd_soc_dai_mode template_codec_modes[] = { + /* codec frame and clock master modes */ + /* 8k */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_8000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 1536, + .bfs = 64, + }, + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_8000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 2304, + .bfs = 64, + }, + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_8000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 1408, + .bfs = 64, + }, + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_8000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 2112, + .bfs = 64, + }, + + /* 32k */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_32000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 384, + .bfs = 64, + }, + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_32000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 576, + .bfs = 64, + }, + + /* 44.1k & 48k */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 256, + .bfs = 64, + }, + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 384, + .bfs = 64, + }, + + /* 88.2 & 96k */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 128, + .bfs = 64, + }, + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_RATE, + .fs = 192, + .bfs = 64, + }, + + /* USB codec frame and clock master modes */ + /* 8k */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_8000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 1500, + .bfs = SND_SOC_FSBD(1), + }, + + /* 44.1k */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_44100, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 272, + .bfs = SND_SOC_FSBD(1), + }, + + /* 48k */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_48000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 250, + .bfs = SND_SOC_FSBD(1), + }, + + /* 88.2k */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_88200, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 136, + .bfs = SND_SOC_FSBD(1), + }, + + /* 96k */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBM_CFM, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = SNDRV_PCM_RATE_96000, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 125, + .bfs = SND_SOC_FSBD(1), + }, + + /* codec frame and clock slave modes */ + { + .fmt = TEMPLATE_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, + .pcmfmt = TEMPLATE_HIFI_BITS, + .pcmrate = TEMPLATE_RATES, + .pcmdir = TEMPLATE_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = SND_SOC_FS_ALL, + .bfs = SND_SOC_FSB_ALL, + }, +}; + +/* + * read template_codec register cache + */ +static inline unsigned int template_codec_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= TEMPLATE_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write template_codec register cache + */ +static inline void template_codec_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= TEMPLATE_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the template codec register space + */ +static int template_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* format the data - codec specific */ + data[0] = reg; + data[1] = value; + + template_codec_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define template_codec_reset(c) template_codec_write(c, TEMPLATE_RESET, 0) + + +/* template codec non DAPM controls */ +static const struct snd_kcontrol_new template_codec_snd_controls[] = { +}; + +/* add non dapm controls */ +static int template_codec_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(template_codec_snd_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&template_codec_snd_controls[i],codec, NULL))) < 0) + return err; + } + + return 0; +} + +/* template codec DAPM controls */ +static const struct snd_soc_dapm_widget template_codec_dapm_widgets[] = { +}; + +/* + * template codec audio interconnectiosn between sink and source. + */ +static const char *audio_map[][3] = { + + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int template_codec_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(template_codec_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &template_codec_dapm_widgets[i]); + } + + /* set up audio path interconnects */ + for(i = 0; intercon[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; +} + +/* + * Configures the codec SYSCLK/MCLK (system or master clock) + */ +static unsigned int template_codec_config_sysclk(struct snd_soc_codec_dai *dai, + struct snd_soc_clock_info *info, unsigned int clk) +{ + dai->mclk = 0; + + /* check that the calculated FS and rate actually match a clock from + * the machine driver */ + if (info->fs * info->rate == clk) + dai->mclk = clk; + + return dai->mclk; +} + +/* + * Alsa operations + * Only implement the required operations for your platform. + * These operations are specific to the codec only. + */ + + /* + * Called by ALSA when a PCM substream is opened, private data can be allocated. + */ +static int template_codec_startup(struct snd_pcm_substream *substream) +{ +} + +/* + * Called by ALSA when a PCM substream is closed. Private data can be + * freed here. + */ +static int template_codec_shutdown(struct snd_pcm_substream *substream) +{ +} + +/* + * 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 template_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ +} + +/* + * Free's resources allocated by hw_params, can be called multiple times + */ +static int template_codec_hw_free(struct snd_pcm_substream *substream) +{ +} + +/* + * Starts (Triggers) audio playback or capture. + * Usually only needed for DMA + */ +static int template_codec_trigger(struct snd_pcm_substream *substream, int cmd) +{ +} + +/* + * Called by ALSA when the PCM substream is prepared, can set format, sample + * rate, etc. This function is non atomic and can be called multiple times, + * it can refer to the runtime info. + */ +static int template_codec_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* set master/slave audio interface */ + switch (rtd->codec_dai->dai_runtime.fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + } + + /* interface format */ + switch (rtd->codec_dai->dai_runtime.fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + case SND_SOC_DAIFMT_DSP_A: + break; + case SND_SOC_DAIFMT_DSP_B: + break; + } + + /* bit size */ + switch (rtd->codec_dai->dai_runtime.pcmfmt) { + case SNDRV_PCM_FMTBIT_S16_LE: + break; + case SNDRV_PCM_FMTBIT_S20_3LE: + break; + case SNDRV_PCM_FMTBIT_S24_LE: + break; + case SNDRV_PCM_FMTBIT_S32_LE: + break; + } + + /* clock inversion */ + switch (rtd->codec_dai->dai_runtime.fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + break; + } + + return 0; +} + +/* + * Enable / Disable codec digital soft mute + */ +static int template_codec_mute(struct snd_soc_codec *codec, + struct snd_soc_codec_dai *dai, int mute) +{ +} + +/* + * Codec DAPM event handler + * This handles codec level DAPM events + */ +static int template_codec_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 reg = template_codec_read_reg_cache(codec, TEMPLATE_PWR) & 0xff7f; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* e.g. vref/mid, osc on, */ + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, */ + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + break; + } + codec->dapm_state = event; + return 0; +} + +/* + * Define codec DAI. + */ +struct snd_soc_codec_dai template_codec_dai = { + .name = "codec xxx", + /* playback and capture stream info */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + }, + /* codec operations */ + .config_sysclk = template_codec_config_sysclk, + .digital_mute = template_codec_mute, + /* alsa PCM operations */ + .ops = { + .startup = template_codec_startup, + .shutdown = template_codec_shutdown, + .prepare = template_codec_prepare, + .trigger = template_codec_trigger, + .hw_params = template_codec_hw_params, + .hw_free = template_codec_hw_free,}, + /* codec capabilities */ + .caps = { + .num_modes = ARRAY_SIZE(template_codec_modes), + .mode = template_codec_modes, + }, +}; +EXPORT_SYMBOL_GPL(template_codec_dai); + +static int template_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; + + template_codec_write(codec, TEMPLATE_ACTIVE, 0x0); + template_codec_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int template_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(template_codec_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + template_codec_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + template_codec_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the TEMPLATE driver + * register the mixer and dsp interfaces with the kernel + */ +static int template_codec_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "TEMPLATE"; + codec->owner = THIS_MODULE; + codec->read = template_codec_read_reg_cache; + codec->write = template_codec_write; + codec->dapm_event = template_codec_dapm_event; + codec->dai = &template_codec_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(template_codec_reg); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(template_codec_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, + template_codec_reg, sizeof(u16) * ARRAY_SIZE(template_codec_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(template_codec_reg); + + template_codec_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + kfree(codec->reg_cache); + return ret; + } + + /* power on device */ + template_codec_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* set the update bits */ + reg = template_codec_read_reg_cache(codec, TEMPLATE_LOUT1V); + template_codec_write(codec, TEMPLATE_LOUT1V, reg | 0x0100); + reg = template_codec_read_reg_cache(codec, TEMPLATE_ROUT1V); + template_codec_write(codec, TEMPLATE_ROUT1V, reg | 0x0100); + reg = template_codec_read_reg_cache(codec, TEMPLATE_LINVOL); + template_codec_write(codec, TEMPLATE_LINVOL, reg | 0x0100); + reg = template_codec_read_reg_cache(codec, TEMPLATE_RINVOL); + template_codec_write(codec, TEMPLATE_RINVOL, reg | 0x0100); + + template_codec_add_controls(codec); + template_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 struct snd_soc_device *template_codec_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * TEMPLATE 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver template_codec_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int template_codec_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = template_codec_socdev; + struct template_codec_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = template_codec_init(socdev); + if (ret < 0) { + err("failed to initialise TEMPLATE\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int template_codec_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec* codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int template_codec_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, template_codec_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver template_codec_i2c_driver = { + .driver = { + .name = "TEMPLATE I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_TEMPLATE, + .attach_adapter = template_codec_i2c_attach, + .detach_client = template_codec_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "TEMPLATE", + .driver = &template_codec_i2c_driver, +}; +#endif + +static int template_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct template_codec_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + info("TEMPLATE Audio Codec %s", TEMPLATE_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); + + template_codec_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&template_codec_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip and remove */ +static int template_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) + template_codec_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&template_codec_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +/* codec device ops */ +struct snd_soc_codec_device soc_codec_dev_template_codec = { + .probe = template_codec_probe, + .remove = template_codec_remove, + .suspend = template_codec_suspend, + .resume = template_codec_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_template_codec); + Index: linux-2.6.21-moko/sound/soc/templates/template-i2s.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/templates/template-i2s.c @@ -0,0 +1,223 @@ +/* + * 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 "template-pcm.h" + +/* supported I2S DAI hardware formats */ +#define TEMPLATE_I2S_DAIFMT \ + (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF) + +/* supported I2S direction */ +#define TEMPLATE_I2S_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +/* supported I2S rates */ +#define TEMPLATE_I2S_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 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +/* I2S controller DAI capabilities */ +static struct snd_soc_dai_mode template_i2s_modes[] = { + /* template I2S frame and clock master modes */ + { + .fmt = TEMPLATE_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, + .pcmfmt = SNDRV_PCM_FMTBIT_S16_LE, + .pcmrate = SNDRV_PCM_RATE_8000, + .pcmdir = TEMPLATE_I2S_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 256, + .bfs = SND_SOC_FSBD(4), + }, + { + .fmt = TEMPLATE_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, + .pcmfmt = SNDRV_PCM_FMTBIT_S16_LE, + .pcmrate = SNDRV_PCM_RATE_11025, + .pcmdir = TEMPLATE_I2S_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 256, + .bfs = SND_SOC_FSBD(4), + }, + { + .fmt = TEMPLATE_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, + .pcmfmt = SNDRV_PCM_FMTBIT_S16_LE, + .pcmrate = SNDRV_PCM_RATE_16000, + .pcmdir = TEMPLATE_I2S_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 256, + .bfs = SND_SOC_FSBD(4), + }, + { + .fmt = TEMPLATE_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, + .pcmfmt = SNDRV_PCM_FMTBIT_S16_LE, + .pcmrate = SNDRV_PCM_RATE_22050, + .pcmdir = TEMPLATE_I2S_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 256, + .bfs = SND_SOC_FSBD(4), + }, + { + .fmt = TEMPLATE_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, + .pcmfmt = SNDRV_PCM_FMTBIT_S16_LE, + .pcmrate = SNDRV_PCM_RATE_44100, + .pcmdir = TEMPLATE_I2S_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 256, + .bfs = SND_SOC_FSBD(4), + }, + { + .fmt = TEMPLATE_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, + .pcmfmt = SNDRV_PCM_FMTBIT_S16_LE, + .pcmrate = SNDRV_PCM_RATE_48000, + .pcmdir = TEMPLATE_I2S_DIR, + .flags = SND_SOC_DAI_BFS_DIV, + .fs = 256, + .bfs = SND_SOC_FSBD(4), + }, + + /* template I2S frame master and clock slave mode */ + { + .fmt = TEMPLATE_I2S_DAIFMT | SND_SOC_DAIFMT_CBM_CFS, + .pcmfmt = SNDRV_PCM_FMTBIT_S16_LE, + .pcmrate = TEMPLATE_I2S_RATES, + .pcmdir = TEMPLATE_I2S_DIR, + .fs = SND_SOC_FS_ALL, + .flags = SND_SOC_DAI_BFS_RATE, + .bfs = 64, + }, +}; + +/* I2S controller platform specific DMA parameters */ +static struct template_pcm_dma_params template_i2s_pcm_stereo_out = { + .name = "I2S PCM Stereo out", + .dev_addr = __PREG(SADR), +}; + +static struct template_pcm_dma_params template_i2s_pcm_stereo_in = { + .name = "I2S PCM Stereo in", + .dev_addr = __PREG(SADR), +}; + +#ifdef CONFIG_PM +/* suspend I2S controller */ +static int template_i2s_suspend(struct platform_device *dev, + struct snd_soc_cpu_dai *dai) +{ +} + +/* resume I2S controller */ +static int template_i2s_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ +} + +#else +#define template_i2s_suspend NULL +#define template_i2s_resume NULL +#endif + +/* configure the I2S controllers MCLK or SYSCLK */ +static unsigned int template_i2s_config_sysclk(struct snd_soc_cpu_dai *iface, + struct snd_soc_clock_info *info, unsigned int clk) +{ +} + + +/* + * Alsa operations + * Only implement the required operations for your platform. + * These operations are specific to the I2S controller and DAI only. + */ + + /* + * Called by ALSA when a PCM substream is opened, private data can be allocated. + */ +static int template_i2s_startup(struct snd_pcm_substream *substream) +{ +} + +/* + * Called by ALSA when a PCM substream is closed. Private data can be + * freed here. + */ +static int template_i2s_shutdown(struct snd_pcm_substream *substream) +{ +} + +/* + * Called by ALSA when the PCM substream is prepared, can set format, sample + * rate, etc. This function is non atomic and can be called multiple times, + * it can refer to the runtime info. + */ +static int template_i2s_prepare(struct snd_pcm_substream *substream) +{ +} + +/* + * 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 template_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ +} + +/* + * Free's resources allocated by hw_params, can be called multiple times + */ +static int template_i2s_hw_free(struct snd_pcm_substream *substream) +{ +} + +/* + * Starts (Triggers) audio playback or capture. + * Usually only needed for DMA + */ +static int template_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ +} + + +struct snd_soc_cpu_dai template_i2s_dai = { + .name = "template-i2s", + .id = 0, + .type = SND_SOC_DAI_I2S, + .suspend = template_i2s_suspend, + .resume = template_i2s_resume, + .config_sysclk = template_i2s_config_sysclk, + .playback = { + .channels_min = 2, + .channels_max = 2,}, + .capture = { + .channels_min = 2, + .channels_max = 2,}, + .ops = { + .startup = template_i2s_startup, + .shutdown = template_i2s_shutdown, + .prepare = template_i2s_prepare, + .trigger = template_i2s_trigger, + .hw_params = template_i2s_hw_params, + .hw_free = template_i2s_hw_free,}, + .caps = { + .num_modes = ARRAY_SIZE(template_i2s_modes), + .mode = template_i2s_modes,}, +}; + +EXPORT_SYMBOL_GPL(template_i2s_dai); Index: linux-2.6.21-moko/sound/soc/templates/template-pcm.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/templates/template-pcm.c @@ -0,0 +1,166 @@ +/* + * 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 "template-pcm.h" + +/* PCM hardware DMA capabilities - platform specific */ +static const struct snd_pcm_hardware template_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 32, + .period_bytes_max = 8192 - 32, + .periods_min = 1, + .periods_max = PAGE_SIZE/sizeof(pxa_dma_desc), + .buffer_bytes_max = 128 * 1024, + .fifo_size = 32, +}; + +/* + * 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 template_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ +} + +/* + * Free's resources allocated by hw_params, can be called multiple times + */ +static int template_pcm_hw_free(struct snd_pcm_substream *substream) +{ +} + +/* + * Called by ALSA when the PCM substream is prepared, can set format, sample + * rate, etc. This function is non atomic and can be called multiple times, + * it can refer to the runtime info. + */ +static int template_pcm_prepare(struct snd_pcm_substream *substream) +{ +} + +/* + * Starts (Triggers) audio playback or capture. + * Usually only needed for DMA + */ +static int template_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct template_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + break; + case SNDRV_PCM_TRIGGER_STOP: + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + case SNDRV_PCM_TRIGGER_RESUME: + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/* + * Returns the DMA audio frame position + */ +static snd_pcm_uframes_t +template_pcm_pointer(struct snd_pcm_substream *substream) +{ +} + + /* + * Called by ALSA when a PCM substream is opened, private data can be allocated. + */ +static int template_pcm_open(struct snd_pcm_substream *substream) +{ +} + +/* + * Called by ALSA when a PCM substream is closed. Private data can be + * freed here. + */ +static int template_pcm_close(struct snd_pcm_substream *substream) +{ +} + +/* map DMA audio buffer into user space */ +static int template_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +/* ALSA PCM operations */ +struct snd_pcm_ops template_pcm_ops = { + .open = template_pcm_open, + .close = template_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = template_pcm_hw_params, + .hw_free = template_pcm_hw_free, + .prepare = template_pcm_prepare, + .trigger = template_pcm_trigger, + .pointer = template_pcm_pointer, + .mmap = template_pcm_mmap, +}; + +/* + * Called by ASoC core to free platform DMA. + */ +static void template_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ +} + +/* + * Called by the ASoC core to create and initialise the platform DMA. + */ +int template_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ +} + +/* template audio platform */ +struct snd_soc_platform template_soc_platform = { + .name = "template-audio", + .pcm_ops = &template_pcm_ops, + .pcm_new = template_pcm_new, + .pcm_free = template_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(template_soc_platform); Index: linux-2.6.21-moko/sound/soc/templates/template-pcm.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/templates/template-pcm.h @@ -0,0 +1,19 @@ +/* + * 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 _TEMPLATE_PCM_H +#define _TEMPLATE_PCM_H + +/* platform specific structs can be declared here */ + +extern struct snd_soc_cpu_dai template_ac97_dai[3]; +extern struct snd_soc_cpu_dai template_i2s_dai; + +/* template platform data */ +extern struct snd_soc_platform template_soc_platform; +extern struct snd_ac97_bus_ops tempalte_ac97_ops; + +#endif Index: linux-2.6.21-moko/sound/soc/templates/template-codec.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/templates/template-codec.h @@ -0,0 +1,21 @@ +/* + * 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 _TEMPLATE_H +#define _TEMPLATE_H + +/* TEMPLATE register space */ + +#define TEMPLATE_CACHEREGNUM 10 + +struct template_codec_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai template_codec_dai; +extern struct snd_soc_codec_device soc_codec_dev_template_codec; + +#endif Index: linux-2.6.21-moko/sound/soc/templates/template-machine.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/templates/template-machine.c @@ -0,0 +1,161 @@ +/* + * 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 "../codecs/template-codec.h" +#include "template-pcm.h" + +/* + * 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 template_machine_startup(struct snd_pcm_substream *substream) +{ +} + +/* + * Called by ALSA when a PCM substream is closed. Private data can be + * freed here. + */ +static int template_machine_shutdown(struct snd_pcm_substream *substream) +{ +} + +/* + * 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 template_machine_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ +} + +/* + * Free's resources allocated by hw_params, can be called multiple times + */ +static int template_machine_hw_free(struct snd_pcm_substream *substream) +{ +} + +/* machine Alsa PCM operations */ +static struct snd_soc_ops template_ops = { + .startup = template_machine_startup, + .shutdown = template_machine_shutdown, + .hw_free = template_machine_hw_free, + .hw_params = template_machine_hw_params, +}; + +/* machine audio map (connections to the codec pins) */ +static const char *audio_map[][3] = { + + {NULL, NULL, NULL}, +}; + +/* + * Initialise the machine audio subsystem. + */ +static int template_machine_init(struct snd_soc_codec *codec) +{ + /* mark unused codec pins as NC */ + + /* Add template specific controls */ + + /* Add template specific widgets */ + + /* Set up template specific audio path audio_map */ + + /* synchronise subsystem */ + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +/* + * Configure the clocking within the audio subsystem + */ +static unsigned int template_config_sysclk(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_clock_info *info) +{ +} + +/* template digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link template_dai = { + .name = "Codec", + .stream_name = "Stream Name", + .cpu_dai = &template_i2s_dai, + .codec_dai = &template_codec_dai, + .init = template_machine_init, + .config_sysclk = template_config_sysclk, +}; + +/* template audio machine driver */ +static struct snd_soc_machine snd_soc_machine_template = { + .name = "Machine", + .dai_link = &template_dai, + .num_links = ARRAY_SIZE(template_dai), + .ops = &template_ops, +}; + +/* template audio private data */ +static struct codec_priv_setup_data template_codec_setup = { + .i2c_address = 0x1b, +}; + +/* template audio subsystem */ +static struct snd_soc_device template_snd_devdata = { + .machine = &snd_soc_machine_template, + .platform = &template_soc_platform, + .codec_dev = &soc_codec_dev_wm8731, + .codec_data = &template_codec_setup, +}; + +static struct platform_device *template_snd_device; + +static int __init template_init(void) +{ + int ret; + + template_snd_device = platform_device_alloc("soc-audio", -1); + if (!template_snd_device) + return -ENOMEM; + + platform_set_drvdata(template_snd_device, &template_snd_devdata); + template_snd_devdata.dev = &template_snd_device->dev; + ret = platform_device_add(template_snd_device); + + if (ret) + platform_device_put(template_snd_device); + + return ret; +} + +static void __exit template_exit(void) +{ + platform_device_unregister(template_snd_device); +} + +module_init(template_init); +module_exit(template_exit); + Index: linux-2.6.21-moko/sound/soc/pxa/pxa2xx-ssp.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/pxa2xx-ssp.h @@ -0,0 +1,43 @@ +/* + * linux/sound/arm/pxa2xx-ssp.h + * + * 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 _PXA2XX_SSP_H +#define _PXA2XX_SSP_H + +/* pxa2xx DAI SSP ID's */ +#define PXA2XX_DAI_SSP1 0 +#define PXA2XX_DAI_SSP2 1 +#define PXA2XX_DAI_SSP3 2 + +/* SSP clock sources */ +#define PXA2XX_SSP_CLK_PLL 0 +#define PXA2XX_SSP_CLK_EXT 1 +#define PXA2XX_SSP_CLK_NET 2 +#define PXA2XX_SSP_CLK_AUDIO 3 +#define PXA2XX_SSP_CLK_NET_PLL 4 + +/* SSP audio dividers */ +#define PXA2XX_SSP_AUDIO_DIV_ACDS 0 +#define PXA2XX_SSP_AUDIO_DIV_SCDB 1 +#define PXA2XX_SSP_DIV_SCR 2 + +/* SSP ACDS audio dividers values */ +#define PXA2XX_SSP_CLK_AUDIO_DIV_1 0 +#define PXA2XX_SSP_CLK_AUDIO_DIV_2 1 +#define PXA2XX_SSP_CLK_AUDIO_DIV_4 2 +#define PXA2XX_SSP_CLK_AUDIO_DIV_8 3 +#define PXA2XX_SSP_CLK_AUDIO_DIV_16 4 +#define PXA2XX_SSP_CLK_AUDIO_DIV_32 5 + +/* SSP divider bypass */ +#define PXA2XX_SSP_CLK_SCDB_4 0 +#define PXA2XX_SSP_CLK_SCDB_1 1 + +extern struct snd_soc_cpu_dai pxa_ssp_dai[3]; + +#endif Index: linux-2.6.21-moko/sound/soc/s3c24xx/Kconfig =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/Kconfig @@ -0,0 +1,32 @@ +menu "SoC Audio for the Samsung S3C24XX" + +config SND_S3C24XX_SOC + tristate "SoC Audio for the Samsung S3C24XX chips" + depends on ARCH_S3C2410 && SND + select SND_PCM + help + Say Y or M if you want to add support for codecs attached to + the S3C24XX AC97, I2S or SSP interface. You will also need + to select the audio interfaces to support below. + +config SND_S3C24XX_SOC_I2S + tristate + +config SND_S3C24XX_SOC_SMDK2440 + tristate "SoC I2S Audio support for SMDK2440" + depends on SND_S3C24XX_SOC && MACH_SMDK + select SND_S3C24XX_SOC_I2S + select SND_SOC_UDA1380 + help + Say Y if you want to add support for SoC audio on SMDK2440 + +config SND_S3C24XX_SOC_NEO1973_WM8753 + tristate "SoC I2S Audio support for NEO1973 - WM8753" + depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01 + select SND_S3C24XX_SOC_I2S + select SND_SOC_WM8753 + help + Say Y if you want to add support for SoC audio on FIC Neo1973 + with the WM8753. +endmenu + Index: linux-2.6.21-moko/sound/soc/s3c24xx/Makefile =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/Makefile @@ -0,0 +1,13 @@ +# S3c24XX Platform Support +snd-soc-s3c24xx-objs := s3c24xx-pcm.o +snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o + +obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o +obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o + +# S3C24XX Machine Support +snd-soc-smdk2440-objs := smdk2440.o +snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o + +obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2440) += snd-soc-smdk2440.o +obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o Index: linux-2.6.21-moko/sound/soc/s3c24xx/s3c24xx-i2s.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/s3c24xx-i2s.c @@ -0,0 +1,439 @@ +/* + * s3c24xx-i2s.c -- ALSA Soc Audio Layer + * + * (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * (c) 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * 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. + * + * + * Revision history + * 11th Dec 2006 Merged with Simtec driver + * 10th Nov 2006 Initial version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "s3c24xx-pcm.h" +#include "s3c24xx-i2s.h" + +#define S3C24XX_I2S_DEBUG 0 +#if S3C24XX_I2S_DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +static struct s3c2410_dma_client s3c24xx_dma_client_out = { + .name = "I2S PCM Stereo out" +}; + +static struct s3c2410_dma_client s3c24xx_dma_client_in = { + .name = "I2S PCM Stereo in" +}; + +static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_out = { + .client = &s3c24xx_dma_client_out, + .channel = DMACH_I2S_OUT, + .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO +}; + +static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_in = { + .client = &s3c24xx_dma_client_in, + .channel = DMACH_I2S_IN, + .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO +}; + +struct s3c24xx_i2s_info { + void __iomem *regs; + struct clk *iis_clk; +}; +static struct s3c24xx_i2s_info s3c24xx_i2s; + +static void s3c24xx_snd_txctrl(int on) +{ + u32 iisfcon; + u32 iiscon; + u32 iismod; + + DBG("Entered %s\n", __FUNCTION__); + + iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + + DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); + + if (on) { + iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE; + iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN; + iiscon &= ~S3C2410_IISCON_TXIDLE; + iismod |= S3C2410_IISMOD_TXMODE; + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + } else { + /* note, we have to disable the FIFOs otherwise bad things + * seem to happen when the DMA stops. According to the + * Samsung supplied kernel, this should allow the DMA + * engine and FIFOs to reset. If this isn't allowed, the + * DMA engine will simply freeze randomly. + */ + + iisfcon &= ~S3C2410_IISFCON_TXENABLE; + iisfcon &= ~S3C2410_IISFCON_TXDMA; + iiscon |= S3C2410_IISCON_TXIDLE; + iiscon &= ~S3C2410_IISCON_TXDMAEN; + iismod &= ~S3C2410_IISMOD_TXMODE; + + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + } + + DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); +} + +static void s3c24xx_snd_rxctrl(int on) +{ + u32 iisfcon; + u32 iiscon; + u32 iismod; + + DBG("Entered %s\n", __FUNCTION__); + + iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + + DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); + + if (on) { + iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; + iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN; + iiscon &= ~S3C2410_IISCON_RXIDLE; + iismod |= S3C2410_IISMOD_RXMODE; + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + } else { + /* note, we have to disable the FIFOs otherwise bad things + * seem to happen when the DMA stops. According to the + * Samsung supplied kernel, this should allow the DMA + * engine and FIFOs to reset. If this isn't allowed, the + * DMA engine will simply freeze randomly. + */ + + iisfcon &= ~S3C2410_IISFCON_RXENABLE; + iisfcon &= ~S3C2410_IISFCON_RXDMA; + iiscon |= S3C2410_IISCON_RXIDLE; + iiscon &= ~S3C2410_IISCON_RXDMAEN; + iismod &= ~S3C2410_IISMOD_RXMODE; + + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + } + + DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); +} + +/* + * Wait for the LR signal to allow synchronisation to the L/R clock + * from the codec. May only be needed for slave mode. + */ +static int s3c24xx_snd_lrsync(void) +{ + u32 iiscon; + unsigned long timeout = jiffies + msecs_to_jiffies(5); + + DBG("Entered %s\n", __FUNCTION__); + + while (1) { + iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + if (iiscon & S3C2410_IISCON_LRINDEX) + break; + + if (timeout < jiffies) + return -ETIMEDOUT; + } + + return 0; +} + +/* + * Check whether CPU is the master or slave + */ +static inline int s3c24xx_snd_is_clkmaster(void) +{ + DBG("Entered %s\n", __FUNCTION__); + + return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1; +} + +/* + * Set S3C24xx I2S DAI format + */ +static int s3c24xx_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, + unsigned int fmt) +{ + u32 iismod; + + DBG("Entered %s\n", __FUNCTION__); + + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + DBG("hw_params r: IISMOD: %lx \n", iismod); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iismod |= S3C2410_IISMOD_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + iismod |= S3C2410_IISMOD_MSB; + break; + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + DBG("hw_params w: IISMOD: %lx \n", iismod); + return 0; +} + +static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + u32 iismod; + + DBG("Entered %s\n", __FUNCTION__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out; + else + rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_in; + + /* Working copies of register */ + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + DBG("hw_params r: IISMOD: %lx\n", iismod); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + break; + case SNDRV_PCM_FORMAT_S16_LE: + iismod |= S3C2410_IISMOD_16BIT; + break; + } + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + DBG("hw_params w: IISMOD: %lx\n", iismod); + return 0; +} + +static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + DBG("Entered %s\n", __FUNCTION__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!s3c24xx_snd_is_clkmaster()) { + ret = s3c24xx_snd_lrsync(); + if (ret) + goto exit_err; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c24xx_snd_rxctrl(1); + else + s3c24xx_snd_txctrl(1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c24xx_snd_rxctrl(0); + else + s3c24xx_snd_txctrl(0); + break; + default: + ret = -EINVAL; + break; + } + +exit_err: + return ret; +} + +/* + * Set S3C24xx Clock source + */ +static int s3c24xx_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + + DBG("Entered %s\n", __FUNCTION__); + + iismod &= ~S3C2440_IISMOD_MPLL; + + switch (clk_id) { + case S3C24XX_CLKSRC_PCLK: + break; + case S3C24XX_CLKSRC_MPLL: + iismod |= S3C2440_IISMOD_MPLL; + break; + default: + return -EINVAL; + } + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + return 0; +} + +/* + * Set S3C24xx Clock dividers + */ +static int s3c24xx_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai, + int div_id, int div) +{ + u32 reg; + + DBG("Entered %s\n", __FUNCTION__); + + switch (div_id) { + case S3C24XX_DIV_MCLK: + reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK; + writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); + break; + case S3C24XX_DIV_BCLK: + reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS); + writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); + break; + case S3C24XX_DIV_PRESCALER: + writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR); + reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * To avoid duplicating clock code, allow machine driver to + * get the clockrate from here. + */ +u32 s3c24xx_i2s_get_clockrate(void) +{ + return clk_get_rate(s3c24xx_i2s.iis_clk); +} +EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); + +static int s3c24xx_i2s_probe(struct platform_device *pdev) +{ + DBG("Entered %s\n", __FUNCTION__); + + s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100); + if (s3c24xx_i2s.regs == NULL) + return -ENXIO; + + s3c24xx_i2s.iis_clk=clk_get(&pdev->dev, "iis"); + if (s3c24xx_i2s.iis_clk == NULL) { + DBG("failed to get iis_clock\n"); + return -ENODEV; + } + clk_enable(s3c24xx_i2s.iis_clk); + + /* Configure the I2S pins in correct mode */ + s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK); + s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK); + s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK); + s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI); + s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO); + + writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON); + + s3c24xx_snd_txctrl(0); + s3c24xx_snd_rxctrl(0); + + return 0; +} + +#define S3C24XX_I2S_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 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +struct snd_soc_cpu_dai s3c24xx_i2s_dai = { + .name = "s3c24xx-i2s", + .id = 0, + .type = SND_SOC_DAI_I2S, + .probe = s3c24xx_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C24XX_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C24XX_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .trigger = s3c24xx_i2s_trigger, + .hw_params = s3c24xx_i2s_hw_params,}, + .dai_ops = { + .set_fmt = s3c24xx_i2s_set_fmt, + .set_clkdiv = s3c24xx_i2s_set_clkdiv, + .set_sysclk = s3c24xx_i2s_set_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(s3c24xx_i2s_dai); + +/* Module information */ +MODULE_AUTHOR("Ben Dooks, "); +MODULE_DESCRIPTION("s3c24xx I2S SoC Interface"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/s3c24xx/s3c24xx-i2s.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/s3c24xx-i2s.h @@ -0,0 +1,35 @@ +/* + * s3c24xx-i2s.c -- ALSA Soc Audio Layer + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 10th Nov 2006 Initial version. + */ + +#ifndef S3C24XXI2S_H_ +#define S3C24XXI2S_H_ + +/* clock sources */ +#define S3C24XX_CLKSRC_PCLK 0 +#define S3C24XX_CLKSRC_MPLL 1 + +/* Clock dividers */ +#define S3C24XX_DIV_MCLK 0 +#define S3C24XX_DIV_BCLK 1 +#define S3C24XX_DIV_PRESCALER 2 + +/* prescaler */ +#define S3C24XX_PRESCALE(a,b) \ + (((a - 1) << S3C2410_IISPSR_INTSHIFT) | ((b - 1) << S3C2410_IISPSR_EXTSHFIT)) + +u32 s3c24xx_i2s_get_clockrate(void); + +#endif /*S3C24XXI2S_H_*/ Index: linux-2.6.21-moko/sound/soc/s3c24xx/s3c24xx-pcm.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/s3c24xx-pcm.c @@ -0,0 +1,464 @@ +/* + * s3c24xx-pcm.c -- ALSA Soc Audio Layer + * + * (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * (c) 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * 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. + * + * Revision history + * 11th Dec 2006 Merged with Simtec driver + * 10th Nov 2006 Initial version. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "s3c24xx-pcm.h" + +#define S3C24XX_PCM_DEBUG 0 +#if S3C24XX_PCM_DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +static const struct snd_pcm_hardware s3c24xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE | + SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S8, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128*1024, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = PAGE_SIZE*2, + .periods_min = 2, + .periods_max = 128, + .fifo_size = 32, +}; + +struct s3c24xx_runtime_data { + spinlock_t lock; + int state; + unsigned int dma_loaded; + unsigned int dma_limit; + unsigned int dma_period; + dma_addr_t dma_start; + dma_addr_t dma_pos; + dma_addr_t dma_end; + struct s3c24xx_pcm_dma_params *params; +}; + +/* s3c24xx_pcm_enqueue + * + * place a dma buffer onto the queue for the dma system + * to handle. +*/ +static void s3c24xx_pcm_enqueue(struct snd_pcm_substream *substream) +{ + struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; + dma_addr_t pos = prtd->dma_pos; + int ret; + + DBG("Entered %s\n", __FUNCTION__); + + while (prtd->dma_loaded < prtd->dma_limit) { + unsigned long len = prtd->dma_period; + + DBG("dma_loaded: %d\n",prtd->dma_loaded); + + if ((pos + len) > prtd->dma_end) { + len = prtd->dma_end - pos; + DBG(KERN_DEBUG "%s: corrected dma len %ld\n", + __FUNCTION__, len); + } + + ret = s3c2410_dma_enqueue(prtd->params->channel, substream, pos, len); + + if (ret == 0) { + prtd->dma_loaded++; + pos += prtd->dma_period; + if (pos >= prtd->dma_end) + pos = prtd->dma_start; + } else + break; + } + + prtd->dma_pos = pos; +} + +static void s3c24xx_audio_buffdone(struct s3c2410_dma_chan *channel, + void *dev_id, int size, + enum s3c2410_dma_buffresult result) +{ + struct snd_pcm_substream *substream = dev_id; + struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; + + DBG("Entered %s\n", __FUNCTION__); + + if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR) + return; + + if (substream) + snd_pcm_period_elapsed(substream); + + spin_lock(&prtd->lock); + if (prtd->state & ST_RUNNING) { + prtd->dma_loaded--; + s3c24xx_pcm_enqueue(substream); + } + + spin_unlock(&prtd->lock); +} + +static int s3c24xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct s3c24xx_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c24xx_pcm_dma_params *dma = rtd->dai->cpu_dai->dma_data; + unsigned long totbytes = params_buffer_bytes(params); + int ret=0; + + DBG("Entered %s\n", __FUNCTION__); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + if (!dma) + return 0; + + /* prepare DMA */ + prtd->params = dma; + + DBG("params %p, client %p, channel %d\n", prtd->params, + prtd->params->client, prtd->params->channel); + + ret = s3c2410_dma_request(prtd->params->channel, + prtd->params->client, NULL); + + if (ret) { + DBG(KERN_ERR "failed to get dma channel\n"); + return ret; + } + + /* channel needs configuring for mem=>device, increment memory addr, + * sync to pclk, half-word transfers to the IIS-FIFO. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + s3c2410_dma_devconfig(prtd->params->channel, + S3C2410_DMASRC_MEM, S3C2410_DISRCC_INC | + S3C2410_DISRCC_APB, prtd->params->dma_addr); + + s3c2410_dma_config(prtd->params->channel, + 2, S3C2410_DCON_SYNC_PCLK | S3C2410_DCON_HANDSHAKE); + } else { + s3c2410_dma_config(prtd->params->channel, + 2, S3C2410_DCON_HANDSHAKE | S3C2410_DCON_SYNC_PCLK); + + s3c2410_dma_devconfig(prtd->params->channel, + S3C2410_DMASRC_HW, 0x3, + prtd->params->dma_addr); + } + + s3c2410_dma_set_buffdone_fn(prtd->params->channel, + s3c24xx_audio_buffdone); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + runtime->dma_bytes = totbytes; + + spin_lock_irq(&prtd->lock); + prtd->dma_loaded = 0; + prtd->dma_limit = runtime->hw.periods_min; + prtd->dma_period = params_period_bytes(params); + prtd->dma_start = runtime->dma_addr; + prtd->dma_pos = prtd->dma_start; + prtd->dma_end = prtd->dma_start + totbytes; + spin_unlock_irq(&prtd->lock); + + return 0; +} + +static int s3c24xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; + + DBG("Entered %s\n", __FUNCTION__); + + /* TODO - do we need to ensure DMA flushed */ + snd_pcm_set_runtime_buffer(substream, NULL); + + if (prtd->params) { + s3c2410_dma_free(prtd->params->channel, prtd->params->client); + prtd->params = NULL; + } + + return 0; +} + +static int s3c24xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + DBG("Entered %s\n", __FUNCTION__); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + if (!prtd->params) + return 0; + + /* flush the DMA channel */ + s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH); + prtd->dma_loaded = 0; + prtd->dma_pos = prtd->dma_start; + + /* enqueue dma buffers */ + s3c24xx_pcm_enqueue(substream); + + return ret; +} + +static int s3c24xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + DBG("Entered %s\n", __FUNCTION__); + + spin_lock(&prtd->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + prtd->state |= ST_RUNNING; + s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START); + s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STARTED); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + prtd->state &= ~ST_RUNNING; + s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP); + break; + + default: + ret = -EINVAL; + break; + } + + spin_unlock(&prtd->lock); + + return ret; +} + +static snd_pcm_uframes_t s3c24xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct s3c24xx_runtime_data *prtd = runtime->private_data; + unsigned long res; + dma_addr_t src, dst; + + DBG("Entered %s\n", __FUNCTION__); + + spin_lock(&prtd->lock); + s3c2410_dma_getposition(prtd->params->channel, &src, &dst); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + res = dst - prtd->dma_start; + else + res = src - prtd->dma_start; + + spin_unlock(&prtd->lock); + + DBG("Pointer %x %x\n",src,dst); + + /* we seem to be getting the odd error from the pcm library due + * to out-of-bounds pointers. this is maybe due to the dma engine + * not having loaded the new values for the channel before being + * callled... (todo - fix ) + */ + + if (res >= snd_pcm_lib_buffer_bytes(substream)) { + if (res == snd_pcm_lib_buffer_bytes(substream)) + res = 0; + } + + return bytes_to_frames(substream->runtime, res); +} + +static int s3c24xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct s3c24xx_runtime_data *prtd; + + int ret; + + DBG("Entered %s\n", __FUNCTION__); + + snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware); + + prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + spin_lock_init(&prtd->lock); + + runtime->private_data = prtd; + return 0; +} + +static int s3c24xx_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct s3c24xx_runtime_data *prtd = runtime->private_data; + + DBG("Entered %s\n", __FUNCTION__); + + if (prtd) + kfree(prtd); + else + DBG("s3c24xx_pcm_close called with prtd == NULL\n"); + + return 0; +} + +static int s3c24xx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + DBG("Entered %s\n", __FUNCTION__); + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops s3c24xx_pcm_ops = { + .open = s3c24xx_pcm_open, + .close = s3c24xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = s3c24xx_pcm_hw_params, + .hw_free = s3c24xx_pcm_hw_free, + .prepare = s3c24xx_pcm_prepare, + .trigger = s3c24xx_pcm_trigger, + .pointer = s3c24xx_pcm_pointer, + .mmap = s3c24xx_pcm_mmap, +}; + +static int s3c24xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = s3c24xx_pcm_hardware.buffer_bytes_max; + + DBG("Entered %s\n", __FUNCTION__); + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} + +static void s3c24xx_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + DBG("Entered %s\n", __FUNCTION__); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static u64 s3c24xx_pcm_dmamask = DMA_32BIT_MASK; + +static int s3c24xx_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + DBG("Entered %s\n", __FUNCTION__); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &s3c24xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = s3c24xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = s3c24xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +struct snd_soc_platform s3c24xx_soc_platform = { + .name = "s3c24xx-audio", + .pcm_ops = &s3c24xx_pcm_ops, + .pcm_new = s3c24xx_pcm_new, + .pcm_free = s3c24xx_pcm_free_dma_buffers, +}; + +EXPORT_SYMBOL_GPL(s3c24xx_soc_platform); + +MODULE_AUTHOR("Ben Dooks, "); +MODULE_DESCRIPTION("Samsung S3C24XX PCM DMA module"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/s3c24xx/s3c24xx-pcm.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/s3c24xx-pcm.h @@ -0,0 +1,32 @@ +/* + * s3c24xx-pcm.h -- + * + * 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. + * + * ALSA PCM interface for the Samsung S3C24xx CPU + */ + +#ifndef _S3C24XX_PCM_H +#define _S3C24XX_PCM_H + +#define ST_RUNNING (1<<0) +#define ST_OPENED (1<<1) + +struct s3c24xx_pcm_dma_params { + struct s3c2410_dma_client *client; /* stream identifier */ + int channel; /* Channel ID */ + dma_addr_t dma_addr; +}; + +#define S3C24XX_DAI_I2S 0 + +extern struct snd_soc_cpu_dai s3c24xx_i2s_dai; + +/* platform data */ +extern struct snd_soc_platform s3c24xx_soc_platform; +extern struct snd_ac97_bus_ops s3c24xx_ac97_ops; + +#endif Index: linux-2.6.21-moko/sound/soc/s3c24xx/smdk2440.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/smdk2440.c @@ -0,0 +1,318 @@ +/* + * smdk2440.c -- ALSA Soc Audio Layer + * + * (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * (c) 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * 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 module is a modified version of the s3c24xx I2S driver supplied by + * Ben Dooks of Simtec and rejigged to the ASoC style at Wolfson Microelectronics + * + * Revision history + * 11th Dec 2006 Merged with Simtec driver + * 10th Nov 2006 Initial version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/uda1380.h" +#include "s3c24xx-pcm.h" +#include "s3c24xx-i2s.h" + +#define SMDK2440_DEBUG 0 +#if SMDK2440_DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +/* audio clock in Hz */ +#define SMDK_CLOCK_SOURCE S3C24XX_CLKSRC_MPLL +#define SMDK_CRYSTAL_CLOCK 16934400 + +static int smdk2440_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->codec; + + DBG("Entered smdk2440_startup\n"); + + return 0; +} + +static void smdk2440_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->codec; + + DBG("Entered smdk2440_shutdown\n"); +} + +static int smdk2440_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_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + unsigned long iis_clkrate; + int div, div256, div384, diff256, diff384, bclk, mclk; + int ret; + unsigned int rate=params_rate(params); + + DBG("Entered %s\n",__FUNCTION__); + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + /* Using PCLK doesnt seem to suit audio particularly well on these cpu's + */ + + div256 = iis_clkrate / (rate * 256); + div384 = iis_clkrate / (rate * 384); + + if (((iis_clkrate / div256) - (rate * 256)) < + ((rate * 256) - (iis_clkrate / (div256 + 1)))) { + diff256 = (iis_clkrate / div256) - (rate * 256); + } else { + div256++; + diff256 = (iis_clkrate / div256) - (rate * 256); + } + + if (((iis_clkrate / div384) - (rate * 384)) < + ((rate * 384) - (iis_clkrate / (div384 + 1)))) { + diff384 = (iis_clkrate / div384) - (rate * 384); + } else { + div384++; + diff384 = (iis_clkrate / div384) - (rate * 384); + } + + DBG("diff256 %d, diff384 %d\n", diff256, diff384); + + if (diff256<=diff384) { + DBG("Selected 256FS\n"); + div = div256 - 1; + bclk = S3C2410_IISMOD_256FS; + } else { + DBG("Selected 384FS\n"); + div = div384 - 1; + bclk = S3C2410_IISMOD_384FS; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.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 = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the audio system clock for DAC and ADC */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, S3C24XX_CLKSRC_PCLK, + rate, SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, S3C2410_IISMOD_32FS ); + if (ret < 0) + return ret; + + /* set BCLK division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, bclk); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(div,div)); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops smdk2440_ops = { + .startup = smdk2440_startup, + .shutdown = smdk2440_shutdown, + .hw_params = smdk2440_hw_params, +}; + +/* smdk2440 machine dapm widgets */ +static const struct snd_soc_dapm_widget smdk2440_dapm_widgets[] = { +SND_SOC_DAPM_HP("Headphone Jack", NULL), +SND_SOC_DAPM_MIC("Mic Jack", NULL), +SND_SOC_DAPM_LINE("Line Jack", NULL), +}; + +/* smdk2440 machine audio map (connections to the codec pins) */ +static const char* audio_map[][3] = { + /* headphone connected to HPOUT */ + {"Headphone Jack", NULL, "HPOUT"}, + + /* mic is connected to MICIN (via right channel of headphone jack) */ + {"MICIN", NULL, "Mic Jack"}, + {"MICIN", NULL, "Line Jack"}, + + {NULL, NULL, NULL}, +}; + +/* + * Logic for a UDA1341 as attached to SMDK2440 + */ +static int smdk2440_uda1341_init(struct snd_soc_codec *codec) +{ + int i, err; + + DBG("Staring smdk2440 init\n"); + + /* Add smdk2440 specific widgets */ + for(i = 0; i < ARRAY_SIZE(smdk2440_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &smdk2440_dapm_widgets[i]); + } + + /* Set up smdk2440 specific audio path audio_mapnects */ + 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_sync_endpoints(codec); + + DBG("Ending smdk2440 init\n"); + + return 0; +} + +/* s3c24xx digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link s3c24xx_dai = { + .name = "WM8731", + .stream_name = "WM8731", + .cpu_dai = &s3c24xx_i2s_dai, + .codec_dai = uda1380_dai, + .init = smdk2440_uda1341_init, + .ops = &smdk2440_ops, +}; + +/* smdk2440 audio machine driver */ +static struct snd_soc_machine snd_soc_machine_smdk2440 = { + .name = "SMDK2440", + .dai_link = &s3c24xx_dai, + .num_links = 1, +}; + +static struct uda1380_setup_data smdk2440_uda1380_setup = { + .i2c_address = 0x00, +}; + +/* s3c24xx audio subsystem */ +static struct snd_soc_device s3c24xx_snd_devdata = { + .machine = &snd_soc_machine_smdk2440, + .platform = &s3c24xx_soc_platform, + .codec_dev = &soc_codec_dev_uda1380, + .codec_data = &smdk2440_uda1380_setup, +}; + +static struct platform_device *s3c24xx_snd_device; + +struct smdk2440_spi_device { + struct device *dev; +}; + +static struct smdk2440_spi_device smdk2440_spi_devdata = { +}; + +struct s3c2410_spigpio_info smdk2440_spi_devinfo = { + .pin_clk = S3C2410_GPF4, + .pin_mosi = S3C2410_GPF5, + .pin_miso = S3C2410_GPF6, + //.board_size, + //.board_info, + .chip_select=NULL, +}; + +static struct platform_device *smdk2440_spi_device; + +static int __init smdk2440_init(void) +{ + int ret; + + if (!machine_is_smdk2440() && !machine_is_s3c2440()) { + DBG("%d\n",machine_arch_type); + DBG("Not a SMDK2440\n"); + return -ENODEV; + } + + s3c24xx_snd_device = platform_device_alloc("soc-audio", -1); + if (!s3c24xx_snd_device) { + DBG("platform_dev_alloc failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(s3c24xx_snd_device, &s3c24xx_snd_devdata); + s3c24xx_snd_devdata.dev = &s3c24xx_snd_device->dev; + ret = platform_device_add(s3c24xx_snd_device); + + if (ret) + platform_device_put(s3c24xx_snd_device); + + // Create a bitbanged SPI device + + smdk2440_spi_device = platform_device_alloc("s3c24xx-spi-gpio",-1); + if (!smdk2440_spi_device) { + DBG("smdk2440_spi_device : platform_dev_alloc failed\n"); + return -ENOMEM; + } + DBG("Return Code %d\n",ret); + + platform_set_drvdata(smdk2440_spi_device, &smdk2440_spi_devdata); + smdk2440_spi_devdata.dev = &smdk2440_spi_device->dev; + smdk2440_spi_devdata.dev->platform_data = &smdk2440_spi_devinfo; + ret = platform_device_add(smdk2440_spi_device); + + if (ret) + platform_device_put(smdk2440_spi_device); + + return ret; +} + +static void __exit smdk2440_exit(void) +{ + platform_device_unregister(s3c24xx_snd_device); +} + +module_init(smdk2440_init); +module_exit(smdk2440_exit); + +/* Module information */ +MODULE_AUTHOR("Ben Dooks, "); +MODULE_DESCRIPTION("ALSA SoC SMDK2440"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8956.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8956.c @@ -0,0 +1,724 @@ +/* + * wm8956.c -- WM8956 ALSA SoC Audio driver + * + * Author: Liam Girdwood + * + * 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 "wm8956.h" + +#define AUDIO_NAME "wm8956" +#define WM8956_VERSION "0.2" + +/* + * Debug + */ + +#define WM8956_DEBUG 0 + +#ifdef WM8956_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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) + +struct snd_soc_codec_device soc_codec_dev_wm8956; + +/* + * wm8956 register cache + * We can't read the WM8956 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8956_reg[WM8956_CACHEREGNUM] = { + 0x0097, 0x0097, 0x0000, 0x0000, + 0x0000, 0x0008, 0x0000, 0x000a, + 0x01c0, 0x0000, 0x00ff, 0x00ff, + 0x0000, 0x0000, 0x0000, 0x0000, //r15 + 0x0000, 0x007b, 0x0100, 0x0032, + 0x0000, 0x00c3, 0x00c3, 0x01c0, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, //r31 + 0x0100, 0x0100, 0x0050, 0x0050, + 0x0050, 0x0050, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0040, 0x0000, + 0x0000, 0x0050, 0x0050, 0x0000, //47 + 0x0002, 0x0037, 0x004d, 0x0080, + 0x0008, 0x0031, 0x0026, 0x00e9, +}; + +/* + * read wm8956 register cache + */ +static inline unsigned int wm8956_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8956_RESET) + return 0; + if (reg >= WM8956_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8956 register cache + */ +static inline void wm8956_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8956_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8956 register space + */ +static int wm8956_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8956 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8956_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8956_reset(c) wm8956_write(c, WM8956_RESET, 0) + +/* todo - complete enumerated controls */ +static const char *wm8956_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum wm8956_enum[] = { + SOC_ENUM_SINGLE(WM8956_DACCTL1, 1, 4, wm8956_deemph), +}; + +/* to complete */ +static const struct snd_kcontrol_new wm8956_snd_controls[] = { + +SOC_DOUBLE_R("Headphone Playback Volume", WM8956_LOUT1, WM8956_ROUT1, + 0, 127, 0), +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8956_LOUT1, WM8956_ROUT1, + 7, 1, 0), +SOC_DOUBLE_R("PCM Volume", WM8956_LDAC, WM8956_RDAC, + 0, 127, 0), + +SOC_ENUM("Playback De-emphasis", wm8956_enum[0]), +}; + +/* add non dapm controls */ +static int wm8956_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8956_snd_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8956_snd_controls[i],codec, NULL))) < 0) + return err; + } + + return 0; +} + +/* Left Output Mixer */ +static const struct snd_kcontrol_new wm8956_loutput_mixer_controls[] = { +SOC_DAPM_SINGLE("Left PCM Playback Switch", WM8956_LOUTMIX1, 8, 1, 0), +}; + +/* Right Output Mixer */ +static const struct snd_kcontrol_new wm8956_routput_mixer_controls[] = { +SOC_DAPM_SINGLE("Right PCM Playback Switch", WM8956_ROUTMIX2, 8, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8956_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8956_loutput_mixer_controls[0], + ARRAY_SIZE(wm8956_loutput_mixer_controls)), +SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8956_loutput_mixer_controls[0], + ARRAY_SIZE(wm8956_routput_mixer_controls)), +}; + +static const char *intercon[][3] = { + /* TODO */ + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8956_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm8956_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm8956_dapm_widgets[i]); + } + + /* set up audio path interconnects */ + for(i = 0; intercon[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, intercon[i][0], + intercon[i][1], intercon[i][2]); + } + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int wm8956_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + wm8956_write(codec, WM8956_IFACE1, iface); + return 0; +} + +static int wm8956_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface = wm8956_read_reg_cache(codec, WM8956_IFACE1) & 0xfff3; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + } + + /* set iface */ + wm8956_write(codec, WM8956_IFACE1, iface); + return 0; +} + +static int wm8956_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8956_read_reg_cache(codec, WM8956_DACCTL1) & 0xfff7; + + if (mute) + wm8956_write(codec, WM8956_DACCTL1, mute_reg | 0x8); + else + wm8956_write(codec, WM8956_DACCTL1, mute_reg); + return 0; +} + +static int wm8956_dapm_event(struct snd_soc_codec *codec, int event) +{ +#if 0 + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, osc on, dac unmute */ + + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, */ + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + break; + } +#endif + // tmp + wm8956_write(codec, WM8956_POWER1, 0xffff); + wm8956_write(codec, WM8956_POWER2, 0xffff); + wm8956_write(codec, WM8956_POWER3, 0xffff); + codec->dapm_state = event; + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 pre_div:1; + u32 n:4; + u32 k:24; +}; + +static struct _pll_div pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div.pre_div = 1; + Ndiv = target / source; + } else + pll_div.pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8956 N value outwith recommended range! N = %d\n",Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +static int wm8956_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + int found = 0; +#if 0 + if (freq_in == 0 || freq_out == 0) { + /* disable the pll */ + /* turn PLL power off */ + } +#endif + + pll_factors(freq_out * 8, freq_in); + + if (!found) + return -EINVAL; + + reg = wm8956_read_reg_cache(codec, WM8956_PLLN) & 0x1e0; + wm8956_write(codec, WM8956_PLLN, reg | (pll_div.pre_div << 4) + | pll_div.n); + wm8956_write(codec, WM8956_PLLK1, pll_div.k >> 16 ); + wm8956_write(codec, WM8956_PLLK2, (pll_div.k >> 8) & 0xff); + wm8956_write(codec, WM8956_PLLK3, pll_div.k &0xff); + wm8956_write(codec, WM8956_CLOCK1, 4); + + return 0; +} + +static int wm8956_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8956_SYSCLKSEL: + reg = wm8956_read_reg_cache(codec, WM8956_CLOCK1) & 0x1fe; + wm8956_write(codec, WM8956_CLOCK1, reg | div); + break; + case WM8956_SYSCLKDIV: + reg = wm8956_read_reg_cache(codec, WM8956_CLOCK1) & 0x1f9; + wm8956_write(codec, WM8956_CLOCK1, reg | div); + break; + case WM8956_DACDIV: + reg = wm8956_read_reg_cache(codec, WM8956_CLOCK1) & 0x1c7; + wm8956_write(codec, WM8956_CLOCK1, reg | div); + break; + case WM8956_OPCLKDIV: + reg = wm8956_read_reg_cache(codec, WM8956_PLLN) & 0x03f; + wm8956_write(codec, WM8956_PLLN, reg | div); + break; + case WM8956_DCLKDIV: + reg = wm8956_read_reg_cache(codec, WM8956_CLOCK2) & 0x03f; + wm8956_write(codec, WM8956_CLOCK2, reg | div); + break; + case WM8956_TOCLKSEL: + reg = wm8956_read_reg_cache(codec, WM8956_ADDCTL1) & 0x1fd; + wm8956_write(codec, WM8956_ADDCTL1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +#define WM8956_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 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8956_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_codec_dai wm8956_dai = { + .name = "WM8956", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8956_RATES, + .formats = WM8956_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8956_RATES, + .formats = WM8956_FORMATS,}, + .ops = { + .hw_params = wm8956_hw_params, + }, + .dai_ops = { + .digital_mute = wm8956_mute, + .set_fmt = wm8956_set_dai_fmt, + .set_clkdiv = wm8956_set_dai_clkdiv, + .set_pll = wm8956_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8956_dai); + + +/* To complete PM */ +static int wm8956_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; + + wm8956_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8956_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8956_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8956_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8956_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8956 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8956_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8956"; + codec->owner = THIS_MODULE; + codec->read = wm8956_read_reg_cache; + codec->write = wm8956_write; + codec->dapm_event = wm8956_dapm_event; + codec->dai = &wm8956_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8956_reg); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8956_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, + wm8956_reg, sizeof(u16) * ARRAY_SIZE(wm8956_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8956_reg); + + wm8956_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8956: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8956_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* set the update bits */ + reg = wm8956_read_reg_cache(codec, WM8956_LOUT1); + wm8956_write(codec, WM8956_LOUT1, reg | 0x0100); + reg = wm8956_read_reg_cache(codec, WM8956_ROUT1); + wm8956_write(codec, WM8956_ROUT1, reg | 0x0100); + + wm8956_add_controls(codec); + wm8956_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8956: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8956_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * WM8956 2 wire address is 0x1a + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8956_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8956_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8956_socdev; + struct wm8956_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8956_init(socdev); + if (ret < 0) { + err("failed to initialise WM8956\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8956_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec* codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8956_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8956_codec_probe); +} + +// tmp +#define I2C_DRIVERID_WM8956 0xfefe + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8956_i2c_driver = { + .driver = { + .name = "WM8956 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8956, + .attach_adapter = wm8956_i2c_attach, + .detach_client = wm8956_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8956", + .driver = &wm8956_i2c_driver, +}; +#endif + +static int wm8956_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8956_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + info("WM8956 Audio Codec %s", WM8956_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); + + wm8956_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8956_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8956_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) + wm8956_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8956_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8956 = { + .probe = wm8956_probe, + .remove = wm8956_remove, + .suspend = wm8956_suspend, + .resume = wm8956_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8956); + +MODULE_DESCRIPTION("ASoC WM8956 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8956.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8956.h @@ -0,0 +1,116 @@ +/* + * wm8956.h -- WM8956 Soc Audio driver + * + * 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 _WM8956_H +#define _WM8956_H + +/* WM8956 register space */ + + +#define WM8956_CACHEREGNUM 56 + +#define WM8956_LINVOL 0x0 +#define WM8956_RINVOL 0x1 +#define WM8956_LOUT1 0x2 +#define WM8956_ROUT1 0x3 +#define WM8956_CLOCK1 0x4 +#define WM8956_DACCTL1 0x5 +#define WM8956_DACCTL2 0x6 +#define WM8956_IFACE1 0x7 +#define WM8956_CLOCK2 0x8 +#define WM8956_IFACE2 0x9 +#define WM8956_LDAC 0xa +#define WM8956_RDAC 0xb + +#define WM8956_RESET 0xf +#define WM8956_3D 0x10 + +#define WM8956_ADDCTL1 0x17 +#define WM8956_ADDCTL2 0x18 +#define WM8956_POWER1 0x19 +#define WM8956_POWER2 0x1a +#define WM8956_ADDCTL3 0x1b +#define WM8956_APOP1 0x1c +#define WM8956_APOP2 0x1d + +#define WM8956_LINPATH 0x20 +#define WM8956_RINPATH 0x21 +#define WM8956_LOUTMIX1 0x22 + +#define WM8956_ROUTMIX2 0x25 +#define WM8956_MONOMIX1 0x26 +#define WM8956_MONOMIX2 0x27 +#define WM8956_LOUT2 0x28 +#define WM8956_ROUT2 0x29 +#define WM8956_MONO 0x2a +#define WM8956_INBMIX1 0x2b +#define WM8956_INBMIX2 0x2c +#define WM8956_BYPASS1 0x2d +#define WM8956_BYPASS2 0x2e +#define WM8956_POWER3 0x2f +#define WM8956_ADDCTL4 0x30 +#define WM8956_CLASSD1 0x31 + +#define WM8956_CLASSD3 0x33 +#define WM8956_PLLN 0x34 +#define WM8956_PLLK1 0x35 +#define WM8956_PLLK2 0x36 +#define WM8956_PLLK3 0x37 + + +/* + * WM8956 Clock dividers + */ +#define WM8956_SYSCLKDIV 0 +#define WM8956_DACDIV 1 +#define WM8956_OPCLKDIV 2 +#define WM8956_DCLKDIV 3 +#define WM8956_TOCLKSEL 4 +#define WM8956_SYSCLKSEL 5 + +#define WM8956_SYSCLK_DIV_1 (0 << 1) +#define WM8956_SYSCLK_DIV_2 (2 << 1) + +#define WM8956_SYSCLK_MCLK (0 << 0) +#define WM8956_SYSCLK_PLL (1 << 0) + +#define WM8956_DAC_DIV_1 (0 << 3) +#define WM8956_DAC_DIV_1_5 (1 << 3) +#define WM8956_DAC_DIV_2 (2 << 3) +#define WM8956_DAC_DIV_3 (3 << 3) +#define WM8956_DAC_DIV_4 (4 << 3) +#define WM8956_DAC_DIV_5_5 (5 << 3) +#define WM8956_DAC_DIV_6 (6 << 3) + +#define WM8956_DCLK_DIV_1_5 (0 << 6) +#define WM8956_DCLK_DIV_2 (1 << 6) +#define WM8956_DCLK_DIV_3 (2 << 6) +#define WM8956_DCLK_DIV_4 (3 << 6) +#define WM8956_DCLK_DIV_6 (4 << 6) +#define WM8956_DCLK_DIV_8 (5 << 6) +#define WM8956_DCLK_DIV_12 (6 << 6) +#define WM8956_DCLK_DIV_16 (7 << 6) + +#define WM8956_TOCLK_F19 (0 << 1) +#define WM8956_TOCLK_F21 (1 << 1) + +#define WM8956_OPCLK_DIV_1 (0 << 0) +#define WM8956_OPCLK_DIV_2 (1 << 0) +#define WM8956_OPCLK_DIV_3 (2 << 0) +#define WM8956_OPCLK_DIV_4 (3 << 0) +#define WM8956_OPCLK_DIV_5_5 (4 << 0) +#define WM8956_OPCLK_DIV_6 (5 << 0) + +struct wm8956_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8956_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8956; + +#endif Index: linux-2.6.21-moko/sound/soc/codecs/wm8960.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8960.c @@ -0,0 +1,766 @@ +/* + * wm8960.c -- WM8960 ALSA SoC Audio driver + * + * Author: Liam Girdwood + * + * 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 "wm8960.h" + +#define AUDIO_NAME "wm8960" +#define WM8960_VERSION "0.1" + +/* + * Debug + */ + +#define WM8960_DEBUG 0 + +#ifdef WM8960_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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) + +struct snd_soc_codec_device soc_codec_dev_wm8960; + +/* + * wm8960 register cache + * We can't read the WM8960 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { + 0x0097, 0x0097, 0x0000, 0x0000, + 0x0000, 0x0008, 0x0000, 0x000a, + 0x01c0, 0x0000, 0x00ff, 0x00ff, + 0x0000, 0x0000, 0x0000, 0x0000, //r15 + 0x0000, 0x007b, 0x0100, 0x0032, + 0x0000, 0x00c3, 0x00c3, 0x01c0, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, //r31 + 0x0100, 0x0100, 0x0050, 0x0050, + 0x0050, 0x0050, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0040, 0x0000, + 0x0000, 0x0050, 0x0050, 0x0000, //47 + 0x0002, 0x0037, 0x004d, 0x0080, + 0x0008, 0x0031, 0x0026, 0x00e9, +}; + +/* + * read wm8960 register cache + */ +static inline unsigned int wm8960_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8960_RESET) + return 0; + if (reg >= WM8960_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8960 register cache + */ +static inline void wm8960_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8960_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8960 register space + */ +static int wm8960_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8960 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8960_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8960_reset(c) wm8960_write(c, WM8960_RESET, 0) + +/* enumerated controls */ +static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", + "Right Inverted", "Stereo Inversion"}; +static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; +static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; +static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; + +static const struct soc_enum wm8960_enum[] = { + SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph), + SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), + SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), + SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), + SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), +}; + +/* to complete */ +static const struct snd_kcontrol_new wm8960_snd_controls[] = { +SOC_DOUBLE_R("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, + 0, 63, 0), +SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, + 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, + 7, 1, 0), +SOC_DOUBLE_R("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, + 0, 127, 0), +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, + 7, 1, 0), +SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), +SOC_ENUM("ADC Polarity", wm8960_enum[1]), +SOC_ENUM("Playback De-emphasis", wm8960_enum[0]), +SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), + +SOC_ENUM("DAC Polarity", wm8960_enum[2]), + +SOC_DOUBLE_R("PCM Volume", WM8960_LDAC, WM8960_RDAC, + 0, 127, 0), + +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]), +SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), + +SOC_ENUM("ALC Function", wm8960_enum[5]), +SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), +SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), +SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), +SOC_ENUM("ALC Mode", wm8960_enum[6]), +SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), + +SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), +SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), + +SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, + 0, 127, 0), +}; + +/* add non dapm controls */ +static int wm8960_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8960_snd_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8960_snd_controls[i],codec, NULL))) < 0) + return err; + } + + return 0; +} + +/* Left Output Mixer */ +static const struct snd_kcontrol_new wm8960_loutput_mixer_controls[] = { +SOC_DAPM_SINGLE("Left PCM Playback Switch", WM8960_LOUTMIX1, 8, 1, 0), +}; + +/* Right Output Mixer */ +static const struct snd_kcontrol_new wm8960_routput_mixer_controls[] = { +SOC_DAPM_SINGLE("Right PCM Playback Switch", WM8960_ROUTMIX2, 8, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8960_loutput_mixer_controls[0], + ARRAY_SIZE(wm8960_loutput_mixer_controls)), +SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8960_loutput_mixer_controls[0], + ARRAY_SIZE(wm8960_routput_mixer_controls)), +}; + +static const char *intercon[][3] = { + /* TODO */ + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8960_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm8960_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm8960_dapm_widgets[i]); + } + + /* set up audio path interconnects */ + for(i = 0; intercon[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, intercon[i][0], + intercon[i][1], intercon[i][2]); + } + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int wm8960_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + wm8960_write(codec, WM8960_IFACE1, iface); + return 0; +} + +static int wm8960_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface = wm8960_read_reg_cache(codec, WM8960_IFACE1) & 0xfff3; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + } + + /* set iface */ + wm8960_write(codec, WM8960_IFACE1, iface); + return 0; +} + +static int wm8960_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8960_read_reg_cache(codec, WM8960_DACCTL1) & 0xfff7; + + if (mute) + wm8960_write(codec, WM8960_DACCTL1, mute_reg | 0x8); + else + wm8960_write(codec, WM8960_DACCTL1, mute_reg); + return 0; +} + +static int wm8960_dapm_event(struct snd_soc_codec *codec, int event) +{ +#if 0 + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, osc on, dac unmute */ + + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, */ + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + break; + } +#endif + // tmp + wm8960_write(codec, WM8960_POWER1, 0xffff); + wm8960_write(codec, WM8960_POWER2, 0xffff); + wm8960_write(codec, WM8960_POWER3, 0xffff); + codec->dapm_state = event; + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 pre_div:1; + u32 n:4; + u32 k:24; +}; + +static struct _pll_div pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div.pre_div = 1; + Ndiv = target / source; + } else + pll_div.pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8960 N value outwith recommended range! N = %d\n",Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +static int wm8960_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + int found = 0; +#if 0 + if (freq_in == 0 || freq_out == 0) { + /* disable the pll */ + /* turn PLL power off */ + } +#endif + + pll_factors(freq_out * 8, freq_in); + + if (!found) + return -EINVAL; + + reg = wm8960_read_reg_cache(codec, WM8960_PLLN) & 0x1e0; + wm8960_write(codec, WM8960_PLLN, reg | (pll_div.pre_div << 4) + | pll_div.n); + wm8960_write(codec, WM8960_PLLK1, pll_div.k >> 16 ); + wm8960_write(codec, WM8960_PLLK2, (pll_div.k >> 8) & 0xff); + wm8960_write(codec, WM8960_PLLK3, pll_div.k &0xff); + wm8960_write(codec, WM8960_CLOCK1, 4); + + return 0; +} + +static int wm8960_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8960_SYSCLKSEL: + reg = wm8960_read_reg_cache(codec, WM8960_CLOCK1) & 0x1fe; + wm8960_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_SYSCLKDIV: + reg = wm8960_read_reg_cache(codec, WM8960_CLOCK1) & 0x1f9; + wm8960_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_DACDIV: + reg = wm8960_read_reg_cache(codec, WM8960_CLOCK1) & 0x1c7; + wm8960_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_OPCLKDIV: + reg = wm8960_read_reg_cache(codec, WM8960_PLLN) & 0x03f; + wm8960_write(codec, WM8960_PLLN, reg | div); + break; + case WM8960_DCLKDIV: + reg = wm8960_read_reg_cache(codec, WM8960_CLOCK2) & 0x03f; + wm8960_write(codec, WM8960_CLOCK2, reg | div); + break; + case WM8960_TOCLKSEL: + reg = wm8960_read_reg_cache(codec, WM8960_ADDCTL1) & 0x1fd; + wm8960_write(codec, WM8960_ADDCTL1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +#define WM8960_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 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8960_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_codec_dai wm8960_dai = { + .name = "WM8960", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .ops = { + .hw_params = wm8960_hw_params, + }, + .dai_ops = { + .digital_mute = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8960_dai); + + +/* To complete PM */ +static int wm8960_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; + + wm8960_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8960_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8960_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8960_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8960 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8960_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8960"; + codec->owner = THIS_MODULE; + codec->read = wm8960_read_reg_cache; + codec->write = wm8960_write; + codec->dapm_event = wm8960_dapm_event; + codec->dai = &wm8960_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8960_reg); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8960_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, + wm8960_reg, sizeof(u16) * ARRAY_SIZE(wm8960_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8960_reg); + + wm8960_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8960: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8960_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* set the update bits */ + reg = wm8960_read_reg_cache(codec, WM8960_LOUT1); + wm8960_write(codec, WM8960_LOUT1, reg | 0x0100); + reg = wm8960_read_reg_cache(codec, WM8960_ROUT1); + wm8960_write(codec, WM8960_ROUT1, reg | 0x0100); + + wm8960_add_controls(codec); + wm8960_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8960: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8960_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +/* + * WM8960 2 wire address is 0x1a + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8960_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8960_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8960_socdev; + struct wm8960_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8960_init(socdev); + if (ret < 0) { + err("failed to initialise WM8960\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8960_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec* codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8960_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8960_codec_probe); +} + +// tmp +#define I2C_DRIVERID_WM8960 0xfefe + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8960_i2c_driver = { + .driver = { + .name = "WM8960 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8960, + .attach_adapter = wm8960_i2c_attach, + .detach_client = wm8960_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8960", + .driver = &wm8960_i2c_driver, +}; +#endif + +static int wm8960_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8960_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + info("WM8960 Audio Codec %s", WM8960_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); + + wm8960_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8960_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8960_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) + wm8960_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8960_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8960 = { + .probe = wm8960_probe, + .remove = wm8960_remove, + .suspend = wm8960_suspend, + .resume = wm8960_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960); + +MODULE_DESCRIPTION("ASoC WM8960 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/wm8960.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/wm8960.h @@ -0,0 +1,121 @@ +/* + * wm8960.h -- WM8960 Soc Audio driver + * + * 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 _WM8960_H +#define _WM8960_H + +/* WM8960 register space */ + + +#define WM8960_CACHEREGNUM 56 + +#define WM8960_LINVOL 0x0 +#define WM8960_RINVOL 0x1 +#define WM8960_LOUT1 0x2 +#define WM8960_ROUT1 0x3 +#define WM8960_CLOCK1 0x4 +#define WM8960_DACCTL1 0x5 +#define WM8960_DACCTL2 0x6 +#define WM8960_IFACE1 0x7 +#define WM8960_CLOCK2 0x8 +#define WM8960_IFACE2 0x9 +#define WM8960_LDAC 0xa +#define WM8960_RDAC 0xb + +#define WM8960_RESET 0xf +#define WM8960_3D 0x10 +#define WM8960_ALC1 0x11 +#define WM8960_ALC2 0x12 +#define WM8960_ALC3 0x13 +#define WM8960_NOISEG 0x14 +#define WM8960_LADC 0x15 +#define WM8960_RADC 0x16 +#define WM8960_ADDCTL1 0x17 +#define WM8960_ADDCTL2 0x18 +#define WM8960_POWER1 0x19 +#define WM8960_POWER2 0x1a +#define WM8960_ADDCTL3 0x1b +#define WM8960_APOP1 0x1c +#define WM8960_APOP2 0x1d + +#define WM8960_LINPATH 0x20 +#define WM8960_RINPATH 0x21 +#define WM8960_LOUTMIX1 0x22 + +#define WM8960_ROUTMIX2 0x25 +#define WM8960_MONOMIX1 0x26 +#define WM8960_MONOMIX2 0x27 +#define WM8960_LOUT2 0x28 +#define WM8960_ROUT2 0x29 +#define WM8960_MONO 0x2a +#define WM8960_INBMIX1 0x2b +#define WM8960_INBMIX2 0x2c +#define WM8960_BYPASS1 0x2d +#define WM8960_BYPASS2 0x2e +#define WM8960_POWER3 0x2f +#define WM8960_ADDCTL4 0x30 +#define WM8960_CLASSD1 0x31 + +#define WM8960_CLASSD3 0x33 +#define WM8960_PLLN 0x34 +#define WM8960_PLLK1 0x35 +#define WM8960_PLLK2 0x36 +#define WM8960_PLLK3 0x37 + + +/* + * WM8960 Clock dividers + */ +#define WM8960_SYSCLKDIV 0 +#define WM8960_DACDIV 1 +#define WM8960_OPCLKDIV 2 +#define WM8960_DCLKDIV 3 +#define WM8960_TOCLKSEL 4 +#define WM8960_SYSCLKSEL 5 + +#define WM8960_SYSCLK_DIV_1 (0 << 1) +#define WM8960_SYSCLK_DIV_2 (2 << 1) + +#define WM8960_SYSCLK_MCLK (0 << 0) +#define WM8960_SYSCLK_PLL (1 << 0) + +#define WM8960_DAC_DIV_1 (0 << 3) +#define WM8960_DAC_DIV_1_5 (1 << 3) +#define WM8960_DAC_DIV_2 (2 << 3) +#define WM8960_DAC_DIV_3 (3 << 3) +#define WM8960_DAC_DIV_4 (4 << 3) +#define WM8960_DAC_DIV_5_5 (5 << 3) +#define WM8960_DAC_DIV_6 (6 << 3) + +#define WM8960_DCLK_DIV_1_5 (0 << 6) +#define WM8960_DCLK_DIV_2 (1 << 6) +#define WM8960_DCLK_DIV_3 (2 << 6) +#define WM8960_DCLK_DIV_4 (3 << 6) +#define WM8960_DCLK_DIV_6 (4 << 6) +#define WM8960_DCLK_DIV_8 (5 << 6) +#define WM8960_DCLK_DIV_12 (6 << 6) +#define WM8960_DCLK_DIV_16 (7 << 6) + +#define WM8960_TOCLK_F19 (0 << 1) +#define WM8960_TOCLK_F21 (1 << 1) + +#define WM8960_OPCLK_DIV_1 (0 << 0) +#define WM8960_OPCLK_DIV_2 (1 << 0) +#define WM8960_OPCLK_DIV_3 (2 << 0) +#define WM8960_OPCLK_DIV_4 (3 << 0) +#define WM8960_OPCLK_DIV_5_5 (4 << 0) +#define WM8960_OPCLK_DIV_6 (5 << 0) + +struct wm8960_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8960_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8960; + +#endif Index: linux-2.6.21-moko/sound/soc/s3c24xx/smdk2440_wm8956.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/smdk2440_wm8956.c @@ -0,0 +1,335 @@ +/* + * smdk2440.c -- ALSA Soc Audio Layer + * + * (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * (c) 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * 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 module is a modified version of the s3c24xx I2S driver supplied by + * Ben Dooks of Simtec and rejigged to the ASoC style at Wolfson Microelectronics + * + * Revision history + * 11th Dec 2006 Merged with Simtec driver + * 10th Nov 2006 Initial version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/wm8956.h" +#include "s3c24xx-pcm.h" +#include "s3c24xx-i2s.h" + +#define SMDK2440_DEBUG 0 +#if SMDK2440_DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +/* audio clock in Hz */ +#define SMDK_CLOCK_SOURCE S3C24XX_CLKSRC_MPLL +#define SMDK_CRYSTAL_CLOCK 12000000 + +static int smdk2440_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->codec; + + DBG("Entered %s\n",__FUNCTION__); + + return 0; +} + +static int smdk2440_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->codec; + + DBG("Entered %s\n",__FUNCTION__); + + return 0; +} + +static int smdk2440_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_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + int bclk, mclk; + int ret; + int pll; + int div=0,sysclkdiv=0; + unsigned int rate = params_rate(params); + + DBG("Entered %s\n",__FUNCTION__); + + /* Work out the pll dividers */ + switch(rate) + { + case 8000: + case 16000: + case 32000: + case 48000: + pll=12288000; + break; + case 96000: + pll=24576000; + break; + case 11025: + case 22050: + case 44100: + pll=11289600; + break; + case 88200: + pll=22579200; + break; + default: + pll=12288000; + } + + /* Work out the DAV Div */ + switch(rate) + { + case 96000: + case 88200: + case 48000: + case 44100: + div=0; + break; + case 32000: + div=1; + break; + case 22050; + div=2; + break; + case 16000: + div=1; + sysclkdiv=2; + break; + case 11025: + div=4; + break; + case 8000: + div=6; + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = codec_dai->dai_ops.set_pll(codec_dai, 0, SMDK_CRYSTAL_CLOCK, pll); + if (ret < 0) + return ret; + + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8956_SYSCLKDIV, sysclkdiv); + if (ret < 0) + return ret; + + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8956_DACDIV, div); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the audio system clock for DAC and ADC */ + /* 12Mhz crystal for this example */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL, + SMDK_CRYSTAL_CLOCK, SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, S3C2410_IISMOD_32FS ); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops smdk2440_ops = { + .startup = smdk2440_startup, + .shutdown = smdk2440_shutdown, + .hw_params = smdk2440_hw_params, +}; + +/* smdk2440 machine dapm widgets */ +static const struct snd_soc_dapm_widget smdk2440_dapm_widgets[] = { +SND_SOC_DAPM_HP("Headphone Jack", NULL), +SND_SOC_DAPM_MIC("Mic Jack", NULL), +SND_SOC_DAPM_LINE("Line Jack", NULL), +}; + +/* smdk2440 machine audio map (connections to the codec pins) */ +static const char* audio_map[][3] = { + /* headphone connected to HPOUT */ + {"Headphone Jack", NULL, "HPOUT"}, + {"MICIN", NULL, "Mic Jack"}, + {"MICIN", NULL, "Line Jack"}, + + {NULL, NULL, NULL}, +}; + +/* + * Logic for a wm8956 as attached to SMDK2440 + */ +static int smdk2440_wm8956_init(struct snd_soc_codec *codec) +{ + int i, err; + + DBG("Entered %s\n",__FUNCTION__); + + + /* Add smdk2440 specific widgets */ + for(i = 0; i < ARRAY_SIZE(smdk2440_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &smdk2440_dapm_widgets[i]); + } + + /* Set up smdk2440 specific audio path audio_mapnects */ + 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_sync_endpoints(codec); + + return 0; +} + +/* s3c24xx digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link s3c24xx_dai = { + .name = "WM8731", + .stream_name = "WM8731", + .cpu_dai = &s3c24xx_i2s_dai, + .codec_dai = &wm8956_dai, + .init = smdk2440_wm8956_init, + .ops = &smdk2440_ops, +}; + +/* smdk2440 audio machine driver */ +static struct snd_soc_machine snd_soc_machine_smdk2440 = { + .name = "SMDK2440", + .dai_link = &s3c24xx_dai, + .num_links = 1, +}; + +static struct wm8956_setup_data smdk2440_wm8956_setup = { + .i2c_address = 0x00, +}; + +/* s3c24xx audio subsystem */ +static struct snd_soc_device s3c24xx_snd_devdata = { + .machine = &snd_soc_machine_smdk2440, + .platform = &s3c24xx_soc_platform, + .codec_dev = &soc_codec_dev_wm8956, + .codec_data = &smdk2440_wm8956_setup, +}; + +static struct platform_device *s3c24xx_snd_device; + +struct smdk2440_spi_device { + struct device *dev; +}; + +static struct smdk2440_spi_device smdk2440_spi_devdata = { +}; + +struct s3c2410_spigpio_info smdk2440_spi_devinfo = { + .pin_clk = S3C2410_GPF4, + .pin_mosi = S3C2410_GPF5, + .pin_miso = S3C2410_GPF6, + //.board_size, + //.board_info, + .chip_select=NULL, +}; + +static struct platform_device *smdk2440_spi_device; + +static int __init smdk2440_init(void) +{ + int ret; + + if (!machine_is_smdk2440() && !machine_is_s3c2440()) { + DBG("%d\n",machine_arch_type); + DBG("Not a SMDK2440\n"); + return -ENODEV; + } + + s3c24xx_snd_device = platform_device_alloc("soc-audio", -1); + if (!s3c24xx_snd_device) { + DBG("platform_dev_alloc failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(s3c24xx_snd_device, &s3c24xx_snd_devdata); + s3c24xx_snd_devdata.dev = &s3c24xx_snd_device->dev; + ret = platform_device_add(s3c24xx_snd_device); + + if (ret) + platform_device_put(s3c24xx_snd_device); + + // Create a bitbanged SPI device + + smdk2440_spi_device = platform_device_alloc("s3c24xx-spi-gpio",-1); + if (!smdk2440_spi_device) { + DBG("smdk2440_spi_device : platform_dev_alloc failed\n"); + return -ENOMEM; + } + DBG("Return Code %d\n",ret); + + platform_set_drvdata(smdk2440_spi_device, &smdk2440_spi_devdata); + smdk2440_spi_devdata.dev = &smdk2440_spi_device->dev; + smdk2440_spi_devdata.dev->platform_data = &smdk2440_spi_devinfo; + ret = platform_device_add(smdk2440_spi_device); + + if (ret) + platform_device_put(smdk2440_spi_device); + + return ret; +} + +static void __exit smdk2440_exit(void) +{ + platform_device_unregister(s3c24xx_snd_device); +} + +module_init(smdk2440_init); +module_exit(smdk2440_exit); + +/* Module information */ +MODULE_AUTHOR("Ben Dooks, "); +MODULE_DESCRIPTION("ALSA SoC SMDK2440"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/imx/imx-ssi.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/imx-ssi.h @@ -0,0 +1,28 @@ +/* + * 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 _IMX_SSI_H +#define _IMX_SSI_H + +/* pxa2xx DAI SSP ID's */ +#define IMX_DAI_SSI1 0 +#define IMX_DAI_SSI2 1 + +/* SSI clock sources */ +#define IMX_SSP_SYS_CLK 0 + +/* SSI audio dividers */ +#define IMX_SSI_DIV_2 0 +#define IMX_SSI_DIV_PSR 1 +#define IMX_SSI_DIV_PM 2 + +/* SSI Div 2 */ +#define IMX_SSI_DIV_2_OFF ~SSI_STCCR_DIV2 +#define IMX_SSI_DIV_2_ON SSI_STCCR_DIV2 + +extern struct snd_soc_cpu_dai imx_ssi_pcm_dai[2]; + +#endif Index: linux-2.6.21-moko/sound/soc/imx/mx31ads_wm8753.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/imx/mx31ads_wm8753.c @@ -0,0 +1,400 @@ +/* + * mx31ads_wm8753.c -- SoC audio for mx31ads + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * mx31ads audio amplifier code taken from arch/arm/mach-pxa/mx31ads.c + * Copyright: MontaVista Software Inc. + * + * 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. + * + * Revision history + * 30th Oct 2005 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../codecs/wm8753.h" +#include "imx31-pcm.h" +#include "imx-ssi.h" + +static struct snd_soc_machine mx31ads; + +static int mx31ads_hifi_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; + unsigned int pll_out = 0, bclk = 0, fmt = 0; + int ret = 0; + + /* + * The WM8753 is better at generating accurate audio clocks than the + * MX31 SSI controller, so we will use it as master when we can. + */ + switch (params_rate(params)) { + case 8000: + case 16000: + fmt = SND_SOC_DAIFMT_CBS_CFS; + pll_out = 12288000; + break; + case 48000: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_4; + pll_out = 12288000; + break; + case 96000: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_2; + pll_out = 12288000; + break; + case 11025: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_16; + pll_out = 11289600; + break; + case 22050: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_8; + pll_out = 11289600; + break; + case 44100: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_4; + pll_out = 11289600; + break; + case 88200: + fmt = SND_SOC_DAIFMT_CBM_CFS; + bclk = WM8753_BCLK_DIV_2; + pll_out = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | fmt); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | fmt); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_MCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the SSI system clock as input (unused) */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk); + if (ret < 0) + return ret; + + /* codec PLL input is 13 MHz */ + ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, 13000000, pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int mx31ads_hifi_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; + + /* disable the PLL */ + return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, 0, 0); +} + +/* + * mx31ads WM8753 HiFi DAI opserations. + */ +static struct snd_soc_ops mx31ads_hifi_ops = { + .hw_params = mx31ads_hifi_hw_params, + .hw_free = mx31ads_hifi_hw_free, +}; + +static int mx31ads_voice_startup(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void mx31ads_voice_shutdown(struct snd_pcm_substream *substream) +{ +} + +static int mx31ads_voice_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; + unsigned int pll_out = 0, bclk = 0, pcmdiv = 0; + int ret = 0; + + /* + * The WM8753 is far better at generating accurate audio clocks than the + * pxa2xx SSP controller, so we will use it as master when we can. + */ + switch (params_rate(params)) { + case 8000: + pll_out = 12288000; + pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 256kHz */ + break; + case 16000: + pll_out = 12288000; + pcmdiv = WM8753_PCM_DIV_3; /* 4.096 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 512kHz */ + break; + case 48000: + pll_out = 12288000; + pcmdiv = WM8753_PCM_DIV_1; /* 12.288 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 1.536 MHz */ + break; + case 11025: + pll_out = 11289600; + pcmdiv = WM8753_PCM_DIV_4; /* 11.2896 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 352.8 kHz */ + break; + case 22050: + pll_out = 11289600; + pcmdiv = WM8753_PCM_DIV_2; /* 11.2896 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 705.6 kHz */ + break; + case 44100: + pll_out = 11289600; + pcmdiv = WM8753_PCM_DIV_1; /* 11.2896 MHz */ + bclk = WM8753_VXCLK_DIV_8; /* 1.4112 MHz */ + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_PCMCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the SSP system clock as input (unused) */ +// ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_SSP_CLK_PLL, 0, +// SND_SOC_CLOCK_IN); +// if (ret < 0) +// return ret; + + /* set codec BCLK division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_VXCLKDIV, bclk); + if (ret < 0) + return ret; + + /* set codec PCM division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv); + if (ret < 0) + return ret; + + /* codec PLL input is 13 MHz */ + ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, 13000000, pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int mx31ads_voice_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; + + /* disable the PLL */ + return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, 0, 0); +} + +static struct snd_soc_ops mx31ads_voice_ops = { + .startup = mx31ads_voice_startup, + .shutdown = mx31ads_voice_shutdown, + .hw_params = mx31ads_voice_hw_params, + .hw_free = mx31ads_voice_hw_free, +}; + +static int mx31ads_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int mx31ads_resume(struct platform_device *pdev) +{ + return 0; +} + +static int mx31ads_probe(struct platform_device *pdev) +{ + return 0; +} + +static int mx31ads_remove(struct platform_device *pdev) +{ + return 0; +} + +/* example machine audio_mapnections */ +static const char* audio_map[][3] = { + + /* mic is connected to mic1 - with bias */ + {"MIC1", NULL, "Mic Bias"}, + {"MIC1N", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic1 Jack"}, + {"Mic Bias", NULL, "Mic1 Jack"}, + + {"ACIN", NULL, "ACOP"}, + {NULL, NULL, NULL}, +}; + +/* headphone detect support on my board */ +static const char * hp_pol[] = {"Headphone", "Speaker"}; +static const struct soc_enum wm8753_enum = + SOC_ENUM_SINGLE(WM8753_OUTCTL, 1, 2, hp_pol); + +static const struct snd_kcontrol_new wm8753_mx31ads_controls[] = { + SOC_SINGLE("Headphone Detect Switch", WM8753_OUTCTL, 6, 1, 0), + SOC_ENUM("Headphone Detect Polarity", wm8753_enum), +}; + +/* + * This is an example machine initialisation for a wm8753 connected to a + * mx31ads II. It is missing logic to detect hp/mic insertions and logic + * to re-route the audio in such an event. + */ +static int mx31ads_wm8753_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* set up mx31ads codec pins */ + snd_soc_dapm_set_endpoint(codec, "RXP", 0); + snd_soc_dapm_set_endpoint(codec, "RXN", 0); + snd_soc_dapm_set_endpoint(codec, "MIC2", 0); + + /* add mx31ads specific controls */ + for (i = 0; i < ARRAY_SIZE(wm8753_mx31ads_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8753_mx31ads_controls[i],codec, NULL))) < 0) + return err; + } + + /* set up mx31ads specific audio path audio_mapnects */ + 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_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_dai_link mx31ads_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .cpu_dai = &imx_ssi_pcm_dai[0], + .codec_dai = &wm8753_dai[WM8753_DAI_HIFI], + .init = mx31ads_wm8753_init, + .ops = &mx31ads_hifi_ops, +}, +//{ /* Voice via BT */ +// .name = "Bluetooth", +// .stream_name = "Voice", +// .cpu_dai = &pxa_ssp_dai[1], +// .codec_dai = &wm8753_dai[WM8753_DAI_VOICE], +// .ops = &mx31ads_voice_ops, +//}, +}; + +static struct snd_soc_machine mx31ads = { + .name = "mx31ads", + .probe = mx31ads_probe, + .remove = mx31ads_remove, + .suspend_pre = mx31ads_suspend, + .resume_post = mx31ads_resume, + .dai_link = mx31ads_dai, + .num_links = ARRAY_SIZE(mx31ads_dai), +}; + +static struct wm8753_setup_data mx31ads_wm8753_setup = { + .i2c_address = 0x1a, +}; + +static struct snd_soc_device mx31ads_snd_devdata = { + .machine = &mx31ads, + .platform = &mxc_soc_platform, + .codec_dev = &soc_codec_dev_wm8753, + .codec_data = &mx31ads_wm8753_setup, +}; + +static struct platform_device *mx31ads_snd_device; + +static int __init mx31ads_init(void) +{ + int ret; + + mx31ads_snd_device = platform_device_alloc("soc-audio", -1); + if (!mx31ads_snd_device) + return -ENOMEM; + + platform_set_drvdata(mx31ads_snd_device, &mx31ads_snd_devdata); + mx31ads_snd_devdata.dev = &mx31ads_snd_device->dev; + ret = platform_device_add(mx31ads_snd_device); + + if (ret) + platform_device_put(mx31ads_snd_device); + + return ret; +} + +static void __exit mx31ads_exit(void) +{ + platform_device_unregister(mx31ads_snd_device); +} + +module_init(mx31ads_init); +module_exit(mx31ads_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM8753 mx31ads"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/s3c24xx/lm4857.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/lm4857.h @@ -0,0 +1,15 @@ +#ifndef LM4857_H_ +#define LM4857_H_ + +/* The register offsets in the cache array */ +#define LM4857_MVOL 0 +#define LM4857_LVOL 1 +#define LM4857_RVOL 2 +#define LM4857_CTRL 3 + +/* the shifts required to set these bits */ +#define LM4857_3D 5 +#define LM4857_WAKEUP 5 +#define LM4857_EPGAIN 4 + +#endif /*LM4857_H_*/ Index: linux-2.6.21-moko/sound/soc/codecs/tlv320.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/tlv320.c @@ -0,0 +1,609 @@ +/* + * tlv320.c -- TLV 320 ALSA Soc Audio driver + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2006 Atlab srl. + * + * Authors: Liam Girdwood + * Nicola Perrino + * + * 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 +#include + +#include "tlv320.h" + +#define AUDIO_NAME "tlv320" +#define TLV320_VERSION "0.1" + +/* + * Debug + */ + +//#define TLV320_DEBUG 0 + +#ifdef TLV320_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#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 TLV320_VOICE_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) + + +#define TLV320_VOICE_BITS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + + +static int caps_charge = 2000; +static int setting = 1; +module_param(caps_charge, int, 0); +module_param(setting, int, 0); +MODULE_PARM_DESC(caps_charge, "TLV320 cap charge time (msecs)"); + +static struct workqueue_struct *tlv320_workq = NULL; +//static struct work_struct tlv320_dapm_work; + +/* codec private data */ +struct tlv320_priv { + unsigned int sysclk; + unsigned int pcmclk; +}; + + +#ifdef TLV320AIC24K +/* ADDR table */ +static const unsigned char tlv320_reg_addr[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x01, /* CONTROL REG 1 */ + 0x02, /* CONTROL REG 2 */ + 0x03, /* CONTROL REG 3A */ + 0x03, /* CONTROL REG 3B */ + 0x03, /* CONTROL REG 3C */ + 0x03, /* CONTROL REG 3D */ + 0x04, /* CONTROL REG 4 */ + 0x04, /* CONTROL REG 4 Bis */ + 0x05, /* CONTROL REG 5A */ + 0x05, /* CONTROL REG 5B */ + 0x05, /* CONTROL REG 5C */ + 0x05, /* CONTROL REG 5D */ + 0x06, /* CONTROL REG 6A */ + 0x06, /* CONTROL REG 6B */ +}; + +/* + * DATA case digital SET1: + * SSP -> DAC -> OUT + * IN -> ADC -> SSP + * IN = HNSI (MIC) + * OUT = HDSO (SPKG) + * Usage: playback, capture streams + */ +static const unsigned char tlv320_reg_data_init_set1[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x49, /* CONTROL REG 1 */ + 0x20, /* CONTROL REG 2 */ + 0x01, /* CONTROL REG 3A */ + 0x40, /* CONTROL REG 3B */ + 0x81, /* CONTROL REG 3C */ + 0xc0, /* CONTROL REG 3D */ + 0x02,//0x42(16khz),//0x10, /* CONTROL REG 4 */ + 0x88,//0x90, /* CONTROL REG 4 Bis */ + 0x00, /* CONTROL REG 5A */ + 0x40,//(0dB) /* CONTROL REG 5B */ + 0xbf, /* CONTROL REG 5C */ + 0xc0, /* CONTROL REG 5D */ + 0x02,//(HNSI) /* CONTROL REG 6A */ + 0x81 //(HDSO) /* CONTROL REG 6B */ +}; + +/* + * DATA case digital SET2: + * SSP -> DAC -> OUT + * IN -> ADC -> SSP + * IN = HDSI (PHONE IN) + * OUT = HNSO (PHONE OUT) + * Usage: playback, capture streams + */ +static const unsigned char tlv320_reg_data_init_set2[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x49, /* CONTROL REG 1 */ + 0x20, /* CONTROL REG 2 */ + 0x01, /* CONTROL REG 3A */ + 0x40, /* CONTROL REG 3B */ + 0x81, /* CONTROL REG 3C */ + 0xc0, /* CONTROL REG 3D */ + 0x02,//0x42(16khz),//0x10, /* CONTROL REG 4 */ + 0x88,//0x90, /* CONTROL REG 4 Bis */ + 0x00, /* CONTROL REG 5A */ + 0x52,//(-27dB) /* CONTROL REG 5B */ + 0xbf, /* CONTROL REG 5C */ + 0xc0, /* CONTROL REG 5D */ + 0x01,//(PHONE IN) /* CONTROL REG 6A */ + 0x82 //(PHONE OUT) /* CONTROL REG 6B */ +}; + +/* + * DATA case analog: + * ADC, DAC, SSP off + * Headset input to output (HDSI2O -> 1) + * Handset input to output (HNSI2O -> 1) + * Usage: room monitor + */ +static const unsigned char tlv320_reg_data_init_set3[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x08, /* CONTROL REG 1 */ + 0x20, /* CONTROL REG 2 */ + 0x11, /* CONTROL REG 3A */ + 0x40, /* CONTROL REG 3B */ + 0x80, /* CONTROL REG 3C */ + 0xc0, /* CONTROL REG 3D */ + 0x00, /* CONTROL REG 4 */ + 0x00, /* CONTROL REG 5A */ + 0x40, /* CONTROL REG 5B */ + 0x80, /* CONTROL REG 5C */ + 0xc0, /* CONTROL REG 5D */ + 0x60, /* CONTROL REG 6A */ + 0x80 /* CONTROL REG 6B */ +}; + +#else // TLV320AIC14k + +/* ADDR table */ +static const unsigned char tlv320_reg_addr[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x01, /* CONTROL REG 1 */ + 0x02, /* CONTROL REG 2 */ + 0x03, /* CONTROL REG 3 */ + 0x04, /* CONTROL REG 4 */ + 0x04, /* CONTROL REG 4 Bis */ + 0x05, /* CONTROL REG 5A */ + 0x05, /* CONTROL REG 5B */ + 0x05, /* CONTROL REG 5C */ + 0x05, /* CONTROL REG 5D */ + 0x06 /* CONTROL REG 6 */ +}; + +/* + * DATA case digital: + * SSP -> DAC -> OUT + * IN -> ADC -> SSP + * Usage: playback, capture streams + */ +static const unsigned char tlv320_reg_data_init_set1[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x41, /* CONTROL REG 1 */ + 0x20, /* CONTROL REG 2 */ + 0x09, /* CONTROL REG 3 */ + 0x02,//0x42(16khz),//0x10, /* CONTROL REG 4 */ + 0x88,//0x90, /* CONTROL REG 4 Bis */ + 0x2A, /* CONTROL REG 5A */ + 0x6A, /* CONTROL REG 5B */ + 0xbc, /* CONTROL REG 5C */ + 0xc0, /* CONTROL REG 5D */ + 0x00 /* CONTROL REG 6 */ +}; +#endif +/* + * read tlv320 register cache + */ +static inline unsigned int tlv320_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + if (reg > ARRAY_SIZE(tlv320_reg_addr)) + return -1; + return cache[reg]; +} + +/* + * write tlv320 register cache + */ +static inline void tlv320_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u8 *cache = codec->reg_cache; + if (reg > ARRAY_SIZE(tlv320_reg_addr)) + return; + cache[reg] = value; +} + +/* + * read tlv320 + */ +static int tlv320_read (struct snd_soc_codec *codec, u8 reg) +{ + return i2c_smbus_read_byte_data(codec->control_data, reg); +} + +/* + * write tlv320 + */ +static int tlv320_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + if (tlv320_reg_addr[reg] > 0x06) + return -1; + + tlv320_write_reg_cache (codec, reg, value); + + return i2c_smbus_write_byte_data(codec->control_data, tlv320_reg_addr[reg], value); +} + + +/* + * write block tlv320 + */ +static int tlv320_write_block (struct snd_soc_codec *codec, + const u8 *data, unsigned int len) +{ + int ret = -1; + int i; + + for (i=0; idapm_state); +#endif +} + +/* + * initialise the TLV320 driver + * register the mixer and dsp interfaces with the kernel + */ +static int tlv320_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "TLV320"; + codec->owner = THIS_MODULE; + codec->read = tlv320_read_reg_cache; + codec->write = tlv320_write; + codec->dai = tlv320_dai; + codec->num_dai = ARRAY_SIZE(tlv320_dai); + codec->reg_cache_size = ARRAY_SIZE(tlv320_reg_addr); + + codec->reg_cache = + kzalloc(sizeof(u8) * ARRAY_SIZE(tlv320_reg_addr), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, tlv320_reg_addr, + sizeof(u8) * ARRAY_SIZE(tlv320_reg_addr)); + codec->reg_cache_size = sizeof(u8) * ARRAY_SIZE(tlv320_reg_addr); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + kfree(codec->reg_cache); + return ret; + } + + queue_delayed_work(tlv320_workq, + &codec->delayed_work, msecs_to_jiffies(caps_charge)); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + } + + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *tlv320_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +#define I2C_DRIVERID_TLV320 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver tlv320_i2c_driver; +static struct i2c_client client_template; + +static int tlv320_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = tlv320_socdev; + struct tlv320_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret, len; + const unsigned char *data; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = tlv320_init(socdev); + if (ret < 0) { + err("failed to initialise TLV320\n"); + goto err; + } + + switch(setting) { + case 1: + data = tlv320_reg_data_init_set1; + len = sizeof(tlv320_reg_data_init_set1); + break; + case 2: + data = tlv320_reg_data_init_set2; + len = sizeof(tlv320_reg_data_init_set2); + break; + case 3: + data = tlv320_reg_data_init_set3; + len = sizeof(tlv320_reg_data_init_set3); + break; + default: + data = tlv320_reg_data_init_set1; + len = sizeof(tlv320_reg_data_init_set1); + break; + } + + ret = tlv320_write_block(codec, data, len); + + if (ret < 0) { + err("attach error: init status %d\n", ret); + } else { + info("attach: chip tlv320 at address 0x%02x", + tlv320_read(codec, 0x02) << 1); + } + + //tlv320_write(codec, CODEC_REG6B, 0x80); +#if 0 + int value; + int i; + + for (i=0; ireg_cache); + kfree(client); + return 0; +} + +static int tlv320_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, tlv320_codec_probe); +} + +/* tlv320 i2c codec control layer */ +static struct i2c_driver tlv320_i2c_driver = { + .driver = { + .name = "tlv320 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_TLV320, + .attach_adapter = tlv320_i2c_attach, + .detach_client = tlv320_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "tlv320", + .driver = &tlv320_i2c_driver, +}; +#endif + +static int tlv320_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct tlv320_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + struct tlv320_priv *tlv320; + + info("TLV320 Audio Codec %s", TLV320_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + tlv320 = kzalloc(sizeof(struct tlv320_priv), GFP_KERNEL); + if (tlv320 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = tlv320; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + tlv320_socdev = socdev; + + INIT_DELAYED_WORK(&codec->delayed_work, tlv320_work); + tlv320_workq = create_workqueue("tlv320"); + if (tlv320_workq == NULL) { + kfree(codec); + return -ENOMEM; + } +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&tlv320_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int tlv320_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (tlv320_workq) + destroy_workqueue(tlv320_workq); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&tlv320_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_tlv320 = { + .probe = tlv320_probe, + .remove = tlv320_remove, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320); + +MODULE_DESCRIPTION("ASoC TLV320 driver"); +MODULE_AUTHOR("Nicola Perrino"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/codecs/tlv320.h =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/codecs/tlv320.h @@ -0,0 +1,111 @@ +/* + * tlv320.h -- TLV 320 ALSA Soc Audio driver + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2006 Atlab srl. + * + * Authors: Liam Girdwood + * Nicola Perrino + * + * 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. + * + */ + +#ifndef _TLV320_H +#define _TLV320_H + +#define TLV320AIC24K + + +/* TLV320 register space */ +#define CODEC_NOOP 0x00 +#define CODEC_REG1 0x01 +#define CODEC_REG2 0x02 +#define CODEC_REG3A 0x03 +#define CODEC_REG3B 0x04 +#define CODEC_REG3C 0x05 +#define CODEC_REG3D 0x06 +#define CODEC_REG4A 0x07 +#define CODEC_REG4B 0x08 +#define CODEC_REG5A 0x09 +#define CODEC_REG5B 0x0a +#define CODEC_REG5C 0x0b +#define CODEC_REG5D 0x0c +#define CODEC_REG6A 0x0d +#define CODEC_REG6B 0x0e + + +// Control Register 1 +#define REG1_CONTINUOUS 0x40 +#define REG1_IIR_EN 0x20 +#define REG1_MIC_BIAS_235 0x08 +#define REG1_ANALOG_LOOP_BACK 0x04 +#define REG1_DIGITAL_LOOP_BACK 0x02 +#define REG1_DAC16 0x01 + +// Control Register 2 +#define REG2_TURBO_EN 0x80 +#define REG2_FIR_BYPASS 0x40 +#define REG2_GPIO 0x02 +#define REG2_GPIO_1 0x06 + +// Control Register 3A +#define REG3_PWDN_ALL 0x30 +#define REG3_PWDN_ADC 0x10 +#define REG3_PWDN_DAC 0x20 +#define REG3_SW_RESET 0x08 +#define REG3_SAMPLING_FACTOR1 0x01 +#define REG3_SAMPLING_FACTOR2 0x02 + +// Control Register 3B +#define REG3_8KBP_EN 0x60 +#define REG3_MUTE_OUTP1 0x42 +#define REG3_MUTE_OUTP2 0x48 +#define REG3_MUTE_OUTP3 0x44 + +// Control Register 4 +#define REG4_FSDIV_M 0x85 //M=5 +#define REG4_FSDIV_NP 0x08 //N=1, P=8 +//#define REG4_FSDIV_NP 0x01 //N=1, P=8 +#define REG4_FSDIV_NP1 0x02 //N=16, P=2 + +// Control Register 5 +#define REG5A_ADC_GAIN 0x02 //3dB +#define REG5A_ADC_MUTE 0x0f //Mute +#define REG5B_DAC_GAIN 0x42 //-3dB +#define REG5B_DAC_MUTE 0x4f //Mute +#define REG5C_SIDETONE_MUTE 0xBF + +// Control Register 6 +#define REG6A_AIC24A_CH1_IN 0x08 //INP1 to ADC +#define REG6B_AIC24A_CH1_OUT 0x82 //OUTP2 to DAC +#define REG6A_AIC24A_CH2_IN 0x02 //INP2 to ADC +#define REG6B_AIC24A_CH2_OUT 0x81 //OUTP3 to DAC + +/* clock inputs */ +#define TLV320_MCLK 0 +#define TLV320_PCMCLK 1 + + +struct tlv320_setup_data { + unsigned short i2c_address; +}; + +/* DAI ifmodes */ +/* mode 1 IFMODE = 00 */ +#define TLV320_DAI_MODE1_VOICE 0 +#define TLV320_DAI_MODE1_HIFI 1 +/* mode 2 IFMODE = 01 */ +#define TLV320_DAI_MODE2_VOICE 2 +/* mode 3 IFMODE = 10 */ +#define TLV320_DAI_MODE3_HIFI 3 +/* mode 4 IFMODE = 11 */ +#define TLV320_DAI_MODE4_HIFI 4 + +extern struct snd_soc_codec_dai tlv320_dai[5]; +extern struct snd_soc_codec_device soc_codec_dev_tlv320; + +#endif Index: linux-2.6.21-moko/sound/soc/pxa/amesom_tlv320.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/pxa/amesom_tlv320.c @@ -0,0 +1,211 @@ +/* + * amesom_tlv320.c -- SoC audio for Amesom + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2006 Atlab srl. + * + * Authors: Liam Girdwood + * Nicola Perrino + * + * 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. + * + * Revision history + * 5th Dec 2006 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/tlv320.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-ssp.h" + + +/* + * SSP2 GPIO's + */ + +#define GPIO11_SSP2RX_MD (11 | GPIO_ALT_FN_2_IN) +#define GPIO13_SSP2TX_MD (13 | GPIO_ALT_FN_1_OUT) +#define GPIO50_SSP2CLKS_MD (50 | GPIO_ALT_FN_3_IN) +#define GPIO14_SSP2FRMS_MD (14 | GPIO_ALT_FN_2_IN) +#define GPIO50_SSP2CLKM_MD (50 | GPIO_ALT_FN_3_OUT) +#define GPIO14_SSP2FRMM_MD (14 | GPIO_ALT_FN_2_OUT) + + +static struct snd_soc_machine amesom; + + +static int amesom_probe(struct platform_device *pdev) +{ + return 0; +} + +static int amesom_remove(struct platform_device *pdev) +{ + return 0; +} + +static int tlv320_voice_startup(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void tlv320_voice_shutdown(struct snd_pcm_substream *substream) +{ + return; +} + +/* + * Tlv320 uses SSP port for playback. + */ +static int tlv320_voice_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; + + //printk("tlv320_voice_hw_params enter\n"); + switch(params_rate(params)) { + case 8000: + //printk("tlv320_voice_hw_params 8000\n"); + break; + case 16000: + //printk("tlv320_voice_hw_params 16000\n"); + break; + default: + break; + } + + // CODEC MASTER, SSP SLAVE + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the SSP system clock as input (unused) */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_SSP_CLK_NET_PLL, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set SSP slots */ + //ret = cpu_dai->dai_ops.set_tdm_slot(cpu_dai, 0x1, slots); + ret = cpu_dai->dai_ops.set_tdm_slot(cpu_dai, 0x3, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int tlv320_voice_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static struct snd_soc_ops tlv320_voice_ops = { + .startup = tlv320_voice_startup, + .shutdown = tlv320_voice_shutdown, + .hw_params = tlv320_voice_hw_params, + .hw_free = tlv320_voice_hw_free, +}; + + +static struct snd_soc_dai_link amesom_dai[] = { +{ + .name = "TLV320", + .stream_name = "TLV320 Voice", + .cpu_dai = &pxa_ssp_dai[PXA2XX_DAI_SSP2], + .codec_dai = &tlv320_dai[TLV320_DAI_MODE1_VOICE], + .ops = &tlv320_voice_ops, +}, +}; + +static struct snd_soc_machine amesom = { + .name = "Amesom", + .probe = amesom_probe, + .remove = amesom_remove, + .dai_link = amesom_dai, + .num_links = ARRAY_SIZE(amesom_dai), +}; + +static struct tlv320_setup_data amesom_tlv320_setup = { +#ifdef TLV320AIC24K //codec2 + .i2c_address = 0x41, +#else // TLV320AIC14k + .i2c_address = 0x40, +#endif +}; + +static struct snd_soc_device amesom_snd_devdata = { + .machine = &amesom, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_tlv320, + .codec_data = &amesom_tlv320_setup, +}; + +static struct platform_device *amesom_snd_device; + +static int __init amesom_init(void) +{ + int ret; + + amesom_snd_device = platform_device_alloc("soc-audio", -1); + if (!amesom_snd_device) + return -ENOMEM; + + platform_set_drvdata(amesom_snd_device, &amesom_snd_devdata); + amesom_snd_devdata.dev = &amesom_snd_device->dev; + ret = platform_device_add(amesom_snd_device); + + if (ret) + platform_device_put(amesom_snd_device); + + + /* SSP port 2 slave */ + pxa_gpio_mode(GPIO11_SSP2RX_MD); + pxa_gpio_mode(GPIO13_SSP2TX_MD); + pxa_gpio_mode(GPIO50_SSP2CLKS_MD); + pxa_gpio_mode(GPIO14_SSP2FRMS_MD); + + return ret; +} + +static void __exit amesom_exit(void) +{ + platform_device_unregister(amesom_snd_device); +} + +module_init(amesom_init); +module_exit(amesom_exit); + +/* Module information */ +MODULE_AUTHOR("Nicola Perrino"); +MODULE_DESCRIPTION("ALSA SoC TLV320 Amesom"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/s3c24xx/neo1973_wm8753.c =================================================================== --- /dev/null +++ linux-2.6.21-moko/sound/soc/s3c24xx/neo1973_wm8753.c @@ -0,0 +1,686 @@ +/* + * neo1973_wm8753.c -- SoC audio for Neo1973 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 20th Jan 2007 Initial version. + * 05th Feb 2007 Rename all to Neo1973 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/wm8753.h" +#include "lm4857.h" +#include "s3c24xx-pcm.h" +#include "s3c24xx-i2s.h" + +#define NEO1973_DEBUG 0 +#if NEO1973_DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +/* define the scenarios */ +#define NEO_AUDIO_OFF 0 +#define NEO_GSM_CALL_AUDIO_HANDSET 1 +#define NEO_GSM_CALL_AUDIO_HEADSET 2 +#define NEO_GSM_CALL_AUDIO_BLUETOOTH 3 +#define NEO_STEREO_TO_SPEAKERS 4 +#define NEO_STEREO_TO_HEADPHONES 5 +#define NEO_CAPTURE_HANDSET 6 +#define NEO_CAPTURE_HEADSET 7 +#define NEO_CAPTURE_BLUETOOTH 8 + +static struct snd_soc_machine neo1973; +static struct i2c_client *i2c; + +static int neo1973_hifi_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; + unsigned int pll_out = 0, bclk = 0; + int ret = 0; + unsigned long iis_clkrate; + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + switch (params_rate(params)) { + case 8000: + case 16000: + pll_out = 12288000; + break; + case 48000: + bclk = WM8753_BCLK_DIV_4; + pll_out = 12288000; + break; + case 96000: + bclk = WM8753_BCLK_DIV_2; + pll_out = 12288000; + break; + case 11025: + bclk = WM8753_BCLK_DIV_16; + pll_out = 11289600; + break; + case 22050: + bclk = WM8753_BCLK_DIV_8; + pll_out = 11289600; + break; + case 44100: + bclk = WM8753_BCLK_DIV_4; + pll_out = 11289600; + break; + case 88200: + bclk = WM8753_BCLK_DIV_2; + pll_out = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.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 = cpu_dai->dai_ops.set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_MCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + S3C2410_IISMOD_32FS ); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(4,4)); + if (ret < 0) + return ret; + + /* codec PLL input is PCLK/4 */ + ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, iis_clkrate/4, + pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_hifi_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; + + /* disable the PLL */ + return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, 0, 0); +} + +/* + * Neo1973 WM8753 HiFi DAI opserations. + */ +static struct snd_soc_ops neo1973_hifi_ops = { + .hw_params = neo1973_hifi_hw_params, + .hw_free = neo1973_hifi_hw_free, +}; + +static int neo1973_voice_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; + unsigned int pcmdiv = 0; + int ret = 0; + unsigned long iis_clkrate; + + /* todo: gg where is sysclk coming from for voice ?? */ + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + if (params_rate(params) != 8000) + return -EINVAL; + if(params_channels(params) != 1) + return -EINVAL; + + pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ + + /* todo: gg check mode (DSP_B) against CSR datasheet */ + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_PCMCLK, 12288000, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec PCM division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv); + if (ret < 0) + return ret; + + /* configue and enable PLL for 12.288MHz output */ + ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, iis_clkrate/4, + 12288000); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_voice_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; + + /* disable the PLL */ + return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, 0, 0); +} + +static struct snd_soc_ops neo1973_voice_ops = { + .hw_params = neo1973_voice_hw_params, + .hw_free = neo1973_voice_hw_free, +}; + +static int neo1973_scenario = 0; + +static int neo1973_get_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = neo1973_scenario; + return 0; +} + +static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario) +{ + switch(neo1973_scenario) { + case NEO_AUDIO_OFF: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_GSM_CALL_AUDIO_HANDSET: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 1); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 1); + break; + case NEO_GSM_CALL_AUDIO_HEADSET: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 1); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 1); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_GSM_CALL_AUDIO_BLUETOOTH: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 1); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_STEREO_TO_SPEAKERS: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_STEREO_TO_HEADPHONES: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_CAPTURE_HANDSET: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 1); + break; + case NEO_CAPTURE_HEADSET: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 1); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + case NEO_CAPTURE_BLUETOOTH: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + break; + default: + snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); + snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + } + + snd_soc_dapm_sync_endpoints(codec); + + return 0; +} + +static int neo1973_set_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (neo1973_scenario == ucontrol->value.integer.value[0]) + return 0; + + neo1973_scenario = ucontrol->value.integer.value[0]; + + set_scenario_endpoints(codec, neo1973_scenario); + + return 1; +} + +static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0}; + +static void lm4857_write_regs( void ) +{ + if( i2c_master_send(i2c, lm4857_regs, 4) != 4) + printk(KERN_WARNING "lm4857: i2c write failed\n"); +} + +static int lm4857_get_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg=kcontrol->private_value & 0xFF; + int shift = (kcontrol->private_value >> 8) & 0x0F; + int mask = (kcontrol->private_value >> 16) & 0xFF; + + ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask; + + return 0; +} + +static int lm4857_set_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg = kcontrol->private_value & 0xFF; + int shift = (kcontrol->private_value >> 8) & 0x0F; + int mask = (kcontrol->private_value >> 16) & 0xFF; + + if (((lm4857_regs[reg] >> shift ) & mask) == + ucontrol->value.integer.value[0]) + return 0; + + lm4857_regs[reg] &= ~ (mask << shift); + lm4857_regs[reg] |= ucontrol->value.integer.value[0] << shift; + + lm4857_write_regs(); + return 1; +} + +static int lm4857_get_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = lm4857_regs[LM4857_CTRL] & 0x0F; + + if (value) + value -= 5; + + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int lm4857_set_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = ucontrol->value.integer.value[0]; + + if (value) + value += 5; + + if ((lm4857_regs[LM4857_CTRL] & 0x0F) == value) + return 0; + + lm4857_regs[LM4857_CTRL] &= 0xF0; + lm4857_regs[LM4857_CTRL] |= value; + + lm4857_write_regs(); + return 1; +} + +static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Audio Out", NULL), + SND_SOC_DAPM_LINE("GSM Line Out", NULL), + SND_SOC_DAPM_LINE("GSM Line In", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Call Mic", NULL), +}; + + +/* example machine audio_mapnections */ +static const char* audio_map[][3] = { + + /* Connections to the lm4857 amp */ + {"Audio Out", NULL, "LOUT1"}, + {"Audio Out", NULL, "ROUT1"}, + + /* Connections to the GSM Module */ + {"GSM Line Out", NULL, "MONO1"}, + {"GSM Line Out", NULL, "MONO2"}, + {"RXP", NULL, "GSM Line In"}, + {"RXN", NULL, "GSM Line In"}, + + /* Connections to Headset */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Mic"}, + + /* Call Mic */ + {"MIC2", NULL, "Mic Bias"}, + {"MIC2N", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Call Mic"}, + + /* Connect the ALC pins */ + {"ACIN", NULL, "ACOP"}, + + {NULL, NULL, NULL}, +}; + +static const char *lm4857_mode[] = { + "Off", + "Call Speaker", + "Stereo Speakers", + "Stereo Speakers + Headphones", + "Headphones" +}; + +static const struct soc_enum lm4857_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(5, lm4857_mode), +}; + +static const char *neo_scenarios[] = { + "Off", + "GSM Handset", + "GSM Headset", + "GSM Bluetooth", + "Speakers", + "Headphones", + "Capture Handset", + "Capture Headset", + "Capture Bluetooth" +}; + +static const struct soc_enum neo_scenario_enum[] = { + SOC_ENUM_SINGLE_EXT(9,neo_scenarios), +}; + +static const struct snd_kcontrol_new wm8753_neo1973_controls[] = { + SOC_SINGLE_EXT("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Mono Playback Volume", LM4857_MVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_ENUM_EXT("Amp Mode", lm4857_mode_enum[0], + lm4857_get_mode, lm4857_set_mode), + SOC_ENUM_EXT("Neo Mode", neo_scenario_enum[0], + neo1973_get_scenario, neo1973_set_scenario), + SOC_SINGLE_EXT("Amp Spk 3D Playback Switch", LM4857_LVOL, 5, 1, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp HP 3d Playback Switch", LM4857_RVOL, 5, 1, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Fast Wakeup Playback Switch", LM4857_CTRL, 5, 1, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Earpiece 6dB Playback Switch", LM4857_CTRL, 4, 1, 0, + lm4857_get_reg, lm4857_set_reg), +}; + +/* + * This is an example machine initialisation for a wm8753 connected to a + * neo1973 II. It is missing logic to detect hp/mic insertions and logic + * to re-route the audio in such an event. + */ +static int neo1973_wm8753_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* set up NC codec pins */ + snd_soc_dapm_set_endpoint(codec, "LOUT2", 0); + snd_soc_dapm_set_endpoint(codec, "ROUT2", 0); + snd_soc_dapm_set_endpoint(codec, "OUT3", 0); + snd_soc_dapm_set_endpoint(codec, "OUT4", 0); + snd_soc_dapm_set_endpoint(codec, "LINE1", 0); + snd_soc_dapm_set_endpoint(codec, "LINE2", 0); + + + /* set endpoints to default mode */ + set_scenario_endpoints(codec, NEO_AUDIO_OFF); + + /* Add neo1973 specific widgets */ + for(i = 0; i < ARRAY_SIZE(wm8753_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &wm8753_dapm_widgets[i]); + + /* add neo1973 specific controls */ + for (i = 0; i < ARRAY_SIZE(wm8753_neo1973_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8753_neo1973_controls[i],codec, NULL))) < 0) + return err; + } + + /* set up neo1973 specific audio path audio_mapnects */ + 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_sync_endpoints(codec); + return 0; +} + +/* + * BT Codec DAI + */ +static struct snd_soc_cpu_dai bt_dai = +{ .name = "Bluetooth", + .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,}, +}; + +static struct snd_soc_dai_link neo1973_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .cpu_dai = &s3c24xx_i2s_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_HIFI], + .init = neo1973_wm8753_init, + .ops = &neo1973_hifi_ops, +}, +{ /* Voice via BT */ + .name = "Bluetooth", + .stream_name = "Voice", + .cpu_dai = &bt_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_VOICE], + .ops = &neo1973_voice_ops, +}, +}; + +static struct snd_soc_machine neo1973 = { + .name = "neo1973", + .dai_link = neo1973_dai, + .num_links = ARRAY_SIZE(neo1973_dai), +}; + +static struct wm8753_setup_data neo1973_wm8753_setup = { + .i2c_address = 0x1a, +}; + +static struct snd_soc_device neo1973_snd_devdata = { + .machine = &neo1973, + .platform = &s3c24xx_soc_platform, + .codec_dev = &soc_codec_dev_wm8753, + .codec_data = &neo1973_wm8753_setup, +}; + +static struct i2c_client client_template; + +static unsigned short normal_i2c[] = { 0x7C, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static int lm4857_amp_probe(struct i2c_adapter *adap, int addr, int kind) +{ + int ret; + + client_template.adapter = adap; + client_template.addr = addr; + + DBG("Entering %s\n", __FUNCTION__); + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL){ + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + + ret = i2c_attach_client(i2c); + if (ret < 0) { + DBG("failed to attach codec at addr %x\n", addr); + goto exit_err; + } + + lm4857_write_regs(); + + return ret; + +exit_err: + kfree(i2c); + return ret; +} + +static int lm4857_i2c_detach(struct i2c_client *client) +{ + i2c_detach_client(client); + kfree(client); + return 0; +} + +static int lm4857_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, lm4857_amp_probe); +} + +#define I2C_DRIVERID_LM4857 0xA5A5 /* liam - need a proper id */ + +/* corgi i2c codec control layer */ +static struct i2c_driver lm4857_i2c_driver = { + .driver = { + .name = "LM4857 I2C Amp", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_LM4857, + .attach_adapter = lm4857_i2c_attach, + .detach_client = lm4857_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "LM4857", + .driver = &lm4857_i2c_driver, +}; + +static struct platform_device *neo1973_snd_device; + +static int __init neo1973_init(void) +{ + int ret; + + neo1973_snd_device = platform_device_alloc("soc-audio", -1); + if (!neo1973_snd_device) + return -ENOMEM; + + platform_set_drvdata(neo1973_snd_device, &neo1973_snd_devdata); + neo1973_snd_devdata.dev = &neo1973_snd_device->dev; + ret = platform_device_add(neo1973_snd_device); + + if (ret) + platform_device_put(neo1973_snd_device); + + ret = i2c_add_driver(&lm4857_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + + return ret; +} + +static void __exit neo1973_exit(void) +{ + platform_device_unregister(neo1973_snd_device); +} + +module_init(neo1973_init); +module_exit(neo1973_exit); + +/* Module information */ +MODULE_AUTHOR("Graeme Gregory, graeme.gregory@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973"); +MODULE_LICENSE("GPL"); Index: linux-2.6.21-moko/sound/soc/Kconfig =================================================================== --- linux-2.6.21-moko.orig/sound/soc/Kconfig +++ linux-2.6.21-moko/sound/soc/Kconfig @@ -25,7 +25,9 @@ menu "SoC Platforms" depends on SND_SOC source "sound/soc/at91/Kconfig" +source "sound/soc/imx/Kconfig" source "sound/soc/pxa/Kconfig" +source "sound/soc/s3c24xx/Kconfig" endmenu # Supported codecs Index: linux-2.6.21-moko/sound/soc/Makefile =================================================================== --- linux-2.6.21-moko.orig/sound/soc/Makefile +++ linux-2.6.21-moko/sound/soc/Makefile @@ -1,4 +1,4 @@ snd-soc-core-objs := soc-core.o soc-dapm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o -obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ +obj-$(CONFIG_SND_SOC) += codecs/ at91/ imx/ pxa/ s3c24xx/ Index: linux-2.6.21-moko/sound/soc/codecs/Kconfig =================================================================== --- linux-2.6.21-moko.orig/sound/soc/codecs/Kconfig +++ linux-2.6.21-moko/sound/soc/codecs/Kconfig @@ -2,6 +2,14 @@ tristate depends on SND_SOC +config SND_SOC_WM8510 + tristate + depends on SND_SOC + +config SND_SOC_WM8711 + tristate + depends on SND_SOC + config SND_SOC_WM8731 tristate depends on SND_SOC @@ -10,6 +18,46 @@ tristate depends on SND_SOC +config SND_SOC_WM8753 + tristate + depends on SND_SOC + +config SND_SOC_WM8772 + tristate + depends on SND_SOC + +config SND_SOC_WM8956 + tristate + depends on SND_SOC + +config SND_SOC_WM8960 + tristate + depends on SND_SOC + +config SND_SOC_WM8971 + tristate + depends on SND_SOC + +config SND_SOC_WM8974 + tristate + depends on SND_SOC + +config SND_SOC_WM8976 + tristate + depends on SND_SOC + +config SND_SOC_WM8980 + tristate + depends on SND_SOC + +config SND_SOC_UDA1380 + tristate + depends on SND_SOC + config SND_SOC_WM9712 tristate depends on SND_SOC + +config SND_SOC_WM9713 + tristate + depends on SND_SOC Index: linux-2.6.21-moko/sound/soc/codecs/Makefile =================================================================== --- linux-2.6.21-moko.orig/sound/soc/codecs/Makefile +++ linux-2.6.21-moko/sound/soc/codecs/Makefile @@ -1,9 +1,33 @@ snd-soc-ac97-objs := ac97.o +snd-soc-wm8711-objs := wm8711.o +snd-soc-wm8510-objs := wm8510.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o +snd-soc-wm8753-objs := wm8753.o +snd-soc-wm8772-objs := wm8772.o +snd-soc-wm8956-objs := wm8956.o +snd-soc-wm8960-objs := wm8960.o +snd-soc-wm8971-objs := wm8971.o +snd-soc-wm8974-objs := wm8974.o +snd-soc-wm8976-objs := wm8976.o +snd-soc-wm8980-objs := wm8980.o +snd-soc-uda1380-objs := uda1380.o snd-soc-wm9712-objs := wm9712.o +snd-soc-wm9713-objs := wm9713.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o +obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o +obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o +obj-$(CONFIG_SND_SOC_WM8772) += snd-soc-wm8772.o +obj-$(CONFIG_SND_SOC_WM8956) += snd-soc-wm8956.o +obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o +obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o +obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o +obj-$(CONFIG_SND_SOC_WM8976) += snd-soc-wm8976.o +obj-$(CONFIG_SND_SOC_WM8980) += snd-soc-wm8980.o +obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o +obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o