Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  nau8810.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * nau8810.c  --  NAU8810 ALSA Soc Audio driver
 *
 * Copyright 2016 Nuvoton Technology Corp.
 *
 * Author: David Lin <ctlin0@nuvoton.com>
 *
 * Based on WM8974.c
 */


#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>

#include "nau8810.h"

#define NAU_PLL_FREQ_MAX 100000000
#define NAU_PLL_FREQ_MIN 90000000
#define NAU_PLL_REF_MAX 33000000
#define NAU_PLL_REF_MIN 8000000
#define NAU_PLL_OPTOP_MIN 6


static const int nau8810_mclk_scaler[] = { 10, 15, 20, 30, 40, 60, 80, 120 };

static const struct reg_default nau8810_reg_defaults[] = {
 { NAU8810_REG_POWER1, 0x0000 },
 { NAU8810_REG_POWER2, 0x0000 },
 { NAU8810_REG_POWER3, 0x0000 },
 { NAU8810_REG_IFACE, 0x0050 },
 { NAU8810_REG_COMP, 0x0000 },
 { NAU8810_REG_CLOCK, 0x0140 },
 { NAU8810_REG_SMPLR, 0x0000 },
 { NAU8810_REG_DAC, 0x0000 },
 { NAU8810_REG_DACGAIN, 0x00FF },
 { NAU8810_REG_ADC, 0x0100 },
 { NAU8810_REG_ADCGAIN, 0x00FF },
 { NAU8810_REG_EQ1, 0x012C },
 { NAU8810_REG_EQ2, 0x002C },
 { NAU8810_REG_EQ3, 0x002C },
 { NAU8810_REG_EQ4, 0x002C },
 { NAU8810_REG_EQ5, 0x002C },
 { NAU8810_REG_DACLIM1, 0x0032 },
 { NAU8810_REG_DACLIM2, 0x0000 },
 { NAU8810_REG_NOTCH1, 0x0000 },
 { NAU8810_REG_NOTCH2, 0x0000 },
 { NAU8810_REG_NOTCH3, 0x0000 },
 { NAU8810_REG_NOTCH4, 0x0000 },
 { NAU8810_REG_ALC1, 0x0038 },
 { NAU8810_REG_ALC2, 0x000B },
 { NAU8810_REG_ALC3, 0x0032 },
 { NAU8810_REG_NOISEGATE, 0x0000 },
 { NAU8810_REG_PLLN, 0x0008 },
 { NAU8810_REG_PLLK1, 0x000C },
 { NAU8810_REG_PLLK2, 0x0093 },
 { NAU8810_REG_PLLK3, 0x00E9 },
 { NAU8810_REG_ATTEN, 0x0000 },
 { NAU8810_REG_INPUT_SIGNAL, 0x0003 },
 { NAU8810_REG_PGAGAIN, 0x0010 },
 { NAU8810_REG_ADCBOOST, 0x0100 },
 { NAU8810_REG_OUTPUT, 0x0002 },
 { NAU8810_REG_SPKMIX, 0x0001 },
 { NAU8810_REG_SPKGAIN, 0x0039 },
 { NAU8810_REG_MONOMIX, 0x0001 },
 { NAU8810_REG_POWER4, 0x0000 },
 { NAU8810_REG_TSLOTCTL1, 0x0000 },
 { NAU8810_REG_TSLOTCTL2, 0x0020 },
 { NAU8810_REG_DEVICE_REVID, 0x0000 },
 { NAU8810_REG_I2C_DEVICEID, 0x001A },
 { NAU8810_REG_ADDITIONID, 0x00CA },
 { NAU8810_REG_RESERVE, 0x0124 },
 { NAU8810_REG_OUTCTL, 0x0001 },
 { NAU8810_REG_ALC1ENHAN1, 0x0010 },
 { NAU8810_REG_ALC1ENHAN2, 0x0000 },
 { NAU8810_REG_MISCCTL, 0x0000 },
 { NAU8810_REG_OUTTIEOFF, 0x0000 },
 { NAU8810_REG_AGCP2POUT, 0x0000 },
 { NAU8810_REG_AGCPOUT, 0x0000 },
 { NAU8810_REG_AMTCTL, 0x0000 },
 { NAU8810_REG_OUTTIEOFFMAN, 0x0000 },
};

static bool nau8810_readable_reg(struct device *dev, unsigned int reg)
{
 switch (reg) {
 case NAU8810_REG_RESET ... NAU8810_REG_SMPLR:
 case NAU8810_REG_DAC ... NAU8810_REG_DACGAIN:
 case NAU8810_REG_ADC ... NAU8810_REG_ADCGAIN:
 case NAU8810_REG_EQ1 ... NAU8810_REG_EQ5:
 case NAU8810_REG_DACLIM1 ... NAU8810_REG_DACLIM2:
 case NAU8810_REG_NOTCH1 ... NAU8810_REG_NOTCH4:
 case NAU8810_REG_ALC1 ... NAU8810_REG_ATTEN:
 case NAU8810_REG_INPUT_SIGNAL ... NAU8810_REG_PGAGAIN:
 case NAU8810_REG_ADCBOOST:
 case NAU8810_REG_OUTPUT ... NAU8810_REG_SPKMIX:
 case NAU8810_REG_SPKGAIN:
 case NAU8810_REG_MONOMIX:
 case NAU8810_REG_POWER4 ... NAU8810_REG_TSLOTCTL2:
 case NAU8810_REG_DEVICE_REVID ... NAU8810_REG_RESERVE:
 case NAU8810_REG_OUTCTL ... NAU8810_REG_ALC1ENHAN2:
 case NAU8810_REG_MISCCTL:
 case NAU8810_REG_OUTTIEOFF ... NAU8810_REG_OUTTIEOFFMAN:
  return true;
 default:
  return false;
 }
}

