From 08d8881e87cf80b9fe04361dde8cc7a592884499 Mon Sep 17 00:00:00 2001 From: afaulkner420 Date: Fri, 25 Mar 2022 20:33:02 +0000 Subject: [PATCH 144/153] allwinner: h6: Support ac200 audio codec --- drivers/mfd/Makefile | 2 +- drivers/mfd/{ac200.c => sunxi-ac200.c} | 16 +- include/linux/mfd/ac200.h | 2 + sound/soc/codecs/Kconfig | 8 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/acx00.c | 1371 ++++++++++++++++++++++++ sound/soc/codecs/acx00.h | 356 ++++++ 7 files changed, 1755 insertions(+), 2 deletions(-) rename drivers/mfd/{ac200.c => sunxi-ac200.c} (93%) create mode 100644 sound/soc/codecs/acx00.c create mode 100644 sound/soc/codecs/acx00.h diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b2e69fbce..0e4a09539 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -141,7 +141,7 @@ obj-$(CONFIG_MFD_DA9052_SPI) += da9052-spi.o obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o obj-$(CONFIG_MFD_AC100) += ac100.o -obj-$(CONFIG_MFD_AC200) += ac200.o +obj-$(CONFIG_MFD_AC200) += sunxi-ac200.o obj-$(CONFIG_MFD_AXP20X) += axp20x.o obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o diff --git a/drivers/mfd/ac200.c b/drivers/mfd/sunxi-ac200.c similarity index 93% rename from drivers/mfd/ac200.c rename to drivers/mfd/sunxi-ac200.c index 570573790..368a54587 100644 --- a/drivers/mfd/ac200.c +++ b/drivers/mfd/sunxi-ac200.c @@ -41,6 +41,7 @@ static const struct regmap_range_cfg ac200_range_cfg[] = { }; static const struct regmap_config ac200_regmap_config = { + .name = "ac200", .reg_bits = 8, .val_bits = 16, .ranges = ac200_range_cfg, @@ -75,6 +76,10 @@ static const struct mfd_cell ac200_cells[] = { .resources = ephy_resource, .of_compatible = "x-powers,ac200-ephy", }, + { + .name = "acx00-codec", + .of_compatible = "x-powers,ac200-codec", + }, }; static int ac200_i2c_probe(struct i2c_client *i2c, @@ -97,8 +102,17 @@ static int ac200_i2c_probe(struct i2c_client *i2c, return ret; } - /* do a reset to put chip in a known state */ + ac200->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ac200->clk)) { + dev_err(dev, "Can't obtain the clock!\n"); + return PTR_ERR(ac200->clk); + } + ret = clk_prepare_enable(ac200->clk); + if (ret) + return ret; + + /* do a reset to put chip in a known state */ ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0); if (ret) return ret; diff --git a/include/linux/mfd/ac200.h b/include/linux/mfd/ac200.h index 0c677094a..c8c140226 100644 --- a/include/linux/mfd/ac200.h +++ b/include/linux/mfd/ac200.h @@ -9,6 +9,7 @@ #define __LINUX_MFD_AC200_H #include +#include /* interface registers (can be accessed from any page) */ #define AC200_TWI_CHANGE_TO_RSB 0x3E @@ -201,6 +202,7 @@ #define AC200_IC_CHARA1 0xA1F2 struct ac200_dev { + struct clk *clk; struct regmap *regmap; struct regmap_irq_chip_data *regmap_irqc; }; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 944d0ea1a..359c515ef 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2170,4 +2170,12 @@ config SND_SOC_LPASS_TX_MACRO select SND_SOC_LPASS_MACRO_COMMON tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" +config SND_SOC_ACX00 + tristate "ACX00 Codec" + select MFD_ACX00 + default n + help + ACX00 now used as SUN50IW6 internal Codec, Connect Through I2S0. + Say Y or M if you want to add support internal audio codec. + endmenu diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index e2d463a31..ccb2a0fe4 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -347,6 +347,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-wsa881x-objs := wsa881x.o snd-soc-wsa883x-objs := wsa883x.o snd-soc-zl38060-objs := zl38060.o +snd-soc-acx00-objs := acx00.o # Amp snd-soc-max9877-objs := max9877.o snd-soc-max98504-objs := max98504.o @@ -709,6 +710,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o +obj-$(CONFIG_SND_SOC_ACX00) += snd-soc-acx00.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/acx00.c b/sound/soc/codecs/acx00.c new file mode 100644 index 000000000..ab7467e4e --- /dev/null +++ b/sound/soc/codecs/acx00.c @@ -0,0 +1,1371 @@ +/* + * 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 "acx00.h" + + +#define ACX00_DEF_VOL 0x9F9F +#undef ACX00_DAPM_LINEOUT + +struct acx00_priv { + struct ac200_dev *acx00; /* parent mfd device struct */ + struct snd_soc_component *component; + struct clk *clk; + unsigned int sample_rate; + unsigned int fmt; + unsigned int enable; + unsigned int spk_gpio; + unsigned int switch_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_MIXERL_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("Master 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_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, AC_SYS_CLK_CTL, + (0x1<dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, 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_component *component = snd_soc_dapm_to_component(w->dapm); + struct acx00_priv *priv = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (!priv->enable) { + snd_soc_component_update_bits(priv->component, AC_LINEOUT_CTL, + (1<component, AC_LINEOUT_CTL, + (1<enable = 1; + } +#ifdef ACX00_DAPM_LINEOUT + snd_soc_component_update_bits(component, 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_component_update_bits(component, AC_LINEOUT_CTL, + (1<regmap, reg, &val); + + if (ret < 0) + return ret; + else + return val; +} + +int acx00_reg_write(struct ac200_dev *acx00, unsigned short reg, unsigned short val) +{ + return regmap_write(acx00->regmap, reg, val); +} + +static void acx00_codec_init(struct snd_soc_component *component) +{ + struct acx00_priv *priv = snd_soc_component_get_drvdata(component); + + acx00_reg_write(priv->acx00, 0x50, 0x82b1); + acx00_reg_write(priv->acx00, 0xc, 0xce01); + + /* acx00_codec sysctl init */ + acx00_reg_write(priv->acx00, 0x0010, 0x03); + acx00_reg_write(priv->acx00, 0x0012, 0x01); + + /* The bit3 need to setup to 1 for bias current. */ + snd_soc_component_update_bits(component, AC_MICBIAS_CTL, + (0x1 << ADDA_BIAS_CUR), (0x1 << ADDA_BIAS_CUR)); + + /* enable the output & global enable bit */ + snd_soc_component_update_bits(component, AC_I2S_CTL, + (1<spk_gpio_used) { + snd_soc_component_update_bits(priv->component, AC_LINEOUT_CTL, + (1<component, AC_LINEOUT_CTL, + (1<enable = 1; + } +#ifndef ACX00_DAPM_LINEOUT + snd_soc_component_update_bits(component, AC_LINEOUT_CTL, (1<component; + int i; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_component_update_bits(component, 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_component_update_bits(component, AC_SYS_SR_CTL, + (SYS_SR_MASK<component; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + /* codec clk & FRM master */ + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_component_update_bits(component, 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_component_update_bits(component, 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_component *component = priv->component; + 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(component->dev, "setting blck div failed\n"); + break; + } + + snd_soc_component_update_bits(component, AC_I2S_CLK, + (I2S_BCLK_DIV_MASK<component; + return 0; +} + +static int acx00_codec_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_component *component = codec_dai->component; + + snd_soc_component_update_bits(component, AC_SYS_CLK_CTL, + (0x1<stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (acx00_loop_en) + snd_soc_component_update_bits(component, AC_I2S_FMT0, + (0x1<component; + + if (mute) + snd_soc_component_write(component, AC_I2S_DAC_VOL, 0); + else + snd_soc_component_write(component, 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_component *component = dai->component; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + acx00_codec_txctrl_enable(component, 0); + else + acx00_codec_rxctrl_enable(component, 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->component); +} + +static int acx00_codec_probe(struct snd_soc_component *component) +{ + struct acx00_priv *priv = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int ret = 0; + + mutex_init(&priv->mutex); + + priv->component = component; +#if 0 + /* Add virtual switch */ + ret = snd_soc_add_component_controls(component, 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)); +#endif + /* using late_initcall to wait 120ms acx00-core to make chip reset */ + acx00_codec_init(component); + INIT_DELAYED_WORK(&priv->spk_work, acx00_spk_enable); + INIT_DELAYED_WORK(&priv->resume_work, acx00_codec_resume_work); + return 0; +} + +static void acx00_codec_remove(struct snd_soc_component *component) +{ + struct acx00_priv *priv = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&priv->spk_work); + cancel_delayed_work_sync(&priv->resume_work); +} + +static unsigned int acx00_codec_read(struct snd_soc_component *component, + unsigned int reg) +{ + unsigned int data; + struct acx00_priv *priv = snd_soc_component_get_drvdata(component); + + /* Device I/O API */ + data = acx00_reg_read(priv->acx00, reg); + return data; +} + +static int acx00_codec_write(struct snd_soc_component *component, + unsigned int reg, unsigned int value) +{ + struct acx00_priv *priv = snd_soc_component_get_drvdata(component); + + return acx00_reg_write(priv->acx00, reg, value); +} + +static int sunxi_gpio_iodisable(u32 gpio) +{ + char pin_name[8]; + u32 config, ret; +#if 0 + sunxi_gpio_to_name(gpio, pin_name); + config = 7 << 16; + ret = pin_config_set(SUNXI_PINCTRL, pin_name, config); +#endif + return ret; +} + +static int acx00_codec_suspend(struct snd_soc_component *component) +{ + struct acx00_priv *priv = snd_soc_component_get_drvdata(component); + + 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_component_update_bits(component, AC_LINEOUT_CTL, + (1<component, AC_LINEOUT_CTL, + (1<component, AC_LINEOUT_CTL, + (1<enable = 0; + } + + pr_debug("Exit %s\n", __func__); + + return 0; +} + +static int acx00_codec_resume(struct snd_soc_component *component) +{ + struct acx00_priv *priv = snd_soc_component_get_drvdata(component); + + pr_debug("Enter %s\n", __func__); + + if (clk_prepare_enable(priv->clk)) { + dev_err(component->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_component *component, + enum snd_soc_bias_level level) +{ + 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%x\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; + unsigned int input_reg_val = 0; + int input_reg_group = 0; + unsigned 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_component_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, + .controls = acx00_codec_controls, + .num_controls = ARRAY_SIZE(acx00_codec_controls), + .dapm_widgets = acx00_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(acx00_codec_dapm_widgets), + .dapm_routes = acx00_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(acx00_codec_dapm_routes), +}; + +/* 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 = of_get_named_gpio(np, "gpio-switch", 0); + if (ret >= 0) { + priv->switch_gpio = ret; + if (!gpio_is_valid(priv->switch_gpio)) { + dev_err(&pdev->dev, "gpio-switch is valid\n"); + ret = -EINVAL; + goto err_devm_kfree; + } else { + ret = devm_gpio_request(&pdev->dev, priv->switch_gpio, "SWITCH"); + if (ret) { + dev_err(&pdev->dev, + "failed request gpio-switch\n"); + ret = -EBUSY; + goto err_devm_kfree; + } else { + gpio_direction_output(priv->switch_gpio, 1); + gpio_set_value(priv->switch_gpio, 1); + } + } + } + } + + ret = snd_soc_register_component(&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_component_update_bits(priv->component, AC_LINEOUT_CTL, + (1<dev); + clk_disable_unprepare(priv->clk); + devm_kfree(&pdev->dev, priv); + return 0; +} + +static const struct of_device_id acx00_codec_match[] = { + { .compatible = "x-powers,ac200-codec" }, + { } +}; +MODULE_DEVICE_TABLE(of, acx00_codec_match); + +static struct platform_driver acx00_codec_driver = { + .driver = { + .name = "acx00-codec", + .of_match_table = acx00_codec_match, + }, + .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"); diff --git a/sound/soc/codecs/acx00.h b/sound/soc/codecs/acx00.h new file mode 100644 index 000000000..5137cf365 --- /dev/null +++ b/sound/soc/codecs/acx00.h @@ -0,0 +1,356 @@ +/* + * sound\soc\codecs\acx00.h + * (C) Copyright 2012-2016 + * Allwinner Technology Co., Ltd. + * Wolfgang Huang + * + * some simple description for this code + * + * 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 __ACX00_H_ +#define __ACX00_H_ + +/* ACX00 register offset list */ +#define AC_SYS_CLK_CTL 0x2000 +#define AC_SYS_MOD_RST 0x2002 +#define AC_SYS_SR_CTL 0x2004 +/* Left blank */ +#define AC_I2S_CTL 0x2100 +#define AC_I2S_CLK 0x2102 +#define AC_I2S_FMT0 0x2104 +/* Left blank */ +#define AC_I2S_FMT1 0x2108 +/* Left blank */ +#define AC_I2S_MIXER_SRC 0x2114 +#define AC_I2S_MIXER_GAIN 0x2116 +#define AC_I2S_DAC_VOL 0x2118 +#define AC_I2S_ADC_VOL 0x211A +/* Left blank */ +#define AC_DAC_CTL 0x2200 +#define AC_DAC_MIXER_SRC 0x2202 +#define AC_DAC_MIXER_GAIN 0x2204 +/* Left blank */ +#define AC_OUT_MIXER_CTL 0x2220 +#define AC_OUT_MIXER_SRC 0x2222 +#define AC_LINEOUT_CTL 0x2224 +/* Left blank */ +#define AC_ADC_CTL 0x2300 +/* Left blank */ +#define AC_MICBIAS_CTL 0x2310 +/* Left blank */ +#define AC_ADC_MIC_CTL 0x2320 +#define AC_ADC_MIXER_SRC 0x2322 +/* Left blank */ +#define AC_BIAS_CTL 0x232A +#define AC_ANALOG_PROF_CTL 0x232C +/* Left blank */ +#define AC_ADC_DAPL_CTRL 0x2500 +#define AC_ADC_DAPR_CTRL 0x2502 +#define AC_ADC_DAPLSTA 0x2504 +#define AC_ADC_DAPRSTA 0x2506 +#define AC_ADC_DAP_LTL 0x2508 +#define AC_ADC_DAP_RTL 0x250A +#define AC_ADC_DAP_LHAC 0x250C +#define AC_ADC_DAP_LLAC 0x250E +#define AC_ADC_DAP_RHAC 0x2510 +#define AC_ADC_DAP_RLAC 0x2512 +#define AC_ADC_DAP_LDT 0x2514 +#define AC_ADC_DAP_LAT 0x2516 +#define AC_ADC_DAP_RDT 0x2518 +#define AC_ADC_DAP_RAT 0x251A +#define AC_ADC_DAP_NTH 0x251C +#define AC_ADC_DAP_LHNAC 0x251E +#define AC_ADC_DAP_LLNAC 0x2520 +#define AC_ADC_DAP_RHNAC 0x2522 +#define AC_ADC_DAP_RLNAC 0x2524 +#define AC_ADC_DAP_HHPFC 0x2526 +#define AC_ADC_DAP_LHPFC 0x2528 +#define AC_ADC_DAP_OPT 0x252A +/* Left blank */ +#define AC_AGC_SEL 0x2480 +/* Left blank */ +#define AC_ADC_DAPL_CTRL 0x2500 +#define AC_ADC_DAPR_CTRL 0x2502 +#define AC_ADC_DAPLSTA 0x2504 +#define AC_ADC_DAPRSTA 0x2506 +#define AC_ADC_DAP_LTL 0x2508 +#define AC_ADC_DAP_RTL 0x250A +#define AC_ADC_DAP_LHAC 0x250C +#define AC_ADC_DAP_LLAC 0x250E +#define AC_ADC_DAP_RHAC 0x2510 +#define AC_ADC_DAP_RLAC 0x2512 +#define AC_ADC_DAP_LDT 0x2514 +#define AC_ADC_DAP_LAT 0x2516 +#define AC_ADC_DAP_RDT 0x2518 +#define AC_ADC_DAP_RAT 0x251A +#define AC_ADC_DAP_NTH 0x251C +#define AC_ADC_DAP_LHNAC 0x251E +#define AC_ADC_DAP_LLNAC 0x2520 +#define AC_ADC_DAP_RHNAC 0x2522 +#define AC_ADC_DAP_RLNAC 0x2524 +#define AC_ADC_DAP_HHPFC 0x2526 +#define AC_ADC_DAP_LHPFC 0x2528 +#define AC_ADC_DAP_OPT 0x252A +/* Left blank */ +#define AC_DRC_SEL 0x2f80 +/* Left blank */ +#define AC_DRC_CHAN_CTRL 0x3000 +#define AC_DRC_HHPFC 0x3002 +#define AC_DRC_LHPFC 0x3004 +#define AC_DRC_CTRL 0x3006 +#define AC_DRC_LPFHAT 0x3008 +#define AC_DRC_LPFLAT 0x300A +#define AC_DRC_RPFHAT 0x300C +#define AC_DRC_RPFLAT 0x300E +#define AC_DRC_LPFHRT 0x3010 +#define AC_DRC_LPFLRT 0x3012 +#define AC_DRC_RPFHRT 0x3014 +#define AC_DRC_RPFLRT 0x3016 +#define AC_DRC_LRMSHAT 0x3018 +#define AC_DRC_LRMSLAT 0x301A +#define AC_DRC_RRMSHAT 0x301C +#define AC_DRC_RRMSLAT 0x301E +#define AC_DRC_HCT 0x3020 +#define AC_DRC_LCT 0x3022 +#define AC_DRC_HKC 0x3024 +#define AC_DRC_LKC 0x3026 +#define AC_DRC_HOPC 0x3028 +#define AC_DRC_LOPC 0x302A +#define AC_DRC_HLT 0x302C +#define AC_DRC_LLT 0x302E +#define AC_DRC_HKI 0x3030 +#define AC_DRC_LKI 0x3032 +#define AC_DRC_HOPL 0x3034 +#define AC_DRC_LOPL 0x3036 +#define AC_DRC_HET 0x3038 +#define AC_DRC_LET 0x303A +#define AC_DRC_HKE 0x303C +#define AC_DRC_LKE 0x303E +#define AC_DRC_HOPE 0x3040 +#define AC_DRC_LOPE 0x3042 +#define AC_DRC_HKN 0x3044 +#define AC_DRC_LKN 0x3046 +#define AC_DRC_SFHAT 0x3048 +#define AC_DRC_SFLAT 0x304A +#define AC_DRC_SFHRT 0x304C +#define AC_DRC_SFLRT 0x304E +#define AC_DRC_MXGHS 0x3050 +#define AC_DRC_MXGLS 0x3052 +#define AC_DRC_MNGHS 0x3054 +#define AC_DRC_MNGLS 0x3056 +#define AC_DRC_EPSHC 0x3058 +#define AC_DRC_EPSLC 0x305A +#define AC_DRC_OPT 0x305C +#define AC_DRC_HPFHGAIN 0x305E +#define AC_DRC_HPFLGAIN 0x3060 +#define AC_DRC_BISTCR 0x3100 +#define AC_DRC_BISTST 0x3102 + +/* AC_SYS_CLK_CTL : 0x2000 */ +#define SYS_CLK_I2S 15 +#define SYS_CLK_AGC 7 +#define SYS_CLK_DRC 6 +#define SYS_CLK_ADC 3 +#define SYS_CLK_DAC 2 + +/* AC_SYS_MOD_RST : 0x2002 */ +#define MOD_RST_I2S 15 +#define MOD_RST_AGC 7 +#define MOD_RST_DRC 6 +#define MOD_RST_ADC 3 +#define MOD_RST_DAC 2 + +/* AC_SYS_SR_CTL : 0x2004 */ +#define SYS_SR_BIT 0 +#define SYS_SR_MASK 0xF +#define SYS_SR_BIT_0 0 /* 8000 */ +#define SYS_SR_BIT_1 1 /* 11025 */ +#define SYS_SR_BIT_2 2 /* 12000 */ +#define SYS_SR_BIT_3 3 /* 16000 */ +#define SYS_SR_BIT_4 4 /* 22050 */ +#define SYS_SR_BIT_5 5 /* 24000 */ +#define SYS_SR_BIT_6 6 /* 32000 */ +#define SYS_SR_BIT_7 7 /* 44100 */ +#define SYS_SR_BIT_8 8 /* 48000 */ +#define SYS_SR_BIT_9 9 /* 96000 */ +#define SYS_SR_BIT_10 10 /* 192000 */ + +/* AC_I2S_CTL : 0x2100 */ +#define I2S_SDO0_EN 3 +#define I2S_TX_EN 2 +#define I2S_RX_EN 1 +#define I2S_GEN 0 + +/* AC_I2S_CLK : 0x2102 */ +#define I2S_BCLK_OUT 15 +#define I2S_LRCK_OUT 14 +#define I2S_BLCK_DIV 10 +#define I2S_LRCK_PERIOD 0 +/* BCLK DIV Define */ +#define I2S_BCLK_DIV_MASK 0xF +#define I2S_BCLK_DIV_1 1 +#define I2S_BCLK_DIV_2 2 +#define I2S_BCLK_DIV_3 3 +#define I2S_BCLK_DIV_4 4 +#define I2S_BCLK_DIV_5 5 +#define I2S_BCLK_DIV_6 6 +#define I2S_BCLK_DIV_7 7 +#define I2S_BCLK_DIV_8 8 +#define I2S_BCLK_DIV_9 9 +#define I2S_BCLK_DIV_10 10 +#define I2S_BCLK_DIV_11 11 +#define I2S_BCLK_DIV_12 12 +#define I2S_BCLK_DIV_13 13 +#define I2S_BCLK_DIV_14 14 +#define I2S_BCLK_DIV_15 15 +#define I2S_LRCK_PERIOD_MASK 0x3FF + +/* AC_I2S_FMT0 : 0x2104 */ +#define I2S_FMT_MODE 14 +#define I2S_FMT_TX_OFFSET 10 +#define I2S_FMT_RX_OFFSET 8 +#define I2S_FMT_SAMPLE 4 +#define I2S_FMT_SLOT_WIDTH 1 +#define I2S_FMT_LOOP 0 + +/* AC_I2S_FMT1 : 0x2108 */ +#define I2S_FMT_BCLK_POLAR 15 +#define I2S_FMT_LRCK_POLAR 14 +#define I2S_FMT_EDGE_TRANSFER 13 +#define I2S_FMT_RX_MLS 11 +#define I2S_FMT_TX_MLS 10 +#define I2S_FMT_EXTEND 9 +#define I2S_FMT_LRCK_WIDTH 4 /* PCM long/short Frame */ +#define I2S_MFT_RX_PDM 2 +#define I2S_FMT_TX_PDM 0 + +/* AC_I2S_MIXER_SRC : 0x2114 */ +#define I2S_MIXERL_SRC_DAC 13 +#define I2S_MIXERL_SRC_ADC 12 +#define I2S_MIXERR_SRC_DAC 9 +#define I2S_MIXERR_SRC_ADC 8 + +/* AC_I2S_MIXER_GAIN : 0x2116 */ +#define I2S_MIXERL_GAIN_DAC 13 +#define I2S_MIXERL_GAIN_ADC 12 +#define I2S_MIXERR_GAIN_DAC 9 +#define I2S_MIXERR_GAIN_ADC 8 + + +/* AC_I2S_DAC_VOL : 0x2118 */ +#define I2S_DACL_VOL 8 +#define I2S_DACR_VOL 0 + +/* AC_I2S_ADC_VOL : 0x211A */ +#define I2S_ADCL_VOL 8 +#define I2S_ADCR_VOL 0 + +/* AC_DAC_CTL : 0x2200 */ +#define DAC_CTL_DAC_EN 15 +#define DAC_CTL_HPF_EN 14 +#define DAC_CTL_FIR 13 +#define DAC_CTL_MODQU 8 + +/* AC_DAC_MIXER_SRC : 0x2202 */ +#define DAC_MIXERL_SRC_DAC 13 +#define DAC_MIXERL_SRC_ADC 12 +#define DAC_MIXERR_SRC_DAC 9 +#define DAC_MIXERR_SRC_ADC 8 + +/* AC_DAC_MIXER_GAIN : 0x2204 */ +#define DAC_MIXERL_GAIN_DAC 13 +#define DAC_MIXERL_GAIN_ADC 12 +#define DAC_MIXERR_GAIN_DAC 9 +#define DAC_MIXERR_GAIN_ADC 8 + +/* AC_OUT_MIXER_CTL : 0x2220 */ +#define OUT_MIXER_DACR_EN 15 +#define OUT_MIXER_DACL_EN 14 +#define OUT_MIXER_RMIX_EN 13 +#define OUT_MIXER_LMIX_EN 12 +#define OUT_MIXER_LINE_VOL 8 +#define OUT_MIXER_MIC1_VOL 4 +#define OUT_MIXER_MIC2_VOL 0 + +/* AC_OUT_MIXER_SRC : 0x2222 */ +#define OUT_MIXERR_SRC_MIC1 14 +#define OUT_MIXERR_SRC_MIC2 13 +#define OUT_MIXERR_SRC_PHPN 12 +#define OUT_MIXERR_SRC_PHP 11 +#define OUT_MIXERR_SRC_LINER 10 +#define OUT_MIXERR_SRC_DACR 9 +#define OUT_MIXERR_SRC_DACL 8 +#define OUT_MIXERL_SRC_MIC1 6 +#define OUT_MIXERL_SRC_MIC2 5 +#define OUT_MIXERL_SRC_PHPN 4 +#define OUT_MIXERL_SRC_PHN 3 +#define OUT_MIXERL_SRC_LINEL 2 +#define OUT_MIXERL_SRC_DACL 1 +#define OUT_MIXERL_SRC_DACR 0 + +/* AC_LINEOUT_CTL : 0x2224 */ +#define LINEOUT_EN 15 +#define LINEL_SRC_EN 14 +#define LINER_SRC_EN 13 +#define LINEL_SRC 12 +#define LINER_SRC 11 +/* ramp just skip */ +#define LINE_SLOPE_SEL 8 +#define LINE_ANTI_TIME 5 +#define LINEOUT_VOL 0 + +/* AC_ADC_CTL : 0x2300 */ +#define ADC_EN 15 +#define ADC_ENDM 14 +#define ADC_FIR 13 +#define ADC_DELAY_TIME 2 +#define ADC_DELAY_EN 1 + +/* AC_MICBIAS_CTL : 0x2310 */ +#define MMBIAS_EN 15 +#define MMBIAS_CHOPPER 14 +#define MMBIAS_CHOP_CLK 12 +#define MMBIAS_SEL 8 +#define ADDA_BIAS_CUR 3 + +/* AC_ADC_MIC_CTL : 0x2320 */ +#define ADCR_EN 15 +#define ADCL_EN 14 +#define ADC_GAIN 8 +#define MIC1_GAIN_EN 7 +#define MIC1_BOOST 4 +#define MIC2_GAIN_EN 3 +#define MIC2_BOOST 0 + +/* AC_ADC_MIXER_SRC : 0x2322 */ +#define ADC_MIXERR_MIC1 14 +#define ADC_MIXERR_MIC2 13 +#define ADC_MIXERR_PHPN 12 +#define ADC_MIXERR_PHP 11 +#define ADC_MIXERR_LINER 10 +#define ADC_MIXERR_MIXR 9 +#define ADC_MIXERR_MIXL 8 +#define ADC_MIXERL_MIC1 6 +#define ADC_MIXERL_MIC2 5 +#define ADC_MIXERL_PHPN 4 +#define ADC_MIXERL_PHN 3 +#define ADC_MIXERL_LINEL 2 +#define ADC_MIXERL_MIXL 1 +#define ADC_MIXERL_MIXR 0 + +/* AC_BIAS_CTL : 0x232A */ + +/* AC_ANALOG_PROF_CTL : 0x232C */ +/* used for current performance measure */ + +/* AC_DLDO_OSC_CTL : 0x2340 */ +/* AC_ALDO_CTL : 0x2342 */ +/* used for digital & analog LDO test... etc */ + +#endif -- 2.35.3