/* * 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 "../jz4740/jz4740-pcm.h" #include "jzcodec.h" #define AUDIO_NAME "jzcodec" #define JZCODEC_VERSION "1.0" /* * Debug */ #define JZCODEC_DEBUG 0 #ifdef JZCODEC_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_jzcodec; /* codec private data */ struct jzcodec_priv { unsigned int sysclk; }; /* * jzcodec register cache */ static u32 jzcodec_reg[JZCODEC_CACHEREGNUM / 2]; /* * codec register is 16 bits width in ALSA, so we define array to store 16 bits configure paras */ static u16 jzcodec_reg_LH[JZCODEC_CACHEREGNUM]; /* * read jzcodec register cache */ static inline unsigned int jzcodec_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) { u16 *cache = codec->reg_cache; if (reg >= JZCODEC_CACHEREGNUM) return -1; return cache[reg]; } /* * write jzcodec register cache */ static inline void jzcodec_write_reg_cache(struct snd_soc_codec *codec, unsigned int reg, u16 value) { u16 *cache = codec->reg_cache; u32 reg_val; if (reg >= JZCODEC_CACHEREGNUM) { return; } cache[reg] = value; /* update internal codec register value */ switch (reg) { case 0: case 1: reg_val = cache[0] & 0xffff; reg_val = reg_val | (cache[1] << 16); jzcodec_reg[0] = reg_val; break; case 2: case 3: reg_val = cache[2] & 0xffff; reg_val = reg_val | (cache[3] << 16); jzcodec_reg[1] = reg_val; break; } } /* * write to the jzcodec register space */ static int jzcodec_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { jzcodec_write_reg_cache(codec, reg, value); if(codec->hw_write) codec->hw_write(&value, NULL, reg); return 0; } static int jzcodec_reset(struct snd_soc_codec *codec) { u16 val; val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW); val = val | 0x1; jzcodec_write(codec, ICODEC_1_LOW, val); mdelay(1); val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW); val = val & ~0x1; jzcodec_write(codec, ICODEC_1_LOW, val); mdelay(1); return 0; } static const struct snd_kcontrol_new jzcodec_snd_controls[] = { //SOC_DOUBLE_R("Master Playback Volume", 1, 1, 0, 3, 0), SOC_DOUBLE_R("Master Playback Volume", ICODEC_2_LOW, ICODEC_2_LOW, 0, 3, 0), //SOC_DOUBLE_R("MICBG", ICODEC_2_LOW, ICODEC_2_LOW, 4, 3, 0), //SOC_DOUBLE_R("Line", 2, 2, 0, 31, 0), SOC_DOUBLE_R("Line", ICODEC_2_HIGH, ICODEC_2_HIGH, 0, 31, 0), }; /* add non dapm controls */ static int jzcodec_add_controls(struct snd_soc_codec *codec) { int err, i; for (i = 0; i < ARRAY_SIZE(jzcodec_snd_controls); i++) { if ((err = snd_ctl_add(codec->card, snd_soc_cnew(&jzcodec_snd_controls[i], codec, NULL))) < 0) return err; } return 0; } static const struct snd_soc_dapm_widget jzcodec_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("LOUT"), SND_SOC_DAPM_OUTPUT("LHPOUT"), SND_SOC_DAPM_OUTPUT("ROUT"), SND_SOC_DAPM_OUTPUT("RHPOUT"), SND_SOC_DAPM_INPUT("MICIN"), SND_SOC_DAPM_INPUT("RLINEIN"), SND_SOC_DAPM_INPUT("LLINEIN"), }; static const char *intercon[][3] = { /* output mixer */ {"Output Mixer", "Line Bypass Switch", "Line Input"}, {"Output Mixer", "HiFi Playback Switch", "DAC"}, {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, /* outputs */ {"RHPOUT", NULL, "Output Mixer"}, {"ROUT", NULL, "Output Mixer"}, {"LHPOUT", NULL, "Output Mixer"}, {"LOUT", NULL, "Output Mixer"}, /* input mux */ {"Input Mux", "Line In", "Line Input"}, {"Input Mux", "Mic", "Mic Bias"}, {"ADC", NULL, "Input Mux"}, /* inputs */ {"Line Input", NULL, "LLINEIN"}, {"Line Input", NULL, "RLINEIN"}, {"Mic Bias", NULL, "MICIN"}, /* terminator */ {NULL, NULL, NULL}, }; static int jzcodec_add_widgets(struct snd_soc_codec *codec) { int i,cnt; cnt = ARRAY_SIZE(jzcodec_dapm_widgets); for(i = 0; i < ARRAY_SIZE(jzcodec_dapm_widgets); i++) { snd_soc_dapm_new_control(codec, &jzcodec_dapm_widgets[i]); } #if 1 /* 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]); } #endif snd_soc_dapm_new_widgets(codec); return 0; } static int jzcodec_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_val = jzcodec_read_reg_cache(codec, ICODEC_2_LOW); #if 0 /* bit size. codec side */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: break; } #endif /* sample rate */ reg_val = reg_val & ~(0xf << 8); switch (params_rate(params)) { case 8000: reg_val |= (0x0 << 8); break; case 11025: reg_val |= (0x1 << 8); break; case 12000: reg_val |= (0x2 << 8); break; case 16000: reg_val |= (0x3 << 8); break; case 22050: reg_val |= (0x4 << 8); break; case 24000: reg_val |= (0x5 << 8); break; case 32000: reg_val |= (0x6 << 8); break; case 44100: reg_val |= (0x7 << 8); break; case 48000: reg_val |= (0x8 << 8); break; default: printk(" invalid rate :0x%08x\n",params_rate(params)); } jzcodec_write(codec, ICODEC_2_LOW, reg_val); return 0; } static int jzcodec_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { int ret = 0; u16 val; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->codec; switch (cmd) { case SNDRV_PCM_TRIGGER_START: //case SNDRV_PCM_TRIGGER_RESUME: //case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { val = 0x7302; jzcodec_write(codec, ICODEC_1_LOW, val); val = 0x0003; jzcodec_write(codec, ICODEC_1_HIGH, val); mdelay(2); val = 0x6000; jzcodec_write(codec, ICODEC_1_LOW, val); val = 0x0300; jzcodec_write(codec, ICODEC_1_HIGH, val); mdelay(2); val = 0x2000; jzcodec_write(codec, ICODEC_1_LOW, val); val = 0x0300; jzcodec_write(codec, ICODEC_1_HIGH, val); } else { val = 0x4300; jzcodec_write(codec, ICODEC_1_LOW, val); val = 0x1402; jzcodec_write(codec, ICODEC_1_HIGH, val); } break; case SNDRV_PCM_TRIGGER_STOP: //case SNDRV_PCM_TRIGGER_SUSPEND: //case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { val = 0x3300; jzcodec_write(codec, ICODEC_1_LOW, val); val = 0x0003; jzcodec_write(codec, ICODEC_1_HIGH, val); } else { val = 0x3300; jzcodec_write(codec, ICODEC_1_LOW, val); val = 0x0003; jzcodec_write(codec, ICODEC_1_HIGH, val); } break; default: ret = -EINVAL; } return ret; } static int jzcodec_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; */ return 0; } static void jzcodec_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); } } static int jzcodec_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u16 reg_val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW); if (mute != 0) mute = 1; if (mute) reg_val = reg_val | (0x1 << 14); else reg_val = reg_val & ~(0x1 << 14); jzcodec_write(codec, ICODEC_1_LOW, reg_val); return 0; } static int jzcodec_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; struct jzcodec_priv *jzcodec = codec->private_data; jzcodec->sysclk = freq; return 0; } /* * Set's ADC and Voice DAC format. called by pavo_hw_params() in pavo.c */ static int jzcodec_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; /* set master/slave audio interface. codec side */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: /* set master mode for codec */ break; case SND_SOC_DAIFMT_CBS_CFS: /* set slave mode for codec */ break; default: return -EINVAL; } /* interface format . set some parameter for codec side */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: /* set I2S mode for codec */ break; case SND_SOC_DAIFMT_RIGHT_J: /* set right J mode */ break; case SND_SOC_DAIFMT_LEFT_J: /* set left J mode */ break; case SND_SOC_DAIFMT_DSP_A: /* set dsp A mode */ break; case SND_SOC_DAIFMT_DSP_B: /* set dsp B mode */ break; default: return -EINVAL; } /* clock inversion. codec side */ switch (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; default: return -EINVAL; } /* jzcodec_write(codec, 0, val); */ return 0; } #if 1 static int jzcodec_dapm_event(struct snd_soc_codec *codec, int event) { /* u16 reg_val; */ switch (event) { case SNDRV_CTL_POWER_D0: /* full On */ /* vref/mid, osc on, dac unmute */ /* u16 reg_val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW); */ /* jzcodec_write(codec, 0, val); */ 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, */ /*reg_val = 0x0800; jzcodec_write_reg_cache(codec, ICODEC_1_LOW, reg_val); reg_val = 0x0017; jzcodec_write_reg_cache(codec, ICODEC_1_HIGH, reg_val); REG_ICDC_CDCCR1 = jzcodec_reg[0]; mdelay(2); reg_val = 0x2102; jzcodec_write_reg_cache(codec, ICODEC_1_LOW, reg_val); reg_val = 0x001f; jzcodec_write_reg_cache(codec, ICODEC_1_HIGH, reg_val); REG_ICDC_CDCCR1 = jzcodec_reg[0]; mdelay(2); reg_val = 0x3302; jzcodec_write_reg_cache(codec, ICODEC_1_LOW, reg_val); reg_val = 0x0003; jzcodec_write_reg_cache(codec, ICODEC_1_HIGH, reg_val); REG_ICDC_CDCCR1 = jzcodec_reg[0];*/ break; case SNDRV_CTL_POWER_D3cold: /* Off, without power */ /* everything off, dac mute, inactive */ /*reg_val = 0x2302; jzcodec_write(codec, ICODEC_1_LOW, reg_val); reg_val = 0x001b; jzcodec_write(codec, ICODEC_1_HIGH, reg_val); mdelay(1); reg_val = 0x2102; jzcodec_write(codec, ICODEC_1_LOW, reg_val); reg_val = 0x001b; jzcodec_write(codec, ICODEC_1_HIGH, reg_val);*/ break; } //codec->dapm_state = event; return 0; } #endif #define JZCODEC_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 JZCODEC_FORMATS (SNDRV_PCM_FORMAT_S8 | SNDRV_PCM_FMTBIT_S16_LE) struct snd_soc_dai jzcodec_dai = { .name = "JZCODEC", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = JZCODEC_RATES, .formats = JZCODEC_FORMATS,}, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = JZCODEC_RATES, .formats = JZCODEC_FORMATS,}, .ops = { .trigger = jzcodec_pcm_trigger, .prepare = jzcodec_pcm_prepare, .hw_params = jzcodec_hw_params, .shutdown = jzcodec_shutdown, }, .dai_ops = { .digital_mute = jzcodec_mute, .set_sysclk = jzcodec_set_dai_sysclk, .set_fmt = jzcodec_set_dai_fmt, } }; EXPORT_SYMBOL_GPL(jzcodec_dai); #ifdef CONFIG_PM static u16 jzcodec_reg_pm[JZCODEC_CACHEREGNUM]; static int jzcodec_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; jzcodec_reg_pm[ICODEC_1_LOW] = jzcodec_read_reg_cache(codec, ICODEC_1_LOW); jzcodec_reg_pm[ICODEC_1_HIGH] = jzcodec_read_reg_cache(codec, ICODEC_1_HIGH); jzcodec_reg_pm[ICODEC_2_LOW] = jzcodec_read_reg_cache(codec, ICODEC_2_LOW); jzcodec_reg_pm[ICODEC_2_HIGH] = jzcodec_read_reg_cache(codec, ICODEC_2_HIGH); jzcodec_dapm_event(codec, SNDRV_CTL_POWER_D3cold); return 0; } static int jzcodec_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; u16 reg_val; jzcodec_dapm_event(codec, SNDRV_CTL_POWER_D3hot); reg_val = jzcodec_reg_pm[ICODEC_1_LOW]; jzcodec_write(codec, ICODEC_1_LOW, reg_val); reg_val = jzcodec_reg_pm[ICODEC_1_HIGH]; jzcodec_write(codec, ICODEC_1_HIGH, reg_val); reg_val = jzcodec_reg_pm[ICODEC_2_LOW]; jzcodec_write(codec, ICODEC_2_LOW, reg_val); reg_val = jzcodec_reg_pm[ICODEC_2_HIGH]; jzcodec_write(codec, ICODEC_2_HIGH, reg_val); jzcodec_dapm_event(codec, codec->suspend_dapm_state); return 0; } #else #define jzcodec_suspend NULL #define jzcodec_resume NULL #endif /* * initialise the JZCODEC driver * register the mixer and dsp interfaces with the kernel */ static int jzcodec_init(struct snd_soc_device *socdev) { struct snd_soc_codec *codec = socdev->codec; int reg, ret = 0; u16 reg_val; for (reg = 0; reg < JZCODEC_CACHEREGNUM / 2; reg++) { switch (reg) { case 0: jzcodec_reg[reg] = REG_ICDC_CDCCR1; jzcodec_reg_LH[ICODEC_1_LOW] = jzcodec_reg[reg] & 0xffff; jzcodec_reg_LH[ICODEC_1_HIGH] = (jzcodec_reg[reg] & 0xffff0000) >> 16; break; case 1: jzcodec_reg[reg] = REG_ICDC_CDCCR2; jzcodec_reg_LH[ICODEC_2_LOW] = jzcodec_reg[reg] & 0xffff; jzcodec_reg_LH[ICODEC_2_HIGH] = (jzcodec_reg[reg] & 0xffff0000) >> 16; break; } } codec->name = "JZCODEC"; codec->owner = THIS_MODULE; codec->read = jzcodec_read_reg_cache; codec->write = jzcodec_write; //codec->dapm_event = jzcodec_dapm_event; codec->dai = &jzcodec_dai; codec->num_dai = 1; codec->reg_cache_size = sizeof(jzcodec_reg_LH); codec->reg_cache = kmemdup(jzcodec_reg_LH, sizeof(jzcodec_reg_LH), GFP_KERNEL); if (codec->reg_cache == NULL) return -ENOMEM; jzcodec_reset(codec); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { printk(KERN_ERR "jzcodec: failed to create pcms\n"); goto pcm_err; } /* power on device */ jzcodec_dapm_event(codec, SNDRV_CTL_POWER_D3hot); /* clear suspend bit of jz4740 internal codec */ reg_val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW); reg_val = reg_val & ~(0x2); jzcodec_write(codec, ICODEC_1_LOW, reg_val); /* set vol bits */ reg_val = jzcodec_read_reg_cache(codec, ICODEC_2_LOW); reg_val = reg_val | 0x3; jzcodec_write(codec, ICODEC_2_LOW, reg_val); /* set line in capture gain bits */ reg_val = jzcodec_read_reg_cache(codec, ICODEC_2_HIGH); reg_val = reg_val | 0x1f; jzcodec_write(codec, ICODEC_2_HIGH, reg_val); /* set mic boost gain bits */ reg_val = jzcodec_read_reg_cache(codec, ICODEC_2_LOW); reg_val = reg_val | (0x3 << 4); jzcodec_write(codec, ICODEC_2_LOW, reg_val); mdelay(5); reg_val = 0x3300; jzcodec_write(codec, ICODEC_1_LOW, reg_val); reg_val = 0x0003; jzcodec_write(codec, ICODEC_1_HIGH, reg_val); jzcodec_add_controls(codec); jzcodec_add_widgets(codec); ret = snd_soc_register_card(socdev); if (ret < 0) { printk(KERN_ERR "jzcodec: 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 *jzcodec_socdev; static int write_codec_reg(u16 * add, char * name, int reg) { switch (reg) { case 0: case 1: REG_ICDC_CDCCR1 = jzcodec_reg[0]; break; case 2: case 3: REG_ICDC_CDCCR2 = jzcodec_reg[1]; break; } return 0; } static int jzcodec_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec; struct jzcodec_priv *jzcodec; int ret = 0; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) return -ENOMEM; jzcodec = kzalloc(sizeof(struct jzcodec_priv), GFP_KERNEL); if (jzcodec == NULL) { kfree(codec); return -ENOMEM; } codec->private_data = jzcodec; socdev->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); jzcodec_socdev = socdev; /* Add other interfaces here ,no I2C connection */ codec->hw_write = (hw_write_t)write_codec_reg; ret = jzcodec_init(jzcodec_socdev); if (ret < 0) { codec = jzcodec_socdev->codec; err("failed to initialise jzcodec\n"); kfree(codec); } return ret; } /* power down chip */ static int jzcodec_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) jzcodec_dapm_event(codec, SNDRV_CTL_POWER_D3cold); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); kfree(codec->private_data); kfree(codec); return 0; } struct snd_soc_codec_device soc_codec_dev_jzcodec = { .probe = jzcodec_probe, .remove = jzcodec_remove, .suspend = jzcodec_suspend, .resume = jzcodec_resume, }; EXPORT_SYMBOL_GPL(soc_codec_dev_jzcodec); MODULE_DESCRIPTION("ASoC JZCODEC driver"); MODULE_AUTHOR("Richard"); MODULE_LICENSE("GPL");