static bool nau8810_writeable_reg(struct device *dev, unsigned int reg)
{
 switch (reg) {
 case NAU8810_REG_RESET ... NAU8810_REG_SMPLR:
 case NAU8810_REG_DAC ... NAU8810_REG_DACGAIN:
 case NAU8810_REG_ADC ... NAU8810_REG_ADCGAIN:
 case NAU8810_REG_EQ1 ... NAU8810_REG_EQ5:
 case NAU8810_REG_DACLIM1 ... NAU8810_REG_DACLIM2:
 case NAU8810_REG_NOTCH1 ... NAU8810_REG_NOTCH4:
 case NAU8810_REG_ALC1 ... NAU8810_REG_ATTEN:
 case NAU8810_REG_INPUT_SIGNAL ... NAU8810_REG_PGAGAIN:
 case NAU8810_REG_ADCBOOST:
 case NAU8810_REG_OUTPUT ... NAU8810_REG_SPKMIX:
 case NAU8810_REG_SPKGAIN:
 case NAU8810_REG_MONOMIX:
 case NAU8810_REG_POWER4 ... NAU8810_REG_TSLOTCTL2:
 case NAU8810_REG_OUTCTL ... NAU8810_REG_ALC1ENHAN2:
 case NAU8810_REG_MISCCTL:
 case NAU8810_REG_OUTTIEOFF ... NAU8810_REG_OUTTIEOFFMAN:
  return true;
 default:
  return false;
 }
}

static bool nau8810_volatile_reg(struct device *dev, unsigned int reg)
{
 switch (reg) {
 case NAU8810_REG_RESET:
 case NAU8810_REG_DEVICE_REVID ... NAU8810_REG_RESERVE:
  return true;
 default:
  return false;
 }
}

/* The EQ parameters get function is to get the 5 band equalizer control.
 * The regmap raw read can't work here because regmap doesn't provide
 * value format for value width of 9 bits. Therefore, the driver reads data
 * from cache and makes value format according to the endianness of
 * bytes type control element.
 */

static int nau8810_eq_get(struct snd_kcontrol *kcontrol,
 struct snd_ctl_elem_value *ucontrol)
{
 struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
 struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component);
 struct soc_bytes_ext *params = (void *)kcontrol->private_value;
 int i, reg, reg_val;
 u16 *val;
 __be16 tmp;

 val = (u16 *)ucontrol->value.bytes.data;
 reg = NAU8810_REG_EQ1;
 for (i = 0; i < params->max / sizeof(u16); i++) {
  regmap_read(nau8810->regmap, reg + i, ®_val);
  /* conversion of 16-bit integers between native CPU format
 * and big endian format
 */

  tmp = cpu_to_be16(reg_val);
  memcpy(val + i, &tmp, sizeof(tmp));
 }

 return 0;
}

/* The EQ parameters put function is to make configuration of 5 band equalizer
 * control. These configuration includes central frequency, equalizer gain,
 * cut-off frequency, bandwidth control, and equalizer path.
 * The regmap raw write can't work here because regmap doesn't provide
 * register and value format for register with address 7 bits and value 9 bits.
 * Therefore, the driver makes value format according to the endianness of
 * bytes type control element and writes data to codec.
 */

static int nau8810_eq_put(struct snd_kcontrol *kcontrol,
 struct snd_ctl_elem_value *ucontrol)
{
 struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
 struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component);
 struct soc_bytes_ext *params = (void *)kcontrol->private_value;
 void *data;
 u16 *val, value;
 int i, reg, ret;
 __be16 *tmp;

 data = kmemdup(ucontrol->value.bytes.data,
  params->max, GFP_KERNEL | GFP_DMA);
 if (!data)
  return -ENOMEM;

 val = (u16 *)data;
 reg = NAU8810_REG_EQ1;
 for (i = 0; i < params->max / sizeof(u16); i++) {
  /* conversion of 16-bit integers between native CPU format
 * and big endian format
 */

  tmp = (__be16 *)(val + i);
  value = be16_to_cpup(tmp);
  ret = regmap_write(nau8810->regmap, reg + i, value);
  if (ret) {
   dev_err(component->dev, "EQ configuration fail, register: %x ret: %d\n",
    reg + i, ret);
   kfree(data);
   return ret;
  }
 }
 kfree(data);

 return 0;
}

static const char * const nau8810_companding[] = {
 "Off""NC""u-law""A-law" };

static const struct soc_enum nau8810_companding_adc_enum =
 SOC_ENUM_SINGLE(NAU8810_REG_COMP, NAU8810_ADCCM_SFT,
  ARRAY_SIZE(nau8810_companding), nau8810_companding);

static const struct soc_enum nau8810_companding_dac_enum =
 SOC_ENUM_SINGLE(NAU8810_REG_COMP, NAU8810_DACCM_SFT,
  ARRAY_SIZE(nau8810_companding), nau8810_companding);

