mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 06:31:49 +00:00
e3753fd3b3
The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. In v6.5 it has also acquired the ability to generate multi-register writes in sync operations, bringing performance up to parity with the rbtree cache there. Update the cs42xx8 driver to use the more modern data structure. Acked-by: Charles Keepax <ckeepax@opensource.cirrus.com> Signed-off-by: Mark Brown <broonie@kernel.org> Link: https://lore.kernel.org/r/20230713-asoc-cirrus-maple-v1-10-a62651831735@kernel.org Signed-off-by: Mark Brown <broonie@kernel.org>
680 lines
20 KiB
C
680 lines
20 KiB
C
/*
|
|
* Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver
|
|
*
|
|
* Copyright (C) 2014 Freescale Semiconductor, Inc.
|
|
*
|
|
* Author: Nicolin Chen <Guangyu.Chen@freescale.com>
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public License
|
|
* version 2. This program is licensed "as is" without any warranty of any
|
|
* kind, whether express or implied.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/tlv.h>
|
|
|
|
#include "cs42xx8.h"
|
|
|
|
#define CS42XX8_NUM_SUPPLIES 4
|
|
static const char *const cs42xx8_supply_names[CS42XX8_NUM_SUPPLIES] = {
|
|
"VA",
|
|
"VD",
|
|
"VLS",
|
|
"VLC",
|
|
};
|
|
|
|
#define CS42XX8_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
|
|
SNDRV_PCM_FMTBIT_S20_3LE | \
|
|
SNDRV_PCM_FMTBIT_S24_LE | \
|
|
SNDRV_PCM_FMTBIT_S32_LE)
|
|
|
|
/* codec private data */
|
|
struct cs42xx8_priv {
|
|
struct regulator_bulk_data supplies[CS42XX8_NUM_SUPPLIES];
|
|
const struct cs42xx8_driver_data *drvdata;
|
|
struct regmap *regmap;
|
|
struct clk *clk;
|
|
|
|
bool slave_mode;
|
|
unsigned long sysclk;
|
|
u32 tx_channels;
|
|
struct gpio_desc *gpiod_reset;
|
|
u32 rate[2];
|
|
};
|
|
|
|
/* -127.5dB to 0dB with step of 0.5dB */
|
|
static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
|
|
/* -64dB to 24dB with step of 0.5dB */
|
|
static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 0);
|
|
|
|
static const char *const cs42xx8_adc_single[] = { "Differential", "Single-Ended" };
|
|
static const char *const cs42xx8_szc[] = { "Immediate Change", "Zero Cross",
|
|
"Soft Ramp", "Soft Ramp on Zero Cross" };
|
|
|
|
static const struct soc_enum adc1_single_enum =
|
|
SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 4, 2, cs42xx8_adc_single);
|
|
static const struct soc_enum adc2_single_enum =
|
|
SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 3, 2, cs42xx8_adc_single);
|
|
static const struct soc_enum adc3_single_enum =
|
|
SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 2, 2, cs42xx8_adc_single);
|
|
static const struct soc_enum dac_szc_enum =
|
|
SOC_ENUM_SINGLE(CS42XX8_TXCTL, 5, 4, cs42xx8_szc);
|
|
static const struct soc_enum adc_szc_enum =
|
|
SOC_ENUM_SINGLE(CS42XX8_TXCTL, 0, 4, cs42xx8_szc);
|
|
|
|
static const struct snd_kcontrol_new cs42xx8_snd_controls[] = {
|
|
SOC_DOUBLE_R_TLV("DAC1 Playback Volume", CS42XX8_VOLAOUT1,
|
|
CS42XX8_VOLAOUT2, 0, 0xff, 1, dac_tlv),
|
|
SOC_DOUBLE_R_TLV("DAC2 Playback Volume", CS42XX8_VOLAOUT3,
|
|
CS42XX8_VOLAOUT4, 0, 0xff, 1, dac_tlv),
|
|
SOC_DOUBLE_R_TLV("DAC3 Playback Volume", CS42XX8_VOLAOUT5,
|
|
CS42XX8_VOLAOUT6, 0, 0xff, 1, dac_tlv),
|
|
SOC_DOUBLE_R_TLV("DAC4 Playback Volume", CS42XX8_VOLAOUT7,
|
|
CS42XX8_VOLAOUT8, 0, 0xff, 1, dac_tlv),
|
|
SOC_DOUBLE_R_S_TLV("ADC1 Capture Volume", CS42XX8_VOLAIN1,
|
|
CS42XX8_VOLAIN2, 0, -0x80, 0x30, 7, 0, adc_tlv),
|
|
SOC_DOUBLE_R_S_TLV("ADC2 Capture Volume", CS42XX8_VOLAIN3,
|
|
CS42XX8_VOLAIN4, 0, -0x80, 0x30, 7, 0, adc_tlv),
|
|
SOC_DOUBLE("DAC1 Invert Switch", CS42XX8_DACINV, 0, 1, 1, 0),
|
|
SOC_DOUBLE("DAC2 Invert Switch", CS42XX8_DACINV, 2, 3, 1, 0),
|
|
SOC_DOUBLE("DAC3 Invert Switch", CS42XX8_DACINV, 4, 5, 1, 0),
|
|
SOC_DOUBLE("DAC4 Invert Switch", CS42XX8_DACINV, 6, 7, 1, 0),
|
|
SOC_DOUBLE("ADC1 Invert Switch", CS42XX8_ADCINV, 0, 1, 1, 0),
|
|
SOC_DOUBLE("ADC2 Invert Switch", CS42XX8_ADCINV, 2, 3, 1, 0),
|
|
SOC_SINGLE("ADC High-Pass Filter Switch", CS42XX8_ADCCTL, 7, 1, 1),
|
|
SOC_SINGLE("DAC De-emphasis Switch", CS42XX8_ADCCTL, 5, 1, 0),
|
|
SOC_ENUM("ADC1 Single Ended Mode Switch", adc1_single_enum),
|
|
SOC_ENUM("ADC2 Single Ended Mode Switch", adc2_single_enum),
|
|
SOC_SINGLE("DAC Single Volume Control Switch", CS42XX8_TXCTL, 7, 1, 0),
|
|
SOC_ENUM("DAC Soft Ramp & Zero Cross Control Switch", dac_szc_enum),
|
|
SOC_SINGLE("DAC Auto Mute Switch", CS42XX8_TXCTL, 4, 1, 0),
|
|
SOC_SINGLE("Mute ADC Serial Port Switch", CS42XX8_TXCTL, 3, 1, 0),
|
|
SOC_SINGLE("ADC Single Volume Control Switch", CS42XX8_TXCTL, 2, 1, 0),
|
|
SOC_ENUM("ADC Soft Ramp & Zero Cross Control Switch", adc_szc_enum),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new cs42xx8_adc3_snd_controls[] = {
|
|
SOC_DOUBLE_R_S_TLV("ADC3 Capture Volume", CS42XX8_VOLAIN5,
|
|
CS42XX8_VOLAIN6, 0, -0x80, 0x30, 7, 0, adc_tlv),
|
|
SOC_DOUBLE("ADC3 Invert Switch", CS42XX8_ADCINV, 4, 5, 1, 0),
|
|
SOC_ENUM("ADC3 Single Ended Mode Switch", adc3_single_enum),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = {
|
|
SND_SOC_DAPM_DAC("DAC1", "Playback", CS42XX8_PWRCTL, 1, 1),
|
|
SND_SOC_DAPM_DAC("DAC2", "Playback", CS42XX8_PWRCTL, 2, 1),
|
|
SND_SOC_DAPM_DAC("DAC3", "Playback", CS42XX8_PWRCTL, 3, 1),
|
|
SND_SOC_DAPM_DAC("DAC4", "Playback", CS42XX8_PWRCTL, 4, 1),
|
|
|
|
SND_SOC_DAPM_OUTPUT("AOUT1L"),
|
|
SND_SOC_DAPM_OUTPUT("AOUT1R"),
|
|
SND_SOC_DAPM_OUTPUT("AOUT2L"),
|
|
SND_SOC_DAPM_OUTPUT("AOUT2R"),
|
|
SND_SOC_DAPM_OUTPUT("AOUT3L"),
|
|
SND_SOC_DAPM_OUTPUT("AOUT3R"),
|
|
SND_SOC_DAPM_OUTPUT("AOUT4L"),
|
|
SND_SOC_DAPM_OUTPUT("AOUT4R"),
|
|
|
|
SND_SOC_DAPM_ADC("ADC1", "Capture", CS42XX8_PWRCTL, 5, 1),
|
|
SND_SOC_DAPM_ADC("ADC2", "Capture", CS42XX8_PWRCTL, 6, 1),
|
|
|
|
SND_SOC_DAPM_INPUT("AIN1L"),
|
|
SND_SOC_DAPM_INPUT("AIN1R"),
|
|
SND_SOC_DAPM_INPUT("AIN2L"),
|
|
SND_SOC_DAPM_INPUT("AIN2R"),
|
|
|
|
SND_SOC_DAPM_SUPPLY("PWR", CS42XX8_PWRCTL, 0, 1, NULL, 0),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = {
|
|
SND_SOC_DAPM_ADC("ADC3", "Capture", CS42XX8_PWRCTL, 7, 1),
|
|
|
|
SND_SOC_DAPM_INPUT("AIN3L"),
|
|
SND_SOC_DAPM_INPUT("AIN3R"),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = {
|
|
/* Playback */
|
|
{ "AOUT1L", NULL, "DAC1" },
|
|
{ "AOUT1R", NULL, "DAC1" },
|
|
{ "DAC1", NULL, "PWR" },
|
|
|
|
{ "AOUT2L", NULL, "DAC2" },
|
|
{ "AOUT2R", NULL, "DAC2" },
|
|
{ "DAC2", NULL, "PWR" },
|
|
|
|
{ "AOUT3L", NULL, "DAC3" },
|
|
{ "AOUT3R", NULL, "DAC3" },
|
|
{ "DAC3", NULL, "PWR" },
|
|
|
|
{ "AOUT4L", NULL, "DAC4" },
|
|
{ "AOUT4R", NULL, "DAC4" },
|
|
{ "DAC4", NULL, "PWR" },
|
|
|
|
/* Capture */
|
|
{ "ADC1", NULL, "AIN1L" },
|
|
{ "ADC1", NULL, "AIN1R" },
|
|
{ "ADC1", NULL, "PWR" },
|
|
|
|
{ "ADC2", NULL, "AIN2L" },
|
|
{ "ADC2", NULL, "AIN2R" },
|
|
{ "ADC2", NULL, "PWR" },
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = {
|
|
/* Capture */
|
|
{ "ADC3", NULL, "AIN3L" },
|
|
{ "ADC3", NULL, "AIN3R" },
|
|
{ "ADC3", NULL, "PWR" },
|
|
};
|
|
|
|
struct cs42xx8_ratios {
|
|
unsigned int mfreq;
|
|
unsigned int min_mclk;
|
|
unsigned int max_mclk;
|
|
unsigned int ratio[3];
|
|
};
|
|
|
|
/*
|
|
* According to reference mannual, define the cs42xx8_ratio struct
|
|
* MFreq2 | MFreq1 | MFreq0 | Description | SSM | DSM | QSM |
|
|
* 0 | 0 | 0 |1.029MHz to 12.8MHz | 256 | 128 | 64 |
|
|
* 0 | 0 | 1 |1.536MHz to 19.2MHz | 384 | 192 | 96 |
|
|
* 0 | 1 | 0 |2.048MHz to 25.6MHz | 512 | 256 | 128 |
|
|
* 0 | 1 | 1 |3.072MHz to 38.4MHz | 768 | 384 | 192 |
|
|
* 1 | x | x |4.096MHz to 51.2MHz |1024 | 512 | 256 |
|
|
*/
|
|
static const struct cs42xx8_ratios cs42xx8_ratios[] = {
|
|
{ 0, 1029000, 12800000, {256, 128, 64} },
|
|
{ 2, 1536000, 19200000, {384, 192, 96} },
|
|
{ 4, 2048000, 25600000, {512, 256, 128} },
|
|
{ 6, 3072000, 38400000, {768, 384, 192} },
|
|
{ 8, 4096000, 51200000, {1024, 512, 256} },
|
|
};
|
|
|
|
static int cs42xx8_set_dai_sysclk(struct snd_soc_dai *codec_dai,
|
|
int clk_id, unsigned int freq, int dir)
|
|
{
|
|
struct snd_soc_component *component = codec_dai->component;
|
|
struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
|
|
|
|
cs42xx8->sysclk = freq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|
unsigned int format)
|
|
{
|
|
struct snd_soc_component *component = codec_dai->component;
|
|
struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
|
|
u32 val;
|
|
|
|
/* Set DAI format */
|
|
switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
val = CS42XX8_INTF_DAC_DIF_LEFTJ | CS42XX8_INTF_ADC_DIF_LEFTJ;
|
|
break;
|
|
case SND_SOC_DAIFMT_I2S:
|
|
val = CS42XX8_INTF_DAC_DIF_I2S | CS42XX8_INTF_ADC_DIF_I2S;
|
|
break;
|
|
case SND_SOC_DAIFMT_RIGHT_J:
|
|
val = CS42XX8_INTF_DAC_DIF_RIGHTJ | CS42XX8_INTF_ADC_DIF_RIGHTJ;
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM;
|
|
break;
|
|
default:
|
|
dev_err(component->dev, "unsupported dai format\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
regmap_update_bits(cs42xx8->regmap, CS42XX8_INTF,
|
|
CS42XX8_INTF_DAC_DIF_MASK |
|
|
CS42XX8_INTF_ADC_DIF_MASK, val);
|
|
|
|
/* Set master/slave audio interface */
|
|
switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
cs42xx8->slave_mode = true;
|
|
break;
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
cs42xx8->slave_mode = false;
|
|
break;
|
|
default:
|
|
dev_err(component->dev, "unsupported master/slave mode\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs42xx8_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 cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
|
|
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
|
u32 ratio[2];
|
|
u32 rate[2];
|
|
u32 fm[2];
|
|
u32 i, val, mask;
|
|
bool condition1, condition2;
|
|
|
|
if (tx)
|
|
cs42xx8->tx_channels = params_channels(params);
|
|
|
|
rate[tx] = params_rate(params);
|
|
rate[!tx] = cs42xx8->rate[!tx];
|
|
|
|
ratio[tx] = rate[tx] > 0 ? cs42xx8->sysclk / rate[tx] : 0;
|
|
ratio[!tx] = rate[!tx] > 0 ? cs42xx8->sysclk / rate[!tx] : 0;
|
|
|
|
/* Get functional mode for tx and rx according to rate */
|
|
for (i = 0; i < 2; i++) {
|
|
if (cs42xx8->slave_mode) {
|
|
fm[i] = CS42XX8_FM_AUTO;
|
|
} else {
|
|
if (rate[i] < 50000) {
|
|
fm[i] = CS42XX8_FM_SINGLE;
|
|
} else if (rate[i] > 50000 && rate[i] < 100000) {
|
|
fm[i] = CS42XX8_FM_DOUBLE;
|
|
} else if (rate[i] > 100000 && rate[i] < 200000) {
|
|
fm[i] = CS42XX8_FM_QUAD;
|
|
} else {
|
|
dev_err(component->dev,
|
|
"unsupported sample rate\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) {
|
|
/* Is the ratio[tx] valid ? */
|
|
condition1 = ((fm[tx] == CS42XX8_FM_AUTO) ?
|
|
(cs42xx8_ratios[i].ratio[0] == ratio[tx] ||
|
|
cs42xx8_ratios[i].ratio[1] == ratio[tx] ||
|
|
cs42xx8_ratios[i].ratio[2] == ratio[tx]) :
|
|
(cs42xx8_ratios[i].ratio[fm[tx]] == ratio[tx])) &&
|
|
cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk &&
|
|
cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk;
|
|
|
|
if (!ratio[tx])
|
|
condition1 = true;
|
|
|
|
/* Is the ratio[!tx] valid ? */
|
|
condition2 = ((fm[!tx] == CS42XX8_FM_AUTO) ?
|
|
(cs42xx8_ratios[i].ratio[0] == ratio[!tx] ||
|
|
cs42xx8_ratios[i].ratio[1] == ratio[!tx] ||
|
|
cs42xx8_ratios[i].ratio[2] == ratio[!tx]) :
|
|
(cs42xx8_ratios[i].ratio[fm[!tx]] == ratio[!tx]));
|
|
|
|
if (!ratio[!tx])
|
|
condition2 = true;
|
|
|
|
/*
|
|
* Both ratio[tx] and ratio[!tx] is valid, then we get
|
|
* a proper MFreq.
|
|
*/
|
|
if (condition1 && condition2)
|
|
break;
|
|
}
|
|
|
|
if (i == ARRAY_SIZE(cs42xx8_ratios)) {
|
|
dev_err(component->dev, "unsupported sysclk ratio\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
cs42xx8->rate[tx] = params_rate(params);
|
|
|
|
mask = CS42XX8_FUNCMOD_MFREQ_MASK;
|
|
val = cs42xx8_ratios[i].mfreq;
|
|
|
|
regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
|
|
CS42XX8_FUNCMOD_xC_FM_MASK(tx) | mask,
|
|
CS42XX8_FUNCMOD_xC_FM(tx, fm[tx]) | val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs42xx8_hw_free(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
|
|
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
|
|
|
/* Clear stored rate */
|
|
cs42xx8->rate[tx] = 0;
|
|
|
|
regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
|
|
CS42XX8_FUNCMOD_xC_FM_MASK(tx),
|
|
CS42XX8_FUNCMOD_xC_FM(tx, CS42XX8_FM_AUTO));
|
|
return 0;
|
|
}
|
|
|
|
static int cs42xx8_mute(struct snd_soc_dai *dai, int mute, int direction)
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
|
|
u8 dac_unmute = cs42xx8->tx_channels ?
|
|
~((0x1 << cs42xx8->tx_channels) - 1) : 0;
|
|
|
|
regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE,
|
|
mute ? CS42XX8_DACMUTE_ALL : dac_unmute);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops cs42xx8_dai_ops = {
|
|
.set_fmt = cs42xx8_set_dai_fmt,
|
|
.set_sysclk = cs42xx8_set_dai_sysclk,
|
|
.hw_params = cs42xx8_hw_params,
|
|
.hw_free = cs42xx8_hw_free,
|
|
.mute_stream = cs42xx8_mute,
|
|
.no_capture_mute = 1,
|
|
};
|
|
|
|
static struct snd_soc_dai_driver cs42xx8_dai = {
|
|
.playback = {
|
|
.stream_name = "Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 8,
|
|
.rates = SNDRV_PCM_RATE_8000_192000,
|
|
.formats = CS42XX8_FORMATS,
|
|
},
|
|
.capture = {
|
|
.stream_name = "Capture",
|
|
.channels_min = 1,
|
|
.rates = SNDRV_PCM_RATE_8000_192000,
|
|
.formats = CS42XX8_FORMATS,
|
|
},
|
|
.ops = &cs42xx8_dai_ops,
|
|
};
|
|
|
|
static const struct reg_default cs42xx8_reg[] = {
|
|
{ 0x02, 0x00 }, /* Power Control */
|
|
{ 0x03, 0xF0 }, /* Functional Mode */
|
|
{ 0x04, 0x46 }, /* Interface Formats */
|
|
{ 0x05, 0x00 }, /* ADC Control & DAC De-Emphasis */
|
|
{ 0x06, 0x10 }, /* Transition Control */
|
|
{ 0x07, 0x00 }, /* DAC Channel Mute */
|
|
{ 0x08, 0x00 }, /* Volume Control AOUT1 */
|
|
{ 0x09, 0x00 }, /* Volume Control AOUT2 */
|
|
{ 0x0a, 0x00 }, /* Volume Control AOUT3 */
|
|
{ 0x0b, 0x00 }, /* Volume Control AOUT4 */
|
|
{ 0x0c, 0x00 }, /* Volume Control AOUT5 */
|
|
{ 0x0d, 0x00 }, /* Volume Control AOUT6 */
|
|
{ 0x0e, 0x00 }, /* Volume Control AOUT7 */
|
|
{ 0x0f, 0x00 }, /* Volume Control AOUT8 */
|
|
{ 0x10, 0x00 }, /* DAC Channel Invert */
|
|
{ 0x11, 0x00 }, /* Volume Control AIN1 */
|
|
{ 0x12, 0x00 }, /* Volume Control AIN2 */
|
|
{ 0x13, 0x00 }, /* Volume Control AIN3 */
|
|
{ 0x14, 0x00 }, /* Volume Control AIN4 */
|
|
{ 0x15, 0x00 }, /* Volume Control AIN5 */
|
|
{ 0x16, 0x00 }, /* Volume Control AIN6 */
|
|
{ 0x17, 0x00 }, /* ADC Channel Invert */
|
|
{ 0x18, 0x00 }, /* Status Control */
|
|
{ 0x1a, 0x00 }, /* Status Mask */
|
|
{ 0x1b, 0x00 }, /* MUTEC Pin Control */
|
|
};
|
|
|
|
static bool cs42xx8_volatile_register(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case CS42XX8_STATUS:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool cs42xx8_writeable_register(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case CS42XX8_CHIPID:
|
|
case CS42XX8_STATUS:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const struct regmap_config cs42xx8_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = CS42XX8_LASTREG,
|
|
.reg_defaults = cs42xx8_reg,
|
|
.num_reg_defaults = ARRAY_SIZE(cs42xx8_reg),
|
|
.volatile_reg = cs42xx8_volatile_register,
|
|
.writeable_reg = cs42xx8_writeable_register,
|
|
.cache_type = REGCACHE_MAPLE,
|
|
};
|
|
EXPORT_SYMBOL_GPL(cs42xx8_regmap_config);
|
|
|
|
static int cs42xx8_component_probe(struct snd_soc_component *component)
|
|
{
|
|
struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
|
|
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
|
|
|
|
switch (cs42xx8->drvdata->num_adcs) {
|
|
case 3:
|
|
snd_soc_add_component_controls(component, cs42xx8_adc3_snd_controls,
|
|
ARRAY_SIZE(cs42xx8_adc3_snd_controls));
|
|
snd_soc_dapm_new_controls(dapm, cs42xx8_adc3_dapm_widgets,
|
|
ARRAY_SIZE(cs42xx8_adc3_dapm_widgets));
|
|
snd_soc_dapm_add_routes(dapm, cs42xx8_adc3_dapm_routes,
|
|
ARRAY_SIZE(cs42xx8_adc3_dapm_routes));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Mute all DAC channels */
|
|
regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_component_driver cs42xx8_driver = {
|
|
.probe = cs42xx8_component_probe,
|
|
.controls = cs42xx8_snd_controls,
|
|
.num_controls = ARRAY_SIZE(cs42xx8_snd_controls),
|
|
.dapm_widgets = cs42xx8_dapm_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(cs42xx8_dapm_widgets),
|
|
.dapm_routes = cs42xx8_dapm_routes,
|
|
.num_dapm_routes = ARRAY_SIZE(cs42xx8_dapm_routes),
|
|
.use_pmdown_time = 1,
|
|
.endianness = 1,
|
|
};
|
|
|
|
const struct cs42xx8_driver_data cs42448_data = {
|
|
.name = "cs42448",
|
|
.num_adcs = 3,
|
|
};
|
|
EXPORT_SYMBOL_GPL(cs42448_data);
|
|
|
|
const struct cs42xx8_driver_data cs42888_data = {
|
|
.name = "cs42888",
|
|
.num_adcs = 2,
|
|
};
|
|
EXPORT_SYMBOL_GPL(cs42888_data);
|
|
|
|
int cs42xx8_probe(struct device *dev, struct regmap *regmap, struct cs42xx8_driver_data *drvdata)
|
|
{
|
|
struct cs42xx8_priv *cs42xx8;
|
|
int ret, val, i;
|
|
|
|
if (IS_ERR(regmap)) {
|
|
ret = PTR_ERR(regmap);
|
|
dev_err(dev, "failed to allocate regmap: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
cs42xx8 = devm_kzalloc(dev, sizeof(*cs42xx8), GFP_KERNEL);
|
|
if (cs42xx8 == NULL)
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(dev, cs42xx8);
|
|
|
|
cs42xx8->regmap = regmap;
|
|
|
|
cs42xx8->drvdata = drvdata;
|
|
|
|
cs42xx8->gpiod_reset = devm_gpiod_get_optional(dev, "reset",
|
|
GPIOD_OUT_HIGH);
|
|
if (IS_ERR(cs42xx8->gpiod_reset))
|
|
return PTR_ERR(cs42xx8->gpiod_reset);
|
|
|
|
gpiod_set_value_cansleep(cs42xx8->gpiod_reset, 0);
|
|
|
|
cs42xx8->clk = devm_clk_get(dev, "mclk");
|
|
if (IS_ERR(cs42xx8->clk)) {
|
|
dev_err(dev, "failed to get the clock: %ld\n",
|
|
PTR_ERR(cs42xx8->clk));
|
|
return -EINVAL;
|
|
}
|
|
|
|
cs42xx8->sysclk = clk_get_rate(cs42xx8->clk);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cs42xx8->supplies); i++)
|
|
cs42xx8->supplies[i].supply = cs42xx8_supply_names[i];
|
|
|
|
ret = devm_regulator_bulk_get(dev,
|
|
ARRAY_SIZE(cs42xx8->supplies), cs42xx8->supplies);
|
|
if (ret) {
|
|
dev_err(dev, "failed to request supplies: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies),
|
|
cs42xx8->supplies);
|
|
if (ret) {
|
|
dev_err(dev, "failed to enable supplies: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Make sure hardware reset done */
|
|
msleep(5);
|
|
|
|
/* Validate the chip ID */
|
|
ret = regmap_read(cs42xx8->regmap, CS42XX8_CHIPID, &val);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to get device ID, ret = %d", ret);
|
|
goto err_enable;
|
|
}
|
|
|
|
/* The top four bits of the chip ID should be 0000 */
|
|
if (((val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4) != 0x00) {
|
|
dev_err(dev, "unmatched chip ID: %d\n",
|
|
(val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4);
|
|
ret = -EINVAL;
|
|
goto err_enable;
|
|
}
|
|
|
|
dev_info(dev, "found device, revision %X\n",
|
|
val & CS42XX8_CHIPID_REV_ID_MASK);
|
|
|
|
cs42xx8_dai.name = cs42xx8->drvdata->name;
|
|
|
|
/* Each adc supports stereo input */
|
|
cs42xx8_dai.capture.channels_max = cs42xx8->drvdata->num_adcs * 2;
|
|
|
|
ret = devm_snd_soc_register_component(dev, &cs42xx8_driver, &cs42xx8_dai, 1);
|
|
if (ret) {
|
|
dev_err(dev, "failed to register component:%d\n", ret);
|
|
goto err_enable;
|
|
}
|
|
|
|
regcache_cache_only(cs42xx8->regmap, true);
|
|
|
|
err_enable:
|
|
regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
|
|
cs42xx8->supplies);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cs42xx8_probe);
|
|
|
|
#ifdef CONFIG_PM
|
|
static int cs42xx8_runtime_resume(struct device *dev)
|
|
{
|
|
struct cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(cs42xx8->clk);
|
|
if (ret) {
|
|
dev_err(dev, "failed to enable mclk: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
gpiod_set_value_cansleep(cs42xx8->gpiod_reset, 0);
|
|
|
|
ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies),
|
|
cs42xx8->supplies);
|
|
if (ret) {
|
|
dev_err(dev, "failed to enable supplies: %d\n", ret);
|
|
goto err_clk;
|
|
}
|
|
|
|
/* Make sure hardware reset done */
|
|
msleep(5);
|
|
|
|
regcache_cache_only(cs42xx8->regmap, false);
|
|
regcache_mark_dirty(cs42xx8->regmap);
|
|
|
|
ret = regcache_sync(cs42xx8->regmap);
|
|
if (ret) {
|
|
dev_err(dev, "failed to sync regmap: %d\n", ret);
|
|
goto err_bulk;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_bulk:
|
|
regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
|
|
cs42xx8->supplies);
|
|
err_clk:
|
|
clk_disable_unprepare(cs42xx8->clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cs42xx8_runtime_suspend(struct device *dev)
|
|
{
|
|
struct cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev);
|
|
|
|
regcache_cache_only(cs42xx8->regmap, true);
|
|
|
|
regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
|
|
cs42xx8->supplies);
|
|
|
|
gpiod_set_value_cansleep(cs42xx8->gpiod_reset, 1);
|
|
|
|
clk_disable_unprepare(cs42xx8->clk);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
const struct dev_pm_ops cs42xx8_pm = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
|
pm_runtime_force_resume)
|
|
SET_RUNTIME_PM_OPS(cs42xx8_runtime_suspend, cs42xx8_runtime_resume, NULL)
|
|
};
|
|
EXPORT_SYMBOL_GPL(cs42xx8_pm);
|
|
|
|
MODULE_DESCRIPTION("Cirrus Logic CS42448/CS42888 ALSA SoC Codec Driver");
|
|
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
|
MODULE_LICENSE("GPL");
|