forked from Minki/linux
0bb423f2ea
Regulator notifiers, that were registered during codec driver probing, must be unregistered during driver release, or device managed versions have to be used. This patch fixes codec drivers, that weren't explicitly unregistering notifiers and simplifies those, that did that manually. Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com> Signed-off-by: Mark Brown <broonie@kernel.org>
747 lines
19 KiB
C
747 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// Driver for the MAX9860 Mono Audio Voice Codec
|
|
//
|
|
// https://datasheets.maximintegrated.com/en/ds/MAX9860.pdf
|
|
//
|
|
// The driver does not support sidetone since the DVST register field is
|
|
// backwards with the mute near the maximum level instead of the minimum.
|
|
//
|
|
// Author: Peter Rosin <peda@axentia.s>
|
|
// Copyright 2016 Axentia Technologies
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/soc-dapm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/tlv.h>
|
|
|
|
#include "max9860.h"
|
|
|
|
struct max9860_priv {
|
|
struct regmap *regmap;
|
|
struct regulator *dvddio;
|
|
struct notifier_block dvddio_nb;
|
|
u8 psclk;
|
|
unsigned long pclk_rate;
|
|
int fmt;
|
|
};
|
|
|
|
static int max9860_dvddio_event(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
struct max9860_priv *max9860 = container_of(nb, struct max9860_priv,
|
|
dvddio_nb);
|
|
if (event & REGULATOR_EVENT_DISABLE) {
|
|
regcache_mark_dirty(max9860->regmap);
|
|
regcache_cache_only(max9860->regmap, true);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct reg_default max9860_reg_defaults[] = {
|
|
{ MAX9860_PWRMAN, 0x00 },
|
|
{ MAX9860_INTEN, 0x00 },
|
|
{ MAX9860_SYSCLK, 0x00 },
|
|
{ MAX9860_AUDIOCLKHIGH, 0x00 },
|
|
{ MAX9860_AUDIOCLKLOW, 0x00 },
|
|
{ MAX9860_IFC1A, 0x00 },
|
|
{ MAX9860_IFC1B, 0x00 },
|
|
{ MAX9860_VOICEFLTR, 0x00 },
|
|
{ MAX9860_DACATTN, 0x00 },
|
|
{ MAX9860_ADCLEVEL, 0x00 },
|
|
{ MAX9860_DACGAIN, 0x00 },
|
|
{ MAX9860_MICGAIN, 0x00 },
|
|
{ MAX9860_MICADC, 0x00 },
|
|
{ MAX9860_NOISEGATE, 0x00 },
|
|
};
|
|
|
|
static bool max9860_readable(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case MAX9860_INTRSTATUS ... MAX9860_MICGAIN:
|
|
case MAX9860_MICADC ... MAX9860_PWRMAN:
|
|
case MAX9860_REVISION:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool max9860_writeable(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case MAX9860_INTEN ... MAX9860_MICGAIN:
|
|
case MAX9860_MICADC ... MAX9860_PWRMAN:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool max9860_volatile(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case MAX9860_INTRSTATUS:
|
|
case MAX9860_MICREADBACK:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool max9860_precious(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case MAX9860_INTRSTATUS:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static const struct regmap_config max9860_regmap = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.readable_reg = max9860_readable,
|
|
.writeable_reg = max9860_writeable,
|
|
.volatile_reg = max9860_volatile,
|
|
.precious_reg = max9860_precious,
|
|
|
|
.max_register = MAX9860_MAX_REGISTER,
|
|
.reg_defaults = max9860_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(max9860_reg_defaults),
|
|
.cache_type = REGCACHE_RBTREE,
|
|
};
|
|
|
|
static const DECLARE_TLV_DB_SCALE(dva_tlv, -9100, 100, 1);
|
|
static const DECLARE_TLV_DB_SCALE(dvg_tlv, 0, 600, 0);
|
|
static const DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 100, 0);
|
|
static const DECLARE_TLV_DB_RANGE(pam_tlv,
|
|
0, MAX9860_PAM_MAX - 1, TLV_DB_SCALE_ITEM(-2000, 2000, 1),
|
|
MAX9860_PAM_MAX, MAX9860_PAM_MAX, TLV_DB_SCALE_ITEM(3000, 0, 0));
|
|
static const DECLARE_TLV_DB_SCALE(pgam_tlv, 0, 100, 0);
|
|
static const DECLARE_TLV_DB_SCALE(anth_tlv, -7600, 400, 1);
|
|
static const DECLARE_TLV_DB_SCALE(agcth_tlv, -1800, 100, 0);
|
|
|
|
static const char * const agchld_text[] = {
|
|
"AGC Disabled", "50ms", "100ms", "400ms"
|
|
};
|
|
|
|
static SOC_ENUM_SINGLE_DECL(agchld_enum, MAX9860_MICADC,
|
|
MAX9860_AGCHLD_SHIFT, agchld_text);
|
|
|
|
static const char * const agcsrc_text[] = {
|
|
"Left ADC", "Left/Right ADC"
|
|
};
|
|
|
|
static SOC_ENUM_SINGLE_DECL(agcsrc_enum, MAX9860_MICADC,
|
|
MAX9860_AGCSRC_SHIFT, agcsrc_text);
|
|
|
|
static const char * const agcatk_text[] = {
|
|
"3ms", "12ms", "50ms", "200ms"
|
|
};
|
|
|
|
static SOC_ENUM_SINGLE_DECL(agcatk_enum, MAX9860_MICADC,
|
|
MAX9860_AGCATK_SHIFT, agcatk_text);
|
|
|
|
static const char * const agcrls_text[] = {
|
|
"78ms", "156ms", "312ms", "625ms",
|
|
"1.25s", "2.5s", "5s", "10s"
|
|
};
|
|
|
|
static SOC_ENUM_SINGLE_DECL(agcrls_enum, MAX9860_MICADC,
|
|
MAX9860_AGCRLS_SHIFT, agcrls_text);
|
|
|
|
static const char * const filter_text[] = {
|
|
"Disabled",
|
|
"Elliptical HP 217Hz notch (16kHz)",
|
|
"Butterworth HP 500Hz (16kHz)",
|
|
"Elliptical HP 217Hz notch (8kHz)",
|
|
"Butterworth HP 500Hz (8kHz)",
|
|
"Butterworth HP 200Hz (48kHz)"
|
|
};
|
|
|
|
static SOC_ENUM_SINGLE_DECL(avflt_enum, MAX9860_VOICEFLTR,
|
|
MAX9860_AVFLT_SHIFT, filter_text);
|
|
|
|
static SOC_ENUM_SINGLE_DECL(dvflt_enum, MAX9860_VOICEFLTR,
|
|
MAX9860_DVFLT_SHIFT, filter_text);
|
|
|
|
static const struct snd_kcontrol_new max9860_controls[] = {
|
|
SOC_SINGLE_TLV("Master Playback Volume", MAX9860_DACATTN,
|
|
MAX9860_DVA_SHIFT, MAX9860_DVA_MUTE, 1, dva_tlv),
|
|
SOC_SINGLE_TLV("DAC Gain Volume", MAX9860_DACGAIN,
|
|
MAX9860_DVG_SHIFT, MAX9860_DVG_MAX, 0, dvg_tlv),
|
|
SOC_DOUBLE_TLV("Line Capture Volume", MAX9860_ADCLEVEL,
|
|
MAX9860_ADCLL_SHIFT, MAX9860_ADCRL_SHIFT, MAX9860_ADCxL_MIN, 1,
|
|
adc_tlv),
|
|
|
|
SOC_ENUM("AGC Hold Time", agchld_enum),
|
|
SOC_ENUM("AGC/Noise Gate Source", agcsrc_enum),
|
|
SOC_ENUM("AGC Attack Time", agcatk_enum),
|
|
SOC_ENUM("AGC Release Time", agcrls_enum),
|
|
|
|
SOC_SINGLE_TLV("Noise Gate Threshold Volume", MAX9860_NOISEGATE,
|
|
MAX9860_ANTH_SHIFT, MAX9860_ANTH_MAX, 0, anth_tlv),
|
|
SOC_SINGLE_TLV("AGC Signal Threshold Volume", MAX9860_NOISEGATE,
|
|
MAX9860_AGCTH_SHIFT, MAX9860_AGCTH_MIN, 1, agcth_tlv),
|
|
|
|
SOC_SINGLE_TLV("Mic PGA Volume", MAX9860_MICGAIN,
|
|
MAX9860_PGAM_SHIFT, MAX9860_PGAM_MIN, 1, pgam_tlv),
|
|
SOC_SINGLE_TLV("Mic Preamp Volume", MAX9860_MICGAIN,
|
|
MAX9860_PAM_SHIFT, MAX9860_PAM_MAX, 0, pam_tlv),
|
|
|
|
SOC_ENUM("ADC Filter", avflt_enum),
|
|
SOC_ENUM("DAC Filter", dvflt_enum),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget max9860_dapm_widgets[] = {
|
|
SND_SOC_DAPM_INPUT("MICL"),
|
|
SND_SOC_DAPM_INPUT("MICR"),
|
|
|
|
SND_SOC_DAPM_ADC("ADCL", NULL, MAX9860_PWRMAN, MAX9860_ADCLEN_SHIFT, 0),
|
|
SND_SOC_DAPM_ADC("ADCR", NULL, MAX9860_PWRMAN, MAX9860_ADCREN_SHIFT, 0),
|
|
|
|
SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),
|
|
SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),
|
|
|
|
SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
|
|
SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
|
|
|
|
SND_SOC_DAPM_DAC("DAC", NULL, MAX9860_PWRMAN, MAX9860_DACEN_SHIFT, 0),
|
|
|
|
SND_SOC_DAPM_OUTPUT("OUT"),
|
|
|
|
SND_SOC_DAPM_SUPPLY("Supply", SND_SOC_NOPM, 0, 0,
|
|
NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
|
SND_SOC_DAPM_REGULATOR_SUPPLY("AVDD", 0, 0),
|
|
SND_SOC_DAPM_REGULATOR_SUPPLY("DVDD", 0, 0),
|
|
SND_SOC_DAPM_CLOCK_SUPPLY("mclk"),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route max9860_dapm_routes[] = {
|
|
{ "ADCL", NULL, "MICL" },
|
|
{ "ADCR", NULL, "MICR" },
|
|
{ "AIFOUTL", NULL, "ADCL" },
|
|
{ "AIFOUTR", NULL, "ADCR" },
|
|
|
|
{ "DAC", NULL, "AIFINL" },
|
|
{ "DAC", NULL, "AIFINR" },
|
|
{ "OUT", NULL, "DAC" },
|
|
|
|
{ "Supply", NULL, "AVDD" },
|
|
{ "Supply", NULL, "DVDD" },
|
|
{ "Supply", NULL, "mclk" },
|
|
|
|
{ "DAC", NULL, "Supply" },
|
|
{ "ADCL", NULL, "Supply" },
|
|
{ "ADCR", NULL, "Supply" },
|
|
};
|
|
|
|
static int max9860_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 max9860_priv *max9860 = snd_soc_component_get_drvdata(component);
|
|
u8 master;
|
|
u8 ifc1a = 0;
|
|
u8 ifc1b = 0;
|
|
u8 sysclk = 0;
|
|
unsigned long n;
|
|
int ret;
|
|
|
|
dev_dbg(component->dev, "hw_params %u Hz, %u channels\n",
|
|
params_rate(params),
|
|
params_channels(params));
|
|
|
|
if (params_channels(params) == 2)
|
|
ifc1b |= MAX9860_ST;
|
|
|
|
switch (max9860->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
master = 0;
|
|
break;
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
master = MAX9860_MASTER;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
ifc1a |= master;
|
|
|
|
if (master) {
|
|
if (params_width(params) * params_channels(params) > 48)
|
|
ifc1b |= MAX9860_BSEL_64X;
|
|
else
|
|
ifc1b |= MAX9860_BSEL_48X;
|
|
}
|
|
|
|
switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_I2S:
|
|
ifc1a |= MAX9860_DDLY;
|
|
ifc1b |= MAX9860_ADLY;
|
|
break;
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
ifc1a |= MAX9860_WCI;
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
if (params_width(params) != 16) {
|
|
dev_err(component->dev,
|
|
"DSP_A works for 16 bits per sample only.\n");
|
|
return -EINVAL;
|
|
}
|
|
ifc1a |= MAX9860_DDLY | MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM;
|
|
ifc1b |= MAX9860_ADLY;
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
if (params_width(params) != 16) {
|
|
dev_err(component->dev,
|
|
"DSP_B works for 16 bits per sample only.\n");
|
|
return -EINVAL;
|
|
}
|
|
ifc1a |= MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (max9860->fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
break;
|
|
case SND_SOC_DAIFMT_NB_IF:
|
|
switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
return -EINVAL;
|
|
}
|
|
ifc1a ^= MAX9860_WCI;
|
|
break;
|
|
case SND_SOC_DAIFMT_IB_IF:
|
|
switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
return -EINVAL;
|
|
}
|
|
ifc1a ^= MAX9860_WCI;
|
|
/* fall through */
|
|
case SND_SOC_DAIFMT_IB_NF:
|
|
ifc1a ^= MAX9860_DBCI;
|
|
ifc1b ^= MAX9860_ABCI;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(component->dev, "IFC1A %02x\n", ifc1a);
|
|
ret = regmap_write(max9860->regmap, MAX9860_IFC1A, ifc1a);
|
|
if (ret) {
|
|
dev_err(component->dev, "Failed to set IFC1A: %d\n", ret);
|
|
return ret;
|
|
}
|
|
dev_dbg(component->dev, "IFC1B %02x\n", ifc1b);
|
|
ret = regmap_write(max9860->regmap, MAX9860_IFC1B, ifc1b);
|
|
if (ret) {
|
|
dev_err(component->dev, "Failed to set IFC1B: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Check if Integer Clock Mode is possible, but avoid it in slave mode
|
|
* since we then do not know if lrclk is derived from pclk and the
|
|
* datasheet mentions that the frequencies have to match exactly in
|
|
* order for this to work.
|
|
*/
|
|
if (params_rate(params) == 8000 || params_rate(params) == 16000) {
|
|
if (master) {
|
|
switch (max9860->pclk_rate) {
|
|
case 12000000:
|
|
sysclk = MAX9860_FREQ_12MHZ;
|
|
break;
|
|
case 13000000:
|
|
sysclk = MAX9860_FREQ_13MHZ;
|
|
break;
|
|
case 19200000:
|
|
sysclk = MAX9860_FREQ_19_2MHZ;
|
|
break;
|
|
default:
|
|
/*
|
|
* Integer Clock Mode not possible. Leave
|
|
* sysclk at zero and fall through to the
|
|
* code below for PLL mode.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
if (sysclk && params_rate(params) == 16000)
|
|
sysclk |= MAX9860_16KHZ;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Largest possible n:
|
|
* 65536 * 96 * 48kHz / 10MHz -> 30199
|
|
* Smallest possible n:
|
|
* 65536 * 96 * 8kHz / 20MHz -> 2517
|
|
* Both fit nicely in the available 15 bits, no need to apply any mask.
|
|
*/
|
|
n = DIV_ROUND_CLOSEST_ULL(65536ULL * 96 * params_rate(params),
|
|
max9860->pclk_rate);
|
|
|
|
if (!sysclk) {
|
|
/* PLL mode */
|
|
if (params_rate(params) > 24000)
|
|
sysclk |= MAX9860_16KHZ;
|
|
|
|
if (!master)
|
|
n |= 1; /* trigger rapid pll lock mode */
|
|
}
|
|
|
|
sysclk |= max9860->psclk;
|
|
dev_dbg(component->dev, "SYSCLK %02x\n", sysclk);
|
|
ret = regmap_write(max9860->regmap,
|
|
MAX9860_SYSCLK, sysclk);
|
|
if (ret) {
|
|
dev_err(component->dev, "Failed to set SYSCLK: %d\n", ret);
|
|
return ret;
|
|
}
|
|
dev_dbg(component->dev, "N %lu\n", n);
|
|
ret = regmap_write(max9860->regmap,
|
|
MAX9860_AUDIOCLKHIGH, n >> 8);
|
|
if (ret) {
|
|
dev_err(component->dev, "Failed to set NHI: %d\n", ret);
|
|
return ret;
|
|
}
|
|
ret = regmap_write(max9860->regmap,
|
|
MAX9860_AUDIOCLKLOW, n & 0xff);
|
|
if (ret) {
|
|
dev_err(component->dev, "Failed to set NLO: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (!master) {
|
|
dev_dbg(component->dev, "Enable PLL\n");
|
|
ret = regmap_update_bits(max9860->regmap, MAX9860_AUDIOCLKHIGH,
|
|
MAX9860_PLL, MAX9860_PLL);
|
|
if (ret) {
|
|
dev_err(component->dev, "Failed to enable PLL: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int max9860_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
struct max9860_priv *max9860 = snd_soc_component_get_drvdata(component);
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
max9860->fmt = fmt;
|
|
return 0;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops max9860_dai_ops = {
|
|
.hw_params = max9860_hw_params,
|
|
.set_fmt = max9860_set_fmt,
|
|
};
|
|
|
|
static struct snd_soc_dai_driver max9860_dai = {
|
|
.name = "max9860-hifi",
|
|
.playback = {
|
|
.stream_name = "Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = SNDRV_PCM_RATE_CONTINUOUS,
|
|
.rate_min = 8000,
|
|
.rate_max = 48000,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S24_LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE,
|
|
},
|
|
.capture = {
|
|
.stream_name = "Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = SNDRV_PCM_RATE_CONTINUOUS,
|
|
.rate_min = 8000,
|
|
.rate_max = 48000,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S24_LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE,
|
|
},
|
|
.ops = &max9860_dai_ops,
|
|
.symmetric_rates = 1,
|
|
};
|
|
|
|
static int max9860_set_bias_level(struct snd_soc_component *component,
|
|
enum snd_soc_bias_level level)
|
|
{
|
|
struct max9860_priv *max9860 = dev_get_drvdata(component->dev);
|
|
int ret;
|
|
|
|
switch (level) {
|
|
case SND_SOC_BIAS_ON:
|
|
case SND_SOC_BIAS_PREPARE:
|
|
break;
|
|
|
|
case SND_SOC_BIAS_STANDBY:
|
|
ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN,
|
|
MAX9860_SHDN, MAX9860_SHDN);
|
|
if (ret) {
|
|
dev_err(component->dev, "Failed to remove SHDN: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
break;
|
|
|
|
case SND_SOC_BIAS_OFF:
|
|
ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN,
|
|
MAX9860_SHDN, 0);
|
|
if (ret) {
|
|
dev_err(component->dev, "Failed to request SHDN: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_component_driver max9860_component_driver = {
|
|
.set_bias_level = max9860_set_bias_level,
|
|
.controls = max9860_controls,
|
|
.num_controls = ARRAY_SIZE(max9860_controls),
|
|
.dapm_widgets = max9860_dapm_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(max9860_dapm_widgets),
|
|
.dapm_routes = max9860_dapm_routes,
|
|
.num_dapm_routes = ARRAY_SIZE(max9860_dapm_routes),
|
|
.use_pmdown_time = 1,
|
|
.endianness = 1,
|
|
.non_legacy_dai_naming = 1,
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
static int max9860_suspend(struct device *dev)
|
|
{
|
|
struct max9860_priv *max9860 = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK,
|
|
MAX9860_PSCLK, MAX9860_PSCLK_OFF);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to disable clock: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
regulator_disable(max9860->dvddio);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int max9860_resume(struct device *dev)
|
|
{
|
|
struct max9860_priv *max9860 = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = regulator_enable(max9860->dvddio);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to enable DVDDIO: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
regcache_cache_only(max9860->regmap, false);
|
|
ret = regcache_sync(max9860->regmap);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to sync cache: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK,
|
|
MAX9860_PSCLK, max9860->psclk);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to enable clock: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops max9860_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(max9860_suspend, max9860_resume, NULL)
|
|
};
|
|
|
|
static int max9860_probe(struct i2c_client *i2c)
|
|
{
|
|
struct device *dev = &i2c->dev;
|
|
struct max9860_priv *max9860;
|
|
int ret;
|
|
struct clk *mclk;
|
|
unsigned long mclk_rate;
|
|
int i;
|
|
int intr;
|
|
|
|
max9860 = devm_kzalloc(dev, sizeof(struct max9860_priv), GFP_KERNEL);
|
|
if (!max9860)
|
|
return -ENOMEM;
|
|
|
|
max9860->dvddio = devm_regulator_get(dev, "DVDDIO");
|
|
if (IS_ERR(max9860->dvddio)) {
|
|
ret = PTR_ERR(max9860->dvddio);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "Failed to get DVDDIO supply: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
max9860->dvddio_nb.notifier_call = max9860_dvddio_event;
|
|
|
|
ret = devm_regulator_register_notifier(max9860->dvddio,
|
|
&max9860->dvddio_nb);
|
|
if (ret)
|
|
dev_err(dev, "Failed to register DVDDIO notifier: %d\n", ret);
|
|
|
|
ret = regulator_enable(max9860->dvddio);
|
|
if (ret != 0) {
|
|
dev_err(dev, "Failed to enable DVDDIO: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
max9860->regmap = devm_regmap_init_i2c(i2c, &max9860_regmap);
|
|
if (IS_ERR(max9860->regmap)) {
|
|
ret = PTR_ERR(max9860->regmap);
|
|
goto err_regulator;
|
|
}
|
|
|
|
dev_set_drvdata(dev, max9860);
|
|
|
|
/*
|
|
* mclk has to be in the 10MHz to 60MHz range.
|
|
* psclk is used to scale mclk into pclk so that
|
|
* pclk is in the 10MHz to 20MHz range.
|
|
*/
|
|
mclk = clk_get(dev, "mclk");
|
|
|
|
if (IS_ERR(mclk)) {
|
|
ret = PTR_ERR(mclk);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "Failed to get MCLK: %d\n", ret);
|
|
goto err_regulator;
|
|
}
|
|
|
|
mclk_rate = clk_get_rate(mclk);
|
|
clk_put(mclk);
|
|
|
|
if (mclk_rate > 60000000 || mclk_rate < 10000000) {
|
|
dev_err(dev, "Bad mclk %luHz (needs 10MHz - 60MHz)\n",
|
|
mclk_rate);
|
|
ret = -EINVAL;
|
|
goto err_regulator;
|
|
}
|
|
if (mclk_rate >= 40000000)
|
|
max9860->psclk = 3;
|
|
else if (mclk_rate >= 20000000)
|
|
max9860->psclk = 2;
|
|
else
|
|
max9860->psclk = 1;
|
|
max9860->pclk_rate = mclk_rate >> (max9860->psclk - 1);
|
|
max9860->psclk <<= MAX9860_PSCLK_SHIFT;
|
|
dev_dbg(dev, "mclk %lu pclk %lu\n", mclk_rate, max9860->pclk_rate);
|
|
|
|
regcache_cache_bypass(max9860->regmap, true);
|
|
for (i = 0; i < max9860_regmap.num_reg_defaults; ++i) {
|
|
ret = regmap_write(max9860->regmap,
|
|
max9860_regmap.reg_defaults[i].reg,
|
|
max9860_regmap.reg_defaults[i].def);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to initialize register %u: %d\n",
|
|
max9860_regmap.reg_defaults[i].reg, ret);
|
|
goto err_regulator;
|
|
}
|
|
}
|
|
regcache_cache_bypass(max9860->regmap, false);
|
|
|
|
ret = regmap_read(max9860->regmap, MAX9860_INTRSTATUS, &intr);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to clear INTRSTATUS: %d\n", ret);
|
|
goto err_regulator;
|
|
}
|
|
|
|
pm_runtime_set_active(dev);
|
|
pm_runtime_enable(dev);
|
|
pm_runtime_idle(dev);
|
|
|
|
ret = devm_snd_soc_register_component(dev, &max9860_component_driver,
|
|
&max9860_dai, 1);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to register CODEC: %d\n", ret);
|
|
goto err_pm;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_pm:
|
|
pm_runtime_disable(dev);
|
|
err_regulator:
|
|
regulator_disable(max9860->dvddio);
|
|
return ret;
|
|
}
|
|
|
|
static int max9860_remove(struct i2c_client *i2c)
|
|
{
|
|
struct device *dev = &i2c->dev;
|
|
struct max9860_priv *max9860 = dev_get_drvdata(dev);
|
|
|
|
pm_runtime_disable(dev);
|
|
regulator_disable(max9860->dvddio);
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id max9860_i2c_id[] = {
|
|
{ "max9860", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, max9860_i2c_id);
|
|
|
|
static const struct of_device_id max9860_of_match[] = {
|
|
{ .compatible = "maxim,max9860", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, max9860_of_match);
|
|
|
|
static struct i2c_driver max9860_i2c_driver = {
|
|
.probe_new = max9860_probe,
|
|
.remove = max9860_remove,
|
|
.id_table = max9860_i2c_id,
|
|
.driver = {
|
|
.name = "max9860",
|
|
.of_match_table = max9860_of_match,
|
|
.pm = &max9860_pm_ops,
|
|
},
|
|
};
|
|
|
|
module_i2c_driver(max9860_i2c_driver);
|
|
|
|
MODULE_DESCRIPTION("ASoC MAX9860 Mono Audio Voice Codec driver");
|
|
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
|
|
MODULE_LICENSE("GPL v2");
|