static const char * const nau8810_deemp[] = {
 "None""32kHz""44.1kHz""48kHz" };

static const struct soc_enum nau8810_deemp_enum =
 SOC_ENUM_SINGLE(NAU8810_REG_DAC, NAU8810_DEEMP_SFT,
  ARRAY_SIZE(nau8810_deemp), nau8810_deemp);

static const char * const nau8810_eqmode[] = {"Capture""Playback" };

static const struct soc_enum nau8810_eqmode_enum =
 SOC_ENUM_SINGLE(NAU8810_REG_EQ1, NAU8810_EQM_SFT,
  ARRAY_SIZE(nau8810_eqmode), nau8810_eqmode);

static const char * const nau8810_alc[] = {"Normal""Limiter" };

static const struct soc_enum nau8810_alc_enum =
 SOC_ENUM_SINGLE(NAU8810_REG_ALC3, NAU8810_ALCM_SFT,
  ARRAY_SIZE(nau8810_alc), nau8810_alc);

static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);

static const struct snd_kcontrol_new nau8810_snd_controls[] = {
 SOC_ENUM("ADC Companding", nau8810_companding_adc_enum),
 SOC_ENUM("DAC Companding", nau8810_companding_dac_enum),
 SOC_ENUM("DAC De-emphasis", nau8810_deemp_enum),

 SOC_ENUM("EQ Function", nau8810_eqmode_enum),
 SND_SOC_BYTES_EXT("EQ Parameters", 10,
    nau8810_eq_get, nau8810_eq_put),

 SOC_SINGLE("DAC Inversion Switch", NAU8810_REG_DAC,
  NAU8810_DACPL_SFT, 1, 0),
 SOC_SINGLE_TLV("Playback Volume", NAU8810_REG_DACGAIN,
  NAU8810_DACGAIN_SFT, 0xff, 0, digital_tlv),

 SOC_SINGLE("High Pass Filter Switch", NAU8810_REG_ADC,
  NAU8810_HPFEN_SFT, 1, 0),
 SOC_SINGLE("High Pass Cut Off", NAU8810_REG_ADC,
  NAU8810_HPF_SFT, 0x7, 0),

 SOC_SINGLE("ADC Inversion Switch", NAU8810_REG_ADC,
  NAU8810_ADCPL_SFT, 1, 0),
 SOC_SINGLE_TLV("Capture Volume", NAU8810_REG_ADCGAIN,
  NAU8810_ADCGAIN_SFT, 0xff, 0, digital_tlv),

 SOC_SINGLE_TLV("EQ1 Volume", NAU8810_REG_EQ1,
  NAU8810_EQ1GC_SFT, 0x18, 1, eq_tlv),
 SOC_SINGLE_TLV("EQ2 Volume", NAU8810_REG_EQ2,
  NAU8810_EQ2GC_SFT, 0x18, 1, eq_tlv),
 SOC_SINGLE_TLV("EQ3 Volume", NAU8810_REG_EQ3,
  NAU8810_EQ3GC_SFT, 0x18, 1, eq_tlv),
 SOC_SINGLE_TLV("EQ4 Volume", NAU8810_REG_EQ4,
  NAU8810_EQ4GC_SFT, 0x18, 1, eq_tlv),
 SOC_SINGLE_TLV("EQ5 Volume", NAU8810_REG_EQ5,
  NAU8810_EQ5GC_SFT, 0x18, 1, eq_tlv),

 SOC_SINGLE("DAC Limiter Switch", NAU8810_REG_DACLIM1,
  NAU8810_DACLIMEN_SFT, 1, 0),
 SOC_SINGLE("DAC Limiter Decay", NAU8810_REG_DACLIM1,
  NAU8810_DACLIMDCY_SFT, 0xf, 0),
 SOC_SINGLE("DAC Limiter Attack", NAU8810_REG_DACLIM1,
  NAU8810_DACLIMATK_SFT, 0xf, 0),
 SOC_SINGLE("DAC Limiter Threshold", NAU8810_REG_DACLIM2,
  NAU8810_DACLIMTHL_SFT, 0x7, 0),
 SOC_SINGLE("DAC Limiter Boost", NAU8810_REG_DACLIM2,
  NAU8810_DACLIMBST_SFT, 0xf, 0),

 SOC_ENUM("ALC Mode", nau8810_alc_enum),
 SOC_SINGLE("ALC Enable Switch", NAU8810_REG_ALC1,
  NAU8810_ALCEN_SFT, 1, 0),
 SOC_SINGLE("ALC Max Volume", NAU8810_REG_ALC1,
  NAU8810_ALCMXGAIN_SFT, 0x7, 0),
 SOC_SINGLE("ALC Min Volume", NAU8810_REG_ALC1,
  NAU8810_ALCMINGAIN_SFT, 0x7, 0),
 SOC_SINGLE("ALC ZC Switch", NAU8810_REG_ALC2,
  NAU8810_ALCZC_SFT, 1, 0),
 SOC_SINGLE("ALC Hold", NAU8810_REG_ALC2,
  NAU8810_ALCHT_SFT, 0xf, 0),
 SOC_SINGLE("ALC Target", NAU8810_REG_ALC2,
  NAU8810_ALCSL_SFT, 0xf, 0),
 SOC_SINGLE("ALC Decay", NAU8810_REG_ALC3,
  NAU8810_ALCDCY_SFT, 0xf, 0),
 SOC_SINGLE("ALC Attack", NAU8810_REG_ALC3,
  NAU8810_ALCATK_SFT, 0xf, 0),
 SOC_SINGLE("ALC Noise Gate Switch", NAU8810_REG_NOISEGATE,
  NAU8810_ALCNEN_SFT, 1, 0),
 SOC_SINGLE("ALC Noise Gate Threshold", NAU8810_REG_NOISEGATE,
  NAU8810_ALCNTH_SFT, 0x7, 0),

 SOC_SINGLE("PGA ZC Switch", NAU8810_REG_PGAGAIN,
  NAU8810_PGAZC_SFT, 1, 0),
 SOC_SINGLE_TLV("PGA Volume", NAU8810_REG_PGAGAIN,
  NAU8810_PGAGAIN_SFT, 0x3f, 0, inpga_tlv),

 SOC_SINGLE("Speaker ZC Switch", NAU8810_REG_SPKGAIN,
  NAU8810_SPKZC_SFT, 1, 0),
 SOC_SINGLE("Speaker Mute Switch", NAU8810_REG_SPKGAIN,
  NAU8810_SPKMT_SFT, 1, 0),
 SOC_SINGLE_TLV("Speaker Volume", NAU8810_REG_SPKGAIN,
  NAU8810_SPKGAIN_SFT, 0x3f, 0, spk_tlv),

 SOC_SINGLE("Capture Boost(+20dB)", NAU8810_REG_ADCBOOST,
  NAU8810_PGABST_SFT, 1, 0),
 SOC_SINGLE("Mono Mute Switch", NAU8810_REG_MONOMIX,
  NAU8810_MOUTMXMT_SFT, 1, 0),

 SOC_SINGLE("DAC Oversampling Rate(128x) Switch", NAU8810_REG_DAC,
  NAU8810_DACOS_SFT, 1, 0),
 SOC_SINGLE("ADC Oversampling Rate(128x) Switch", NAU8810_REG_ADC,
  NAU8810_ADCOS_SFT, 1, 0),
};

