[alsa-devel] [PATCH] ASoC: nau8825: Add driver for headset chip Nuvoton 8825
Signed-off-by: Anatol Pomozov anatol.pomozov@gmail.com --- .../devicetree/bindings/sound/nau8825.txt | 14 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/nau8825.c | 860 +++++++++++++++++++++ sound/soc/codecs/nau8825.h | 258 +++++++ 5 files changed, 1138 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/nau8825.txt create mode 100644 sound/soc/codecs/nau8825.c create mode 100644 sound/soc/codecs/nau8825.h
diff --git a/Documentation/devicetree/bindings/sound/nau8825.txt b/Documentation/devicetree/bindings/sound/nau8825.txt new file mode 100644 index 0000000..0334be3 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nau8825.txt @@ -0,0 +1,14 @@ +Nuvoton NAU8825 audio codec + +This device supports I2C only. + +Required properties: + - compatible : Must be "nuvoton,nau8825" + - reg : the I2C address of the device. This is either 0x1a (CSB=0) or 0x1b (CSB=1). + +Example: + + codec: nau8825@1a { + compatible = "nuvoton,nau8825"; + reg = <0x1a>; + }; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7fcbc22..1a3639c 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -78,6 +78,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX9877 if I2C select SND_SOC_MC13783 if MFD_MC13XXX select SND_SOC_ML26124 if I2C + select SND_SOC_NAU8825 if I2C select SND_SOC_HDMI_CODEC select SND_SOC_PCM1681 if I2C select SND_SOC_PCM1792A if SPI_MASTER @@ -892,6 +893,9 @@ config SND_SOC_MC13783 config SND_SOC_ML26124 tristate
+config SND_SOC_NAU8825 + tristate + config SND_SOC_TPA6130A2 tristate "Texas Instruments TPA6130A2 headphone amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 5b46c8f..4874881 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -71,6 +71,7 @@ snd-soc-max98925-objs := max98925.o snd-soc-max9850-objs := max9850.o snd-soc-mc13783-objs := mc13783.o snd-soc-ml26124-objs := ml26124.o +snd-soc-nau8825-objs := nau8825.o snd-soc-hdmi-codec-objs := hdmi.o snd-soc-pcm1681-objs := pcm1681.o snd-soc-pcm1792a-codec-objs := pcm1792a.o @@ -262,6 +263,7 @@ obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o +obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o obj-$(CONFIG_SND_SOC_PCM1792A) += snd-soc-pcm1792a-codec.o diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c new file mode 100644 index 0000000..1c99308b --- /dev/null +++ b/sound/soc/codecs/nau8825.c @@ -0,0 +1,860 @@ +/* + * NAU8825 audio codec driver + * + * Copyright 2015 Nuvoton Technology Corp. + * Copyright 2015 Google Chromium project. + * Author: Anatol Pomozov anatol@chromium.org + * + * Licensed under the GPL-2. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <linux/input.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "nau8825.h" + +struct nau8825 { + struct device *dev; + struct regmap *regmap; + struct snd_soc_dapm_context *dapm; + int irq; + struct snd_soc_jack *jack; + int button_pressed; +}; + +static const struct reg_default nau8825_reg_defaults[] = { + { NAU8825_REG_ENA_CTRL, 0x00ff }, + { NAU8825_REG_CLK_DIVIDER, 0x0050 }, + { NAU8825_REG_FLL1, 0x0 }, + { NAU8825_REG_FLL2, 0x3126 }, + { NAU8825_REG_FLL3, 0x0008 }, + { NAU8825_REG_FLL4, 0x0010 }, + { NAU8825_REG_FLL5, 0x0 }, + { NAU8825_REG_FLL6, 0x6000 }, + { NAU8825_REG_FLL_VCO_RSV, 0xf13c }, + { NAU8825_REG_HSD_CTRL, 0x000c }, + { NAU8825_REG_JACK_DET_CTRL, 0x0 }, + { NAU8825_REG_INTERRUPT_MASK, 0x0 }, + { NAU8825_REG_INTERRUPT_DIS_CTRL, 0xffff }, + { NAU8825_REG_SAR_CTRL, 0x0015 }, + { NAU8825_REG_KEYDET_CTRL, 0x0110 }, + { NAU8825_REG_VDET_THRESHOLD_1, 0x0 }, + { NAU8825_REG_VDET_THRESHOLD_2, 0x0 }, + { NAU8825_REG_VDET_THRESHOLD_3, 0x0 }, + { NAU8825_REG_VDET_THRESHOLD_4, 0x0 }, + { NAU8825_REG_GPIO34_CTRL, 0x0 }, + { NAU8825_REG_GPIO12_CTRL, 0x0 }, + { NAU8825_REG_TDM_CTRL, 0x0 }, + { NAU8825_REG_I2S_PCM_CTRL1, 0x000b }, + { NAU8825_REG_I2S_PCM_CTRL2, 0x8010 }, + { NAU8825_REG_LEFT_TIME_SLOT, 0x0 }, + { NAU8825_REG_RIGHT_TIME_SLOT, 0x0 }, + { NAU8825_REG_BIQ_CTRL, 0x0 }, + { NAU8825_REG_BIQ_COF1, 0x0 }, + { NAU8825_REG_BIQ_COF2, 0x0 }, + { NAU8825_REG_BIQ_COF3, 0x0 }, + { NAU8825_REG_BIQ_COF4, 0x0 }, + { NAU8825_REG_BIQ_COF5, 0x0 }, + { NAU8825_REG_BIQ_COF6, 0x0 }, + { NAU8825_REG_BIQ_COF7, 0x0 }, + { NAU8825_REG_BIQ_COF8, 0x0 }, + { NAU8825_REG_BIQ_COF9, 0x0 }, + { NAU8825_REG_BIQ_COF10, 0x0 }, + { NAU8825_REG_ADC_RATE, 0x0010 }, + { NAU8825_REG_DAC_CTRL1, 0x0001 }, + { NAU8825_REG_DAC_CTRL2, 0x0 }, + { NAU8825_REG_DAC_DGAIN_CTRL, 0x0 }, + { NAU8825_REG_ADC_DGAIN_CTRL, 0x00cf }, + { NAU8825_REG_MUTE_CTRL, 0x0 }, + { NAU8825_REG_HSVOL_CTRL, 0x0 }, + { NAU8825_REG_DACL_CTRL, 0x02cf }, + { NAU8825_REG_DACR_CTRL, 0x00cf }, + { NAU8825_REG_ADC_DRC_KNEE_IP12, 0x1486 }, + { NAU8825_REG_ADC_DRC_KNEE_IP34, 0x0f12 }, + { NAU8825_REG_ADC_DRC_SLOPES, 0x25ff }, + { NAU8825_REG_ADC_DRC_ATKDCY, 0x3457 }, + { NAU8825_REG_DAC_DRC_KNEE_IP12, 0x1486 }, + { NAU8825_REG_DAC_DRC_KNEE_IP34, 0x0f12 }, + { NAU8825_REG_DAC_DRC_SLOPES, 0x25f9 }, + { NAU8825_REG_DAC_DRC_ATKDCY, 0x3457 }, + { NAU8825_REG_IMM_MODE_CTRL, 0x0 }, + { NAU8825_REG_CLASSG_CTRL, 0x0 }, + { NAU8825_REG_OPT_EFUSE_CTRL, 0x0 }, + { NAU8825_REG_MISC_CTRL, 0x0 }, + { NAU8825_REG_BIAS_ADJ, 0x0 }, + { NAU8825_REG_TRIM_SETTINGS, 0x0 }, + { NAU8825_REG_ANALOG_CONTROL_1, 0x0 }, + { NAU8825_REG_ANALOG_CONTROL_2, 0x0 }, + { NAU8825_REG_ANALOG_ADC_1, 0x0011 }, + { NAU8825_REG_ANALOG_ADC_2, 0x0020 }, + { NAU8825_REG_RDAC, 0x0008 }, + { NAU8825_REG_MIC_BIAS, 0x0006 }, + { NAU8825_REG_BOOST, 0x0 }, + { NAU8825_REG_FEPGA, 0x0 }, + { NAU8825_REG_POWER_UP_CONTROL, 0x0 }, + { NAU8825_REG_CHARGE_PUMP, 0x0 }, +}; + +static bool nau8825_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8825_REG_ENA_CTRL: + case NAU8825_REG_CLK_DIVIDER ... NAU8825_REG_FLL_VCO_RSV: + case NAU8825_REG_HSD_CTRL ... NAU8825_REG_JACK_DET_CTRL: + case NAU8825_REG_INTERRUPT_MASK ... NAU8825_REG_KEYDET_CTRL: + case NAU8825_REG_VDET_THRESHOLD_1 ... NAU8825_REG_DACR_CTRL: + case NAU8825_REG_ADC_DRC_KNEE_IP12 ... NAU8825_REG_ADC_DRC_ATKDCY: + case NAU8825_REG_DAC_DRC_KNEE_IP12 ... NAU8825_REG_DAC_DRC_ATKDCY: + case NAU8825_REG_IMM_MODE_CTRL ... NAU8825_REG_IMM_RMS_R: + case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL: + case NAU8825_REG_MISC_CTRL: + case NAU8825_REG_I2C_DEVICE_ID ... NAU8825_REG_SARDOUT_RAM_STATUS: + case NAU8825_REG_BIAS_ADJ: + case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2: + case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS: + case NAU8825_REG_BOOST ... NAU8825_REG_FEPGA: + case NAU8825_REG_POWER_UP_CONTROL ... NAU8825_REG_GENERAL_STATUS: + return true; + default: + return false; + } + +} + +static bool nau8825_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8825_REG_RESET ... NAU8825_REG_ENA_CTRL: + case NAU8825_REG_CLK_DIVIDER ... NAU8825_REG_FLL_VCO_RSV: + case NAU8825_REG_HSD_CTRL ... NAU8825_REG_JACK_DET_CTRL: + case NAU8825_REG_INTERRUPT_MASK: + case NAU8825_REG_INT_CLR_KEY_STATUS ... NAU8825_REG_KEYDET_CTRL: + case NAU8825_REG_VDET_THRESHOLD_1 ... NAU8825_REG_DACR_CTRL: + case NAU8825_REG_ADC_DRC_KNEE_IP12 ... NAU8825_REG_ADC_DRC_ATKDCY: + case NAU8825_REG_DAC_DRC_KNEE_IP12 ... NAU8825_REG_DAC_DRC_ATKDCY: + case NAU8825_REG_IMM_MODE_CTRL: + case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL: + case NAU8825_REG_MISC_CTRL: + case NAU8825_REG_BIAS_ADJ: + case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2: + case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS: + case NAU8825_REG_BOOST ... NAU8825_REG_FEPGA: + case NAU8825_REG_POWER_UP_CONTROL ... NAU8825_REG_CHARGE_PUMP: + return true; + default: + return false; + } +} + +static bool nau8825_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8825_REG_RESET: + case NAU8825_REG_IRQ_STATUS: + case NAU8825_REG_INT_CLR_KEY_STATUS: + case NAU8825_REG_IMM_RMS_L: + case NAU8825_REG_IMM_RMS_R: + case NAU8825_REG_I2C_DEVICE_ID: + case NAU8825_REG_SARDOUT_RAM_STATUS: + case NAU8825_REG_CHARGE_PUMP_INPUT_READ: + case NAU8825_REG_GENERAL_STATUS: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_MINMAX_MUTE(adc_vol_tlv, -10300, 2400); +static const DECLARE_TLV_DB_MINMAX_MUTE(sidetone_vol_tlv, -4200, 0); +static const DECLARE_TLV_DB_MINMAX(dac_vol_tlv, -5400, 0); +static const DECLARE_TLV_DB_MINMAX(fepga_gain_tlv, -100, 3600); + +static const struct snd_kcontrol_new nau8825_snd_controls[] = { + SOC_SINGLE_TLV("MIC Volume", NAU8825_REG_ADC_DGAIN_CTRL, + 0, 0xff, 0, adc_vol_tlv), + SOC_DOUBLE_TLV("HP Sidetone Volume", NAU8825_REG_ADC_DGAIN_CTRL, + 12, 8, 0x0f, 0, sidetone_vol_tlv), + SOC_DOUBLE_TLV("HP Volume", NAU8825_REG_HSVOL_CTRL, + 6, 0, 0x3f, 1, dac_vol_tlv), + SOC_SINGLE_TLV("Frontend PGA Gain", NAU8825_REG_POWER_UP_CONTROL, + 8, 37, 0, fepga_gain_tlv), +}; + +static int nau8825_charge_pump_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + struct regmap *regmap = nau8825->regmap; + + if (SND_SOC_DAPM_EVENT_ON(event)) { + regmap_update_bits(regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW, NAU8825_JAMNODCLOW); + } else { + regmap_update_bits(regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW, 0); + } + + return 0; +} + +static int nau8825_output_driver_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + struct regmap *regmap = nau8825->regmap; + + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* Power Up L and R Output Drivers */ + regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3c, + 0x3c); + /* Power up Main Output Drivers (The main driver must be turned + on after the predriver to avoid pops) */ + regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3, + 0x3); + } else { + /* Power Down L and R Output Drivers */ + regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3f, + 0x0); + } + + return 0; +} + +/* DAC Mux 0x33[9] and 0x34[9] */ +static const char * const nau8825_dac_src[] = { + "DACL", "DACR", +}; + +static SOC_ENUM_SINGLE_DECL( + nau8825_dacl_enum, NAU8825_REG_DACL_CTRL, + NAU8825_DACL_CH_SEL_SFT, nau8825_dac_src); + +static SOC_ENUM_SINGLE_DECL( + nau8825_dacr_enum, NAU8825_REG_DACR_CTRL, + NAU8825_DACR_CH_SEL_SFT, nau8825_dac_src); + +static const struct snd_kcontrol_new nau8825_dacl_mux = + SOC_DAPM_ENUM("DACL Source", nau8825_dacl_enum); + +static const struct snd_kcontrol_new nau8825_dacr_mux = + SOC_DAPM_ENUM("DACR Source", nau8825_dacr_enum); + +static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_MICBIAS("MICBIAS", NAU8825_REG_MIC_BIAS, 8, 0), + + SND_SOC_DAPM_PGA("Frontend PGA", NAU8825_REG_POWER_UP_CONTROL, 14, 0, + NULL, 0), + + SND_SOC_DAPM_ADC("ADC", NULL, NAU8825_REG_ENA_CTRL, 8, 0), + SND_SOC_DAPM_SUPPLY("ADC Clock", NAU8825_REG_ENA_CTRL, 7, 0, NULL, 0), + + /* ADC for button press detection */ + SND_SOC_DAPM_ADC("SAR", NULL, NAU8825_REG_SAR_CTRL, + NAU8825_SAR_ADC_EN_SFT, 0), + + SND_SOC_DAPM_DAC("ADACL", NULL, NAU8825_REG_RDAC, 12, 0), + SND_SOC_DAPM_DAC("ADACR", NULL, NAU8825_REG_RDAC, 13, 0), + SND_SOC_DAPM_SUPPLY("ADACL Clock", NAU8825_REG_RDAC, 8, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADACR Clock", NAU8825_REG_RDAC, 9, 0, NULL, 0), + + SND_SOC_DAPM_DAC("DDACR", NULL, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACR_SFT, 0), + SND_SOC_DAPM_DAC("DDACL", NULL, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACL_SFT, 0), + SND_SOC_DAPM_SUPPLY("DDAC Clock", NAU8825_REG_ENA_CTRL, 6, 0, NULL, 0), + + SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, + &nau8825_dacl_mux), + SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, + &nau8825_dacr_mux), + + SND_SOC_DAPM_PGA("HP amp L", NAU8825_REG_CLASSG_CTRL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP amp R", NAU8825_REG_CLASSG_CTRL, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HP amp power", NAU8825_REG_CLASSG_CTRL, 0, 0, NULL, + 0), + + SND_SOC_DAPM_SUPPLY("Change Pump", NAU8825_REG_CHARGE_PUMP, 5, 0, + nau8825_charge_pump_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY("DACL Power", NAU8825_REG_CHARGE_PUMP, 8, 1, NULL, + 0), + SND_SOC_DAPM_SUPPLY("DACR Power", NAU8825_REG_CHARGE_PUMP, 9, 1, NULL, + 0), + + SND_SOC_DAPM_OUT_DRV_E("Output Driver", SND_SOC_NOPM, 0, 0, NULL, 0, + nau8825_output_driver_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_route nau8825_dapm_routes[] = { + {"Frontend PGA", NULL, "MIC"}, + {"ADC", NULL, "Frontend PGA"}, + {"ADC", NULL, "ADC Clock"}, + {"Capture", NULL, "ADC"}, + + {"DDACL", NULL, "Playback"}, + {"DDACR", NULL, "Playback"}, + {"DDACL", NULL, "DDAC Clock"}, + {"DDACR", NULL, "DDAC Clock"}, + {"DDACL", NULL, "DACL Power"}, + {"DDACR", NULL, "DACR Power"}, + {"DACL Mux", "DACL", "DDACL"}, + {"DACL Mux", "DACR", "DDACR"}, + {"DACR Mux", "DACL", "DDACL"}, + {"DACR Mux", "DACR", "DDACR"}, + {"HP amp L", NULL, "Change Pump"}, + {"HP amp R", NULL, "Change Pump"}, + {"HP amp L", NULL, "DACL Mux"}, + {"HP amp R", NULL, "DACR Mux"}, + {"HP amp L", NULL, "HP amp power"}, + {"HP amp R", NULL, "HP amp power"}, + {"ADACL", NULL, "HP amp L"}, + {"ADACR", NULL, "HP amp R"}, + {"ADACL", NULL, "ADACL Clock"}, + {"ADACR", NULL, "ADACR Clock"}, + {"Output Driver", NULL, "ADACL"}, + {"Output Driver", NULL, "ADACR"}, + {"HPOL", NULL, "Output Driver"}, + {"HPOR", NULL, "Output Driver"}, +}; + +static int nau8825_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + unsigned int val_len = 0; + + switch (params_width(params)) { + case 16: + val_len |= NAU8825_I2S_DL_16; + break; + case 20: + val_len |= NAU8825_I2S_DL_20; + break; + case 24: + val_len |= NAU8825_I2S_DL_24; + break; + case 32: + val_len |= NAU8825_I2S_DL_32; + break; + default: + return -EINVAL; + } + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL1, + NAU8825_I2S_DL_MASK, val_len); + + return 0; +} + +static int nau8825_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + int reg_val; + + reg_val = mute ? NAU8825_HP_MUTE : 0; + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL, + NAU8825_HP_MUTE, reg_val); + + return 0; +} + +static int nau8825_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + unsigned int ctrl1_val = 0, ctrl2_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl2_val |= NAU8825_I2S_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8825_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8825_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8825_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1_val |= NAU8825_I2S_DF_RIGTH; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8825_I2S_DF_PCM_AB; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1_val |= NAU8825_I2S_DF_PCM_AB; + ctrl1_val |= NAU8825_I2S_PCMB_EN; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL1, + NAU8825_I2S_DL_MASK | NAU8825_I2S_DF_MASK | + NAU8825_I2S_BP_MASK | NAU8825_I2S_PCMB_MASK, + ctrl1_val); + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK, ctrl2_val); + + return 0; +} + +static const struct snd_soc_dai_ops nau8825_dai_ops = { + .hw_params = nau8825_hw_params, + .digital_mute = nau8825_mute, + .set_fmt = nau8825_set_dai_fmt, +}; + +#define NAU8825_RATES SNDRV_PCM_RATE_8000_192000 +#define NAU8825_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver nau8825_dai = { + .name = "nau8825-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8825_RATES, + .formats = NAU8825_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = NAU8825_RATES, + .formats = NAU8825_FORMATS, + }, + .ops = &nau8825_dai_ops, +}; + +/** + * nau8825_enable_jack_detect - Specify a jack for event reporting + * + * @component: component to register the jack with + * @jack: jack to use to report headset and button events on + * + * After this function has been called the headset insert/remove and button + * events will be routed to the given jack. Jack can be null to stop + * reporting. + */ +int nau8825_enable_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack) +{ + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + nau8825->jack = jack; + + return 0; +} +EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect); + + +static bool nau8825_is_jack_inserted(struct regmap *regmap) +{ + int status; + + regmap_read(regmap, NAU8825_REG_I2C_DEVICE_ID, &status); + return !(status & NAU8825_GPIO2JD1); +} + +static void nau8825_restart_jack_detection(struct regmap *regmap) +{ + /* this will restart the entire jack detection process including MIC/GND + * switching and create interrupts. We have to go from 0 to 1 and back + * to 0 to restart. + */ + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_RESTART, NAU8825_JACK_DET_RESTART); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_RESTART, 0); +} + +static void nau8825_eject_jack(struct nau8825 *nau8825) +{ + struct snd_soc_dapm_context *dapm = nau8825->dapm; + struct regmap *regmap = nau8825->regmap; + + snd_soc_dapm_disable_pin(dapm, "SAR"); + snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + /* Detach 2kOhm Resistors from MICBIAS to MICGND1/2 */ + regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS, + NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2, 0); + /* ground HPL/HPR, MICGRND1/2 */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0xf, 0xf); + + snd_soc_dapm_sync(dapm); +} + +static int nau8825_button_decode(int value) +{ + int buttons; + + if (value & BIT(0)) + buttons |= SND_JACK_BTN_0; + if (value & BIT(1)) + buttons |= SND_JACK_BTN_1; + if (value & BIT(2)) + buttons |= SND_JACK_BTN_2; + if (value & BIT(3)) + buttons |= SND_JACK_BTN_3; + + return buttons; +} + +static int nau8825_jack_insert(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + struct snd_soc_dapm_context *dapm = nau8825->dapm; + int jack_status_reg, mic_detected; + int type; + + regmap_read(regmap, NAU8825_REG_GENERAL_STATUS, &jack_status_reg); + mic_detected = (jack_status_reg >> 10) & 3; + + switch (mic_detected) { + case 0: + /* no mic */ + type = SND_JACK_HEADPHONE; + break; + case 1: + dev_info(nau8825->dev, "OMTP (micgnd1) mic connected\n"); + type = SND_JACK_HEADSET; + + /* Unground MICGND1 */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 3 << 2, + 1 << 2); + /* Unground HPL/R */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0x3, 0); + /* Attach 2kOhm Resistor from MICBIAS to MICGND1 */ + regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS, + NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2, + NAU8825_MICBIAS_JKR2); + /* Attach SARADC to MICGND1 */ + regmap_update_bits(regmap, NAU8825_REG_SAR_CTRL, + NAU8825_SAR_INPUT_MASK, + NAU8825_SAR_INPUT_JKR2); + + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + snd_soc_dapm_force_enable_pin(dapm, "SAR"); + snd_soc_dapm_sync(dapm); + break; + case 2: + case 3: + dev_info(nau8825->dev, "CTIA (micgnd2) mic connected\n"); + type = SND_JACK_HEADSET; + + /* Unground MICGND2 */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 3 << 2, + 2 << 2); + /* Unground HPL/R */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0x3, 0); + /* Attach 2kOhm Resistor from MICBIAS to MICGND2 */ + regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS, + NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2, + NAU8825_MICBIAS_JKSLV); + /* Attach SARADC to MICGND2 */ + regmap_update_bits(regmap, NAU8825_REG_SAR_CTRL, + NAU8825_SAR_INPUT_MASK, + NAU8825_SAR_INPUT_JKSLV); + + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + snd_soc_dapm_force_enable_pin(dapm, "SAR"); + snd_soc_dapm_sync(dapm); + break; + } + + return type; +} + +#define NAU8825_BUTTONS (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3) + +static irqreturn_t nau8825_interrupt(int irq, void *data) +{ + struct nau8825 *nau8825 = (struct nau8825 *)data; + struct regmap *regmap = nau8825->regmap; + int active_irq, clear_irq = 0, event = 0, event_mask = 0; + + regmap_read(regmap, NAU8825_REG_IRQ_STATUS, &active_irq); + + if ((active_irq & NAU8825_JACK_EJECTION_IRQ_MASK) == + NAU8825_JACK_EJECTION_DETECTED) { + + nau8825_eject_jack(nau8825); + event_mask |= SND_JACK_HEADSET; + clear_irq = NAU8825_JACK_EJECTION_IRQ_MASK; + } else if (active_irq & NAU8825_KEY_SHORT_PRESS_IRQ) { + int key_status; + + regmap_read(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, + &key_status); + + /* upper 8 bits of the register are for short pressed keys, + lower 8 bits - for long pressed buttons */ + nau8825->button_pressed = nau8825_button_decode( + key_status >> 8); + + event |= nau8825->button_pressed; + event_mask |= NAU8825_BUTTONS; + clear_irq = NAU8825_KEY_SHORT_PRESS_IRQ; + } else if (active_irq & NAU8825_KEY_RELEASE_IRQ) { + event_mask = NAU8825_BUTTONS; + clear_irq = NAU8825_KEY_RELEASE_IRQ; + } else if (active_irq & NAU8825_HEADSET_COMPLETION_IRQ) { + if (nau8825_is_jack_inserted(regmap)) { + event |= nau8825_jack_insert(nau8825); + } else { + dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n"); + nau8825_eject_jack(nau8825); + } + + event_mask |= SND_JACK_HEADSET; + clear_irq = NAU8825_HEADSET_COMPLETION_IRQ; + } + + if (!clear_irq) + clear_irq = active_irq; + /* clears the rightmost interruption */ + regmap_write(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq); + + if (event_mask) + snd_soc_jack_report(nau8825->jack, event, event_mask); + + return IRQ_HANDLED; +} + +static void nau8825_setup_buttons(struct regmap *regmap) +{ + /* Setup Button Detect (Debounce, number of buttons, and Hysteresis) */ + regmap_write(regmap, NAU8825_REG_KEYDET_CTRL, 0x7311); + + /* Setup 4 buttons impedane according to Android specification + * https://source.android.com/accessories/headset-spec.html + * Button 0 - 0-70 Ohm + * Button 1 - 110-180 Ohm + * Button 2 - 210-290 Ohm + * Button 3 - 360-680 Ohm + */ + regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_1, 0x0f1f); + regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_2, 0x325f); +} + +static void nau8825_init_regs(struct regmap *regmap) +{ + /* Power down DACs for power savings */ + regmap_write(regmap, NAU8825_REG_CHARGE_PUMP, 0x0300); + /* DAC Zero Crossing Enable */ + regmap_write(regmap, NAU8825_REG_MUTE_CTRL, 0x1000); + /* VMID Enable and Tieoff */ + regmap_write(regmap, NAU8825_REG_BIAS_ADJ, 0x0060); + /* Analog Bias Enable, Disable Boost Driver, + Automatic Short circuit protection enable */ + regmap_write(regmap, NAU8825_REG_BOOST, 0x3140); + /* Ground HP Outputs[1:0], + Enable Automatic Mic/Gnd switching reading on insert interrupt[6] */ + regmap_write(regmap, NAU8825_REG_HSD_CTRL, 0x004f); + + regmap_write(regmap, NAU8825_REG_JACK_DET_CTRL, 0x01e0); + /* IRQ Output Enable, Mask Insert Interrupt */ + regmap_write(regmap, NAU8825_REG_INTERRUPT_MASK, 0x0801); + /* Jack Detect pull up (High=eject, Low=insert) */ + regmap_write(regmap, NAU8825_REG_GPIO12_CTRL, 0x0800); + /* Setup SAR ADC */ + regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280); + nau8825_setup_buttons(regmap); + + /* Setup ADC x128 OSR */ + regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002); + /* Setup DAC x128 OSR */ + regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082); + /* DAC bias settings */ + regmap_write(regmap, NAU8825_REG_ANALOG_CONTROL_2, 0x1003); + /* ADC Bias Settings */ + regmap_write(regmap, NAU8825_REG_ANALOG_ADC_2, 0x0260); + /* Maximize Jack Insert Debounce */ + regmap_write(regmap, NAU8825_REG_JACK_DET_CTRL, 0x00e0); + + /* Setup clocks */ + regmap_write(regmap, NAU8825_REG_ENA_CTRL, 0x0416); + + /* Chip needs one FSCLK cycle in order to generate interrupts, + as we cannot guarantee one will be provided by the system. Turning + master mode on then off enables us to generate that FSCLK cycle + with a minimum of contention on the clock bus. + Enables master mode and correctly divides BCLK and FSCLK from MCLK. + */ + regmap_write(regmap, NAU8825_REG_I2S_PCM_CTRL2, 0x2019); + regmap_write(regmap, NAU8825_REG_I2S_PCM_CTRL2, 0x0010); + + /* Mask jack insert interruption */ + regmap_write(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0x0011); +} + +static const struct regmap_config nau8825_regmap_config = { + .val_bits = 16, + .reg_bits = 16, + + .max_register = NAU8825_REG_MAX, + .readable_reg = nau8825_readable_reg, + .writeable_reg = nau8825_writeable_reg, + .volatile_reg = nau8825_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8825_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8825_reg_defaults), +}; + +static int nau8825_codec_probe(struct snd_soc_codec *codec) +{ + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + struct device *dev = codec->dev; + + nau8825->dapm = &codec->dapm; + + nau8825_init_regs(nau8825->regmap); + /* The interrupt clock is gated by x1[10:8], + * one of them needs to be enabled all the time for + * interrupts to happen. */ + snd_soc_dapm_force_enable_pin(&codec->dapm, "DDACR"); + snd_soc_dapm_sync(&codec->dapm); + + if (nau8825->irq) { + int ret = devm_request_threaded_irq(dev, nau8825->irq, NULL, + nau8825_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "nau8825", nau8825); + + if (ret) { + dev_err(dev, "Cannot request irq %d (%d)\n", + nau8825->irq, ret); + return ret; + } + } + nau8825_restart_jack_detection(nau8825->regmap); + + return 0; +} + +static struct snd_soc_codec_driver nau8825_codec_driver = { + .probe = nau8825_codec_probe, + + .controls = nau8825_snd_controls, + .num_controls = ARRAY_SIZE(nau8825_snd_controls), + .dapm_widgets = nau8825_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8825_dapm_widgets), + .dapm_routes = nau8825_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8825_dapm_routes), +}; + +static void nau8825_reset_chip(struct regmap *regmap) +{ + regmap_write(regmap, NAU8825_REG_RESET, 0x00); + regmap_write(regmap, NAU8825_REG_RESET, 0x00); +} + +static int nau8825_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct nau8825 *nau8825; + int ret, value; + + nau8825 = devm_kzalloc(dev, sizeof(*nau8825), GFP_KERNEL); + if (!nau8825) + return -ENOMEM; + + i2c_set_clientdata(i2c, nau8825); + + nau8825->regmap = devm_regmap_init_i2c(i2c, &nau8825_regmap_config); + if (IS_ERR(nau8825->regmap)) + return PTR_ERR(nau8825->regmap); + nau8825->irq = i2c->irq; + nau8825->dev = dev; + + nau8825_reset_chip(nau8825->regmap); + ret = regmap_read(nau8825->regmap, NAU8825_REG_I2C_DEVICE_ID, &value); + if (ret < 0) { + dev_err(dev, "Failed to read device id from the NAU8825: %d\n", + ret); + return ret; + } + if ((value & NAU8825_SOFTWARE_ID_MASK) != + NAU8825_SOFTWARE_ID_NAU8825) { + dev_err(dev, "Not a NAU8825 chip\n"); + return -ENODEV; + } + + return snd_soc_register_codec(&i2c->dev, &nau8825_codec_driver, + &nau8825_dai, 1); +} + +static int nau8825_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id nau8825_i2c_ids[] = { + { "nau8825", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau8825_i2c_ids); + +static struct i2c_driver nau8825_driver = { + .driver = { + .name = "nau8825", + .owner = THIS_MODULE, + }, + .probe = nau8825_i2c_probe, + .remove = nau8825_i2c_remove, + .id_table = nau8825_i2c_ids, +}; +module_i2c_driver(nau8825_driver); + +MODULE_DESCRIPTION("ASoC nau8825 driver"); +MODULE_AUTHOR("Anatol Pomozov anatol@chromium.org"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/nau8825.h b/sound/soc/codecs/nau8825.h new file mode 100644 index 0000000..e1eb769 --- /dev/null +++ b/sound/soc/codecs/nau8825.h @@ -0,0 +1,258 @@ +/* + * NAU8825 ALSA SoC audio driver + * + * Copyright 2015 Google Inc. + * Author: Anatol Pomozov anatol.pomozov@chrominium.org + * + * 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 __NAU8825_H__ +#define __NAU8825_H__ + +#define NAU8825_REG_RESET 0x00 +#define NAU8825_REG_ENA_CTRL 0x01 +#define NAU8825_REG_CLK_DIVIDER 0x03 +#define NAU8825_REG_FLL1 0x04 +#define NAU8825_REG_FLL2 0x05 +#define NAU8825_REG_FLL3 0x06 +#define NAU8825_REG_FLL4 0x07 +#define NAU8825_REG_FLL5 0x08 +#define NAU8825_REG_FLL6 0x09 +#define NAU8825_REG_FLL_VCO_RSV 0x0a +#define NAU8825_REG_HSD_CTRL 0x0c +#define NAU8825_REG_JACK_DET_CTRL 0x0d +#define NAU8825_REG_INTERRUPT_MASK 0x0f +#define NAU8825_REG_IRQ_STATUS 0x10 +#define NAU8825_REG_INT_CLR_KEY_STATUS 0x11 +#define NAU8825_REG_INTERRUPT_DIS_CTRL 0x12 +#define NAU8825_REG_SAR_CTRL 0x13 +#define NAU8825_REG_KEYDET_CTRL 0x14 +#define NAU8825_REG_VDET_THRESHOLD_1 0x15 +#define NAU8825_REG_VDET_THRESHOLD_2 0x16 +#define NAU8825_REG_VDET_THRESHOLD_3 0x17 +#define NAU8825_REG_VDET_THRESHOLD_4 0x18 +#define NAU8825_REG_GPIO34_CTRL 0x19 +#define NAU8825_REG_GPIO12_CTRL 0x1a +#define NAU8825_REG_TDM_CTRL 0x1b +#define NAU8825_REG_I2S_PCM_CTRL1 0x1c +#define NAU8825_REG_I2S_PCM_CTRL2 0x1d +#define NAU8825_REG_LEFT_TIME_SLOT 0x1e +#define NAU8825_REG_RIGHT_TIME_SLOT 0x1f +#define NAU8825_REG_BIQ_CTRL 0x20 +#define NAU8825_REG_BIQ_COF1 0x21 +#define NAU8825_REG_BIQ_COF2 0x22 +#define NAU8825_REG_BIQ_COF3 0x23 +#define NAU8825_REG_BIQ_COF4 0x24 +#define NAU8825_REG_BIQ_COF5 0x25 +#define NAU8825_REG_BIQ_COF6 0x26 +#define NAU8825_REG_BIQ_COF7 0x27 +#define NAU8825_REG_BIQ_COF8 0x28 +#define NAU8825_REG_BIQ_COF9 0x29 +#define NAU8825_REG_BIQ_COF10 0x2a +#define NAU8825_REG_ADC_RATE 0x2b +#define NAU8825_REG_DAC_CTRL1 0x2c +#define NAU8825_REG_DAC_CTRL2 0x2d +#define NAU8825_REG_DAC_DGAIN_CTRL 0x2f +#define NAU8825_REG_ADC_DGAIN_CTRL 0x30 +#define NAU8825_REG_MUTE_CTRL 0x31 +#define NAU8825_REG_HSVOL_CTRL 0x32 +#define NAU8825_REG_DACL_CTRL 0x33 +#define NAU8825_REG_DACR_CTRL 0x34 +#define NAU8825_REG_ADC_DRC_KNEE_IP12 0x38 +#define NAU8825_REG_ADC_DRC_KNEE_IP34 0x39 +#define NAU8825_REG_ADC_DRC_SLOPES 0x3a +#define NAU8825_REG_ADC_DRC_ATKDCY 0x3b +#define NAU8825_REG_DAC_DRC_KNEE_IP12 0x45 +#define NAU8825_REG_DAC_DRC_KNEE_IP34 0x46 +#define NAU8825_REG_DAC_DRC_SLOPES 0x47 +#define NAU8825_REG_DAC_DRC_ATKDCY 0x48 +#define NAU8825_REG_IMM_MODE_CTRL 0x4c +#define NAU8825_REG_IMM_RMS_L 0x4d +#define NAU8825_REG_IMM_RMS_R 0x4e +#define NAU8825_REG_CLASSG_CTRL 0x50 +#define NAU8825_REG_OPT_EFUSE_CTRL 0x51 +#define NAU8825_REG_MISC_CTRL 0x55 +#define NAU8825_REG_I2C_DEVICE_ID 0x58 +#define NAU8825_REG_SARDOUT_RAM_STATUS 0x59 +#define NAU8825_REG_BIAS_ADJ 0x66 +#define NAU8825_REG_TRIM_SETTINGS 0x68 +#define NAU8825_REG_ANALOG_CONTROL_1 0x69 +#define NAU8825_REG_ANALOG_CONTROL_2 0x6a +#define NAU8825_REG_ANALOG_ADC_1 0x71 +#define NAU8825_REG_ANALOG_ADC_2 0x72 +#define NAU8825_REG_RDAC 0x73 +#define NAU8825_REG_MIC_BIAS 0x74 +#define NAU8825_REG_BOOST 0x76 +#define NAU8825_REG_FEPGA 0x77 +#define NAU8825_REG_POWER_UP_CONTROL 0x7f +#define NAU8825_REG_CHARGE_PUMP 0x80 +#define NAU8825_REG_CHARGE_PUMP_INPUT_READ 0x81 +#define NAU8825_REG_GENERAL_STATUS 0x82 +#define NAU8825_REG_MAX NAU8825_REG_GENERAL_STATUS + +/* ENA_CTRL (0x1) */ +#define NAU8825_ENABLE_DACR_SFT 10 +#define NAU8825_ENABLE_DACL_SFT 9 +#define NAU8825_ENABLE_ADC_SFT 8 +#define NAU8825_ENABLE_SAR_SFT 1 + +/* CLK_DIVIDER (0x3) */ +#define NAU8825_CLK_SRC_SFT 15 +#define NAU8825_CLK_SRC_MASK (1 << NAU8825_CLK_SRC_SFT) +#define NAU8825_CLK_SRC_VCO (1 << NAU8825_CLK_SRC_SFT) +#define NAU8825_CLK_SRC_MCLK (0 << NAU8825_CLK_SRC_SFT) + +/* HSD_CTRL (0xc) */ +#define NAU8825_HSD_AUTO_MODE (1 << 6) +#define NAU8825_SPKR_DWN1R_SHORT (1 << 1) +#define NAU8825_SPKR_DWN1L_SHORT (1 << 0) + +/* JACK_DET_CTRL (0xd) */ +#define NAU8825_JACK_DET_RESTART (1 << 9) + +/* IRQ_STATUS (0x10) */ +#define NAU8825_HEADSET_COMPLETION_IRQ (1 << 10) +#define NAU8825_SHORT_CIRCUIT_IRQ (1 << 9) +#define NAU8825_IMPEDANCE_MEAS_IRQ (1 << 8) +#define NAU8825_KEY_IRQ_MASK (0x7 << 5) +#define NAU8825_KEY_RELEASE_IRQ (1 << 7) +#define NAU8825_KEY_LONG_PRESS_IRQ (1 << 6) +#define NAU8825_KEY_SHORT_PRESS_IRQ (1 << 5) +#define NAU8825_MIC_DETECTION_IRQ (1 << 4) +#define NAU8825_JACK_EJECTION_IRQ_MASK (3 << 2) +#define NAU8825_JACK_EJECTION_DETECTED (1 << 2) +#define NAU8825_JACK_INSERTION_IRQ_MASK (3 << 0) +#define NAU8825_JACK_INSERTION_DETECTED (1 << 0) + +/* SAR_CTRL (0x13) */ +#define NAU8825_SAR_ADC_EN_SFT 12 +#define NAU8825_SAR_ADC_EN (1 << NAU8825_SAR_ADC_EN_SFT) +#define NAU8825_SAR_INPUT_MASK (1 << 11) +#define NAU8825_SAR_INPUT_JKSLV (1 << 11) +#define NAU8825_SAR_INPUT_JKR2 (0 << 11) + +/* KEYDET_CTRL (0x14) */ +#define NAU8825_KEYDET_LEVELS_NR_SFT 8 +#define NAU8825_KEYDET_LEVELS_NR_MASK (0x7 << 8) + +/* I2S_PCM_CTRL1 (0x1c) */ +#define NAU8825_I2S_BP_SFT 7 +#define NAU8825_I2S_BP_MASK (1 << NAU8825_I2S_BP_SFT) +#define NAU8825_I2S_BP_INV (1 << NAU8825_I2S_BP_SFT) +#define NAU8825_I2S_PCMB_SFT 6 +#define NAU8825_I2S_PCMB_MASK (1 << NAU8825_I2S_PCMB_SFT) +#define NAU8825_I2S_PCMB_EN (1 << NAU8825_I2S_PCMB_SFT) +#define NAU8825_I2S_DL_SFT 2 +#define NAU8825_I2S_DL_MASK (0x3 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DL_16 (0 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DL_20 (1 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DL_24 (2 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DL_32 (3 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DF_SFT 0 +#define NAU8825_I2S_DF_MASK (0x3 << NAU8825_I2S_DF_SFT) +#define NAU8825_I2S_DF_RIGTH (0 << NAU8825_I2S_DF_SFT) +#define NAU8825_I2S_DF_LEFT (1 << NAU8825_I2S_DF_SFT) +#define NAU8825_I2S_DF_I2S (2 << NAU8825_I2S_DF_SFT) +#define NAU8825_I2S_DF_PCM_AB (3 << NAU8825_I2S_DF_SFT) + +/* I2S_PCM_CTRL2 (0x1d) */ +#define NAU8825_I2S_MS_SFT 3 +#define NAU8825_I2S_MS_MASK (1 << NAU8825_I2S_MS_SFT) +#define NAU8825_I2S_MS_MASTER (1 << NAU8825_I2S_MS_SFT) +#define NAU8825_I2S_MS_SLAVE (0 << NAU8825_I2S_MS_SFT) + +/* ADC_RATE (0x2b) */ +#define NAU8825_ADC_SINC4 (1 << 4) +#define NAU8825_ADC_SINC_DOWN_MASK 3 +#define NAU8825_ADC_SINC_DOWN_32 0 +#define NAU8825_ADC_SINC_DOWN_64 1 +#define NAU8825_ADC_SINC_DOWN_128 2 +#define NAU8825_ADC_SINC_DOWN_256 3 + +/* DAC_CTRL1 (0x2c) */ +#define NAU8825_DAC_CLIP_OFF (1 << 7) +#define NAU8825_DAC_OVERSAMPLE_MASK 7 +#define NAU8825_DAC_OVERSAMPLE_64 0 +#define NAU8825_DAC_OVERSAMPLE_256 1 +#define NAU8825_DAC_OVERSAMPLE_128 2 +#define NAU8825_DAC_OVERSAMPLE_32 4 + +/* MUTE_CTRL (0x31) */ +#define NAU8825_DAC_ZERO_CROSSING_EN (1 << 9) +#define NAU8825_DAC_SOFT_MUTE (1 << 9) + +/* HSVOL_CTRL (0x32) */ +#define NAU8825_HP_MUTE (1 << 15) + +/* DACL_CTRL (0x33) */ +#define NAU8825_DACL_CH_SEL_SFT 9 + +/* DACR_CTRL (0x34) */ +#define NAU8825_DACR_CH_SEL_SFT 9 + +/* I2C_DEVICE_ID (0x58) */ +#define NAU8825_GPIO2JD1 (1 << 7) +#define NAU8825_SOFTWARE_ID_MASK 0x3 +#define NAU8825_SOFTWARE_ID_NAU8825 0x0 + +/* BIAS_ADJ (0x66) */ +#define NAU8825_BIAS_VMID (1 << 6) +#define NAU8825_BIAS_VMID_SEL_MASK (3 << 4) +#define NAU8825_BIAS_VMID_SEL_OPEN (0 << 4) +#define NAU8825_BIAS_VMID_SEL_25KOHM (1 << 4) +#define NAU8825_BIAS_VMID_SEL_125KOHM (2 << 4) +#define NAU8825_BIAS_VMID_SEL_2_5KOHM (3 << 4) + +/* ANALOG_CONTROL_2 (0x6a) */ +#define NAU8825_HP_NON_CLASSG_CURRENT_2xADJ (1 << 12) +#define NAU8825_DAC_CAPACITOR_MSB (1 << 1) +#define NAU8825_DAC_CAPACITOR_LSB (1 << 0) + +/* ANALOG_ADC_2 (0x72) */ +#define NAU8825_ADC_VREFSEL_MASK (0x3 << 8) +#define NAU8825_ADC_VREFSEL_ANALOG (0 << 8) +#define NAU8825_ADC_VREFSEL_VMID (1 << 8) +#define NAU8825_ADC_VREFSEL_VMID_PLUS_0_5DB (2 << 8) +#define NAU8825_ADC_VREFSEL_VMID_PLUS_1DB (3 << 8) +#define NAU8825_POWERUP_ADCL (1 << 6) + +/* MIC_BIAS (0x74) */ +#define NAU8825_MICBIAS_JKSLV (1 << 14) +#define NAU8825_MICBIAS_JKR2 (1 << 12) +#define NAU8825_MICBIAS_POWERUP_SFT 8 + +/* BOOST (0x76) */ +#define NAU8825_PRECHARGE_DIS (1 << 13) +#define NAU8825_GLOBAL_BIAS_EN (1 << 12) +#define NAU8825_HP_BOOST_DIS (1 << 12) +#define NAU8825_SHORT_SHUTDOWN_EN (1 << 6) + +/* POWER_UP_CONTROL (0x7f) */ +#define NAU8825_POWERUP_HP_DRV_L (1 << 0) +#define NAU8825_POWERUP_HP_DRV_R (1 << 1) +#define NAU8825_POWERUP_DRV_IN_L (1 << 2) +#define NAU8825_POWERUP_DRV_IN_R (1 << 3) +#define NAU8825_POWERUP_INTEGR_L (1 << 4) +#define NAU8825_POWERUP_INTEGR_R (1 << 5) + +/* CHARGE_PUMP_AND_POWER_DOWN_CONTROL (0x80) */ +#define NAU8825_JAMNODCLOW (1 << 10) +#define NAU8825_POWER_DOWN_DACR (1 << 9) +#define NAU8825_POWER_DOWN_DACL (1 << 8) +#define NAU8825_CHANRGE_PUMP_EN (1 << 5) + + +/* System Clock Source */ +enum { + NAU8825_CLK_MCLK = 0, + NAU8825_CLK_INTERNAL, +}; + +int nau8825_enable_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack); + + +#endif /* __NAU8825_H__ */
On Mon, Jul 27, 2015 at 04:13:57PM -0700, Anatol Pomozov wrote:
Looks mostly good, a few things below though:
break;
- case 1:
dev_info(nau8825->dev, "OMTP (micgnd1) mic connected\n");
This is far too noisy - these should be dev_dbg() at most.
} else {
dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n");
nau8825_eject_jack(nau8825);
}
Things like this that aren't supposed to happen are fine but normal operation shouild be quiet.
- /* VMID Enable and Tieoff */
- regmap_write(regmap, NAU8825_REG_BIAS_ADJ, 0x0060);
You're leavinng VMID enabled all the timme?
- /* Jack Detect pull up (High=eject, Low=insert) */
- regmap_write(regmap, NAU8825_REG_GPIO12_CTRL, 0x0800);
This seems like it should be a board setting?
- /* Setup SAR ADC */
- regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280);
Lots of magic numbers in these things... some of them I can guess what's going on but this one is a bit obscure, perhaps it should be user controllable?
- /* Setup ADC x128 OSR */
- regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
- /* Setup DAC x128 OSR */
- regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);
I'd expect this to be user controllable.
+static int nau8825_i2c_probe(struct i2c_client *i2c,
- const struct i2c_device_id *id)
+{
- struct device *dev = &i2c->dev;
- struct nau8825 *nau8825;
- int ret, value;
- nau8825 = devm_kzalloc(dev, sizeof(*nau8825), GFP_KERNEL);
- if (!nau8825)
return -ENOMEM;
- i2c_set_clientdata(i2c, nau8825);
- nau8825->regmap = devm_regmap_init_i2c(i2c, &nau8825_regmap_config);
- if (IS_ERR(nau8825->regmap))
return PTR_ERR(nau8825->regmap);
- nau8825->irq = i2c->irq;
- nau8825->dev = dev;
- nau8825_reset_chip(nau8825->regmap);
- ret = regmap_read(nau8825->regmap, NAU8825_REG_I2C_DEVICE_ID, &value);
- if (ret < 0) {
dev_err(dev, "Failed to read device id from the NAU8825: %d\n",
ret);
return ret;
- }
- if ((value & NAU8825_SOFTWARE_ID_MASK) !=
NAU8825_SOFTWARE_ID_NAU8825) {
dev_err(dev, "Not a NAU8825 chip\n");
return -ENODEV;
- }
- return snd_soc_register_codec(&i2c->dev, &nau8825_codec_driver,
&nau8825_dai, 1);
+}
I'd expect any initial register initialistion to happen here (if only so we save power until the card registers).
Hi
On Fri, Jul 31, 2015 at 11:27 AM, Mark Brown broonie@kernel.org wrote:
On Mon, Jul 27, 2015 at 04:13:57PM -0700, Anatol Pomozov wrote:
Looks mostly good, a few things below though:
break;
case 1:
dev_info(nau8825->dev, "OMTP (micgnd1) mic connected\n");
This is far too noisy - these should be dev_dbg() at most.
Done
} else {
dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n");
nau8825_eject_jack(nau8825);
}
Things like this that aren't supposed to happen are fine but normal operation shouild be quiet.
/* VMID Enable and Tieoff */
regmap_write(regmap, NAU8825_REG_BIAS_ADJ, 0x0060);
You're leavinng VMID enabled all the timme?
Currently yes. One of the next steps is to look at the power management for this chip (set_bias and suspend/resume). I need to work with Nuvoton HW engineers to find correct sequence of steps.
/* Jack Detect pull up (High=eject, Low=insert) */
regmap_write(regmap, NAU8825_REG_GPIO12_CTRL, 0x0800);
This seems like it should be a board setting?
Created DTS property for it. Thanks.
/* Setup SAR ADC */
regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280);
Lots of magic numbers in these things...
These init config settings recommended by Nuvoton engineers. I found that chip requires quite a lot of initial preconfiguration to get it into operating state.
some of them I can guess what's going on but this one is a bit obscure, perhaps it should be user controllable?
This setting configures SARADC (ADC for sensing button presses). It sets sample rate, series resistor and ADC voltage range. It does not look like they need to be user-controllable.
/* Setup ADC x128 OSR */Moved to
regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
/* Setup DAC x128 OSR */
regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);
I'd expect this to be user controllable.
The oversampling configuration is important for chip audio quality. There is audible hissing without these settings.
My understanding that all users need to set these values and it is better to move it to the driver initialization sequence.
+static int nau8825_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
+{
struct device *dev = &i2c->dev;
struct nau8825 *nau8825;
int ret, value;
nau8825 = devm_kzalloc(dev, sizeof(*nau8825), GFP_KERNEL);
if (!nau8825)
return -ENOMEM;
i2c_set_clientdata(i2c, nau8825);
nau8825->regmap = devm_regmap_init_i2c(i2c, &nau8825_regmap_config);
if (IS_ERR(nau8825->regmap))
return PTR_ERR(nau8825->regmap);
nau8825->irq = i2c->irq;
nau8825->dev = dev;
nau8825_reset_chip(nau8825->regmap);
ret = regmap_read(nau8825->regmap, NAU8825_REG_I2C_DEVICE_ID, &value);
if (ret < 0) {
dev_err(dev, "Failed to read device id from the NAU8825: %d\n",
ret);
return ret;
}
if ((value & NAU8825_SOFTWARE_ID_MASK) !=
NAU8825_SOFTWARE_ID_NAU8825) {
dev_err(dev, "Not a NAU8825 chip\n");
return -ENODEV;
}
return snd_soc_register_codec(&i2c->dev, &nau8825_codec_driver,
&nau8825_dai, 1);
+}
I'd expect any initial register initialistion to happen here (if only so we save power until the card registers).
Moved most of the initialization here. The only part is left in codec_probe() is interruption initialization via I2S master mode toggling. That toggling trick depends on MCLK signal to initialize interruption block correctly. In our case (a tegra SoC device) MCLK is initialized later at audio platform driver probe.
On Mon, Aug 03, 2015 at 08:13:40PM -0700, Anatol Pomozov wrote:
On Fri, Jul 31, 2015 at 11:27 AM, Mark Brown broonie@kernel.org wrote:
On Mon, Jul 27, 2015 at 04:13:57PM -0700, Anatol Pomozov wrote:
/* Setup SAR ADC */
regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280);
Lots of magic numbers in these things...
These init config settings recommended by Nuvoton engineers. I found that chip requires quite a lot of initial preconfiguration to get it into operating state.
Random register write lists from the vendor for documented registers are not generally a good thing to rely on too much, they frequently set up use case specific things in the middle of other perhaps useful changes.
some of them I can guess what's going on but this one is a bit obscure, perhaps it should be user controllable?
This setting configures SARADC (ADC for sensing button presses). It sets sample rate, series resistor and ADC voltage range. It does not look like they need to be user-controllable.
Those sound like they may well be system dependent, the sample rate will most likely feed back into the sensitivity of the detection (how long the button needs to be pressed for and so on) and the other bits sound like they could change depending on the configuration of the accessory.
/* Setup ADC x128 OSR */Moved to
regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
/* Setup DAC x128 OSR */
regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);
I'd expect this to be user controllable.
The oversampling configuration is important for chip audio quality. There is audible hissing without these settings.
My understanding that all users need to set these values and it is better to move it to the driver initialization sequence.
That sounds like spectacularly poor quality of implementation, but even if there's a noticeable reduction in audio quality the control should still be given to the user, they may have an application where power is much more important than audio quality (eg, if they are doing some form of recognition on the audio then all they need is something good enough for their algorithm).
I'd expect any initial register initialistion to happen here (if only so we save power until the card registers).
Moved most of the initialization here. The only part is left in codec_probe() is interruption initialization via I2S master mode toggling. That toggling trick depends on MCLK signal to initialize interruption block correctly. In our case (a tegra SoC device) MCLK is initialized later at audio platform driver probe.
If you need to control MCLK you should be using the clock API to get MCLK which you can do in the driver model probe. Relying on the machine driver to control MCLK for you in this way is at best fragile.
Hi
On Tue, Aug 4, 2015 at 2:47 AM, Mark Brown broonie@kernel.org wrote:
On Mon, Aug 03, 2015 at 08:13:40PM -0700, Anatol Pomozov wrote:
On Fri, Jul 31, 2015 at 11:27 AM, Mark Brown broonie@kernel.org wrote:
On Mon, Jul 27, 2015 at 04:13:57PM -0700, Anatol Pomozov wrote:
/* Setup SAR ADC */
regmap_write(regmap, NAU8825_REG_SAR_CTRL, 0x0280);
Lots of magic numbers in these things...
Okay, I moved the magic number to board configuration options. Now most of these numbers can be configured via dts.
/* Setup ADC x128 OSR */Moved to
regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
/* Setup DAC x128 OSR */
regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);
I'd expect this to be user controllable.
The oversampling configuration is important for chip audio quality. There is audible hissing without these settings.
My understanding that all users need to set these values and it is better to move it to the driver initialization sequence.
That sounds like spectacularly poor quality of implementation, but even if there's a noticeable reduction in audio quality the control should still be given to the user, they may have an application where power is much more important than audio quality (eg, if they are doing some form of recognition on the audio then all they need is something good enough for their algorithm).
Moved it to ALSA controls. Though I still feel a bit uncomfortable that the default control value is not usable. I have to set "ADC Decimation Rate" and "DAC Oversampling Rate" controls in userspace from its defaults to "128".
I'd expect any initial register initialistion to happen here (if only so we save power until the card registers).
Moved most of the initialization here. The only part is left in codec_probe() is interruption initialization via I2S master mode toggling. That toggling trick depends on MCLK signal to initialize interruption block correctly. In our case (a tegra SoC device) MCLK is initialized later at audio platform driver probe.
If you need to control MCLK you should be using the clock API to get MCLK which you can do in the driver model probe. Relying on the machine driver to control MCLK for you in this way is at best fragile.
After discussing it with my teammates who works on Intel+NAU8825 platform I found that they use MCLK only at playback/capture time. The rest of the time VCO is suppose to be sysclk source. Thus using get_clk() is not flexible for all cases.
What I propose is to initialize driver with VCO as a sysclk source. And later a platform driver can change it to MCLK. Added set_sysclk() stub that works well with VCO->MCKL transition, tested it on TegraX1. Follow-up changes should improve VCO/FLL configuration.
codec: nau8825@1a {
compatible = "nuvoton,nau8825";
reg = <0x1a>;
nuvoton,jkdet-pullup = "true";
...this isn't how boolean properties are done, the property simply needs to exist.
Fixed.
+static int nau8825_output_driver_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
+{
struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
struct regmap *regmap = nau8825->regmap;
if (SND_SOC_DAPM_EVENT_ON(event)) {
/* Power Up L and R Output Drivers */
regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3c,
0x3c);
/* Power up Main Output Drivers (The main driver must be turned
on after the predriver to avoid pops) */
regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3,
0x3);
} else {
/* Power Down L and R Output Drivers */
regmap_update_bits(regmap, NAU8825_REG_POWER_UP_CONTROL, 0x3f,
0x0);
You should be able to implement this with _S widgets without requiring explicit code, they're designed for exactly this situation.
I've decided to split this function into 6 different widgets. And it seems enough. No audible pop/clicks here.
+int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
struct snd_soc_jack *jack)
+{
struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA);
snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
The driver shouldn't do this - it's up to the system integration to define how the buttons are mapped.
I was following code from ts3a227e driver that does the same.
nau8825->jack = jack;
return 0;
+} +EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect);
This function doesn't appear to affect the hardware. I would expect that the jack detection hardware is turned off when not in use.
Disabling jack detection means disable HeadsetCompletion IRQ. But the IRQ handler does a bunch of manual stuff like grounding/ungrounding jack pins. If IRQ handler is not called then neither playback nor capture will not work.
At this point this function just sets jack structure needed later in IRQ handler.
+static void nau8825_setup_buttons(struct regmap *regmap) +{
/* Setup Button Detect (Debounce, number of buttons, and Hysteresis) */
regmap_write(regmap, NAU8825_REG_KEYDET_CTRL, 0x7311);
/* Setup 4 buttons impedane according to Android specification
* https://source.android.com/accessories/headset-spec.html
* Button 0 - 0-70 Ohm
* Button 1 - 110-180 Ohm
* Button 2 - 210-290 Ohm
* Button 3 - 360-680 Ohm
*/
regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_1, 0x0f1f);
regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_2, 0x325f);
+}
This looks like system configuration...
Moved to DTS.
/* The interrupt clock is gated by x1[10:8],
* one of them needs to be enabled all the time for
* interrupts to happen. */
snd_soc_dapm_force_enable_pin(&codec->dapm, "DDACR");
snd_soc_dapm_sync(&codec->dapm);
This should be tied to jack detection, not done unconditionally.
On Mon, Aug 10, 2015 at 05:32:32PM -0700, Anatol Pomozov wrote:
On Tue, Aug 4, 2015 at 2:47 AM, Mark Brown broonie@kernel.org wrote:
On Mon, Aug 03, 2015 at 08:13:40PM -0700, Anatol Pomozov wrote:
/* Setup ADC x128 OSR */Moved to
regmap_write(regmap, NAU8825_REG_ADC_RATE, 0x0002);
/* Setup DAC x128 OSR */
regmap_write(regmap, NAU8825_REG_DAC_CTRL1, 0x0082);
I'd expect this to be user controllable.
The oversampling configuration is important for chip audio quality. There is audible hissing without these settings.
My understanding that all users need to set these values and it is better to move it to the driver initialization sequence.
That sounds like spectacularly poor quality of implementation, but even if there's a noticeable reduction in audio quality the control should still be given to the user, they may have an application where power is
Moved it to ALSA controls. Though I still feel a bit uncomfortable that the default control value is not usable. I have to set "ADC Decimation Rate" and "DAC Oversampling Rate" controls in userspace from its defaults to "128".
Setting the default isn't too bad in this case (especially with a comment explaining why) - that doesn't preclude also providing user configuration.
If you need to control MCLK you should be using the clock API to get MCLK which you can do in the driver model probe. Relying on the machine driver to control MCLK for you in this way is at best fragile.
After discussing it with my teammates who works on Intel+NAU8825 platform I found that they use MCLK only at playback/capture time. The rest of the time VCO is suppose to be sysclk source. Thus using get_clk() is not flexible for all cases.
I'm sorry, I don't understand. What makes you say that "using get_clk() is not flexible for all cases", I can't quite parse that bit?
+int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
struct snd_soc_jack *jack)
+{
struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA);
snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
The driver shouldn't do this - it's up to the system integration to define how the buttons are mapped.
I was following code from ts3a227e driver that does the same.
That device is specifically tied to Chromebooks which have a defined mapping, as opposed to being a generic device which is usable on a range of devices.
nau8825->jack = jack;
return 0;
+} +EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect);
This function doesn't appear to affect the hardware. I would expect that the jack detection hardware is turned off when not in use.
Disabling jack detection means disable HeadsetCompletion IRQ. But the IRQ handler does a bunch of manual stuff like grounding/ungrounding jack pins. If IRQ handler is not called then neither playback nor capture will not work.
The assumption would be that if the system doesn't have or need jack detection then the jack pins won't be reconfigurable anyway.
At this point this function just sets jack structure needed later in IRQ handler.
I can see what it's doing, I'd just also expect it to also be starting and stopping the detection in the hardware.
Hi
Moved it to ALSA controls. Though I still feel a bit uncomfortable that the default control value is not usable. I have to set "ADC Decimation Rate" and "DAC Oversampling Rate" controls in userspace from its defaults to "128".
Setting the default isn't too bad in this case (especially with a comment explaining why) - that doesn't preclude also providing user configuration.
What is the best way to set default control value? Just a regmap_write()?
After discussing it with my teammates who works on Intel+NAU8825 platform I found that they use MCLK only at playback/capture time. The rest of the time VCO is suppose to be sysclk source. Thus using get_clk() is not flexible for all cases.
I'm sorry, I don't understand. What makes you say that "using get_clk() is not flexible for all cases", I can't quite parse that bit?
My initial assumption was that you are talking about moving complete clock handling (clock initialization, MCLK frequency calculation based on rate, policy of switching clock sources i.e. everything that platform drivers do) to the the driver code.
I've looked at other codec drivers and did not find many examples of using clk api. The drivers seems expect clock signal is pre-configured already. e.g. tegra has a utility function that configures audio clocks tegra_asoc_utils_set_rate() and it actively used in tegra-based platform drivers.
Locally I tried to move clock initialization out of platform code to codec itself. Codec calls devm_clk_get() in init and sets frequency in nau8825_set_sysclk(). See it here https://github.com/anatol/linux/tree/nau8825 It works fine on a Tegra-based device. But I found that devm_clk_get() is DTS specific and does not work with ACPI. We need NAU8825 driver for Intel platform as well.
I was following code from ts3a227e driver that does the same.
That device is specifically tied to Chromebooks which have a defined mapping, as opposed to being a generic device which is usable on a range of devices.
I do not see anything Chromebook specific about this chip
http://www.ti.com/product/ts3a227e
This function doesn't appear to affect the hardware. I would expect that the jack detection hardware is turned off when not in use.
Disabling jack detection means disable HeadsetCompletion IRQ. But the IRQ handler does a bunch of manual stuff like grounding/ungrounding jack pins. If IRQ handler is not called then neither playback nor capture will not work.
The assumption would be that if the system doesn't have or need jack detection then the jack pins won't be reconfigurable anyway.
At this point this function just sets jack structure needed later in IRQ handler.
I can see what it's doing, I'd just also expect it to also be starting and stopping the detection in the hardware.
If we move IRQ enabling from init() function to enable_jack_detect() then it means chip will be unusable after the init(). It won't be able to playback nor capture audio. All developers will be forced to call nau88225_enable_jack_detect() from their platform driver to enable basic chip functionality. I think it is better if basic functionality is enabled during driver init and does not require additional steps.
It is better if I rename nau8825_enable_jack_detect() to nau8825_set_jack() to make cleaner what the function suppose to do. It sets jack structure that driver uses to report events to upper level. If the jack pointer is not set then driver still handles jack insert/eject internally but does not report it to the userspace.
On Wed, Aug 12, 2015 at 04:08:31PM -0700, Anatol Pomozov wrote:
Moved it to ALSA controls. Though I still feel a bit uncomfortable that the default control value is not usable. I have to set "ADC Decimation Rate" and "DAC Oversampling Rate" controls in userspace from its defaults to "128".
Setting the default isn't too bad in this case (especially with a comment explaining why) - that doesn't preclude also providing user configuration.
What is the best way to set default control value? Just a regmap_write()?
Yes.
I'm sorry, I don't understand. What makes you say that "using get_clk() is not flexible for all cases", I can't quite parse that bit?
My initial assumption was that you are talking about moving complete clock handling (clock initialization, MCLK frequency calculation based on rate, policy of switching clock sources i.e. everything that platform drivers do) to the the driver code.
That is the ideal thing.
I've looked at other codec drivers and did not find many examples of using clk api. The drivers seems expect clock signal is pre-configured already. e.g. tegra has a utility function that configures audio clocks tegra_asoc_utils_set_rate() and it actively used in tegra-based platform drivers.
Most of these drivers predate the clock API, while there's no pressing need to update things (and it's pretty disruptive to existing systems especially older ones) if you're running into problems with new code that are best fixed with the clock API then you should do that.
Locally I tried to move clock initialization out of platform code to codec itself. Codec calls devm_clk_get() in init and sets frequency in nau8825_set_sysclk(). See it here https://github.com/anatol/linux/tree/nau8825 It works fine on a Tegra-based device. But I found that devm_clk_get() is DTS specific and does not work with ACPI. We need NAU8825 driver for Intel platform as well.
No, that is not the case at all - what makes you claim that devm_clk_get() is DT specific?
I was following code from ts3a227e driver that does the same.
That device is specifically tied to Chromebooks which have a defined mapping, as opposed to being a generic device which is usable on a range of devices.
I do not see anything Chromebook specific about this chip
My understanding is that the accessories it supports are Chrombook/Android ones (note that there's no configuration of ranges for detection in the code).
At this point this function just sets jack structure needed later in IRQ handler.
I can see what it's doing, I'd just also expect it to also be starting and stopping the detection in the hardware.
If we move IRQ enabling from init() function to enable_jack_detect() then it means chip will be unusable after the init(). It won't be able to playback nor capture audio. All developers will be forced to call nau88225_enable_jack_detect() from their platform driver to enable basic chip functionality. I think it is better if basic functionality is enabled during driver init and does not require additional steps.
I very much doubt that this will be a problem for all boards. To repeat what I said in my previous reply:
| The assumption would be that if the system doesn't have or need jack | detection then the jack pins won't be reconfigurable anyway.
Your board may be wired up with jack detection configured in a way that needs this, I would be surprised if all boards were.
It is better if I rename nau8825_enable_jack_detect() to nau8825_set_jack() to make cleaner what the function suppose to do. It sets jack structure that driver uses to report events to upper level. If the jack pointer is not set then driver still handles jack insert/eject internally but does not report it to the userspace.
No, that doesn't help.
Hi
On Thu, Aug 13, 2015 at 4:26 AM, Mark Brown broonie@kernel.org wrote:
Locally I tried to move clock initialization out of platform code to codec itself. Codec calls devm_clk_get() in init and sets frequency in nau8825_set_sysclk(). See it here https://github.com/anatol/linux/tree/nau8825 It works fine on a Tegra-based device. But I found that devm_clk_get() is DTS specific and does not work with ACPI. We need NAU8825 driver for Intel platform as well.
No, that is not the case at all - what makes you claim that devm_clk_get() is DT specific?
clk_dev uses of_clk_get_by_name() that is implemented for DTS only. See include/linux/clk.h.
ACPI has a compatible API for getting simple properties (strings/numbers), but no API for dealing with clocks.
I very much doubt that this will be a problem for all boards. To repeat what I said in my previous reply:
| The assumption would be that if the system doesn't have or need jack | detection then the jack pins won't be reconfigurable anyway.
Your board may be wired up with jack detection configured in a way that needs this, I would be surprised if all boards were.
I am still trying to understand the use-case for NAU8825 without jack detection functionality. Are you talking about a board configuration that does not wire jack to JKDET codec pin and (maybe) statically connects output to speakers?
If platform driver does not enable jack detection, what NAU8825 functionality suppose to work? Does output (HPL/HPR) suppose to work? Does mic suppose to work? Is OMTP/CTIA mic detection functionality needed?
On Thu, Aug 13, 2015 at 12:44:33PM -0700, Anatol Pomozov wrote:
On Thu, Aug 13, 2015 at 4:26 AM, Mark Brown broonie@kernel.org wrote:
No, that is not the case at all - what makes you claim that devm_clk_get() is DT specific?
clk_dev uses of_clk_get_by_name() that is implemented for DTS only. See include/linux/clk.h.
That means that the clock API can work with DT, not that DT is mandatory - notice how there are stubs for the non-DT case.
ACPI has a compatible API for getting simple properties (strings/numbers), but no API for dealing with clocks.
So ACPI based platforms have to deal with providing clocks in some other way, that's something that is abstracted away by the clock API. There are a number of drivers for both board file based and ACPI based systems already in tree which manage to make use of the clock API.
I very much doubt that this will be a problem for all boards. To repeat what I said in my previous reply:
| The assumption would be that if the system doesn't have or need jack | detection then the jack pins won't be reconfigurable anyway.
Your board may be wired up with jack detection configured in a way that needs this, I would be surprised if all boards were.
I am still trying to understand the use-case for NAU8825 without jack detection functionality. Are you talking about a board configuration that does not wire jack to JKDET codec pin and (maybe) statically connects output to speakers?
I'm not specifically familiar with this CODEC, however this sort of detection circuitry is all pretty standard. I'm thinking of the sort of straightforward configurations where the detection inputs and any outputs required to flip polarity of the jack are just not wired up.
If platform driver does not enable jack detection, what NAU8825 functionality suppose to work? Does output (HPL/HPR) suppose to work? Does mic suppose to work? Is OMTP/CTIA mic detection functionality needed?
I'd expect the detection functionality wouldn't work without being wired up as expected but I'd expect it to be perfectly possible to support the headphones with the default ground confirguration. I'd also expect mics to work, either connected to the headset with a fixed polarity or just elsewhere in the system. If the device is capable of supprorting headphones it must be capable of supporting them with a fixed ground, and similarly for microphones.
Hi
On Thu, Aug 13, 2015 at 3:33 PM, Mark Brown broonie@kernel.org wrote:
On Thu, Aug 13, 2015 at 12:44:33PM -0700, Anatol Pomozov wrote:
On Thu, Aug 13, 2015 at 4:26 AM, Mark Brown broonie@kernel.org wrote:
No, that is not the case at all - what makes you claim that devm_clk_get() is DT specific?
clk_dev uses of_clk_get_by_name() that is implemented for DTS only. See include/linux/clk.h.
That means that the clock API can work with DT, not that DT is mandatory - notice how there are stubs for the non-DT case.
ACPI has a compatible API for getting simple properties (strings/numbers), but no API for dealing with clocks.
So ACPI based platforms have to deal with providing clocks in some other way, that's something that is abstracted away by the clock API. There are a number of drivers for both board file based and ACPI based systems already in tree which manage to make use of the clock API.
What is the best example of driver that uses clk API in both DT and ACPI friendly way? I will use it as a template for my driver.
I am still trying to understand the use-case for NAU8825 without jack detection functionality. Are you talking about a board configuration that does not wire jack to JKDET codec pin and (maybe) statically connects output to speakers?
I'm not specifically familiar with this CODEC, however this sort of detection circuitry is all pretty standard. I'm thinking of the sort of straightforward configurations where the detection inputs and any outputs required to flip polarity of the jack are just not wired up.
This corresponds to codec's JKDET/GPIO1 pin that is used as a default source for jack detection interruption. And better place to describe a board configuration like this is DTS.
Looking at Nuvoton's documentation I see it mentions that jack detection IRQ source can be set to one of the GPIO pins or its combination. In DTS it will look something like nuvoton,jkdet-source = <N>; // 0 to disable jkdet functionality, 1 for gpio1 (chip default), 2 for gpio2
The chip documentation on jack detection is sparse so I need to clarify all aspects of configuration with Nuvoton engs.
And I believe this jack detection source configuration can be added later and should not block the rest of the driver.
On Sun, Aug 16, 2015 at 12:38:41AM -0700, Anatol Pomozov wrote:
On Thu, Aug 13, 2015 at 3:33 PM, Mark Brown broonie@kernel.org wrote:
So ACPI based platforms have to deal with providing clocks in some other way, that's something that is abstracted away by the clock API. There are a number of drivers for both board file based and ACPI based systems already in tree which manage to make use of the clock API.
What is the best example of driver that uses clk API in both DT and ACPI friendly way? I will use it as a template for my driver.
Any driver using the clock API should provide a suitable reference. The mechanism used to resolve clocks is completely opaque to the driver using the clock API.
Hi
Sorry for the delay.
On Mon, Aug 17, 2015 at 11:20 AM, Mark Brown broonie@kernel.org wrote:
On Sun, Aug 16, 2015 at 12:38:41AM -0700, Anatol Pomozov wrote:
On Thu, Aug 13, 2015 at 3:33 PM, Mark Brown broonie@kernel.org wrote:
So ACPI based platforms have to deal with providing clocks in some other way, that's something that is abstracted away by the clock API. There are a number of drivers for both board file based and ACPI based systems already in tree which manage to make use of the clock API.
What is the best example of driver that uses clk API in both DT and ACPI friendly way? I will use it as a template for my driver.
Any driver using the clock API should provide a suitable reference. The mechanism used to resolve clocks is completely opaque to the driver using the clock API.
Okay, I added clk support for MCLK. set_sysclk() callback sets rate for this clock. Platform driver needs to call snd_soc_codec_set_sysclk() to choose codec clock source and set its rate.
Another change - added 10ms delay after enabling charge pump to eliminate pop.
participants (2)
-
Anatol Pomozov
-
Mark Brown