/* * acx00.c -- ACX00 ALSA Soc Audio Codec driver * * (C) Copyright 2010-2016 Allwinnertech Technology., Ltd. * * Author: Wolfgang Huang * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "acx00.h" #define ACX00_DEF_VOL 0x9F9F #undef ACX00_DAPM_LINEOUT struct acx00_priv { struct acx00 *acx00; /* parent mfd device struct */ struct snd_soc_codec *codec; struct clk *clk; unsigned int sample_rate; unsigned int fmt; unsigned int enable; unsigned int spk_gpio; bool spk_gpio_used; struct mutex mutex; struct delayed_work spk_work; struct delayed_work resume_work; }; struct sample_rate { unsigned int samplerate; unsigned int rate_bit; }; static const struct sample_rate sample_rate_conv[] = { {44100, 7}, {48000, 8}, {8000, 0}, {32000, 6}, {22050, 4}, {24000, 5}, {16000, 3}, {11025, 1}, {12000, 2}, {192000, 10}, {96000, 9}, }; void __iomem *io_stat_addr; static const DECLARE_TLV_DB_SCALE(i2s_mixer_adc_tlv, -600, 600, 1); static const DECLARE_TLV_DB_SCALE(i2s_mixer_dac_tlv, -600, 600, 1); static const DECLARE_TLV_DB_SCALE(dac_mixer_adc_tlv, -600, 600, 1); static const DECLARE_TLV_DB_SCALE(dac_mixer_dac_tlv, -600, 600, 1); static const DECLARE_TLV_DB_SCALE(line_out_tlv, -450, 150, 0); static const DECLARE_TLV_DB_SCALE(mic_out_tlv, -450, 150, 0); static const DECLARE_TLV_DB_SCALE(phoneout_tlv, -450, 150, 0); static const DECLARE_TLV_DB_SCALE(adc_input_tlv, -450, 150, 0); static const DECLARE_TLV_DB_SCALE(lineout_tlv, -4800, 150, 1); static const unsigned int mic_boost_tlv[] = { TLV_DB_RANGE_HEAD(2), 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0), }; static const struct snd_kcontrol_new acx00_codec_controls[] = { SOC_DOUBLE_TLV("I2S Mixer ADC Volume", AC_I2S_MIXER_GAIN, I2S_MIXERL_GAIN_ADC, I2S_MIXERR_GAIN_ADC, 0x1, 0, i2s_mixer_adc_tlv), SOC_DOUBLE_TLV("I2S Mixer DAC Volume", AC_I2S_MIXER_GAIN, I2S_MIXERR_GAIN_DAC, I2S_MIXERR_GAIN_DAC, 0x1, 0, i2s_mixer_dac_tlv), SOC_DOUBLE_TLV("DAC Mixer ADC Volume", AC_DAC_MIXER_GAIN, DAC_MIXERL_GAIN_ADC, DAC_MIXERR_GAIN_ADC, 0x1, 0, dac_mixer_adc_tlv), SOC_DOUBLE_TLV("DAC Mxier DAC Volume", AC_DAC_MIXER_GAIN, DAC_MIXERL_GAIN_DAC, DAC_MIXERR_GAIN_DAC, 0x1, 0, dac_mixer_dac_tlv), SOC_SINGLE_TLV("Line Out Mixer Volume", AC_OUT_MIXER_CTL, OUT_MIXER_LINE_VOL, 0x7, 0, line_out_tlv), SOC_DOUBLE_TLV("MIC Out Mixer Volume", AC_OUT_MIXER_CTL, OUT_MIXER_MIC1_VOL, OUT_MIXER_MIC2_VOL, 0x7, 0, mic_out_tlv), SOC_SINGLE_TLV("ADC Input Volume", AC_ADC_MIC_CTL, ADC_GAIN, 0x07, 0, adc_input_tlv), SOC_SINGLE_TLV("LINEOUT Volume", AC_LINEOUT_CTL, LINEOUT_VOL, 0x1f, 0, lineout_tlv), SOC_SINGLE_TLV("MIC1 Boost Volume", AC_ADC_MIC_CTL, MIC1_BOOST, 0x07, 0, mic_boost_tlv), SOC_SINGLE_TLV("MIC2 Boost Volume", AC_ADC_MIC_CTL, MIC2_BOOST, 0x07, 0, mic_boost_tlv), }; /* Enable I2S & DAC clk, then enable the DAC digital part */ static int acx00_playback_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); switch (event) { case SND_SOC_DAPM_POST_PMU: snd_soc_update_bits(codec, AC_SYS_CLK_CTL, (0x1<dapm); switch (event) { case SND_SOC_DAPM_POST_PMU: snd_soc_update_bits(codec, AC_SYS_CLK_CTL, (0x1<spk_gpio, 1); } static int acx00_lineout_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_POST_PMU: if (!priv->enable) { snd_soc_update_bits(priv->codec, AC_LINEOUT_CTL, (1<codec, AC_LINEOUT_CTL, (1<enable = 1; } #ifdef ACX00_DAPM_LINEOUT snd_soc_update_bits(codec, AC_LINEOUT_CTL, (1<spk_gpio_used) { if (spk_delay == 0) { gpio_set_value(priv->spk_gpio, 1); /* * time delay to wait spk pa work fine, * general setting 50ms */ mdelay(50); } else schedule_delayed_work(&priv->spk_work, msecs_to_jiffies(spk_delay)); } break; case SND_SOC_DAPM_PRE_PMD: mdelay(50); if (priv->spk_gpio_used) { gpio_set_value(priv->spk_gpio, 0); msleep(50); } #ifdef ACX00_DAPM_LINEOUT snd_soc_update_bits(codec, AC_LINEOUT_CTL, (1<acx00, 0x0010, 0x03); acx00_reg_write(priv->acx00, 0x0012, 0x01); /* enable the output & global enable bit */ snd_soc_update_bits(codec, AC_I2S_CTL, (1<spk_gpio_used) { snd_soc_update_bits(priv->codec, AC_LINEOUT_CTL, (1<codec, AC_LINEOUT_CTL, (1<enable = 1; } #ifndef ACX00_DAPM_LINEOUT snd_soc_update_bits(codec, AC_LINEOUT_CTL, (1<codec; int i; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: snd_soc_update_bits(codec, AC_I2S_FMT0, (7<dev, "unrecognized format support\n"); break; } for (i = 0; i < ARRAY_SIZE(sample_rate_conv); i++) { if (sample_rate_conv[i].samplerate == params_rate(params)) { snd_soc_update_bits(codec, AC_SYS_SR_CTL, (SYS_SR_MASK<codec; switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { /* codec clk & FRM master */ case SND_SOC_DAIFMT_CBM_CFM: snd_soc_update_bits(codec, AC_I2S_CLK, (0x1<dev, "format setting failed\n"); break; } switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: snd_soc_update_bits(codec, AC_I2S_FMT1, (0x1<dev, "invert clk setting failed\n"); return -EINVAL; } return 0; } static int acx00_codec_dai_set_clkdiv(struct snd_soc_dai *codec_dai, int clk_id, int clk_div) { struct acx00_priv *priv = snd_soc_dai_get_drvdata(codec_dai); struct snd_soc_codec *codec = priv->codec; unsigned int bclk_div; /* * when PCM mode, setting as 64fs, when I2S mode as 32fs, * then two channel, then just as 64fs */ unsigned int div_ratio = clk_div / 64; switch (div_ratio) { case 1: bclk_div = I2S_BCLK_DIV_1; break; case 2: bclk_div = I2S_BCLK_DIV_2; break; case 4: bclk_div = I2S_BCLK_DIV_3; break; case 6: bclk_div = I2S_BCLK_DIV_4; break; case 8: bclk_div = I2S_BCLK_DIV_5; break; case 12: bclk_div = I2S_BCLK_DIV_6; break; case 16: bclk_div = I2S_BCLK_DIV_7; break; case 24: bclk_div = I2S_BCLK_DIV_8; break; case 32: bclk_div = I2S_BCLK_DIV_9; break; case 48: bclk_div = I2S_BCLK_DIV_10; break; case 64: bclk_div = I2S_BCLK_DIV_11; break; case 96: bclk_div = I2S_BCLK_DIV_12; break; case 128: bclk_div = I2S_BCLK_DIV_13; break; case 176: bclk_div = I2S_BCLK_DIV_14; break; case 192: bclk_div = I2S_BCLK_DIV_15; break; default: dev_err(codec->dev, "setting blck div failed\n"); break; } snd_soc_update_bits(codec, AC_I2S_CLK, (I2S_BCLK_DIV_MASK<codec, AC_SYS_CLK_CTL, (0x1<codec, AC_SYS_MOD_RST, (0x1<stream == SNDRV_PCM_STREAM_PLAYBACK) { if (acx00_loop_en) snd_soc_update_bits(codec_dai->codec, AC_I2S_FMT0, (0x1<codec, AC_I2S_FMT0, (0x1<codec, 1); } else acx00_codec_rxctrl_enable(codec_dai->codec, 1); return 0; } static int acx00_codec_digital_mute(struct snd_soc_dai *codec_dai, int mute) { struct snd_soc_codec *codec = codec_dai->codec; if (mute) snd_soc_write(codec, AC_I2S_DAC_VOL, 0); else snd_soc_write(codec, AC_I2S_DAC_VOL, ACX00_DEF_VOL); return 0; } static void acx00_codec_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) acx00_codec_txctrl_enable(codec, 0); else acx00_codec_rxctrl_enable(codec, 0); } static const struct snd_soc_dai_ops acx00_codec_dai_ops = { .hw_params = acx00_codec_hw_params, .shutdown = acx00_codec_shutdown, .digital_mute = acx00_codec_digital_mute, .set_sysclk = acx00_codec_dai_set_sysclk, .set_fmt = acx00_codec_dai_set_fmt, .set_clkdiv = acx00_codec_dai_set_clkdiv, .startup = acx00_codec_startup, .trigger = acx00_codec_trigger, .prepare = acx00_codec_prepare, }; static struct snd_soc_dai_driver acx00_codec_dai[] = { { .name = "acx00-dai", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &acx00_codec_dai_ops, }, }; static void acx00_codec_resume_work(struct work_struct *work) { struct acx00_priv *priv = container_of(work, struct acx00_priv, resume_work.work); acx00_codec_init(priv->codec); } static int acx00_codec_probe(struct snd_soc_codec *codec) { struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); int ret = 0; mutex_init(&priv->mutex); priv->codec = codec; /* Add virtual switch */ ret = snd_soc_add_codec_controls(codec, acx00_codec_controls, ARRAY_SIZE(acx00_codec_controls)); if (ret) { pr_err("[audio-codec] Failed to register audio mode control, will continue without it.\n"); } snd_soc_dapm_new_controls(dapm, acx00_codec_dapm_widgets, ARRAY_SIZE(acx00_codec_dapm_widgets)); snd_soc_dapm_add_routes(dapm, acx00_codec_dapm_routes, ARRAY_SIZE(acx00_codec_dapm_routes)); /* using late_initcall to wait 120ms acx00-core to make chip reset */ acx00_codec_init(codec); INIT_DELAYED_WORK(&priv->spk_work, acx00_spk_enable); INIT_DELAYED_WORK(&priv->resume_work, acx00_codec_resume_work); return 0; } static int acx00_codec_remove(struct snd_soc_codec *codec) { struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); cancel_delayed_work_sync(&priv->spk_work); cancel_delayed_work_sync(&priv->resume_work); return 0; } static unsigned int acx00_codec_read(struct snd_soc_codec *codec, unsigned int reg) { unsigned int data; struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); /* Device I/O API */ data = acx00_reg_read(priv->acx00, reg); return data; } static int acx00_codec_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); return acx00_reg_write(priv->acx00, reg, value); } static int sunxi_gpio_iodisable(u32 gpio) { char pin_name[8]; u32 config, ret; sunxi_gpio_to_name(gpio, pin_name); config = 7 << 16; ret = pin_config_set(SUNXI_PINCTRL, pin_name, config); return ret; } static int acx00_codec_suspend(struct snd_soc_codec *codec) { struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); pr_debug("Enter %s\n", __func__); clk_disable_unprepare(priv->clk); /* PA_CTRL first setting low state, then make it iodisabled */ if (priv->spk_gpio_used) { sunxi_gpio_iodisable(priv->spk_gpio); msleep(30); } /* * when codec suspend, then the register reset, if auto reset produce * Pop & Click noise, then we should cut down the LINEOUT in this town. */ if (priv->enable) { snd_soc_update_bits(codec, AC_LINEOUT_CTL, (1<codec, AC_LINEOUT_CTL, (1<codec, AC_LINEOUT_CTL, (1<enable = 0; } pr_debug("Exit %s\n", __func__); return 0; } static int acx00_codec_resume(struct snd_soc_codec *codec) { struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); pr_debug("Enter %s\n", __func__); if (clk_prepare_enable(priv->clk)) { dev_err(codec->dev, "codec resume clk failed\n"); return -EBUSY; } schedule_delayed_work(&priv->resume_work, msecs_to_jiffies(300)); if (priv->spk_gpio_used) { gpio_direction_output(priv->spk_gpio, 1); gpio_set_value(priv->spk_gpio, 0); } pr_debug("Exit %s\n", __func__); return 0; } static int acx00_codec_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { codec->component.dapm.bias_level = level; return 0; } struct label { const char *name; int value; }; #define LABEL(constant) { #constant, constant } #define LABEL_END { NULL, -1 } static struct label reg_labels[] = { LABEL(AC_SYS_CLK_CTL), LABEL(AC_SYS_MOD_RST), LABEL(AC_SYS_SR_CTL), LABEL(AC_I2S_CTL), LABEL(AC_I2S_CLK), LABEL(AC_I2S_FMT0), LABEL(AC_I2S_FMT1), LABEL(AC_I2S_MIXER_SRC), LABEL(AC_I2S_MIXER_GAIN), LABEL(AC_I2S_DAC_VOL), LABEL(AC_I2S_ADC_VOL), LABEL(AC_DAC_CTL), LABEL(AC_DAC_MIXER_SRC), LABEL(AC_DAC_MIXER_GAIN), LABEL(AC_OUT_MIXER_CTL), LABEL(AC_OUT_MIXER_SRC), LABEL(AC_LINEOUT_CTL), LABEL(AC_ADC_CTL), LABEL(AC_MICBIAS_CTL), LABEL(AC_ADC_MIC_CTL), LABEL(AC_ADC_MIXER_SRC), LABEL(AC_BIAS_CTL), LABEL(AC_ANALOG_PROF_CTL), LABEL_END, }; static ssize_t show_audio_reg(struct device *dev, struct device_attribute *attr, char *buf) { struct acx00_priv *priv = dev_get_drvdata(dev); int count = 0, i = 0; unsigned int reg_val; count += sprintf(buf, "dump audio reg:\n"); while (reg_labels[i].name != NULL) { reg_val = acx00_reg_read(priv->acx00, reg_labels[i].value); count += sprintf(buf + count, "%s 0x%x: 0x%04x\n", reg_labels[i].name, (reg_labels[i].value), reg_val); i++; } return count; } /* * param 1: 0 read;1 write * param 2: 1 digital reg; 2 analog reg * param 3: reg value; * param 4: write value; * read: * echo 0,1,0x00> audio_reg * echo 0,2,0x00> audio_reg * write: * echo 1,1,0x00,0xa > audio_reg * echo 1,2,0x00,0xff > audio_reg */ static ssize_t store_audio_reg(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; int rw_flag; int input_reg_val = 0; int input_reg_group = 0; int input_reg_offset = 0; struct acx00_priv *priv = dev_get_drvdata(dev); ret = sscanf(buf, "%d,%d,0x%x,0x%x", &rw_flag, &input_reg_group, &input_reg_offset, &input_reg_val); dev_info(dev, "ret:%d, reg_group:%d, reg_offset:%d, reg_val:0x%x\n", ret, input_reg_group, input_reg_offset, input_reg_val); if (input_reg_group != 1) { pr_err("not exist reg group\n"); ret = count; goto out; } if (!(rw_flag == 1 || rw_flag == 0)) { pr_err("not rw_flag\n"); ret = count; goto out; } if (rw_flag) { acx00_reg_write(priv->acx00, input_reg_offset, input_reg_val); } else { input_reg_val = acx00_reg_read(priv->acx00, input_reg_offset); dev_info(dev, "\n\n Reg[0x%x] : 0x%04x\n\n", input_reg_offset, input_reg_val); } ret = count; out: return ret; } static DEVICE_ATTR(audio_reg, 0644, show_audio_reg, store_audio_reg); static struct attribute *audio_debug_attrs[] = { &dev_attr_audio_reg.attr, NULL, }; static struct attribute_group audio_debug_attr_group = { .name = "audio_reg_debug", .attrs = audio_debug_attrs, }; static struct snd_soc_codec_driver soc_codec_driver_acx00 = { .probe = acx00_codec_probe, .remove = acx00_codec_remove, .suspend = acx00_codec_suspend, .resume = acx00_codec_resume, .read = acx00_codec_read, .write = acx00_codec_write, .ignore_pmdown_time = 1, .set_bias_level = acx00_codec_set_bias_level, }; /* through acx00 is part of mfd devices, after the mfd */ static int acx00_codec_dev_probe(struct platform_device *pdev) { struct acx00_priv *priv; int ret; struct device_node *np = of_find_compatible_node(NULL, NULL, "allwinner,ac200_codec"); priv = devm_kzalloc(&pdev->dev, sizeof(struct acx00_priv), GFP_KERNEL); if (!priv) { dev_err(&pdev->dev, "acx00 codec priv mem alloc failed\n"); return -ENOMEM; } platform_set_drvdata(pdev, priv); priv->acx00 = dev_get_drvdata(pdev->dev.parent); if (np) { ret = of_get_named_gpio(np, "gpio-spk", 0); if (ret >= 0) { priv->spk_gpio_used = 1; priv->spk_gpio = ret; if (!gpio_is_valid(priv->spk_gpio)) { dev_err(&pdev->dev, "gpio-spk is valid\n"); ret = -EINVAL; goto err_devm_kfree; } else { ret = devm_gpio_request(&pdev->dev, priv->spk_gpio, "SPK"); if (ret) { dev_err(&pdev->dev, "failed request gpio-spk\n"); ret = -EBUSY; goto err_devm_kfree; } else { gpio_direction_output(priv->spk_gpio, 1); gpio_set_value(priv->spk_gpio, 0); } } } else { priv->spk_gpio_used = 0; } } ret = snd_soc_register_codec(&pdev->dev, &soc_codec_driver_acx00, acx00_codec_dai, ARRAY_SIZE(acx00_codec_dai)); if (ret < 0) dev_err(&pdev->dev, "Failed register acx00: %d\n", ret); ret = sysfs_create_group(&pdev->dev.kobj, &audio_debug_attr_group); if (ret) dev_warn(&pdev->dev, "failed to create attr group\n"); return 0; err_devm_kfree: devm_kfree(&pdev->dev, priv); return ret; } /* Mark this space to clear the LINEOUT & gpio */ static void acx00_codec_dev_shutdown(struct platform_device *pdev) { struct acx00_priv *priv = platform_get_drvdata(pdev); if (priv->spk_gpio_used) gpio_set_value(priv->spk_gpio, 0); } static int acx00_codec_dev_remove(struct platform_device *pdev) { struct acx00_priv *priv = platform_get_drvdata(pdev); #ifndef ACX00_DAPM_LINEOUT snd_soc_update_bits(priv->codec, AC_LINEOUT_CTL, (1<dev); clk_disable_unprepare(priv->clk); devm_kfree(&pdev->dev, priv); return 0; } static struct platform_driver acx00_codec_driver = { .driver = { .name = "acx00-codec", .owner = THIS_MODULE, }, .probe = acx00_codec_dev_probe, .remove = acx00_codec_dev_remove, .shutdown = acx00_codec_dev_shutdown, }; static int __init acx00_codec_driver_init(void) { return platform_driver_register(&acx00_codec_driver); } static void __exit acx00_codec_driver_exit(void) { platform_driver_unregister(&acx00_codec_driver); } late_initcall(acx00_codec_driver_init); module_exit(acx00_codec_driver_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("SUNXI ASoC ACX00 Codec Driver"); MODULE_AUTHOR("wolfgang huang"); MODULE_ALIAS("platform:acx00-codec");