/* Speaker Output Mixer */
static const struct snd_kcontrol_new nau8810_speaker_mixer_controls[] = {
 SOC_DAPM_SINGLE("AUX Bypass Switch", NAU8810_REG_SPKMIX,
  NAU8810_AUXSPK_SFT, 1, 0),
 SOC_DAPM_SINGLE("Line Bypass Switch", NAU8810_REG_SPKMIX,
  NAU8810_BYPSPK_SFT, 1, 0),
 SOC_DAPM_SINGLE("PCM Playback Switch", NAU8810_REG_SPKMIX,
  NAU8810_DACSPK_SFT, 1, 0),
};

/* Mono Output Mixer */
static const struct snd_kcontrol_new nau8810_mono_mixer_controls[] = {
 SOC_DAPM_SINGLE("AUX Bypass Switch", NAU8810_REG_MONOMIX,
  NAU8810_AUXMOUT_SFT, 1, 0),
 SOC_DAPM_SINGLE("Line Bypass Switch", NAU8810_REG_MONOMIX,
  NAU8810_BYPMOUT_SFT, 1, 0),
 SOC_DAPM_SINGLE("PCM Playback Switch", NAU8810_REG_MONOMIX,
  NAU8810_DACMOUT_SFT, 1, 0),
};

/* PGA Mute */
static const struct snd_kcontrol_new nau8810_pgaboost_mixer_controls[] = {
 SOC_DAPM_SINGLE("AUX PGA Switch", NAU8810_REG_ADCBOOST,
  NAU8810_AUXBSTGAIN_SFT, 0x7, 0),
 SOC_DAPM_SINGLE("PGA Mute Switch", NAU8810_REG_PGAGAIN,
  NAU8810_PGAMT_SFT, 1, 1),
 SOC_DAPM_SINGLE("PMIC PGA Switch", NAU8810_REG_ADCBOOST,
  NAU8810_PMICBSTGAIN_SFT, 0x7, 0),
};

/* Input PGA */
static const struct snd_kcontrol_new nau8810_inpga[] = {
 SOC_DAPM_SINGLE("AUX Switch", NAU8810_REG_INPUT_SIGNAL,
  NAU8810_AUXPGA_SFT, 1, 0),
 SOC_DAPM_SINGLE("MicN Switch", NAU8810_REG_INPUT_SIGNAL,
  NAU8810_NMICPGA_SFT, 1, 0),
 SOC_DAPM_SINGLE("MicP Switch", NAU8810_REG_INPUT_SIGNAL,
  NAU8810_PMICPGA_SFT, 1, 0),
};

/* Loopback Switch */
static const struct snd_kcontrol_new nau8810_loopback =
 SOC_DAPM_SINGLE("Switch", NAU8810_REG_COMP,
  NAU8810_ADDAP_SFT, 1, 0);

static int check_mclk_select_pll(struct snd_soc_dapm_widget *source,
    struct snd_soc_dapm_widget *sink)
{
 struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm);
 struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component);
 unsigned int value;

 regmap_read(nau8810->regmap, NAU8810_REG_CLOCK, &value);
 return (value & NAU8810_CLKM_MASK);
}

static int check_mic_enabled(struct snd_soc_dapm_widget *source,
 struct snd_soc_dapm_widget *sink)
{
 struct snd_soc_component *component =
  snd_soc_dapm_to_component(source->dapm);
 struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component);
 unsigned int value;

 regmap_read(nau8810->regmap, NAU8810_REG_INPUT_SIGNAL, &value);
 if (value & NAU8810_PMICPGA_EN || value & NAU8810_NMICPGA_EN)
  return 1;
 regmap_read(nau8810->regmap, NAU8810_REG_ADCBOOST, &value);
 if (value & NAU8810_PMICBSTGAIN_MASK)
  return 1;
 return 0;
}

static const struct snd_soc_dapm_widget nau8810_dapm_widgets[] = {
 SND_SOC_DAPM_MIXER("Speaker Mixer", NAU8810_REG_POWER3,
  NAU8810_SPKMX_EN_SFT, 0, &nau8810_speaker_mixer_controls[0],
  ARRAY_SIZE(nau8810_speaker_mixer_controls)),
 SND_SOC_DAPM_MIXER("Mono Mixer", NAU8810_REG_POWER3,
  NAU8810_MOUTMX_EN_SFT, 0, &nau8810_mono_mixer_controls[0],
  ARRAY_SIZE(nau8810_mono_mixer_controls)),
 SND_SOC_DAPM_DAC("DAC""Playback", NAU8810_REG_POWER3,
  NAU8810_DAC_EN_SFT, 0),
 SND_SOC_DAPM_ADC("ADC""Capture", NAU8810_REG_POWER2,
  NAU8810_ADC_EN_SFT, 0),
 SND_SOC_DAPM_PGA("SpkN Out", NAU8810_REG_POWER3,
  NAU8810_NSPK_EN_SFT, 0, NULL, 0),
 SND_SOC_DAPM_PGA("SpkP Out", NAU8810_REG_POWER3,
  NAU8810_PSPK_EN_SFT, 0, NULL, 0),
 SND_SOC_DAPM_PGA("Mono Out", NAU8810_REG_POWER3,
  NAU8810_MOUT_EN_SFT, 0, NULL, 0),

 SND_SOC_DAPM_MIXER("Input PGA", NAU8810_REG_POWER2,
  NAU8810_PGA_EN_SFT, 0, nau8810_inpga,
  ARRAY_SIZE(nau8810_inpga)),
 SND_SOC_DAPM_MIXER("Input Boost Stage", NAU8810_REG_POWER2,
  NAU8810_BST_EN_SFT, 0, nau8810_pgaboost_mixer_controls,
  ARRAY_SIZE(nau8810_pgaboost_mixer_controls)),
 SND_SOC_DAPM_PGA("AUX Input", NAU8810_REG_POWER1,
  NAU8810_AUX_EN_SFT, 0, NULL, 0),

 SND_SOC_DAPM_SUPPLY("Mic Bias", NAU8810_REG_POWER1,
  NAU8810_MICBIAS_EN_SFT, 0, NULL, 0),
 SND_SOC_DAPM_SUPPLY("PLL", NAU8810_REG_POWER1,
  NAU8810_PLL_EN_SFT, 0, NULL, 0),

 SND_SOC_DAPM_SWITCH("Digital Loopback", SND_SOC_NOPM, 0, 0,
  &nau8810_loopback),

 SND_SOC_DAPM_INPUT("AUX"),
 SND_SOC_DAPM_INPUT("MICN"),
 SND_SOC_DAPM_INPUT("MICP"),
 SND_SOC_DAPM_OUTPUT("MONOOUT"),
 SND_SOC_DAPM_OUTPUT("SPKOUTP"),
 SND_SOC_DAPM_OUTPUT("SPKOUTN"),
};

static const struct snd_soc_dapm_route nau8810_dapm_routes[] = {
 {"DAC", NULL, "PLL", check_mclk_select_pll},

 /* Mono output mixer */
 {"Mono Mixer""AUX Bypass Switch""AUX Input"},
 {"Mono Mixer""PCM Playback Switch""DAC"},
 {"Mono Mixer""Line Bypass Switch""Input Boost Stage"},

 /* Speaker output mixer */
 {"Speaker Mixer""AUX Bypass Switch""AUX Input"},
 {"Speaker Mixer""PCM Playback Switch""DAC"},
 {"Speaker Mixer""Line Bypass Switch""Input Boost Stage"},

 /* Outputs */
 {"Mono Out", NULL, "Mono Mixer"},
 {"MONOOUT", NULL, "Mono Out"},
 {"SpkN Out", NULL, "Speaker Mixer"},
 {"SpkP Out", NULL, "Speaker Mixer"},
 {"SPKOUTN", NULL, "SpkN Out"},
 {"SPKOUTP", NULL, "SpkP Out"},

 /* Input Boost Stage */
 {"ADC", NULL, "Input Boost Stage"},
 {"ADC", NULL, "PLL", check_mclk_select_pll},
 {"Input Boost Stage""AUX PGA Switch""AUX Input"},
 {"Input Boost Stage""PGA Mute Switch""Input PGA"},
 {"Input Boost Stage""PMIC PGA Switch""MICP"},

 /* Input PGA */
 {"Input PGA", NULL, "Mic Bias", check_mic_enabled},
 {"Input PGA""AUX Switch""AUX Input"},
 {"Input PGA""MicN Switch""MICN"},
 {"Input PGA""MicP Switch""MICP"},
 {"AUX Input", NULL, "AUX"},

 /* Digital Looptack */
 {"Digital Loopback""Switch""ADC"},
 {"DAC", NULL, "Digital Loopback"},
};

static int nau8810_set_sysclk(struct snd_soc_dai *dai,
     int clk_id, unsigned int freq, int dir)
{
 struct snd_soc_component *component = dai->component;
 struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component);

 nau8810->clk_id = clk_id;
 nau8810->sysclk = freq;
 dev_dbg(nau8810->dev, "master sysclk %dHz, source %s\n",
  freq, clk_id == NAU8810_SCLK_PLL ? "PLL" : "MCLK");

 return 0;
}

static int nau8810_calc_pll(unsigned int pll_in,
 unsigned int fs, struct nau8810_pll *pll_param)
{
 u64 f2, f2_max, pll_ratio;
 int i, scal_sel;

 if (pll_in > NAU_PLL_REF_MAX || pll_in < NAU_PLL_REF_MIN)
  return -EINVAL;

 f2_max = 0;
 scal_sel = ARRAY_SIZE(nau8810_mclk_scaler);
 for (i = 0; i < ARRAY_SIZE(nau8810_mclk_scaler); i++) {
  f2 = 256ULL * fs * 4 * nau8810_mclk_scaler[i];
  f2 = div_u64(f2, 10);
  if (f2 > NAU_PLL_FREQ_MIN && f2 < NAU_PLL_FREQ_MAX &&
   f2_max < f2) {
   f2_max = f2;
   scal_sel = i;
  }
 }
 if (ARRAY_SIZE(nau8810_mclk_scaler) == scal_sel)
  return -EINVAL;
 pll_param->mclk_scaler = scal_sel;
 f2 = f2_max;

 /* Calculate the PLL 4-bit integer input and the PLL 24-bit fractional
 * input; round up the 24+4bit.
 */

 pll_ratio = div_u64(f2 << 28, pll_in);
 pll_param->pre_factor = 0;
 if (((pll_ratio >> 28) & 0xF) < NAU_PLL_OPTOP_MIN) {
  pll_ratio <<= 1;
  pll_param->pre_factor = 1;
 }
 pll_param->pll_int = (pll_ratio >> 28) & 0xF;
 pll_param->pll_frac = ((pll_ratio & 0xFFFFFFF) >> 4);

 return 0;
}

static int nau8810_set_pll(struct snd_soc_dai *codec_dai, int pll_id,
 int source, unsigned int freq_in, unsigned int freq_out)
{
 struct snd_soc_component *component = codec_dai->component;
 struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component);
 struct regmap *map = nau8810->regmap;
 struct nau8810_pll *pll_param = &nau8810->pll;
 int ret, fs;

 fs = freq_out / 256;
 ret = nau8810_calc_pll(freq_in, fs, pll_param);
 if (ret < 0) {
  dev_err(nau8810->dev, "Unsupported input clock %d\n", freq_in);
  return ret;
 }
 dev_info(nau8810->dev, "pll_int=%x pll_frac=%x mclk_scaler=%x pre_factor=%x\n",
  pll_param->pll_int, pll_param->pll_frac, pll_param->mclk_scaler,
  pll_param->pre_factor);

 regmap_update_bits(map, NAU8810_REG_PLLN,
  NAU8810_PLLMCLK_DIV2 | NAU8810_PLLN_MASK,
  (pll_param->pre_factor ? NAU8810_PLLMCLK_DIV2 : 0) |
  pll_param->pll_int);
 regmap_write(map, NAU8810_REG_PLLK1,
  (pll_param->pll_frac >> NAU8810_PLLK1_SFT) &
  NAU8810_PLLK1_MASK);
 regmap_write(map, NAU8810_REG_PLLK2,
  (pll_param->pll_frac >> NAU8810_PLLK2_SFT) &
  NAU8810_PLLK2_MASK);
 regmap_write(map, NAU8810_REG_PLLK3,
  pll_param->pll_frac & NAU8810_PLLK3_MASK);
 regmap_update_bits(map, NAU8810_REG_CLOCK, NAU8810_MCLKSEL_MASK,
  pll_param->mclk_scaler << NAU8810_MCLKSEL_SFT);
 regmap_update_bits(map, NAU8810_REG_CLOCK,
  NAU8810_CLKM_MASK, NAU8810_CLKM_PLL);

 return 0;
}

static int nau8810_set_dai_fmt(struct snd_soc_dai *codec_dai,
  unsigned int fmt)
{
 struct snd_soc_component *component = codec_dai->component;
 struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component);
 u16 ctrl1_val = 0, ctrl2_val = 0;

 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
 case SND_SOC_DAIFMT_CBP_CFP:
  ctrl2_val |= NAU8810_CLKIO_MASTER;
  break;
 case SND_SOC_DAIFMT_CBC_CFC:
  break;
 default:
  return -EINVAL;
 }

 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
 case SND_SOC_DAIFMT_I2S:
  ctrl1_val |= NAU8810_AIFMT_I2S;
  break;
 case SND_SOC_DAIFMT_RIGHT_J:
  break;
 case SND_SOC_DAIFMT_LEFT_J:
  ctrl1_val |= NAU8810_AIFMT_LEFT;
  break;
 case SND_SOC_DAIFMT_DSP_A:
  ctrl1_val |= NAU8810_AIFMT_PCM_A;
  break;
 default:
  return -EINVAL;
 }

 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
 case SND_SOC_DAIFMT_NB_NF:
  break;
 case SND_SOC_DAIFMT_IB_IF:
  ctrl1_val |= NAU8810_BCLKP_IB | NAU8810_FSP_IF;
  break;
 case SND_SOC_DAIFMT_IB_NF:
  ctrl1_val |= NAU8810_BCLKP_IB;
  break;
 case SND_SOC_DAIFMT_NB_IF:
  ctrl1_val |= NAU8810_FSP_IF;
  break;
 default:
  return -EINVAL;
 }

 regmap_update_bits(nau8810->regmap, NAU8810_REG_IFACE,
  NAU8810_AIFMT_MASK | NAU8810_FSP_IF |
  NAU8810_BCLKP_IB, ctrl1_val);
 regmap_update_bits(nau8810->regmap, NAU8810_REG_CLOCK,
  NAU8810_CLKIO_MASK, ctrl2_val);

 return 0;
}

static int nau8810_mclk_clkdiv(struct nau8810 *nau8810, int rate)
{
 int i, sclk, imclk = rate * 256, div = 0;

 if (!nau8810->sysclk) {
  dev_err(nau8810->dev, "Make mclk div configuration fail because of invalid system clock\n");
  return -EINVAL;
 }

 /* Configure the master clock prescaler div to make system
 * clock to approximate the internal master clock (IMCLK);
 * and large or equal to IMCLK.
 */

 for (i = 1; i < ARRAY_SIZE(nau8810_mclk_scaler); i++) {
  sclk = (nau8810->sysclk * 10) /
   nau8810_mclk_scaler[i];
  if (sclk < imclk)
   break;
  div = i;
 }
 dev_dbg(nau8810->dev,
  "master clock prescaler %x for fs %d\n", div, rate);

 /* master clock from MCLK and disable PLL */
 regmap_update_bits(nau8810->regmap, NAU8810_REG_CLOCK,
  NAU8810_MCLKSEL_MASK, (div << NAU8810_MCLKSEL_SFT));
 regmap_update_bits(nau8810->regmap, NAU8810_REG_CLOCK,
  NAU8810_CLKM_MASK, NAU8810_CLKM_MCLK);

 return 0;
}

static int nau8810_pcm_hw_params(struct snd_pcm_substream *substream,
 struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
 struct snd_soc_component *component = dai->component;
 struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component);
 int val_len = 0, val_rate = 0, ret = 0;
 unsigned int ctrl_val, bclk_fs, bclk_div;

 /* Select BCLK configuration if the codec as master. */
 regmap_read(nau8810->regmap, NAU8810_REG_CLOCK, &ctrl_val);
 if (ctrl_val & NAU8810_CLKIO_MASTER) {
  /* get the bclk and fs ratio */
  bclk_fs = snd_soc_params_to_bclk(params) / params_rate(params);
  if (bclk_fs <= 32)
   bclk_div = NAU8810_BCLKDIV_8;
  else if (bclk_fs <= 64)
   bclk_div = NAU8810_BCLKDIV_4;
  else if (bclk_fs <= 128)
   bclk_div = NAU8810_BCLKDIV_2;
  else
   return -EINVAL;
  regmap_update_bits(nau8810->regmap, NAU8810_REG_CLOCK,
   NAU8810_BCLKSEL_MASK, bclk_div);
 }

 switch (params_width(params)) {
 case 16:
  break;
 case 20:
  val_len |= NAU8810_WLEN_20;
  break;
 case 24:
  val_len |= NAU8810_WLEN_24;
  break;
 case 32:
  val_len |= NAU8810_WLEN_32;
  break;
 }

 switch (params_rate(params)) {
 case 8000:
  val_rate |= NAU8810_SMPLR_8K;
  break;
 case 11025:
  val_rate |= NAU8810_SMPLR_12K;
  break;
 case 16000:
  val_rate |= NAU8810_SMPLR_16K;
  break;
 case 22050:
  val_rate |= NAU8810_SMPLR_24K;
  break;
 case 32000:
  val_rate |= NAU8810_SMPLR_32K;
  break;
 case 44100:
 case 48000:
  break;
 }

 regmap_update_bits(nau8810->regmap, NAU8810_REG_IFACE,
  NAU8810_WLEN_MASK, val_len);
 regmap_update_bits(nau8810->regmap, NAU8810_REG_SMPLR,
  NAU8810_SMPLR_MASK, val_rate);

 /* If the master clock is from MCLK, provide the runtime FS for driver
 * to get the master clock prescaler configuration.
 */

 if (nau8810->clk_id == NAU8810_SCLK_MCLK) {
  ret = nau8810_mclk_clkdiv(nau8810, params_rate(params));
  if (ret < 0)
   dev_err(nau8810->dev, "MCLK div configuration fail\n");
 }

 return ret;
}

static int nau8810_set_bias_level(struct snd_soc_component *component,
 enum snd_soc_bias_level level)
{
 struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component);
 struct regmap *map = nau8810->regmap;

 switch (level) {
 case SND_SOC_BIAS_ON:
 case SND_SOC_BIAS_PREPARE:
  regmap_update_bits(map, NAU8810_REG_POWER1,
   NAU8810_REFIMP_MASK, NAU8810_REFIMP_80K);
  break;

 case SND_SOC_BIAS_STANDBY:
  regmap_update_bits(map, NAU8810_REG_POWER1,
   NAU8810_IOBUF_EN | NAU8810_ABIAS_EN,
   NAU8810_IOBUF_EN | NAU8810_ABIAS_EN);

  if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) {
   regcache_sync(map);
   regmap_update_bits(map, NAU8810_REG_POWER1,
    NAU8810_REFIMP_MASK, NAU8810_REFIMP_3K);
   mdelay(100);
  }
  regmap_update_bits(map, NAU8810_REG_POWER1,
   NAU8810_REFIMP_MASK, NAU8810_REFIMP_300K);
  break;

 case SND_SOC_BIAS_OFF:
  regmap_write(map, NAU8810_REG_POWER1, 0);
  regmap_write(map, NAU8810_REG_POWER2, 0);
  regmap_write(map, NAU8810_REG_POWER3, 0);
  break;
 }

 return 0;
}


#define NAU8810_RATES (SNDRV_PCM_RATE_8000_48000)

#define NAU8810_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)

static const struct snd_soc_dai_ops nau8810_ops = {
 .hw_params = nau8810_pcm_hw_params,
 .set_fmt = nau8810_set_dai_fmt,
 .set_sysclk = nau8810_set_sysclk,
 .set_pll = nau8810_set_pll,
};

static struct snd_soc_dai_driver nau8810_dai = {
 .name = "nau8810-hifi",
 .playback = {
  .stream_name = "Playback",
  .channels_min = 1,
  .channels_max = 2,   /* Only 1 channel of data */
  .rates = NAU8810_RATES,
  .formats = NAU8810_FORMATS,
 },
 .capture = {
  .stream_name = "Capture",
  .channels_min = 1,
  .channels_max = 2,   /* Only 1 channel of data */
  .rates = NAU8810_RATES,
  .formats = NAU8810_FORMATS,
 },
 .ops = &nau8810_ops,
 .symmetric_rate = 1,
};

static const struct regmap_config nau8810_regmap_config = {
 .reg_bits = 7,
 .val_bits = 9,

 .max_register = NAU8810_REG_MAX,
 .readable_reg = nau8810_readable_reg,
 .writeable_reg = nau8810_writeable_reg,
 .volatile_reg = nau8810_volatile_reg,

 .cache_type = REGCACHE_RBTREE,
 .reg_defaults = nau8810_reg_defaults,
 .num_reg_defaults = ARRAY_SIZE(nau8810_reg_defaults),
};

static const struct snd_soc_component_driver nau8810_component_driver = {
 .set_bias_level  = nau8810_set_bias_level,
 .controls  = nau8810_snd_controls,
 .num_controls  = ARRAY_SIZE(nau8810_snd_controls),
 .dapm_widgets  = nau8810_dapm_widgets,
 .num_dapm_widgets = ARRAY_SIZE(nau8810_dapm_widgets),
 .dapm_routes  = nau8810_dapm_routes,
 .num_dapm_routes = ARRAY_SIZE(nau8810_dapm_routes),
 .suspend_bias_off = 1,
 .idle_bias_on  = 1,
 .use_pmdown_time = 1,
 .endianness  = 1,
};

static int nau8810_i2c_probe(struct i2c_client *i2c)
{
 struct device *dev = &i2c->dev;
 struct nau8810 *nau8810 = dev_get_platdata(dev);

 if (!nau8810) {
  nau8810 = devm_kzalloc(dev, sizeof(*nau8810), GFP_KERNEL);
  if (!nau8810)
   return -ENOMEM;
 }
 i2c_set_clientdata(i2c, nau8810);

 nau8810->regmap = devm_regmap_init_i2c(i2c, &nau8810_regmap_config);
 if (IS_ERR(nau8810->regmap))
  return PTR_ERR(nau8810->regmap);
 nau8810->dev = dev;

 regmap_write(nau8810->regmap, NAU8810_REG_RESET, 0x00);

 return devm_snd_soc_register_component(dev,
  &nau8810_component_driver, &nau8810_dai, 1);
}

static const struct i2c_device_id nau8810_i2c_id[] = {
 { "nau8810" },
 { "nau8812" },
 { "nau8814" },
 { }
};
MODULE_DEVICE_TABLE(i2c, nau8810_i2c_id);

#ifdef CONFIG_OF
static const struct of_device_id nau8810_of_match[] = {
 { .compatible = "nuvoton,nau8810", },
 { .compatible = "nuvoton,nau8812", },
 { .compatible = "nuvoton,nau8814", },
 { }
};
MODULE_DEVICE_TABLE(of, nau8810_of_match);
#endif

static struct i2c_driver nau8810_i2c_driver = {
 .driver = {
  .name = "nau8810",
  .of_match_table = of_match_ptr(nau8810_of_match),
 },
 .probe = nau8810_i2c_probe,
 .id_table = nau8810_i2c_id,
};

module_i2c_driver(nau8810_i2c_driver);

MODULE_DESCRIPTION("ASoC NAU8810 driver");
MODULE_AUTHOR("David Lin ");
MODULE_LICENSE("GPL v2");

Messung V0.5
C=92 H=98 G=94

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge