Commit c704f4e3 authored by Mark Brown's avatar Mark Brown

Merge remote-tracking branches 'asoc/topic/max98504', 'asoc/topic/max9867',...

Merge remote-tracking branches 'asoc/topic/max98504', 'asoc/topic/max9867', 'asoc/topic/max9877', 'asoc/topic/mtk' and 'asoc/topic/nau8825' into asoc-next
Maxim MAX98504 class D mono speaker amplifier
This device supports I2C control interface and an IRQ output signal. It features
a PCM and PDM digital audio interface (DAI) and a differential analog input.
Required properties:
- compatible : "maxim,max98504"
- reg : should contain the I2C slave device address
- DVDD-supply, DIOVDD-supply, PVDD-supply: power supplies for the device,
as covered in ../regulator/regulator.txt
- interrupts : should specify the interrupt line the device is connected to,
as described in ../interrupt-controller/interrupts.txt
Optional properties:
- maxim,brownout-threshold - the PVDD brownout threshold, the value must be
from 0, 1...21 range, corresponding to 2.6V, 2.65V...3.65V voltage range
- maxim,brownout-attenuation - the brownout attenuation to the speaker gain
applied during the "attack hold" and "timed hold" phase, the value must be
from 0...6 (dB) range
- maxim,brownout-attack-hold-ms - the brownout attack hold phase time in ms,
0...255 (VBATBROWN_ATTK_HOLD, register 0x0018)
- maxim,brownout-timed-hold-ms - the brownout timed hold phase time in ms,
0...255 (VBATBROWN_TIME_HOLD, register 0x0019)
- maxim,brownout-release-rate-ms - the brownout release phase step time in ms,
0...255 (VBATBROWN_RELEASE, register 0x001A)
The default value when the above properties are not specified is 0,
the maxim,brownout-threshold property must be specified to actually enable
the PVDD brownout protection.
Example:
max98504@31 {
compatible = "maxim,max98504";
reg = <0x31>;
interrupt-parent = <&gpio_bank_0>;
interrupts = <2 0>;
DVDD-supply = <&regulator>;
DIOVDD-supply = <&regulator>;
PVDD-supply = <&regulator>;
};
Mediatek AFE PCM controller for mt2701
Required properties:
- compatible = "mediatek,mt2701-audio";
- reg: register location and size
- interrupts: Should contain AFE interrupt
- clock-names: should have these clock names:
"infra_sys_audio_clk",
"top_audio_mux1_sel",
"top_audio_mux2_sel",
"top_audio_mux1_div",
"top_audio_mux2_div",
"top_audio_48k_timing",
"top_audio_44k_timing",
"top_audpll_mux_sel",
"top_apll_sel",
"top_aud1_pll_98M",
"top_aud2_pll_90M",
"top_hadds2_pll_98M",
"top_hadds2_pll_294M",
"top_audpll",
"top_audpll_d4",
"top_audpll_d8",
"top_audpll_d16",
"top_audpll_d24",
"top_audintbus_sel",
"clk_26m",
"top_syspll1_d4",
"top_aud_k1_src_sel",
"top_aud_k2_src_sel",
"top_aud_k3_src_sel",
"top_aud_k4_src_sel",
"top_aud_k5_src_sel",
"top_aud_k6_src_sel",
"top_aud_k1_src_div",
"top_aud_k2_src_div",
"top_aud_k3_src_div",
"top_aud_k4_src_div",
"top_aud_k5_src_div",
"top_aud_k6_src_div",
"top_aud_i2s1_mclk",
"top_aud_i2s2_mclk",
"top_aud_i2s3_mclk",
"top_aud_i2s4_mclk",
"top_aud_i2s5_mclk",
"top_aud_i2s6_mclk",
"top_asm_m_sel",
"top_asm_h_sel",
"top_univpll2_d4",
"top_univpll2_d2",
"top_syspll_d5";
Example:
afe: mt2701-afe-pcm@11220000 {
compatible = "mediatek,mt2701-audio";
reg = <0 0x11220000 0 0x2000>,
<0 0x112A0000 0 0x20000>;
interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_LOW>,
<GIC_SPI 132 IRQ_TYPE_LEVEL_LOW>;
clocks = <&infracfg CLK_INFRA_AUDIO>,
<&topckgen CLK_TOP_AUD_MUX1_SEL>,
<&topckgen CLK_TOP_AUD_MUX2_SEL>,
<&topckgen CLK_TOP_AUD_MUX1_DIV>,
<&topckgen CLK_TOP_AUD_MUX2_DIV>,
<&topckgen CLK_TOP_AUD_48K_TIMING>,
<&topckgen CLK_TOP_AUD_44K_TIMING>,
<&topckgen CLK_TOP_AUDPLL_MUX_SEL>,
<&topckgen CLK_TOP_APLL_SEL>,
<&topckgen CLK_TOP_AUD1PLL_98M>,
<&topckgen CLK_TOP_AUD2PLL_90M>,
<&topckgen CLK_TOP_HADDS2PLL_98M>,
<&topckgen CLK_TOP_HADDS2PLL_294M>,
<&topckgen CLK_TOP_AUDPLL>,
<&topckgen CLK_TOP_AUDPLL_D4>,
<&topckgen CLK_TOP_AUDPLL_D8>,
<&topckgen CLK_TOP_AUDPLL_D16>,
<&topckgen CLK_TOP_AUDPLL_D24>,
<&topckgen CLK_TOP_AUDINTBUS_SEL>,
<&clk26m>,
<&topckgen CLK_TOP_SYSPLL1_D4>,
<&topckgen CLK_TOP_AUD_K1_SRC_SEL>,
<&topckgen CLK_TOP_AUD_K2_SRC_SEL>,
<&topckgen CLK_TOP_AUD_K3_SRC_SEL>,
<&topckgen CLK_TOP_AUD_K4_SRC_SEL>,
<&topckgen CLK_TOP_AUD_K5_SRC_SEL>,
<&topckgen CLK_TOP_AUD_K6_SRC_SEL>,
<&topckgen CLK_TOP_AUD_K1_SRC_DIV>,
<&topckgen CLK_TOP_AUD_K2_SRC_DIV>,
<&topckgen CLK_TOP_AUD_K3_SRC_DIV>,
<&topckgen CLK_TOP_AUD_K4_SRC_DIV>,
<&topckgen CLK_TOP_AUD_K5_SRC_DIV>,
<&topckgen CLK_TOP_AUD_K6_SRC_DIV>,
<&topckgen CLK_TOP_AUD_I2S1_MCLK>,
<&topckgen CLK_TOP_AUD_I2S2_MCLK>,
<&topckgen CLK_TOP_AUD_I2S3_MCLK>,
<&topckgen CLK_TOP_AUD_I2S4_MCLK>,
<&topckgen CLK_TOP_AUD_I2S5_MCLK>,
<&topckgen CLK_TOP_AUD_I2S6_MCLK>,
<&topckgen CLK_TOP_ASM_M_SEL>,
<&topckgen CLK_TOP_ASM_H_SEL>,
<&topckgen CLK_TOP_UNIVPLL2_D4>,
<&topckgen CLK_TOP_UNIVPLL2_D2>,
<&topckgen CLK_TOP_SYSPLL_D5>;
clock-names = "infra_sys_audio_clk",
"top_audio_mux1_sel",
"top_audio_mux2_sel",
"top_audio_mux1_div",
"top_audio_mux2_div",
"top_audio_48k_timing",
"top_audio_44k_timing",
"top_audpll_mux_sel",
"top_apll_sel",
"top_aud1_pll_98M",
"top_aud2_pll_90M",
"top_hadds2_pll_98M",
"top_hadds2_pll_294M",
"top_audpll",
"top_audpll_d4",
"top_audpll_d8",
"top_audpll_d16",
"top_audpll_d24",
"top_audintbus_sel",
"clk_26m",
"top_syspll1_d4",
"top_aud_k1_src_sel",
"top_aud_k2_src_sel",
"top_aud_k3_src_sel",
"top_aud_k4_src_sel",
"top_aud_k5_src_sel",
"top_aud_k6_src_sel",
"top_aud_k1_src_div",
"top_aud_k2_src_div",
"top_aud_k3_src_div",
"top_aud_k4_src_div",
"top_aud_k5_src_div",
"top_aud_k6_src_div",
"top_aud_i2s1_mclk",
"top_aud_i2s2_mclk",
"top_aud_i2s3_mclk",
"top_aud_i2s4_mclk",
"top_aud_i2s5_mclk",
"top_aud_i2s6_mclk",
"top_asm_m_sel",
"top_asm_h_sel",
"top_univpll2_d4",
"top_univpll2_d2",
"top_syspll_d5";
};
MT2701 with CS42448 CODEC
Required properties:
- compatible: "mediatek,mt2701-cs42448-machine"
- mediatek,platform: the phandle of MT2701 ASoC platform
- audio-routing: a list of the connections between audio
- mediatek,audio-codec: the phandles of cs42448 codec
- mediatek,audio-codec-bt-mrg the phandles of bt-sco dummy codec
- pinctrl-names: Should contain only one value - "default"
- pinctrl-0: Should specify pin control groups used for this controller.
- i2s1-in-sel-gpio1, i2s1-in-sel-gpio2: Should specify two gpio pins to
control I2S1-in mux.
Example:
sound:sound {
compatible = "mediatek,mt2701-cs42448-machine";
mediatek,platform = <&afe>;
/* CS42448 Machine name */
audio-routing =
"Line Out Jack", "AOUT1L",
"Line Out Jack", "AOUT1R",
"Line Out Jack", "AOUT2L",
"Line Out Jack", "AOUT2R",
"Line Out Jack", "AOUT3L",
"Line Out Jack", "AOUT3R",
"Line Out Jack", "AOUT4L",
"Line Out Jack", "AOUT4R",
"AIN1L", "AMIC",
"AIN1R", "AMIC",
"AIN2L", "Tuner In",
"AIN2R", "Tuner In",
"AIN3L", "Satellite Tuner In",
"AIN3R", "Satellite Tuner In",
"AIN3L", "AUX In",
"AIN3R", "AUX In";
mediatek,audio-codec = <&cs42448>;
mediatek,audio-codec-bt-mrg = <&bt_sco_codec>;
pinctrl-names = "default";
pinctrl-0 = <&aud_pins_default>;
i2s1-in-sel-gpio1 = <&pio 53 0>;
i2s1-in-sel-gpio2 = <&pio 54 0>;
};
MT8173 with RT5650 CODECS
MT8173 with RT5650 CODECS and HDMI via I2S
Required properties:
- compatible : "mediatek,mt8173-rt5650"
- mediatek,audio-codec: the phandles of rt5650 codecs
and of the hdmi encoder node
- mediatek,platform: the phandle of MT8173 ASoC platform
Optional subnodes:
......@@ -12,12 +13,17 @@ Required codec-capture subnode properties:
<&rt5650 0> : Default setting. Connect rt5650 I2S1 for capture. (dai_name = rt5645-aif1)
<&rt5650 1> : Connect rt5650 I2S2 for capture. (dai_name = rt5645-aif2)
- mediatek,mclk: the MCLK source
0 : external oscillator, MCLK = 12.288M
1 : internal source from mt8173, MCLK = sampling rate*256
Example:
sound {
compatible = "mediatek,mt8173-rt5650";
mediatek,audio-codec = <&rt5650>;
mediatek,audio-codec = <&rt5650 &hdmi0>;
mediatek,platform = <&afe>;
mediatek,mclk = <0>;
codec-capture {
sound-dai = <&rt5650 1>;
};
......
......@@ -557,6 +557,10 @@ config SND_SOC_MAX98357A
config SND_SOC_MAX98371
tristate
config SND_SOC_MAX98504
tristate "Maxim MAX98504 speaker amplifier"
depends on I2C
config SND_SOC_MAX9867
tristate
......
......@@ -213,6 +213,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o
# Amp
snd-soc-max9877-objs := max9877.o
snd-soc-max98504-objs := max98504.o
snd-soc-tpa6130a2-objs := tpa6130a2.o
snd-soc-tas2552-objs := tas2552.o
......@@ -429,4 +430,5 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o
obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o
/*
* MAX98504 ALSA SoC Audio driver
*
* Copyright 2013 - 2014 Maxim Integrated Products
* Copyright 2016 Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <sound/soc.h>
#include "max98504.h"
static const char * const max98504_supply_names[] = {
"DVDD",
"DIOVDD",
"PVDD",
};
#define MAX98504_NUM_SUPPLIES ARRAY_SIZE(max98504_supply_names)
struct max98504_priv {
struct regmap *regmap;
struct regulator_bulk_data supplies[MAX98504_NUM_SUPPLIES];
unsigned int pcm_rx_channels;
bool brownout_enable;
unsigned int brownout_threshold;
unsigned int brownout_attenuation;
unsigned int brownout_attack_hold;
unsigned int brownout_timed_hold;
unsigned int brownout_release_rate;
};
static struct reg_default max98504_reg_defaults[] = {
{ 0x01, 0},
{ 0x02, 0},
{ 0x03, 0},
{ 0x04, 0},
{ 0x10, 0},
{ 0x11, 0},
{ 0x12, 0},
{ 0x13, 0},
{ 0x14, 0},
{ 0x15, 0},
{ 0x16, 0},
{ 0x17, 0},
{ 0x18, 0},
{ 0x19, 0},
{ 0x1A, 0},
{ 0x20, 0},
{ 0x21, 0},
{ 0x22, 0},
{ 0x23, 0},
{ 0x24, 0},
{ 0x25, 0},
{ 0x26, 0},
{ 0x27, 0},
{ 0x28, 0},
{ 0x30, 0},
{ 0x31, 0},
{ 0x32, 0},
{ 0x33, 0},
{ 0x34, 0},
{ 0x35, 0},
{ 0x36, 0},
{ 0x37, 0},
{ 0x38, 0},
{ 0x39, 0},
{ 0x40, 0},
{ 0x41, 0},
};
static bool max98504_volatile_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case MAX98504_INTERRUPT_STATUS:
case MAX98504_INTERRUPT_FLAGS:
case MAX98504_INTERRUPT_FLAG_CLEARS:
case MAX98504_WATCHDOG_CLEAR:
case MAX98504_GLOBAL_ENABLE:
case MAX98504_SOFTWARE_RESET:
return true;
default:
return false;
}
}
static bool max98504_readable_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case MAX98504_SOFTWARE_RESET:
case MAX98504_WATCHDOG_CLEAR:
case MAX98504_INTERRUPT_FLAG_CLEARS:
return false;
default:
return true;
}
}
static int max98504_pcm_rx_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE,
max98504->pcm_rx_channels);
break;
case SND_SOC_DAPM_POST_PMD:
regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE, 0);
break;
}
return 0;
}
static int max98504_component_probe(struct snd_soc_component *c)
{
struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c);
struct regmap *map = max98504->regmap;
int ret;
ret = regulator_bulk_enable(MAX98504_NUM_SUPPLIES, max98504->supplies);
if (ret < 0)
return ret;
regmap_write(map, MAX98504_SOFTWARE_RESET, 0x1);
msleep(20);
if (!max98504->brownout_enable)
return 0;
regmap_write(map, MAX98504_PVDD_BROWNOUT_ENABLE, 0x1);
regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_1,
(max98504->brownout_threshold & 0x1f) << 3 |
(max98504->brownout_attenuation & 0x3));
regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_2,
max98504->brownout_attack_hold & 0xff);
regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_3,
max98504->brownout_timed_hold & 0xff);
regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_4,
max98504->brownout_release_rate & 0xff);
return 0;
}
static void max98504_component_remove(struct snd_soc_component *c)
{
struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c);
regulator_bulk_disable(MAX98504_NUM_SUPPLIES, max98504->supplies);
}
static const char *spk_source_mux_text[] = {
"PCM Monomix", "Analog In", "PDM Left", "PDM Right"
};
static const struct soc_enum spk_source_mux_enum =
SOC_ENUM_SINGLE(MAX98504_SPEAKER_SOURCE_SELECT,
0, ARRAY_SIZE(spk_source_mux_text),
spk_source_mux_text);
static const struct snd_kcontrol_new spk_source_mux =
SOC_DAPM_ENUM("SPK Source", spk_source_mux_enum);
static const struct snd_soc_dapm_route max98504_dapm_routes[] = {
{ "SPKOUT", NULL, "Global Enable" },
{ "SPK Source", "PCM Monomix", "DAC PCM" },
{ "SPK Source", "Analog In", "AIN" },
{ "SPK Source", "PDM Left", "DAC PDM" },
{ "SPK Source", "PDM Right", "DAC PDM" },
};
static const struct snd_soc_dapm_widget max98504_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("Global Enable", MAX98504_GLOBAL_ENABLE,
0, 0, NULL, 0),
SND_SOC_DAPM_INPUT("AIN"),
SND_SOC_DAPM_AIF_OUT("AIF2OUTL", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AIF2OUTR", "AIF2 Capture", 1, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_DAC_E("DAC PCM", NULL, SND_SOC_NOPM, 0, 0,
max98504_pcm_rx_ev,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_DAC("DAC PDM", NULL, MAX98504_PDM_RX_ENABLE, 0, 0),
SND_SOC_DAPM_MUX("SPK Source", SND_SOC_NOPM, 0, 0, &spk_source_mux),
SND_SOC_DAPM_REG(snd_soc_dapm_spk, "SPKOUT",
MAX98504_SPEAKER_ENABLE, 0, 1, 1, 0),
};
static int max98504_set_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width)
{
struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai);
struct regmap *map = max98504->regmap;
switch (dai->id) {
case MAX98504_DAI_ID_PCM:
regmap_write(map, MAX98504_PCM_TX_ENABLE, tx_mask);
max98504->pcm_rx_channels = rx_mask;
break;
case MAX98504_DAI_ID_PDM:
regmap_write(map, MAX98504_PDM_TX_ENABLE, tx_mask);
break;
default:
WARN_ON(1);
}
return 0;
}
static int max98504_set_channel_map(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot)
{
struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai);
struct regmap *map = max98504->regmap;
unsigned int i, sources = 0;
for (i = 0; i < tx_num; i++)
if (tx_slot[i])
sources |= (1 << i);
switch (dai->id) {
case MAX98504_DAI_ID_PCM:
regmap_write(map, MAX98504_PCM_TX_CHANNEL_SOURCES,
sources);
break;
case MAX98504_DAI_ID_PDM:
regmap_write(map, MAX98504_PDM_TX_CONTROL, sources);
break;
default:
WARN_ON(1);
}
regmap_write(map, MAX98504_MEASUREMENT_ENABLE, sources ? 0x3 : 0x01);
return 0;
}
static const struct snd_soc_dai_ops max98504_dai_ops = {
.set_tdm_slot = max98504_set_tdm_slot,
.set_channel_map = max98504_set_channel_map,
};
#define MAX98504_FORMATS (SNDRV_PCM_FMTBIT_S8|SNDRV_PCM_FMTBIT_S16_LE|\
SNDRV_PCM_FMTBIT_S24_LE|SNDRV_PCM_FMTBIT_S32_LE)
#define MAX98504_PDM_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\
SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100|\
SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_88200|\
SNDRV_PCM_RATE_96000)
static struct snd_soc_dai_driver max98504_dai[] = {
/* TODO: Add the PCM interface definitions */
{
.name = "max98504-aif2",
.id = MAX98504_DAI_ID_PDM,
.playback = {
.stream_name = "AIF2 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = MAX98504_PDM_RATES,
.formats = MAX98504_FORMATS,
},
.capture = {
.stream_name = "AIF2 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = MAX98504_PDM_RATES,
.formats = MAX98504_FORMATS,
},
.ops = &max98504_dai_ops,
},
};
static const struct snd_soc_component_driver max98504_component_driver = {
.probe = max98504_component_probe,
.remove = max98504_component_remove,
.dapm_widgets = max98504_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(max98504_dapm_widgets),
.dapm_routes = max98504_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(max98504_dapm_routes),
};
static const struct regmap_config max98504_regmap = {
.reg_bits = 16,
.val_bits = 8,
.max_register = MAX98504_MAX_REGISTER,
.reg_defaults = max98504_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(max98504_reg_defaults),
.volatile_reg = max98504_volatile_register,
.readable_reg = max98504_readable_register,
.cache_type = REGCACHE_RBTREE,
};
static int max98504_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct device_node *node = dev->of_node;
struct max98504_priv *max98504;
int i, ret;
max98504 = devm_kzalloc(dev, sizeof(*max98504), GFP_KERNEL);
if (!max98504)
return -ENOMEM;
if (node) {
if (!of_property_read_u32(node, "maxim,brownout-threshold",
&max98504->brownout_threshold))
max98504->brownout_enable = true;
of_property_read_u32(node, "maxim,brownout-attenuation",
&max98504->brownout_attenuation);
of_property_read_u32(node, "maxim,brownout-attack-hold-ms",
&max98504->brownout_attack_hold);
of_property_read_u32(node, "maxim,brownout-timed-hold-ms",
&max98504->brownout_timed_hold);
of_property_read_u32(node, "maxim,brownout-release-rate-ms",
&max98504->brownout_release_rate);
}
max98504->regmap = devm_regmap_init_i2c(client, &max98504_regmap);
if (IS_ERR(max98504->regmap)) {
ret = PTR_ERR(max98504->regmap);
dev_err(&client->dev, "regmap initialization failed: %d\n", ret);
return ret;
}
for (i = 0; i < MAX98504_NUM_SUPPLIES; i++)
max98504->supplies[i].supply = max98504_supply_names[i];
ret = devm_regulator_bulk_get(dev, MAX98504_NUM_SUPPLIES,
max98504->supplies);
if (ret < 0)
return ret;
i2c_set_clientdata(client, max98504);
return devm_snd_soc_register_component(dev, &max98504_component_driver,
max98504_dai, ARRAY_SIZE(max98504_dai));
}
#ifdef CONFIG_OF
static const struct of_device_id max98504_of_match[] = {
{ .compatible = "maxim,max98504" },
{ },
};
MODULE_DEVICE_TABLE(of, max98504_of_match);
#endif
static const struct i2c_device_id max98504_i2c_id[] = {
{ "max98504" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max98504_i2c_id);
static struct i2c_driver max98504_i2c_driver = {
.driver = {
.name = "max98504",
.of_match_table = of_match_ptr(max98504_of_match),
},
.probe = max98504_i2c_probe,
.id_table = max98504_i2c_id,
};
module_i2c_driver(max98504_i2c_driver);
MODULE_DESCRIPTION("ASoC MAX98504 driver");
MODULE_LICENSE("GPL");
/*
* MAX98504 ALSA SoC Audio driver
*
* Copyright 2011 - 2012 Maxim Integrated Products
* Copyright 2016 Samsung Electronics Co., Ltd.
*
* 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 MAX98504_H_
#define MAX98504_H_
/*
* MAX98504 Register Definitions
*/
#define MAX98504_INTERRUPT_STATUS 0x01
#define MAX98504_INTERRUPT_FLAGS 0x02
#define MAX98504_INTERRUPT_ENABLE 0x03
#define MAX98504_INTERRUPT_FLAG_CLEARS 0x04
#define MAX98504_GPIO_ENABLE 0x10
#define MAX98504_GPIO_CONFIG 0x11
#define MAX98504_WATCHDOG_ENABLE 0x12
#define MAX98504_WATCHDOG_CONFIG 0x13
#define MAX98504_WATCHDOG_CLEAR 0x14
#define MAX98504_CLOCK_MONITOR_ENABLE 0x15
#define MAX98504_PVDD_BROWNOUT_ENABLE 0x16
#define MAX98504_PVDD_BROWNOUT_CONFIG_1 0x17
#define MAX98504_PVDD_BROWNOUT_CONFIG_2 0x18
#define MAX98504_PVDD_BROWNOUT_CONFIG_3 0x19
#define MAX98504_PVDD_BROWNOUT_CONFIG_4 0x1a
#define MAX98504_PCM_RX_ENABLE 0x20
#define MAX98504_PCM_TX_ENABLE 0x21
#define MAX98504_PCM_TX_HIZ_CONTROL 0x22
#define MAX98504_PCM_TX_CHANNEL_SOURCES 0x23
#define MAX98504_PCM_MODE_CONFIG 0x24
#define MAX98504_PCM_DSP_CONFIG 0x25
#define MAX98504_PCM_CLOCK_SETUP 0x26
#define MAX98504_PCM_SAMPLE_RATE_SETUP 0x27
#define MAX98504_PCM_TO_SPEAKER_MONOMIX 0x28
#define MAX98504_PDM_TX_ENABLE 0x30
#define MAX98504_PDM_TX_HIZ_CONTROL 0x31
#define MAX98504_PDM_TX_CONTROL 0x32
#define MAX98504_PDM_RX_ENABLE 0x33
#define MAX98504_SPEAKER_ENABLE 0x34
#define MAX98504_SPEAKER_SOURCE_SELECT 0x35
#define MAX98504_MEASUREMENT_ENABLE 0x36
#define MAX98504_ANALOGUE_INPUT_GAIN 0x37
#define MAX98504_TEMPERATURE_LIMIT_CONFIG 0x38
#define MAX98504_GLOBAL_ENABLE 0x40
#define MAX98504_SOFTWARE_RESET 0x41
#define MAX98504_REV_ID 0x7fff
#define MAX98504_MAX_REGISTER 0x7fff
#define MAX98504_DAI_ID_PCM 1
#define MAX98504_DAI_ID_PDM 2
#endif /* MAX98504_H_ */
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
......@@ -32,6 +32,4 @@
#define MAX9877_BYPASS (1 << 6)
#define MAX9877_SHDN (1 << 7)
extern int max9877_add_controls(struct snd_soc_codec *codec);
#endif
......@@ -18,6 +18,7 @@
#include <linux/clk.h>
#include <linux/acpi.h>
#include <linux/math64.h>
#include <linux/semaphore.h>
#include <sound/initval.h>
#include <sound/tlv.h>
......@@ -30,10 +31,22 @@
#include "nau8825.h"
#define NUVOTON_CODEC_DAI "nau8825-hifi"
#define NAU_FREF_MAX 13500000
#define NAU_FVCO_MAX 100000000
#define NAU_FVCO_MAX 124000000
#define NAU_FVCO_MIN 90000000
/* cross talk suppression detection */
#define LOG10_MAGIC 646456993
#define GAIN_AUGMENT 22500
#define SIDETONE_BASE 207000
static int nau8825_configure_sysclk(struct nau8825 *nau8825,
int clk_id, unsigned int freq);
struct nau8825_fll {
int mclk_src;
int ratio;
......@@ -156,6 +169,661 @@ static const struct reg_default nau8825_reg_defaults[] = {
{ NAU8825_REG_CHARGE_PUMP, 0x0 },
};
/* register backup table when cross talk detection */
static struct reg_default nau8825_xtalk_baktab[] = {
{ NAU8825_REG_ADC_DGAIN_CTRL, 0 },
{ NAU8825_REG_HSVOL_CTRL, 0 },
{ NAU8825_REG_DACL_CTRL, 0 },
{ NAU8825_REG_DACR_CTRL, 0 },
};
static const unsigned short logtable[256] = {
0x0000, 0x0171, 0x02e0, 0x044e, 0x05ba, 0x0725, 0x088e, 0x09f7,
0x0b5d, 0x0cc3, 0x0e27, 0x0f8a, 0x10eb, 0x124b, 0x13aa, 0x1508,
0x1664, 0x17bf, 0x1919, 0x1a71, 0x1bc8, 0x1d1e, 0x1e73, 0x1fc6,
0x2119, 0x226a, 0x23ba, 0x2508, 0x2656, 0x27a2, 0x28ed, 0x2a37,
0x2b80, 0x2cc8, 0x2e0f, 0x2f54, 0x3098, 0x31dc, 0x331e, 0x345f,
0x359f, 0x36de, 0x381b, 0x3958, 0x3a94, 0x3bce, 0x3d08, 0x3e41,
0x3f78, 0x40af, 0x41e4, 0x4319, 0x444c, 0x457f, 0x46b0, 0x47e1,
0x4910, 0x4a3f, 0x4b6c, 0x4c99, 0x4dc5, 0x4eef, 0x5019, 0x5142,
0x526a, 0x5391, 0x54b7, 0x55dc, 0x5700, 0x5824, 0x5946, 0x5a68,
0x5b89, 0x5ca8, 0x5dc7, 0x5ee5, 0x6003, 0x611f, 0x623a, 0x6355,
0x646f, 0x6588, 0x66a0, 0x67b7, 0x68ce, 0x69e4, 0x6af8, 0x6c0c,
0x6d20, 0x6e32, 0x6f44, 0x7055, 0x7165, 0x7274, 0x7383, 0x7490,
0x759d, 0x76aa, 0x77b5, 0x78c0, 0x79ca, 0x7ad3, 0x7bdb, 0x7ce3,
0x7dea, 0x7ef0, 0x7ff6, 0x80fb, 0x81ff, 0x8302, 0x8405, 0x8507,
0x8608, 0x8709, 0x8809, 0x8908, 0x8a06, 0x8b04, 0x8c01, 0x8cfe,
0x8dfa, 0x8ef5, 0x8fef, 0x90e9, 0x91e2, 0x92db, 0x93d2, 0x94ca,
0x95c0, 0x96b6, 0x97ab, 0x98a0, 0x9994, 0x9a87, 0x9b7a, 0x9c6c,
0x9d5e, 0x9e4f, 0x9f3f, 0xa02e, 0xa11e, 0xa20c, 0xa2fa, 0xa3e7,
0xa4d4, 0xa5c0, 0xa6ab, 0xa796, 0xa881, 0xa96a, 0xaa53, 0xab3c,
0xac24, 0xad0c, 0xadf2, 0xaed9, 0xafbe, 0xb0a4, 0xb188, 0xb26c,
0xb350, 0xb433, 0xb515, 0xb5f7, 0xb6d9, 0xb7ba, 0xb89a, 0xb97a,
0xba59, 0xbb38, 0xbc16, 0xbcf4, 0xbdd1, 0xbead, 0xbf8a, 0xc065,
0xc140, 0xc21b, 0xc2f5, 0xc3cf, 0xc4a8, 0xc580, 0xc658, 0xc730,
0xc807, 0xc8de, 0xc9b4, 0xca8a, 0xcb5f, 0xcc34, 0xcd08, 0xcddc,
0xceaf, 0xcf82, 0xd054, 0xd126, 0xd1f7, 0xd2c8, 0xd399, 0xd469,
0xd538, 0xd607, 0xd6d6, 0xd7a4, 0xd872, 0xd93f, 0xda0c, 0xdad9,
0xdba5, 0xdc70, 0xdd3b, 0xde06, 0xded0, 0xdf9a, 0xe063, 0xe12c,
0xe1f5, 0xe2bd, 0xe385, 0xe44c, 0xe513, 0xe5d9, 0xe69f, 0xe765,
0xe82a, 0xe8ef, 0xe9b3, 0xea77, 0xeb3b, 0xebfe, 0xecc1, 0xed83,
0xee45, 0xef06, 0xefc8, 0xf088, 0xf149, 0xf209, 0xf2c8, 0xf387,
0xf446, 0xf505, 0xf5c3, 0xf680, 0xf73e, 0xf7fb, 0xf8b7, 0xf973,
0xfa2f, 0xfaea, 0xfba5, 0xfc60, 0xfd1a, 0xfdd4, 0xfe8e, 0xff47
};
static struct snd_soc_dai *nau8825_get_codec_dai(struct nau8825 *nau8825)
{
struct snd_soc_codec *codec = snd_soc_dapm_to_codec(nau8825->dapm);
struct snd_soc_component *component = &codec->component;
struct snd_soc_dai *codec_dai, *_dai;
list_for_each_entry_safe(codec_dai, _dai, &component->dai_list, list) {
if (!strncmp(codec_dai->name, NUVOTON_CODEC_DAI,
strlen(NUVOTON_CODEC_DAI)))
return codec_dai;
}
return NULL;
}
static bool nau8825_dai_is_active(struct nau8825 *nau8825)
{
struct snd_soc_dai *codec_dai = nau8825_get_codec_dai(nau8825);
if (codec_dai) {
if (codec_dai->playback_active || codec_dai->capture_active)
return true;
}
return false;
}
/**
* nau8825_sema_acquire - acquire the semaphore of nau88l25
* @nau8825: component to register the codec private data with
* @timeout: how long in jiffies to wait before failure or zero to wait
* until release
*
* Attempts to acquire the semaphore with number of jiffies. If no more
* tasks are allowed to acquire the semaphore, calling this function will
* put the task to sleep. If the semaphore is not released within the
* specified number of jiffies, this function returns.
* Acquires the semaphore without jiffies. If no more tasks are allowed
* to acquire the semaphore, calling this function will put the task to
* sleep until the semaphore is released.
* It returns if the semaphore was acquired.
*/
static void nau8825_sema_acquire(struct nau8825 *nau8825, long timeout)
{
int ret;
if (timeout)
ret = down_timeout(&nau8825->xtalk_sem, timeout);
else
ret = down_interruptible(&nau8825->xtalk_sem);
if (ret < 0)
dev_warn(nau8825->dev, "Acquire semaphone fail\n");
}
/**
* nau8825_sema_release - release the semaphore of nau88l25
* @nau8825: component to register the codec private data with
*
* Release the semaphore which may be called from any context and
* even by tasks which have never called down().
*/
static inline void nau8825_sema_release(struct nau8825 *nau8825)
{
up(&nau8825->xtalk_sem);
}
/**
* nau8825_sema_reset - reset the semaphore for nau88l25
* @nau8825: component to register the codec private data with
*
* Reset the counter of the semaphore. Call this function to restart
* a new round task management.
*/
static inline void nau8825_sema_reset(struct nau8825 *nau8825)
{
nau8825->xtalk_sem.count = 1;
}
/**
* Ramp up the headphone volume change gradually to target level.
*
* @nau8825: component to register the codec private data with
* @vol_from: the volume to start up
* @vol_to: the target volume
* @step: the volume span to move on
*
* The headphone volume is from 0dB to minimum -54dB and -1dB per step.
* If the volume changes sharp, there is a pop noise heard in headphone. We
* provide the function to ramp up the volume up or down by delaying 10ms
* per step.
*/
static void nau8825_hpvol_ramp(struct nau8825 *nau8825,
unsigned int vol_from, unsigned int vol_to, unsigned int step)
{
unsigned int value, volume, ramp_up, from, to;
if (vol_from == vol_to || step == 0) {
return;
} else if (vol_from < vol_to) {
ramp_up = true;
from = vol_from;
to = vol_to;
} else {
ramp_up = false;
from = vol_to;
to = vol_from;
}
/* only handle volume from 0dB to minimum -54dB */
if (to > NAU8825_HP_VOL_MIN)
to = NAU8825_HP_VOL_MIN;
for (volume = from; volume < to; volume += step) {
if (ramp_up)
value = volume;
else
value = to - volume + from;
regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL,
NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK,
(value << NAU8825_HPL_VOL_SFT) | value);
usleep_range(10000, 10500);
}
if (ramp_up)
value = to;
else
value = from;
regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL,
NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK,
(value << NAU8825_HPL_VOL_SFT) | value);
}
/**
* Computes log10 of a value; the result is round off to 3 decimal. This func-
* tion takes reference to dvb-math. The source code locates as the following.
* Linux/drivers/media/dvb-core/dvb_math.c
*
* return log10(value) * 1000
*/
static u32 nau8825_intlog10_dec3(u32 value)
{
u32 msb, logentry, significand, interpolation, log10val;
u64 log2val;
/* first detect the msb (count begins at 0) */
msb = fls(value) - 1;
/**
* now we use a logtable after the following method:
*
* log2(2^x * y) * 2^24 = x * 2^24 + log2(y) * 2^24
* where x = msb and therefore 1 <= y < 2
* first y is determined by shifting the value left
* so that msb is bit 31
* 0x00231f56 -> 0x8C7D5800
* the result is y * 2^31 -> "significand"
* then the highest 9 bits are used for a table lookup
* the highest bit is discarded because it's always set
* the highest nine bits in our example are 100011000
* so we would use the entry 0x18
*/
significand = value << (31 - msb);
logentry = (significand >> 23) & 0xff;
/**
* last step we do is interpolation because of the
* limitations of the log table the error is that part of
* the significand which isn't used for lookup then we
* compute the ratio between the error and the next table entry
* and interpolate it between the log table entry used and the
* next one the biggest error possible is 0x7fffff
* (in our example it's 0x7D5800)
* needed value for next table entry is 0x800000
* so the interpolation is
* (error / 0x800000) * (logtable_next - logtable_current)
* in the implementation the division is moved to the end for
* better accuracy there is also an overflow correction if
* logtable_next is 256
*/
interpolation = ((significand & 0x7fffff) *
((logtable[(logentry + 1) & 0xff] -
logtable[logentry]) & 0xffff)) >> 15;
log2val = ((msb << 24) + (logtable[logentry] << 8) + interpolation);
/**
* log10(x) = log2(x) * log10(2)
*/
log10val = (log2val * LOG10_MAGIC) >> 31;
/**
* the result is round off to 3 decimal
*/
return log10val / ((1 << 24) / 1000);
}
/**
* computes cross talk suppression sidetone gain.
*
* @sig_org: orignal signal level
* @sig_cros: cross talk signal level
*
* The orignal and cross talk signal vlues need to be characterized.
* Once these values have been characterized, this sidetone value
* can be converted to decibel with the equation below.
* sidetone = 20 * log (original signal level / crosstalk signal level)
*
* return cross talk sidetone gain
*/
static u32 nau8825_xtalk_sidetone(u32 sig_org, u32 sig_cros)
{
u32 gain, sidetone;
if (unlikely(sig_org == 0) || unlikely(sig_cros == 0)) {
WARN_ON(1);
return 0;
}
sig_org = nau8825_intlog10_dec3(sig_org);
sig_cros = nau8825_intlog10_dec3(sig_cros);
if (sig_org >= sig_cros)
gain = (sig_org - sig_cros) * 20 + GAIN_AUGMENT;
else
gain = (sig_cros - sig_org) * 20 + GAIN_AUGMENT;
sidetone = SIDETONE_BASE - gain * 2;
sidetone /= 1000;
return sidetone;
}
static int nau8825_xtalk_baktab_index_by_reg(unsigned int reg)
{
int index;
for (index = 0; index < ARRAY_SIZE(nau8825_xtalk_baktab); index++)
if (nau8825_xtalk_baktab[index].reg == reg)
return index;
return -EINVAL;
}
static void nau8825_xtalk_backup(struct nau8825 *nau8825)
{
int i;
/* Backup some register values to backup table */
for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++)
regmap_read(nau8825->regmap, nau8825_xtalk_baktab[i].reg,
&nau8825_xtalk_baktab[i].def);
}
static void nau8825_xtalk_restore(struct nau8825 *nau8825)
{
int i, volume;
/* Restore register values from backup table; When the driver restores
* the headphone volumem, it needs recover to original level gradually
* with 3dB per step for less pop noise.
*/
for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++) {
if (nau8825_xtalk_baktab[i].reg == NAU8825_REG_HSVOL_CTRL) {
/* Ramping up the volume change to reduce pop noise */
volume = nau8825_xtalk_baktab[i].def &
NAU8825_HPR_VOL_MASK;
nau8825_hpvol_ramp(nau8825, 0, volume, 3);
continue;
}
regmap_write(nau8825->regmap, nau8825_xtalk_baktab[i].reg,
nau8825_xtalk_baktab[i].def);
}
}
static void nau8825_xtalk_prepare_dac(struct nau8825 *nau8825)
{
/* Enable power of DAC path */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL |
NAU8825_ENABLE_ADC | NAU8825_ENABLE_ADC_CLK |
NAU8825_ENABLE_DAC_CLK, NAU8825_ENABLE_DACR |
NAU8825_ENABLE_DACL | NAU8825_ENABLE_ADC |
NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK);
/* Prevent startup click by letting charge pump to ramp up and
* change bump enable
*/
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN,
NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN);
/* Enable clock sync of DAC and DAC clock */
regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC,
NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN |
NAU8825_RDAC_FS_BCLK_ENB,
NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN);
/* Power up output driver with 2 stage */
regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L |
NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L,
NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L |
NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L);
regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L,
NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L);
/* HP outputs not shouted to ground */
regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL,
NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, 0);
/* Enable HP boost driver */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST,
NAU8825_HP_BOOST_DIS, NAU8825_HP_BOOST_DIS);
/* Enable class G compare path to supply 1.8V or 0.9V. */
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLASSG_CTRL,
NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN,
NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN);
}
static void nau8825_xtalk_prepare_adc(struct nau8825 *nau8825)
{
/* Power up left ADC and raise 5dB than Vmid for Vref */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2,
NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK,
NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_VMID_PLUS_0_5DB);
}
static void nau8825_xtalk_clock(struct nau8825 *nau8825)
{
/* Recover FLL default value */
regmap_write(nau8825->regmap, NAU8825_REG_FLL1, 0x0);
regmap_write(nau8825->regmap, NAU8825_REG_FLL2, 0x3126);
regmap_write(nau8825->regmap, NAU8825_REG_FLL3, 0x0008);
regmap_write(nau8825->regmap, NAU8825_REG_FLL4, 0x0010);
regmap_write(nau8825->regmap, NAU8825_REG_FLL5, 0x0);
regmap_write(nau8825->regmap, NAU8825_REG_FLL6, 0x6000);
/* Enable internal VCO clock for detection signal generated */
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO);
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN,
NAU8825_DCO_EN);
/* Given specific clock frequency of internal clock to
* generate signal.
*/
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_MCLK_SRC_MASK, 0xf);
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1,
NAU8825_FLL_RATIO_MASK, 0x10);
}
static void nau8825_xtalk_prepare(struct nau8825 *nau8825)
{
int volume, index;
/* Backup those registers changed by cross talk detection */
nau8825_xtalk_backup(nau8825);
/* Config IIS as master to output signal by codec */
regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK | NAU8825_I2S_DRV_MASK |
NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_MASTER |
(0x2 << NAU8825_I2S_DRV_SFT) | 0x1);
/* Ramp up headphone volume to 0dB to get better performance and
* avoid pop noise in headphone.
*/
index = nau8825_xtalk_baktab_index_by_reg(NAU8825_REG_HSVOL_CTRL);
if (index != -EINVAL) {
volume = nau8825_xtalk_baktab[index].def &
NAU8825_HPR_VOL_MASK;
nau8825_hpvol_ramp(nau8825, volume, 0, 3);
}
nau8825_xtalk_clock(nau8825);
nau8825_xtalk_prepare_dac(nau8825);
nau8825_xtalk_prepare_adc(nau8825);
/* Config channel path and digital gain */
regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL,
NAU8825_DACL_CH_SEL_MASK | NAU8825_DACL_CH_VOL_MASK,
NAU8825_DACL_CH_SEL_L | 0xab);
regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL,
NAU8825_DACR_CH_SEL_MASK | NAU8825_DACR_CH_VOL_MASK,
NAU8825_DACR_CH_SEL_R | 0xab);
/* Config cross talk parameters and generate the 23Hz sine wave with
* 1/16 full scale of signal level for impedance measurement.
*/
regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL,
NAU8825_IMM_THD_MASK | NAU8825_IMM_GEN_VOL_MASK |
NAU8825_IMM_CYC_MASK | NAU8825_IMM_DAC_SRC_MASK,
(0x9 << NAU8825_IMM_THD_SFT) | NAU8825_IMM_GEN_VOL_1_16th |
NAU8825_IMM_CYC_8192 | NAU8825_IMM_DAC_SRC_SIN);
/* RMS intrruption enable */
regmap_update_bits(nau8825->regmap,
NAU8825_REG_INTERRUPT_MASK, NAU8825_IRQ_RMS_EN, 0);
/* Power up left and right DAC */
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0);
}
static void nau8825_xtalk_clean_dac(struct nau8825 *nau8825)
{
/* Disable HP boost driver */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST,
NAU8825_HP_BOOST_DIS, 0);
/* HP outputs shouted to ground */
regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL,
NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L,
NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L);
/* Power down left and right DAC */
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL,
NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL);
/* Enable the TESTDAC and disable L/R HP impedance */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP |
NAU8825_BIAS_TESTDAC_EN, NAU8825_BIAS_TESTDAC_EN);
/* Power down output driver with 2 stage */
regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L, 0);
regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L |
NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L, 0);
/* Disable clock sync of DAC and DAC clock */
regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC,
NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN, 0);
/* Disable charge pump ramp up function and change bump */
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN, 0);
/* Disable power of DAC path */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL |
NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK, 0);
if (!nau8825->irq)
regmap_update_bits(nau8825->regmap,
NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0);
}
static void nau8825_xtalk_clean_adc(struct nau8825 *nau8825)
{
/* Power down left ADC and restore voltage to Vmid */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2,
NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK, 0);
}
static void nau8825_xtalk_clean(struct nau8825 *nau8825)
{
/* Enable internal VCO needed for interruptions */
nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0);
nau8825_xtalk_clean_dac(nau8825);
nau8825_xtalk_clean_adc(nau8825);
/* Clear cross talk parameters and disable */
regmap_write(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, 0);
/* RMS intrruption disable */
regmap_update_bits(nau8825->regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_RMS_EN, NAU8825_IRQ_RMS_EN);
/* Recover default value for IIS */
regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK | NAU8825_I2S_DRV_MASK |
NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_SLAVE);
/* Restore value of specific register for cross talk */
nau8825_xtalk_restore(nau8825);
}
static void nau8825_xtalk_imm_start(struct nau8825 *nau8825, int vol)
{
/* Apply ADC volume for better cross talk performance */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ADC_DGAIN_CTRL,
NAU8825_ADC_DIG_VOL_MASK, vol);
/* Disables JKTIP(HPL) DAC channel for right to left measurement.
* Do it before sending signal in order to erase pop noise.
*/
regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
NAU8825_BIAS_TESTDACR_EN | NAU8825_BIAS_TESTDACL_EN,
NAU8825_BIAS_TESTDACL_EN);
switch (nau8825->xtalk_state) {
case NAU8825_XTALK_HPR_R2L:
/* Enable right headphone impedance */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP,
NAU8825_BIAS_HPR_IMP);
break;
case NAU8825_XTALK_HPL_R2L:
/* Enable left headphone impedance */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP,
NAU8825_BIAS_HPL_IMP);
break;
default:
break;
}
msleep(100);
/* Impedance measurement mode enable */
regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL,
NAU8825_IMM_EN, NAU8825_IMM_EN);
}
static void nau8825_xtalk_imm_stop(struct nau8825 *nau8825)
{
/* Impedance measurement mode disable */
regmap_update_bits(nau8825->regmap,
NAU8825_REG_IMM_MODE_CTRL, NAU8825_IMM_EN, 0);
}
/* The cross talk measurement function can reduce cross talk across the
* JKTIP(HPL) and JKR1(HPR) outputs which measures the cross talk signal
* level to determine what cross talk reduction gain is. This system works by
* sending a 23Hz -24dBV sine wave into the headset output DAC and through
* the PGA. The output of the PGA is then connected to an internal current
* sense which measures the attenuated 23Hz signal and passing the output to
* an ADC which converts the measurement to a binary code. With two separated
* measurement, one for JKR1(HPR) and the other JKTIP(HPL), measurement data
* can be separated read in IMM_RMS_L for HSR and HSL after each measurement.
* Thus, the measurement function has four states to complete whole sequence.
* 1. Prepare state : Prepare the resource for detection and transfer to HPR
* IMM stat to make JKR1(HPR) impedance measure.
* 2. HPR IMM state : Read out orignal signal level of JKR1(HPR) and transfer
* to HPL IMM state to make JKTIP(HPL) impedance measure.
* 3. HPL IMM state : Read out cross talk signal level of JKTIP(HPL) and
* transfer to IMM state to determine suppression sidetone gain.
* 4. IMM state : Computes cross talk suppression sidetone gain with orignal
* and cross talk signal level. Apply this gain and then restore codec
* configuration. Then transfer to Done state for ending.
*/
static void nau8825_xtalk_measure(struct nau8825 *nau8825)
{
u32 sidetone;
switch (nau8825->xtalk_state) {
case NAU8825_XTALK_PREPARE:
/* In prepare state, set up clock, intrruption, DAC path, ADC
* path and cross talk detection parameters for preparation.
*/
nau8825_xtalk_prepare(nau8825);
msleep(280);
/* Trigger right headphone impedance detection */
nau8825->xtalk_state = NAU8825_XTALK_HPR_R2L;
nau8825_xtalk_imm_start(nau8825, 0x00d2);
break;
case NAU8825_XTALK_HPR_R2L:
/* In right headphone IMM state, read out right headphone
* impedance measure result, and then start up left side.
*/
regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L,
&nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]);
dev_dbg(nau8825->dev, "HPR_R2L imm: %x\n",
nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]);
/* Disable then re-enable IMM mode to update */
nau8825_xtalk_imm_stop(nau8825);
/* Trigger left headphone impedance detection */
nau8825->xtalk_state = NAU8825_XTALK_HPL_R2L;
nau8825_xtalk_imm_start(nau8825, 0x00ff);
break;
case NAU8825_XTALK_HPL_R2L:
/* In left headphone IMM state, read out left headphone
* impedance measure result, and delay some time to wait
* detection sine wave output finish. Then, we can calculate
* the cross talk suppresstion side tone according to the L/R
* headphone imedance.
*/
regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L,
&nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]);
dev_dbg(nau8825->dev, "HPL_R2L imm: %x\n",
nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]);
nau8825_xtalk_imm_stop(nau8825);
msleep(150);
nau8825->xtalk_state = NAU8825_XTALK_IMM;
break;
case NAU8825_XTALK_IMM:
/* In impedance measure state, the orignal and cross talk
* signal level vlues are ready. The side tone gain is deter-
* mined with these signal level. After all, restore codec
* configuration.
*/
sidetone = nau8825_xtalk_sidetone(
nau8825->imp_rms[NAU8825_XTALK_HPR_R2L],
nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]);
dev_dbg(nau8825->dev, "cross talk sidetone: %x\n", sidetone);
regmap_write(nau8825->regmap, NAU8825_REG_DAC_DGAIN_CTRL,
(sidetone << 8) | sidetone);
nau8825_xtalk_clean(nau8825);
nau8825->xtalk_state = NAU8825_XTALK_DONE;
break;
default:
break;
}
}
static void nau8825_xtalk_work(struct work_struct *work)
{
struct nau8825 *nau8825 = container_of(
work, struct nau8825, xtalk_work);
nau8825_xtalk_measure(nau8825);
/* To determine the cross talk side tone gain when reach
* the impedance measure state.
*/
if (nau8825->xtalk_state == NAU8825_XTALK_IMM)
nau8825_xtalk_measure(nau8825);
/* Delay jack report until cross talk detection process
* completed. It can avoid application to do playback
* preparation before cross talk detection is still working.
* Meanwhile, the protection of the cross talk detection
* is released.
*/
if (nau8825->xtalk_state == NAU8825_XTALK_DONE) {
snd_soc_jack_report(nau8825->jack, nau8825->xtalk_event,
nau8825->xtalk_event_mask);
nau8825_sema_release(nau8825);
nau8825->xtalk_protect = false;
}
}
static void nau8825_xtalk_cancel(struct nau8825 *nau8825)
{
/* If the xtalk_protect is true, that means the process is still
* on going. The driver forces to cancel the cross talk task and
* restores the configuration to original status.
*/
if (nau8825->xtalk_protect) {
cancel_work_sync(&nau8825->xtalk_work);
nau8825_xtalk_clean(nau8825);
}
/* Reset parameters for cross talk suppression function */
nau8825_sema_reset(nau8825);
nau8825->xtalk_state = NAU8825_XTALK_DONE;
nau8825->xtalk_protect = false;
}
static bool nau8825_readable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
......@@ -217,12 +885,36 @@ static bool nau8825_volatile_reg(struct device *dev, unsigned int reg)
case NAU8825_REG_SARDOUT_RAM_STATUS:
case NAU8825_REG_CHARGE_PUMP_INPUT_READ:
case NAU8825_REG_GENERAL_STATUS:
case NAU8825_REG_BIQ_CTRL ... NAU8825_REG_BIQ_COF10:
return true;
default:
return false;
}
}
static int nau8825_adc_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);
switch (event) {
case SND_SOC_DAPM_POST_PMU:
regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC);
break;
case SND_SOC_DAPM_POST_PMD:
if (!nau8825->irq)
regmap_update_bits(nau8825->regmap,
NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0);
break;
default:
return -EINVAL;
}
return 0;
}
static int nau8825_pump_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
......@@ -270,6 +962,54 @@ static int nau8825_output_dac_event(struct snd_soc_dapm_widget *w,
return 0;
}
static int nau8825_biq_coeff_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_bytes_ext *params = (void *)kcontrol->private_value;
if (!component->regmap)
return -EINVAL;
regmap_raw_read(component->regmap, NAU8825_REG_BIQ_COF1,
ucontrol->value.bytes.data, params->max);
return 0;
}
static int nau8825_biq_coeff_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_bytes_ext *params = (void *)kcontrol->private_value;
void *data;
if (!component->regmap)
return -EINVAL;
data = kmemdup(ucontrol->value.bytes.data,
params->max, GFP_KERNEL | GFP_DMA);
if (!data)
return -ENOMEM;
regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL,
NAU8825_BIQ_WRT_EN, 0);
regmap_raw_write(component->regmap, NAU8825_REG_BIQ_COF1,
data, params->max);
regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL,
NAU8825_BIQ_WRT_EN, NAU8825_BIQ_WRT_EN);
kfree(data);
return 0;
}
static const char * const nau8825_biq_path[] = {
"ADC", "DAC"
};
static const struct soc_enum nau8825_biq_path_enum =
SOC_ENUM_SINGLE(NAU8825_REG_BIQ_CTRL, NAU8825_BIQ_PATH_SFT,
ARRAY_SIZE(nau8825_biq_path), nau8825_biq_path);
static const char * const nau8825_adc_decimation[] = {
"32", "64", "128", "256"
};
......@@ -306,6 +1046,10 @@ static const struct snd_kcontrol_new nau8825_controls[] = {
SOC_ENUM("ADC Decimation Rate", nau8825_adc_decimation_enum),
SOC_ENUM("DAC Oversampling Rate", nau8825_dac_oversampl_enum),
/* programmable biquad filter */
SOC_ENUM("BIQ Path Select", nau8825_biq_path_enum),
SND_SOC_BYTES_EXT("BIQ Coefficients", 20,
nau8825_biq_coeff_get, nau8825_biq_coeff_put),
};
/* DAC Mux 0x33[9] and 0x34[9] */
......@@ -338,7 +1082,9 @@ static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = {
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_ADC_E("ADC", NULL, SND_SOC_NOPM, 0, 0,
nau8825_adc_event, SND_SOC_DAPM_POST_PMU |
SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("ADC Clock", NAU8825_REG_ENA_CTRL, 7, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC Power", NAU8825_REG_ANALOG_ADC_2, 6, 0, NULL,
0),
......@@ -592,9 +1338,6 @@ int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L,
NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L);
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0);
return 0;
}
EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect);
......@@ -602,24 +1345,21 @@ EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect);
static bool nau8825_is_jack_inserted(struct regmap *regmap)
{
int status;
bool active_high, is_high;
int status, jkdet;
regmap_read(regmap, NAU8825_REG_JACK_DET_CTRL, &jkdet);
active_high = jkdet & NAU8825_JACK_POLARITY;
regmap_read(regmap, NAU8825_REG_I2C_DEVICE_ID, &status);
return !(status & NAU8825_GPIO2JD1);
is_high = status & NAU8825_GPIO2JD1;
/* return jack connection status according to jack insertion logic
* active high or active low.
*/
return active_high == is_high;
}
static void nau8825_restart_jack_detection(struct regmap *regmap)
{
/* 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.
*/
regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER);
regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE);
/* 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.
......@@ -630,11 +1370,30 @@ static void nau8825_restart_jack_detection(struct regmap *regmap)
NAU8825_JACK_DET_RESTART, 0);
}
static void nau8825_int_status_clear_all(struct regmap *regmap)
{
int active_irq, clear_irq, i;
/* Reset the intrruption status from rightmost bit if the corres-
* ponding irq event occurs.
*/
regmap_read(regmap, NAU8825_REG_IRQ_STATUS, &active_irq);
for (i = 0; i < NAU8825_REG_DATA_LEN; i++) {
clear_irq = (0x1 << i);
if (active_irq & clear_irq)
regmap_write(regmap,
NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq);
}
}
static void nau8825_eject_jack(struct nau8825 *nau8825)
{
struct snd_soc_dapm_context *dapm = nau8825->dapm;
struct regmap *regmap = nau8825->regmap;
/* Force to cancel the cross talk detection process */
nau8825_xtalk_cancel(nau8825);
snd_soc_dapm_disable_pin(dapm, "SAR");
snd_soc_dapm_disable_pin(dapm, "MICBIAS");
/* Detach 2kOhm Resistors from MICBIAS to MICGND1/2 */
......@@ -644,6 +1403,69 @@ static void nau8825_eject_jack(struct nau8825 *nau8825)
regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0xf, 0xf);
snd_soc_dapm_sync(dapm);
/* Clear all interruption status */
nau8825_int_status_clear_all(regmap);
/* Enable the insertion interruption, disable the ejection inter-
* ruption, and then bypass de-bounce circuit.
*/
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL,
NAU8825_IRQ_EJECT_DIS | NAU8825_IRQ_INSERT_DIS,
NAU8825_IRQ_EJECT_DIS);
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN |
NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_INSERT_EN,
NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN |
NAU8825_IRQ_HEADSET_COMPLETE_EN);
regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS);
/* Disable ADC needed for interruptions at audo mode */
regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_ADC, 0);
/* Close clock for jack type detection at manual mode */
nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0);
}
/* Enable audo mode interruptions with internal clock. */
static void nau8825_setup_auto_irq(struct nau8825 *nau8825)
{
struct regmap *regmap = nau8825->regmap;
/* Enable headset jack type detection complete interruption and
* jack ejection interruption.
*/
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0);
/* Enable internal VCO needed for interruptions */
nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0);
/* Enable ADC needed for interruptions */
regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC);
/* Chip needs one FSCLK cycle in order to generate interruptions,
* 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.
*/
regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER);
regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE);
/* Not bypass de-bounce circuit */
regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
NAU8825_JACK_DET_DB_BYPASS, 0);
/* Unmask all interruptions */
regmap_write(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0);
/* Restart the jack detection process at auto mode */
nau8825_restart_jack_detection(regmap);
}
static int nau8825_button_decode(int value)
......@@ -676,6 +1498,11 @@ static int nau8825_jack_insert(struct nau8825 *nau8825)
regmap_read(regmap, NAU8825_REG_GENERAL_STATUS, &jack_status_reg);
mic_detected = (jack_status_reg >> 10) & 3;
/* The JKSLV and JKR2 all detected in high impedance headset */
if (mic_detected == 0x3)
nau8825->high_imped = true;
else
nau8825->high_imped = false;
switch (mic_detected) {
case 0:
......@@ -773,6 +1600,33 @@ static irqreturn_t nau8825_interrupt(int irq, void *data)
} else if (active_irq & NAU8825_HEADSET_COMPLETION_IRQ) {
if (nau8825_is_jack_inserted(regmap)) {
event |= nau8825_jack_insert(nau8825);
if (!nau8825->high_imped) {
/* Apply the cross talk suppression in the
* headset without high impedance.
*/
if (!nau8825->xtalk_protect) {
/* Raise protection for cross talk de-
* tection if no protection before.
* The driver has to cancel the pro-
* cess and restore changes if process
* is ongoing when ejection.
*/
nau8825->xtalk_protect = true;
nau8825_sema_acquire(nau8825, 0);
}
/* Startup cross talk detection process */
nau8825->xtalk_state = NAU8825_XTALK_PREPARE;
schedule_work(&nau8825->xtalk_work);
} else {
/* The cross talk suppression shouldn't apply
* in the headset with high impedance. Thus,
* relieve the protection raised before.
*/
if (nau8825->xtalk_protect) {
nau8825_sema_release(nau8825);
nau8825->xtalk_protect = false;
}
}
} else {
dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n");
nau8825_eject_jack(nau8825);
......@@ -780,6 +1634,37 @@ static irqreturn_t nau8825_interrupt(int irq, void *data)
event_mask |= SND_JACK_HEADSET;
clear_irq = NAU8825_HEADSET_COMPLETION_IRQ;
/* Record the interruption report event for driver to report
* the event later. The jack report will delay until cross
* talk detection process is done.
*/
if (nau8825->xtalk_state == NAU8825_XTALK_PREPARE) {
nau8825->xtalk_event = event;
nau8825->xtalk_event_mask = event_mask;
}
} else if (active_irq & NAU8825_IMPEDANCE_MEAS_IRQ) {
schedule_work(&nau8825->xtalk_work);
clear_irq = NAU8825_IMPEDANCE_MEAS_IRQ;
} else if ((active_irq & NAU8825_JACK_INSERTION_IRQ_MASK) ==
NAU8825_JACK_INSERTION_DETECTED) {
/* One more step to check GPIO status directly. Thus, the
* driver can confirm the real insertion interruption because
* the intrruption at manual mode has bypassed debounce
* circuit which can get rid of unstable status.
*/
if (nau8825_is_jack_inserted(regmap)) {
/* Turn off insertion interruption at manual mode */
regmap_update_bits(regmap,
NAU8825_REG_INTERRUPT_DIS_CTRL,
NAU8825_IRQ_INSERT_DIS,
NAU8825_IRQ_INSERT_DIS);
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_INSERT_EN, NAU8825_IRQ_INSERT_EN);
/* Enable interruption for jack type detection at audo
* mode which can detect microphone and jack type.
*/
nau8825_setup_auto_irq(nau8825);
}
}
if (!clear_irq)
......@@ -787,7 +1672,12 @@ static irqreturn_t nau8825_interrupt(int irq, void *data)
/* clears the rightmost interruption */
regmap_write(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq);
if (event_mask)
/* Delay jack report until cross talk detection is done. It can avoid
* application to do playback preparation when cross talk detection
* process is still working. Otherwise, the resource like clock and
* power will be issued by them at the same time and conflict happens.
*/
if (event_mask && nau8825->xtalk_state == NAU8825_XTALK_DONE)
snd_soc_jack_report(nau8825->jack, event, event_mask);
return IRQ_HANDLED;
......@@ -921,11 +1811,16 @@ static void nau8825_init_regs(struct nau8825 *nau8825)
NAU8825_RDAC_CLK_DELAY_MASK | NAU8825_RDAC_VREF_MASK,
(0x2 << NAU8825_RDAC_CLK_DELAY_SFT) |
(0x3 << NAU8825_RDAC_VREF_SFT));
/* Config L/R channel */
regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL,
NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_L);
regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL,
NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_R);
}
static const struct regmap_config nau8825_regmap_config = {
.val_bits = 16,
.reg_bits = 16,
.val_bits = NAU8825_REG_DATA_LEN,
.reg_bits = NAU8825_REG_ADDR_LEN,
.max_register = NAU8825_REG_MAX,
.readable_reg = nau8825_readable_reg,
......@@ -944,18 +1839,15 @@ static int nau8825_codec_probe(struct snd_soc_codec *codec)
nau8825->dapm = dapm;
/* 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(dapm, "DDACR");
snd_soc_dapm_sync(dapm);
return 0;
}
/* Unmask interruptions. Handler uses dapm object so we can enable
* interruptions only after dapm is fully initialized.
*/
regmap_write(nau8825->regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0);
nau8825_restart_jack_detection(nau8825->regmap);
static int nau8825_codec_remove(struct snd_soc_codec *codec)
{
struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
/* Cancel and reset cross tak suppresstion detection funciton */
nau8825_xtalk_cancel(nau8825);
return 0;
}
......@@ -973,8 +1865,8 @@ static int nau8825_codec_probe(struct snd_soc_codec *codec)
static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs,
struct nau8825_fll *fll_param)
{
u64 fvco;
unsigned int fref, i;
u64 fvco, fvco_max;
unsigned int fref, i, fvco_sel;
/* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing
* freq_in by 1, 2, 4, or 8 using FLL pre-scalar.
......@@ -999,18 +1891,23 @@ static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs,
fll_param->ratio = fll_ratio[i].val;
/* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs.
* FDCO must be within the 90MHz - 100MHz or the FFL cannot be
* FDCO must be within the 90MHz - 124MHz or the FFL cannot be
* guaranteed across the full range of operation.
* FDCO = freq_out * 2 * mclk_src_scaling
*/
fvco_max = 0;
fvco_sel = ARRAY_SIZE(mclk_src_scaling);
for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) {
fvco = 256 * fs * 2 * mclk_src_scaling[i].param;
if (NAU_FVCO_MIN < fvco && fvco < NAU_FVCO_MAX)
break;
if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX &&
fvco_max < fvco) {
fvco_max = fvco;
fvco_sel = i;
}
}
if (i == ARRAY_SIZE(mclk_src_scaling))
if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel)
return -EINVAL;
fll_param->mclk_src = mclk_src_scaling[i].val;
fll_param->mclk_src = mclk_src_scaling[fvco_sel].val;
/* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional
* input based on FDCO, FREF and FLL ratio.
......@@ -1025,7 +1922,8 @@ static void nau8825_fll_apply(struct nau8825 *nau8825,
struct nau8825_fll *fll_param)
{
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_MCLK_SRC_MASK, fll_param->mclk_src);
NAU8825_CLK_SRC_MASK | NAU8825_CLK_MCLK_SRC_MASK,
NAU8825_CLK_SRC_MCLK | fll_param->mclk_src);
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1,
NAU8825_FLL_RATIO_MASK, fll_param->ratio);
/* FLL 16-bit fractional input */
......@@ -1038,10 +1936,25 @@ static void nau8825_fll_apply(struct nau8825 *nau8825,
NAU8825_FLL_REF_DIV_MASK, fll_param->clk_ref_div);
/* select divided VCO input */
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5,
NAU8825_FLL_FILTER_SW_MASK, 0x0000);
/* FLL sigma delta modulator enable */
NAU8825_FLL_CLK_SW_MASK, NAU8825_FLL_CLK_SW_REF);
/* Disable free-running mode */
regmap_update_bits(nau8825->regmap,
NAU8825_REG_FLL6, NAU8825_DCO_EN, 0);
if (fll_param->fll_frac) {
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5,
NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN |
NAU8825_FLL_FTR_SW_MASK,
NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN |
NAU8825_FLL_FTR_SW_FILTER);
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6,
NAU8825_SDM_EN_MASK, NAU8825_SDM_EN);
NAU8825_SDM_EN, NAU8825_SDM_EN);
} else {
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5,
NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN |
NAU8825_FLL_FTR_SW_MASK, NAU8825_FLL_FTR_SW_ACCU);
regmap_update_bits(nau8825->regmap,
NAU8825_REG_FLL6, NAU8825_SDM_EN, 0);
}
}
/* freq_out must be 256*Fs in order to achieve the best performance */
......@@ -1069,21 +1982,15 @@ static int nau8825_set_pll(struct snd_soc_codec *codec, int pll_id, int source,
return 0;
}
static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id,
unsigned int freq)
static int nau8825_mclk_prepare(struct nau8825 *nau8825, unsigned int freq)
{
struct regmap *regmap = nau8825->regmap;
int ret;
int ret = 0;
switch (clk_id) {
case NAU8825_CLK_MCLK:
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK);
regmap_update_bits(regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN, 0);
/* We selected MCLK source but the clock itself managed externally */
if (!nau8825->mclk)
break;
nau8825->mclk = devm_clk_get(nau8825->dev, "mclk");
if (IS_ERR(nau8825->mclk)) {
dev_info(nau8825->dev, "No 'mclk' clock found, assume MCLK is managed externally");
return 0;
}
if (!nau8825->mclk_freq) {
ret = clk_prepare_enable(nau8825->mclk);
......@@ -1094,22 +2001,133 @@ static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id,
}
if (nau8825->mclk_freq != freq) {
nau8825->mclk_freq = freq;
freq = clk_round_rate(nau8825->mclk, freq);
ret = clk_set_rate(nau8825->mclk, freq);
if (ret) {
dev_err(nau8825->dev, "Unable to set mclk rate\n");
return ret;
}
nau8825->mclk_freq = freq;
}
return 0;
}
static void nau8825_configure_mclk_as_sysclk(struct regmap *regmap)
{
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK);
regmap_update_bits(regmap, NAU8825_REG_FLL6,
NAU8825_DCO_EN, 0);
}
static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id,
unsigned int freq)
{
struct regmap *regmap = nau8825->regmap;
int ret;
switch (clk_id) {
case NAU8825_CLK_DIS:
/* Clock provided externally and disable internal VCO clock */
nau8825_configure_mclk_as_sysclk(regmap);
if (nau8825->mclk_freq) {
clk_disable_unprepare(nau8825->mclk);
nau8825->mclk_freq = 0;
}
break;
case NAU8825_CLK_MCLK:
/* Acquire the semaphone to synchronize the playback and
* interrupt handler. In order to avoid the playback inter-
* fered by cross talk process, the driver make the playback
* preparation halted until cross talk process finish.
*/
nau8825_sema_acquire(nau8825, 2 * HZ);
nau8825_configure_mclk_as_sysclk(regmap);
/* MCLK not changed by clock tree */
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_MCLK_SRC_MASK, 0);
/* Release the semaphone. */
nau8825_sema_release(nau8825);
ret = nau8825_mclk_prepare(nau8825, freq);
if (ret)
return ret;
break;
case NAU8825_CLK_INTERNAL:
regmap_update_bits(regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN,
NAU8825_DCO_EN);
if (nau8825_is_jack_inserted(nau8825->regmap)) {
regmap_update_bits(regmap, NAU8825_REG_FLL6,
NAU8825_DCO_EN, NAU8825_DCO_EN);
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO);
/* Decrease the VCO frequency for power saving */
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_MCLK_SRC_MASK, 0xf);
regmap_update_bits(regmap, NAU8825_REG_FLL1,
NAU8825_FLL_RATIO_MASK, 0x10);
regmap_update_bits(regmap, NAU8825_REG_FLL6,
NAU8825_SDM_EN, NAU8825_SDM_EN);
} else {
/* The clock turns off intentionally for power saving
* when no headset connected.
*/
nau8825_configure_mclk_as_sysclk(regmap);
dev_warn(nau8825->dev, "Disable clock for power saving when no headset connected\n");
}
if (nau8825->mclk_freq) {
clk_disable_unprepare(nau8825->mclk);
nau8825->mclk_freq = 0;
}
break;
case NAU8825_CLK_FLL_MCLK:
/* Acquire the semaphone to synchronize the playback and
* interrupt handler. In order to avoid the playback inter-
* fered by cross talk process, the driver make the playback
* preparation halted until cross talk process finish.
*/
nau8825_sema_acquire(nau8825, 2 * HZ);
regmap_update_bits(regmap, NAU8825_REG_FLL3,
NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_MCLK);
/* Release the semaphone. */
nau8825_sema_release(nau8825);
ret = nau8825_mclk_prepare(nau8825, freq);
if (ret)
return ret;
break;
case NAU8825_CLK_FLL_BLK:
/* Acquire the semaphone to synchronize the playback and
* interrupt handler. In order to avoid the playback inter-
* fered by cross talk process, the driver make the playback
* preparation halted until cross talk process finish.
*/
nau8825_sema_acquire(nau8825, 2 * HZ);
regmap_update_bits(regmap, NAU8825_REG_FLL3,
NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_BLK);
/* Release the semaphone. */
nau8825_sema_release(nau8825);
if (nau8825->mclk_freq) {
clk_disable_unprepare(nau8825->mclk);
nau8825->mclk_freq = 0;
}
break;
case NAU8825_CLK_FLL_FS:
/* Acquire the semaphone to synchronize the playback and
* interrupt handler. In order to avoid the playback inter-
* fered by cross talk process, the driver make the playback
* preparation halted until cross talk process finish.
*/
nau8825_sema_acquire(nau8825, 2 * HZ);
regmap_update_bits(regmap, NAU8825_REG_FLL3,
NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_FS);
/* Release the semaphone. */
nau8825_sema_release(nau8825);
if (nau8825->mclk_freq) {
clk_disable_unprepare(nau8825->mclk);
......@@ -1135,6 +2153,31 @@ static int nau8825_set_sysclk(struct snd_soc_codec *codec, int clk_id,
return nau8825_configure_sysclk(nau8825, clk_id, freq);
}
static int nau8825_resume_setup(struct nau8825 *nau8825)
{
struct regmap *regmap = nau8825->regmap;
/* Close clock when jack type detection at manual mode */
nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0);
/* Clear all interruption status */
nau8825_int_status_clear_all(regmap);
/* Enable both insertion and ejection interruptions, and then
* bypass de-bounce circuit.
*/
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN |
NAU8825_IRQ_EJECT_EN | NAU8825_IRQ_INSERT_EN,
NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN);
regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS);
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL,
NAU8825_IRQ_INSERT_DIS | NAU8825_IRQ_EJECT_DIS, 0);
return 0;
}
static int nau8825_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
......@@ -1157,10 +2200,22 @@ static int nau8825_set_bias_level(struct snd_soc_codec *codec,
return ret;
}
}
/* Setup codec configuration after resume */
nau8825_resume_setup(nau8825);
}
break;
case SND_SOC_BIAS_OFF:
/* Cancel and reset cross talk detection funciton */
nau8825_xtalk_cancel(nau8825);
/* Turn off all interruptions before system shutdown. Keep the
* interruption quiet before resume setup completes.
*/
regmap_write(nau8825->regmap,
NAU8825_REG_INTERRUPT_DIS_CTRL, 0xffff);
/* Disable ADC needed for interruptions at audo mode */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_ADC, 0);
if (nau8825->mclk_freq)
clk_disable_unprepare(nau8825->mclk);
break;
......@@ -1168,57 +2223,46 @@ static int nau8825_set_bias_level(struct snd_soc_codec *codec,
return 0;
}
#ifdef CONFIG_PM
static int nau8825_suspend(struct snd_soc_codec *codec)
static int __maybe_unused nau8825_suspend(struct snd_soc_codec *codec)
{
struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
disable_irq(nau8825->irq);
snd_soc_codec_force_bias_level(codec, SND_SOC_BIAS_OFF);
regcache_cache_only(nau8825->regmap, true);
regcache_mark_dirty(nau8825->regmap);
return 0;
}
static int nau8825_resume(struct snd_soc_codec *codec)
static int __maybe_unused nau8825_resume(struct snd_soc_codec *codec)
{
struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
/* The chip may lose power and reset in S3. regcache_sync restores
* register values including configurations for sysclk, irq, and
* jack/button detection.
*/
regcache_cache_only(nau8825->regmap, false);
regcache_sync(nau8825->regmap);
/* Check the jack plug status directly. If the headset is unplugged
* during S3 when the chip has no power, there will be no jack
* detection irq even after the nau8825_restart_jack_detection below,
* because the chip just thinks no headset has ever been plugged in.
if (nau8825_is_jack_inserted(nau8825->regmap)) {
/* If the jack is inserted, we need to check whether the play-
* back is active before suspend. If active, the driver has to
* raise the protection for cross talk function to avoid the
* playback recovers before cross talk process finish. Other-
* wise, the playback will be interfered by cross talk func-
* tion. It is better to apply hardware related parameters
* before starting playback or record.
*/
if (!nau8825_is_jack_inserted(nau8825->regmap)) {
nau8825_eject_jack(nau8825);
snd_soc_jack_report(nau8825->jack, 0, SND_JACK_HEADSET);
if (nau8825_dai_is_active(nau8825)) {
nau8825->xtalk_protect = true;
nau8825_sema_acquire(nau8825, 0);
}
}
enable_irq(nau8825->irq);
/* Run jack detection to check the type (OMTP or CTIA) of the headset
* if there is one. This handles the case where a different type of
* headset is plugged in during S3. This triggers an IRQ iff a headset
* is already plugged in.
*/
nau8825_restart_jack_detection(nau8825->regmap);
return 0;
}
#else
#define nau8825_suspend NULL
#define nau8825_resume NULL
#endif
static struct snd_soc_codec_driver nau8825_codec_driver = {
.probe = nau8825_codec_probe,
.remove = nau8825_codec_remove,
.set_sysclk = nau8825_set_sysclk,
.set_pll = nau8825_set_pll,
.set_bias_level = nau8825_set_bias_level,
......@@ -1318,22 +2362,8 @@ static int nau8825_read_device_properties(struct device *dev,
static int nau8825_setup_irq(struct nau8825 *nau8825)
{
struct regmap *regmap = nau8825->regmap;
int ret;
/* IRQ Output Enable */
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_OUTPUT_EN, NAU8825_IRQ_OUTPUT_EN);
/* Enable internal VCO needed for interruptions */
nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0);
/* Enable DDACR needed for interrupts
* It is the same as force_enable_pin("DDACR") we do later
*/
regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_DACR, NAU8825_ENABLE_DACR);
ret = devm_request_threaded_irq(nau8825->dev, nau8825->irq, NULL,
nau8825_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"nau8825", nau8825);
......@@ -1370,6 +2400,13 @@ static int nau8825_i2c_probe(struct i2c_client *i2c,
return PTR_ERR(nau8825->regmap);
nau8825->dev = dev;
nau8825->irq = i2c->irq;
/* Initiate parameters, semaphone and work queue which are needed in
* cross talk suppression measurment function.
*/
nau8825->xtalk_state = NAU8825_XTALK_DONE;
nau8825->xtalk_protect = false;
sema_init(&nau8825->xtalk_sem, 1);
INIT_WORK(&nau8825->xtalk_work, nau8825_xtalk_work);
nau8825_print_device_properties(nau8825);
......@@ -1405,6 +2442,7 @@ static const struct i2c_device_id nau8825_i2c_ids[] = {
{ "nau8825", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, nau8825_i2c_ids);
#ifdef CONFIG_OF
static const struct of_device_id nau8825_of_ids[] = {
......
......@@ -93,12 +93,21 @@
#define NAU8825_REG_CHARGE_PUMP_INPUT_READ 0x81
#define NAU8825_REG_GENERAL_STATUS 0x82
#define NAU8825_REG_MAX NAU8825_REG_GENERAL_STATUS
/* 16-bit control register address, and 16-bits control register data */
#define NAU8825_REG_ADDR_LEN 16
#define NAU8825_REG_DATA_LEN 16
/* ENA_CTRL (0x1) */
#define NAU8825_ENABLE_DACR_SFT 10
#define NAU8825_ENABLE_DACR (1 << NAU8825_ENABLE_DACR_SFT)
#define NAU8825_ENABLE_DACL_SFT 9
#define NAU8825_ENABLE_DACL (1 << NAU8825_ENABLE_DACL_SFT)
#define NAU8825_ENABLE_ADC_SFT 8
#define NAU8825_ENABLE_ADC (1 << NAU8825_ENABLE_ADC_SFT)
#define NAU8825_ENABLE_ADC_CLK_SFT 7
#define NAU8825_ENABLE_ADC_CLK (1 << NAU8825_ENABLE_ADC_CLK_SFT)
#define NAU8825_ENABLE_DAC_CLK_SFT 6
#define NAU8825_ENABLE_DAC_CLK (1 << NAU8825_ENABLE_DAC_CLK_SFT)
#define NAU8825_ENABLE_SAR_SFT 1
/* CLK_DIVIDER (0x3) */
......@@ -113,20 +122,28 @@
/* FLL3 (0x06) */
#define NAU8825_FLL_INTEGER_MASK (0x3ff << 0)
#define NAU8825_FLL_CLK_SRC_SFT 10
#define NAU8825_FLL_CLK_SRC_MASK (0x3 << NAU8825_FLL_CLK_SRC_SFT)
#define NAU8825_FLL_CLK_SRC_MCLK (0 << NAU8825_FLL_CLK_SRC_SFT)
#define NAU8825_FLL_CLK_SRC_BLK (0x2 << NAU8825_FLL_CLK_SRC_SFT)
#define NAU8825_FLL_CLK_SRC_FS (0x3 << NAU8825_FLL_CLK_SRC_SFT)
/* FLL4 (0x07) */
#define NAU8825_FLL_REF_DIV_MASK (0x3 << 10)
/* FLL5 (0x08) */
#define NAU8825_FLL_FILTER_SW_MASK (0x1 << 14)
#define NAU8825_FLL_PDB_DAC_EN (0x1 << 15)
#define NAU8825_FLL_LOOP_FTR_EN (0x1 << 14)
#define NAU8825_FLL_CLK_SW_MASK (0x1 << 13)
#define NAU8825_FLL_CLK_SW_N2 (0x1 << 13)
#define NAU8825_FLL_CLK_SW_REF (0x0 << 13)
#define NAU8825_FLL_FTR_SW_MASK (0x1 << 12)
#define NAU8825_FLL_FTR_SW_ACCU (0x1 << 12)
#define NAU8825_FLL_FTR_SW_FILTER (0x0 << 12)
/* FLL6 (0x9) */
#define NAU8825_DCO_EN_MASK (0x1 << 15)
#define NAU8825_DCO_EN (0x1 << 15)
#define NAU8825_DCO_DIS (0x0 << 15)
#define NAU8825_SDM_EN_MASK (0x1 << 14)
#define NAU8825_SDM_EN (0x1 << 14)
#define NAU8825_SDM_DIS (0x0 << 14)
/* HSD_CTRL (0xc) */
#define NAU8825_HSD_AUTO_MODE (1 << 6)
......@@ -136,6 +153,7 @@
/* JACK_DET_CTRL (0xd) */
#define NAU8825_JACK_DET_RESTART (1 << 9)
#define NAU8825_JACK_DET_DB_BYPASS (1 << 8)
#define NAU8825_JACK_INSERT_DEBOUNCE_SFT 5
#define NAU8825_JACK_INSERT_DEBOUNCE_MASK (0x7 << NAU8825_JACK_INSERT_DEBOUNCE_SFT)
#define NAU8825_JACK_EJECT_DEBOUNCE_SFT 2
......@@ -145,9 +163,11 @@
/* INTERRUPT_MASK (0xf) */
#define NAU8825_IRQ_OUTPUT_EN (1 << 11)
#define NAU8825_IRQ_HEADSET_COMPLETE_EN (1 << 10)
#define NAU8825_IRQ_RMS_EN (1 << 8)
#define NAU8825_IRQ_KEY_RELEASE_EN (1 << 7)
#define NAU8825_IRQ_KEY_SHORT_PRESS_EN (1 << 5)
#define NAU8825_IRQ_EJECT_EN (1 << 2)
#define NAU8825_IRQ_INSERT_EN (1 << 0)
/* IRQ_STATUS (0x10) */
#define NAU8825_HEADSET_COMPLETION_IRQ (1 << 10)
......@@ -168,6 +188,7 @@
#define NAU8825_IRQ_KEY_RELEASE_DIS (1 << 7)
#define NAU8825_IRQ_KEY_SHORT_PRESS_DIS (1 << 5)
#define NAU8825_IRQ_EJECT_DIS (1 << 2)
#define NAU8825_IRQ_INSERT_DIS (1 << 0)
/* SAR_CTRL (0x13) */
#define NAU8825_SAR_ADC_EN_SFT 12
......@@ -217,10 +238,21 @@
/* I2S_PCM_CTRL2 (0x1d) */
#define NAU8825_I2S_TRISTATE (1 << 15) /* 0 - normal mode, 1 - Hi-Z output */
#define NAU8825_I2S_DRV_SFT 12
#define NAU8825_I2S_DRV_MASK (0x3 << NAU8825_I2S_DRV_SFT)
#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)
#define NAU8825_I2S_BLK_DIV_MASK 0x7
/* BIQ_CTRL (0x20) */
#define NAU8825_BIQ_WRT_SFT 4
#define NAU8825_BIQ_WRT_EN (1 << NAU8825_BIQ_WRT_SFT)
#define NAU8825_BIQ_PATH_SFT 0
#define NAU8825_BIQ_PATH_MASK (1 << NAU8825_BIQ_PATH_SFT)
#define NAU8825_BIQ_PATH_ADC (0 << NAU8825_BIQ_PATH_SFT)
#define NAU8825_BIQ_PATH_DAC (1 << NAU8825_BIQ_PATH_SFT)
/* ADC_RATE (0x2b) */
#define NAU8825_ADC_SYNC_DOWN_SFT 0
......@@ -239,22 +271,72 @@
#define NAU8825_DAC_OVERSAMPLE_128 2
#define NAU8825_DAC_OVERSAMPLE_32 4
/* ADC_DGAIN_CTRL (0x30) */
#define NAU8825_ADC_DIG_VOL_MASK 0xff
/* 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)
#define NAU8825_HP_MUTE_AUTO (1 << 14)
#define NAU8825_HPL_MUTE (1 << 13)
#define NAU8825_HPR_MUTE (1 << 12)
#define NAU8825_HPL_VOL_SFT 6
#define NAU8825_HPL_VOL_MASK (0x3f << NAU8825_HPL_VOL_SFT)
#define NAU8825_HPR_VOL_SFT 0
#define NAU8825_HPR_VOL_MASK (0x3f << NAU8825_HPR_VOL_SFT)
#define NAU8825_HP_VOL_MIN 0x36
/* DACL_CTRL (0x33) */
#define NAU8825_DACL_CH_SEL_SFT 9
#define NAU8825_DACL_CH_SEL_MASK (0x1 << NAU8825_DACL_CH_SEL_SFT)
#define NAU8825_DACL_CH_SEL_L (0x0 << NAU8825_DACL_CH_SEL_SFT)
#define NAU8825_DACL_CH_SEL_R (0x1 << NAU8825_DACL_CH_SEL_SFT)
#define NAU8825_DACL_CH_VOL_MASK 0xff
/* DACR_CTRL (0x34) */
#define NAU8825_DACR_CH_SEL_SFT 9
#define NAU8825_DACR_CH_SEL_MASK (0x1 << NAU8825_DACR_CH_SEL_SFT)
#define NAU8825_DACR_CH_SEL_L (0x0 << NAU8825_DACR_CH_SEL_SFT)
#define NAU8825_DACR_CH_SEL_R (0x1 << NAU8825_DACR_CH_SEL_SFT)
#define NAU8825_DACR_CH_VOL_MASK 0xff
/* IMM_MODE_CTRL (0x4C) */
#define NAU8825_IMM_THD_SFT 8
#define NAU8825_IMM_THD_MASK (0x3f << NAU8825_IMM_THD_SFT)
#define NAU8825_IMM_GEN_VOL_SFT 6
#define NAU8825_IMM_GEN_VOL_MASK (0x3 << NAU8825_IMM_GEN_VOL_SFT)
#define NAU8825_IMM_GEN_VOL_1_2nd (0x0 << NAU8825_IMM_GEN_VOL_SFT)
#define NAU8825_IMM_GEN_VOL_1_4th (0x1 << NAU8825_IMM_GEN_VOL_SFT)
#define NAU8825_IMM_GEN_VOL_1_8th (0x2 << NAU8825_IMM_GEN_VOL_SFT)
#define NAU8825_IMM_GEN_VOL_1_16th (0x3 << NAU8825_IMM_GEN_VOL_SFT)
#define NAU8825_IMM_CYC_SFT 4
#define NAU8825_IMM_CYC_MASK (0x3 << NAU8825_IMM_CYC_SFT)
#define NAU8825_IMM_CYC_1024 (0x0 << NAU8825_IMM_CYC_SFT)
#define NAU8825_IMM_CYC_2048 (0x1 << NAU8825_IMM_CYC_SFT)
#define NAU8825_IMM_CYC_4096 (0x2 << NAU8825_IMM_CYC_SFT)
#define NAU8825_IMM_CYC_8192 (0x3 << NAU8825_IMM_CYC_SFT)
#define NAU8825_IMM_EN (1 << 3)
#define NAU8825_IMM_DAC_SRC_MASK 0x7
#define NAU8825_IMM_DAC_SRC_BIQ 0x0
#define NAU8825_IMM_DAC_SRC_DRC 0x1
#define NAU8825_IMM_DAC_SRC_MIX 0x2
#define NAU8825_IMM_DAC_SRC_SIN 0x3
/* CLASSG_CTRL (0x50) */
#define NAU8825_CLASSG_TIMER_SFT 8
#define NAU8825_CLASSG_TIMER_MASK (0x3f << NAU8825_CLASSG_TIMER_SFT)
#define NAU8825_CLASSG_TIMER_1ms (0x1 << NAU8825_CLASSG_TIMER_SFT)
#define NAU8825_CLASSG_TIMER_2ms (0x2 << NAU8825_CLASSG_TIMER_SFT)
#define NAU8825_CLASSG_TIMER_8ms (0x4 << NAU8825_CLASSG_TIMER_SFT)
#define NAU8825_CLASSG_TIMER_16ms (0x8 << NAU8825_CLASSG_TIMER_SFT)
#define NAU8825_CLASSG_TIMER_32ms (0x10 << NAU8825_CLASSG_TIMER_SFT)
#define NAU8825_CLASSG_TIMER_64ms (0x20 << NAU8825_CLASSG_TIMER_SFT)
#define NAU8825_CLASSG_LDAC_EN (0x1 << 2)
#define NAU8825_CLASSG_RDAC_EN (0x1 << 1)
#define NAU8825_CLASSG_EN (1 << 0)
/* I2C_DEVICE_ID (0x58) */
......@@ -263,7 +345,12 @@
#define NAU8825_SOFTWARE_ID_NAU8825 0x0
/* BIAS_ADJ (0x66) */
#define NAU8825_BIAS_TESTDAC_EN (0x3 << 8)
#define NAU8825_BIAS_HPR_IMP (1 << 15)
#define NAU8825_BIAS_HPL_IMP (1 << 14)
#define NAU8825_BIAS_TESTDAC_SFT 8
#define NAU8825_BIAS_TESTDAC_EN (0x3 << NAU8825_BIAS_TESTDAC_SFT)
#define NAU8825_BIAS_TESTDACR_EN (0x2 << NAU8825_BIAS_TESTDAC_SFT)
#define NAU8825_BIAS_TESTDACL_EN (0x1 << NAU8825_BIAS_TESTDAC_SFT)
#define NAU8825_BIAS_VMID (1 << 6)
#define NAU8825_BIAS_VMID_SEL_SFT 4
#define NAU8825_BIAS_VMID_SEL_MASK (3 << NAU8825_BIAS_VMID_SEL_SFT)
......@@ -282,6 +369,11 @@
#define NAU8825_POWERUP_ADCL (1 << 6)
/* RDAC (0x73) */
#define NAU8825_RDAC_FS_BCLK_ENB (1 << 15)
#define NAU8825_RDAC_EN_SFT 12
#define NAU8825_RDAC_EN (0x3 << NAU8825_RDAC_EN_SFT)
#define NAU8825_RDAC_CLK_EN_SFT 8
#define NAU8825_RDAC_CLK_EN (0x3 << NAU8825_RDAC_CLK_EN_SFT)
#define NAU8825_RDAC_CLK_DELAY_SFT 4
#define NAU8825_RDAC_CLK_DELAY_MASK (0x7 << NAU8825_RDAC_CLK_DELAY_SFT)
#define NAU8825_RDAC_VREF_SFT 2
......@@ -318,8 +410,21 @@
/* System Clock Source */
enum {
NAU8825_CLK_MCLK = 0,
NAU8825_CLK_DIS = 0,
NAU8825_CLK_MCLK,
NAU8825_CLK_INTERNAL,
NAU8825_CLK_FLL_MCLK,
NAU8825_CLK_FLL_BLK,
NAU8825_CLK_FLL_FS,
};
/* Cross talk detection state */
enum {
NAU8825_XTALK_PREPARE = 0,
NAU8825_XTALK_HPR_R2L,
NAU8825_XTALK_HPL_R2L,
NAU8825_XTALK_IMM,
NAU8825_XTALK_DONE,
};
struct nau8825 {
......@@ -328,6 +433,8 @@ struct nau8825 {
struct snd_soc_dapm_context *dapm;
struct snd_soc_jack *jack;
struct clk *mclk;
struct work_struct xtalk_work;
struct semaphore xtalk_sem;
int irq;
int mclk_freq; /* 0 - mclk is disabled */
int button_pressed;
......@@ -346,6 +453,12 @@ struct nau8825 {
int key_debounce;
int jack_insert_debounce;
int jack_eject_debounce;
int high_imped;
int xtalk_state;
int xtalk_event;
int xtalk_event_mask;
bool xtalk_protect;
int imp_rms[NAU8825_XTALK_IMM];
};
int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
......
config SND_SOC_MEDIATEK
tristate "ASoC support for Mediatek chip"
tristate
config SND_SOC_MT2701
tristate "ASoC support for Mediatek MT2701 chip"
depends on ARCH_MEDIATEK
select SND_SOC_MEDIATEK
help
This adds ASoC driver for Mediatek MT2701 boards
that can be used with other codecs.
Select Y if you have such device.
If unsure select "N".
config SND_SOC_MT2701_CS42448
tristate "ASoc Audio driver for MT2701 with CS42448 codec"
depends on SND_SOC_MT2701
select SND_SOC_CS42XX8_I2C
select SND_SOC_BT_SCO
help
This adds ASoC driver for Mediatek MT2701 boards
with the CS42448 codecs.
Select Y if you have such device.
If unsure select "N".
config SND_SOC_MT8173
tristate "ASoC support for Mediatek MT8173 chip"
depends on ARCH_MEDIATEK
select SND_SOC_MEDIATEK
help
This adds ASoC platform driver support for Mediatek chip
This adds ASoC platform driver support for Mediatek MT8173 chip
that can be used with other codecs.
Select Y if you have such device.
Ex: MT8173
config SND_SOC_MT8173_MAX98090
tristate "ASoC Audio driver for MT8173 with MAX98090 codec"
depends on SND_SOC_MEDIATEK && I2C
depends on SND_SOC_MT8173 && I2C
select SND_SOC_MAX98090
help
This adds ASoC driver for Mediatek MT8173 boards
......@@ -19,8 +44,9 @@ config SND_SOC_MT8173_MAX98090
config SND_SOC_MT8173_RT5650
tristate "ASoC Audio driver for MT8173 with RT5650 codec"
depends on SND_SOC_MEDIATEK && I2C
depends on SND_SOC_MT8173 && I2C
select SND_SOC_RT5645
select SND_SOC_HDMI_CODEC
help
This adds ASoC driver for Mediatek MT8173 boards
with the RT5650 audio codec.
......@@ -29,7 +55,7 @@ config SND_SOC_MT8173_RT5650
config SND_SOC_MT8173_RT5650_RT5514
tristate "ASoC Audio driver for MT8173 with RT5650 RT5514 codecs"
depends on SND_SOC_MEDIATEK && I2C
depends on SND_SOC_MT8173 && I2C
select SND_SOC_RT5645
select SND_SOC_RT5514
help
......@@ -40,7 +66,7 @@ config SND_SOC_MT8173_RT5650_RT5514
config SND_SOC_MT8173_RT5650_RT5676
tristate "ASoC Audio driver for MT8173 with RT5650 RT5676 codecs"
depends on SND_SOC_MEDIATEK && I2C
depends on SND_SOC_MT8173 && I2C
select SND_SOC_RT5645
select SND_SOC_RT5677
select SND_SOC_HDMI_CODEC
......
# MTK Platform Support
obj-$(CONFIG_SND_SOC_MEDIATEK) += mtk-afe-pcm.o
# Machine support
obj-$(CONFIG_SND_SOC_MT8173_MAX98090) += mt8173-max98090.o
obj-$(CONFIG_SND_SOC_MT8173_RT5650) += mt8173-rt5650.o
obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5514) += mt8173-rt5650-rt5514.o
obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5676) += mt8173-rt5650-rt5676.o
obj-$(CONFIG_SND_SOC_MEDIATEK) += common/
obj-$(CONFIG_SND_SOC_MT2701) += mt2701/
obj-$(CONFIG_SND_SOC_MT8173) += mt8173/
#
# Copyright (C) 2015 MediaTek Inc.
#
# 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# platform driver
snd-soc-mtk-common-objs := mtk-afe-platform-driver.o mtk-afe-fe-dai.o
obj-$(CONFIG_SND_SOC_MEDIATEK) += snd-soc-mtk-common.o
/*
* mtk-afe-fe-dais.c -- Mediatek afe fe dai operator
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "mtk-afe-fe-dai.h"
#include "mtk-base-afe.h"
#define AFE_BASE_END_OFFSET 8
int mtk_regmap_update_bits(struct regmap *map, int reg, unsigned int mask,
unsigned int val)
{
if (reg < 0)
return 0;
return regmap_update_bits(map, reg, mask, val);
}
int mtk_regmap_write(struct regmap *map, int reg, unsigned int val)
{
if (reg < 0)
return 0;
return regmap_write(map, reg, val);
}
int mtk_afe_fe_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct snd_pcm_runtime *runtime = substream->runtime;
int memif_num = rtd->cpu_dai->id;
struct mtk_base_afe_memif *memif = &afe->memif[memif_num];
const struct snd_pcm_hardware *mtk_afe_hardware = afe->mtk_afe_hardware;
int ret;
memif->substream = substream;
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16);
/* enable agent */
mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg,
1 << memif->data->agent_disable_shift,
0 << memif->data->agent_disable_shift);
snd_soc_set_runtime_hwparams(substream, mtk_afe_hardware);
/*
* Capture cannot use ping-pong buffer since hw_ptr at IRQ may be
* smaller than period_size due to AFE's internal buffer.
* This easily leads to overrun when avail_min is period_size.
* One more period can hold the possible unread buffer.
*/
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
int periods_max = mtk_afe_hardware->periods_max;
ret = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIODS,
3, periods_max);
if (ret < 0) {
dev_err(afe->dev, "hw_constraint_minmax failed\n");
return ret;
}
}
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n");
/* dynamic allocate irq to memif */
if (memif->irq_usage < 0) {
int irq_id = mtk_dynamic_irq_acquire(afe);
if (irq_id != afe->irqs_size) {
/* link */
memif->irq_usage = irq_id;
} else {
dev_err(afe->dev, "%s() error: no more asys irq\n",
__func__);
ret = -EBUSY;
}
}
return ret;
}
EXPORT_SYMBOL_GPL(mtk_afe_fe_startup);
void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
int irq_id;
irq_id = memif->irq_usage;
mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg,
1 << memif->data->agent_disable_shift,
1 << memif->data->agent_disable_shift);
if (!memif->const_irq) {
mtk_dynamic_irq_release(afe, irq_id);
memif->irq_usage = -1;
memif->substream = NULL;
}
}
EXPORT_SYMBOL_GPL(mtk_afe_fe_shutdown);
int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
int msb_at_bit33 = 0;
int ret, fs = 0;
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (ret < 0)
return ret;
msb_at_bit33 = upper_32_bits(substream->runtime->dma_addr) ? 1 : 0;
memif->phys_buf_addr = lower_32_bits(substream->runtime->dma_addr);
memif->buffer_size = substream->runtime->dma_bytes;
/* start */
mtk_regmap_write(afe->regmap, memif->data->reg_ofs_base,
memif->phys_buf_addr);
/* end */
mtk_regmap_write(afe->regmap,
memif->data->reg_ofs_base + AFE_BASE_END_OFFSET,
memif->phys_buf_addr + memif->buffer_size - 1);
/* set MSB to 33-bit */
mtk_regmap_update_bits(afe->regmap, memif->data->msb_reg,
1 << memif->data->msb_shift,
msb_at_bit33 << memif->data->msb_shift);
/* set channel */
if (memif->data->mono_shift >= 0) {
unsigned int mono = (params_channels(params) == 1) ? 1 : 0;
mtk_regmap_update_bits(afe->regmap, memif->data->mono_reg,
1 << memif->data->mono_shift,
mono << memif->data->mono_shift);
}
/* set rate */
if (memif->data->fs_shift < 0)
return 0;
fs = afe->memif_fs(substream, params_rate(params));
if (fs < 0)
return -EINVAL;
mtk_regmap_update_bits(afe->regmap, memif->data->fs_reg,
memif->data->fs_maskbit << memif->data->fs_shift,
fs << memif->data->fs_shift);
return 0;
}
EXPORT_SYMBOL_GPL(mtk_afe_fe_hw_params);
int mtk_afe_fe_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
return snd_pcm_lib_free_pages(substream);
}
EXPORT_SYMBOL_GPL(mtk_afe_fe_hw_free);
int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime * const runtime = substream->runtime;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
struct mtk_base_afe_irq *irqs = &afe->irqs[memif->irq_usage];
const struct mtk_base_irq_data *irq_data = irqs->irq_data;
unsigned int counter = runtime->period_size;
int fs;
dev_dbg(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
if (memif->data->enable_shift >= 0)
mtk_regmap_update_bits(afe->regmap,
memif->data->enable_reg,
1 << memif->data->enable_shift,
1 << memif->data->enable_shift);
/* set irq counter */
mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg,
irq_data->irq_cnt_maskbit
<< irq_data->irq_cnt_shift,
counter << irq_data->irq_cnt_shift);
/* set irq fs */
fs = afe->irq_fs(substream, runtime->rate);
if (fs < 0)
return -EINVAL;
mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg,
irq_data->irq_fs_maskbit
<< irq_data->irq_fs_shift,
fs << irq_data->irq_fs_shift);
/* enable interrupt */
mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg,
1 << irq_data->irq_en_shift,
1 << irq_data->irq_en_shift);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
mtk_regmap_update_bits(afe->regmap, memif->data->enable_reg,
1 << memif->data->enable_shift, 0);
/* disable interrupt */
mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg,
1 << irq_data->irq_en_shift,
0 << irq_data->irq_en_shift);
/* and clear pending IRQ */
mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg,
1 << irq_data->irq_clr_shift);
return 0;
default:
return -EINVAL;
}
}
EXPORT_SYMBOL_GPL(mtk_afe_fe_trigger);
int mtk_afe_fe_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
int hd_audio = 0;
/* set hd mode */
switch (substream->runtime->format) {
case SNDRV_PCM_FORMAT_S16_LE:
hd_audio = 0;
break;
case SNDRV_PCM_FORMAT_S32_LE:
hd_audio = 1;
break;
case SNDRV_PCM_FORMAT_S24_LE:
hd_audio = 1;
break;
default:
dev_err(afe->dev, "%s() error: unsupported format %d\n",
__func__, substream->runtime->format);
break;
}
mtk_regmap_update_bits(afe->regmap, memif->data->hd_reg,
1 << memif->data->hd_shift,
hd_audio << memif->data->hd_shift);
return 0;
}
EXPORT_SYMBOL_GPL(mtk_afe_fe_prepare);
const struct snd_soc_dai_ops mtk_afe_fe_ops = {
.startup = mtk_afe_fe_startup,
.shutdown = mtk_afe_fe_shutdown,
.hw_params = mtk_afe_fe_hw_params,
.hw_free = mtk_afe_fe_hw_free,
.prepare = mtk_afe_fe_prepare,
.trigger = mtk_afe_fe_trigger,
};
EXPORT_SYMBOL_GPL(mtk_afe_fe_ops);
static DEFINE_MUTEX(irqs_lock);
int mtk_dynamic_irq_acquire(struct mtk_base_afe *afe)
{
int i;
mutex_lock(&afe->irq_alloc_lock);
for (i = 0; i < afe->irqs_size; ++i) {
if (afe->irqs[i].irq_occupyed == 0) {
afe->irqs[i].irq_occupyed = 1;
mutex_unlock(&afe->irq_alloc_lock);
return i;
}
}
mutex_unlock(&afe->irq_alloc_lock);
return afe->irqs_size;
}
EXPORT_SYMBOL_GPL(mtk_dynamic_irq_acquire);
int mtk_dynamic_irq_release(struct mtk_base_afe *afe, int irq_id)
{
mutex_lock(&afe->irq_alloc_lock);
if (irq_id >= 0 && irq_id < afe->irqs_size) {
afe->irqs[irq_id].irq_occupyed = 0;
mutex_unlock(&afe->irq_alloc_lock);
return 0;
}
mutex_unlock(&afe->irq_alloc_lock);
return -EINVAL;
}
EXPORT_SYMBOL_GPL(mtk_dynamic_irq_release);
int mtk_afe_dai_suspend(struct snd_soc_dai *dai)
{
struct mtk_base_afe *afe = dev_get_drvdata(dai->dev);
struct device *dev = afe->dev;
struct regmap *regmap = afe->regmap;
int i;
if (pm_runtime_status_suspended(dev) || afe->suspended)
return 0;
if (!afe->reg_back_up)
afe->reg_back_up =
devm_kcalloc(dev, afe->reg_back_up_list_num,
sizeof(unsigned int), GFP_KERNEL);
for (i = 0; i < afe->reg_back_up_list_num; i++)
regmap_read(regmap, afe->reg_back_up_list[i],
&afe->reg_back_up[i]);
afe->suspended = true;
afe->runtime_suspend(dev);
return 0;
}
EXPORT_SYMBOL_GPL(mtk_afe_dai_suspend);
int mtk_afe_dai_resume(struct snd_soc_dai *dai)
{
struct mtk_base_afe *afe = dev_get_drvdata(dai->dev);
struct device *dev = afe->dev;
struct regmap *regmap = afe->regmap;
int i = 0;
if (pm_runtime_status_suspended(dev) || !afe->suspended)
return 0;
afe->runtime_resume(dev);
if (!afe->reg_back_up)
dev_dbg(dev, "%s no reg_backup\n", __func__);
for (i = 0; i < afe->reg_back_up_list_num; i++)
mtk_regmap_write(regmap, afe->reg_back_up_list[i],
afe->reg_back_up[i]);
afe->suspended = false;
return 0;
}
EXPORT_SYMBOL_GPL(mtk_afe_dai_resume);
MODULE_DESCRIPTION("Mediatek simple fe dai operator");
MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>");
MODULE_LICENSE("GPL v2");
/*
* mtk-afe-fe-dais.h -- Mediatek afe fe dai operator definition
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _MTK_AFE_FE_DAI_H_
#define _MTK_AFE_FE_DAI_H_
struct snd_soc_dai_ops;
struct mtk_base_afe;
struct mtk_base_afe_memif;
int mtk_afe_fe_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai);
int mtk_afe_fe_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
int mtk_afe_fe_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai);
extern const struct snd_soc_dai_ops mtk_afe_fe_ops;
int mtk_dynamic_irq_acquire(struct mtk_base_afe *afe);
int mtk_dynamic_irq_release(struct mtk_base_afe *afe, int irq_id);
int mtk_afe_dai_suspend(struct snd_soc_dai *dai);
int mtk_afe_dai_resume(struct snd_soc_dai *dai);
#endif
/*
* mtk-afe-platform-driver.c -- Mediatek afe platform driver
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <sound/soc.h>
#include "mtk-afe-platform-driver.h"
#include "mtk-base-afe.h"
static snd_pcm_uframes_t mtk_afe_pcm_pointer
(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
const struct mtk_base_memif_data *memif_data = memif->data;
struct regmap *regmap = afe->regmap;
struct device *dev = afe->dev;
int reg_ofs_base = memif_data->reg_ofs_base;
int reg_ofs_cur = memif_data->reg_ofs_cur;
unsigned int hw_ptr = 0, hw_base = 0;
int ret, pcm_ptr_bytes;
ret = regmap_read(regmap, reg_ofs_cur, &hw_ptr);
if (ret || hw_ptr == 0) {
dev_err(dev, "%s hw_ptr err\n", __func__);
pcm_ptr_bytes = 0;
goto POINTER_RETURN_FRAMES;
}
ret = regmap_read(regmap, reg_ofs_base, &hw_base);
if (ret || hw_base == 0) {
dev_err(dev, "%s hw_ptr err\n", __func__);
pcm_ptr_bytes = 0;
goto POINTER_RETURN_FRAMES;
}
pcm_ptr_bytes = hw_ptr - hw_base;
POINTER_RETURN_FRAMES:
return bytes_to_frames(substream->runtime, pcm_ptr_bytes);
}
static const struct snd_pcm_ops mtk_afe_pcm_ops = {
.ioctl = snd_pcm_lib_ioctl,
.pointer = mtk_afe_pcm_pointer,
};
static int mtk_afe_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
size_t size;
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
size = afe->mtk_afe_hardware->buffer_bytes_max;
return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
card->dev, size, size);
}
static void mtk_afe_pcm_free(struct snd_pcm *pcm)
{
snd_pcm_lib_preallocate_free_for_all(pcm);
}
const struct snd_soc_platform_driver mtk_afe_pcm_platform = {
.ops = &mtk_afe_pcm_ops,
.pcm_new = mtk_afe_pcm_new,
.pcm_free = mtk_afe_pcm_free,
};
EXPORT_SYMBOL_GPL(mtk_afe_pcm_platform);
MODULE_DESCRIPTION("Mediatek simple platform driver");
MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>");
MODULE_LICENSE("GPL v2");
/*
* mtk-afe-platform-driver.h -- Mediatek afe platform driver definition
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _MTK_AFE_PLATFORM_DRIVER_H_
#define _MTK_AFE_PLATFORM_DRIVER_H_
extern const struct snd_soc_platform_driver mtk_afe_pcm_platform;
#endif
/*
* mtk-base-afe.h -- Mediatek base afe structure
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _MTK_BASE_AFE_H_
#define _MTK_BASE_AFE_H_
struct mtk_base_memif_data {
int id;
const char *name;
int reg_ofs_base;
int reg_ofs_cur;
int fs_reg;
int fs_shift;
int fs_maskbit;
int mono_reg;
int mono_shift;
int enable_reg;
int enable_shift;
int hd_reg;
int hd_shift;
int msb_reg;
int msb_shift;
int agent_disable_reg;
int agent_disable_shift;
};
struct mtk_base_irq_data {
int id;
int irq_cnt_reg;
int irq_cnt_shift;
int irq_cnt_maskbit;
int irq_fs_reg;
int irq_fs_shift;
int irq_fs_maskbit;
int irq_en_reg;
int irq_en_shift;
int irq_clr_reg;
int irq_clr_shift;
};
struct device;
struct mtk_base_afe_memif;
struct mtk_base_afe_irq;
struct regmap;
struct snd_pcm_substream;
struct snd_soc_dai;
struct mtk_base_afe {
void __iomem *base_addr;
struct device *dev;
struct regmap *regmap;
struct mutex irq_alloc_lock; /* dynamic alloc irq lock */
unsigned int const *reg_back_up_list;
unsigned int *reg_back_up;
unsigned int reg_back_up_list_num;
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
bool suspended;
struct mtk_base_afe_memif *memif;
int memif_size;
struct mtk_base_afe_irq *irqs;
int irqs_size;
const struct snd_pcm_hardware *mtk_afe_hardware;
int (*memif_fs)(struct snd_pcm_substream *substream,
unsigned int rate);
int (*irq_fs)(struct snd_pcm_substream *substream,
unsigned int rate);
void *platform_priv;
};
struct mtk_base_afe_memif {
unsigned int phys_buf_addr;
int buffer_size;
struct snd_pcm_substream *substream;
const struct mtk_base_memif_data *data;
int irq_usage;
int const_irq;
};
struct mtk_base_afe_irq {
const struct mtk_base_irq_data *irq_data;
int irq_occupyed;
};
#endif
#
# Copyright (C) 2015 MediaTek Inc.
#
# 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# platform driver
snd-soc-mt2701-afe-objs := mt2701-afe-pcm.o mt2701-afe-clock-ctrl.o
obj-$(CONFIG_SND_SOC_MT2701) += snd-soc-mt2701-afe.o
# machine driver
obj-$(CONFIG_SND_SOC_MT2701_CS42448) += mt2701-cs42448.o
/*
* mt2701-afe-clock-ctrl.c -- Mediatek 2701 afe clock ctrl
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <sound/soc.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include "mt2701-afe-common.h"
#include "mt2701-afe-clock-ctrl.h"
static const char *aud_clks[MT2701_CLOCK_NUM] = {
[MT2701_AUD_INFRA_SYS_AUDIO] = "infra_sys_audio_clk",
[MT2701_AUD_AUD_MUX1_SEL] = "top_audio_mux1_sel",
[MT2701_AUD_AUD_MUX2_SEL] = "top_audio_mux2_sel",
[MT2701_AUD_AUD_MUX1_DIV] = "top_audio_mux1_div",
[MT2701_AUD_AUD_MUX2_DIV] = "top_audio_mux2_div",
[MT2701_AUD_AUD_48K_TIMING] = "top_audio_48k_timing",
[MT2701_AUD_AUD_44K_TIMING] = "top_audio_44k_timing",
[MT2701_AUD_AUDPLL_MUX_SEL] = "top_audpll_mux_sel",
[MT2701_AUD_APLL_SEL] = "top_apll_sel",
[MT2701_AUD_AUD1PLL_98M] = "top_aud1_pll_98M",
[MT2701_AUD_AUD2PLL_90M] = "top_aud2_pll_90M",
[MT2701_AUD_HADDS2PLL_98M] = "top_hadds2_pll_98M",
[MT2701_AUD_HADDS2PLL_294M] = "top_hadds2_pll_294M",
[MT2701_AUD_AUDPLL] = "top_audpll",
[MT2701_AUD_AUDPLL_D4] = "top_audpll_d4",
[MT2701_AUD_AUDPLL_D8] = "top_audpll_d8",
[MT2701_AUD_AUDPLL_D16] = "top_audpll_d16",
[MT2701_AUD_AUDPLL_D24] = "top_audpll_d24",
[MT2701_AUD_AUDINTBUS] = "top_audintbus_sel",
[MT2701_AUD_CLK_26M] = "clk_26m",
[MT2701_AUD_SYSPLL1_D4] = "top_syspll1_d4",
[MT2701_AUD_AUD_K1_SRC_SEL] = "top_aud_k1_src_sel",
[MT2701_AUD_AUD_K2_SRC_SEL] = "top_aud_k2_src_sel",
[MT2701_AUD_AUD_K3_SRC_SEL] = "top_aud_k3_src_sel",
[MT2701_AUD_AUD_K4_SRC_SEL] = "top_aud_k4_src_sel",
[MT2701_AUD_AUD_K5_SRC_SEL] = "top_aud_k5_src_sel",
[MT2701_AUD_AUD_K6_SRC_SEL] = "top_aud_k6_src_sel",
[MT2701_AUD_AUD_K1_SRC_DIV] = "top_aud_k1_src_div",
[MT2701_AUD_AUD_K2_SRC_DIV] = "top_aud_k2_src_div",
[MT2701_AUD_AUD_K3_SRC_DIV] = "top_aud_k3_src_div",
[MT2701_AUD_AUD_K4_SRC_DIV] = "top_aud_k4_src_div",
[MT2701_AUD_AUD_K5_SRC_DIV] = "top_aud_k5_src_div",
[MT2701_AUD_AUD_K6_SRC_DIV] = "top_aud_k6_src_div",
[MT2701_AUD_AUD_I2S1_MCLK] = "top_aud_i2s1_mclk",
[MT2701_AUD_AUD_I2S2_MCLK] = "top_aud_i2s2_mclk",
[MT2701_AUD_AUD_I2S3_MCLK] = "top_aud_i2s3_mclk",
[MT2701_AUD_AUD_I2S4_MCLK] = "top_aud_i2s4_mclk",
[MT2701_AUD_AUD_I2S5_MCLK] = "top_aud_i2s5_mclk",
[MT2701_AUD_AUD_I2S6_MCLK] = "top_aud_i2s6_mclk",
[MT2701_AUD_ASM_M_SEL] = "top_asm_m_sel",
[MT2701_AUD_ASM_H_SEL] = "top_asm_h_sel",
[MT2701_AUD_UNIVPLL2_D4] = "top_univpll2_d4",
[MT2701_AUD_UNIVPLL2_D2] = "top_univpll2_d2",
[MT2701_AUD_SYSPLL_D5] = "top_syspll_d5",
};
int mt2701_init_clock(struct mtk_base_afe *afe)
{
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int i = 0;
for (i = 0; i < MT2701_CLOCK_NUM; i++) {
afe_priv->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]);
if (IS_ERR(aud_clks[i])) {
dev_warn(afe->dev, "%s devm_clk_get %s fail\n",
__func__, aud_clks[i]);
return PTR_ERR(aud_clks[i]);
}
}
return 0;
}
int mt2701_afe_enable_clock(struct mtk_base_afe *afe)
{
int ret = 0;
ret = mt2701_turn_on_a1sys_clock(afe);
if (ret) {
dev_err(afe->dev, "%s turn_on_a1sys_clock fail %d\n",
__func__, ret);
return ret;
}
ret = mt2701_turn_on_a2sys_clock(afe);
if (ret) {
dev_err(afe->dev, "%s turn_on_a2sys_clock fail %d\n",
__func__, ret);
mt2701_turn_off_a1sys_clock(afe);
return ret;
}
ret = mt2701_turn_on_afe_clock(afe);
if (ret) {
dev_err(afe->dev, "%s turn_on_afe_clock fail %d\n",
__func__, ret);
mt2701_turn_off_a1sys_clock(afe);
mt2701_turn_off_a2sys_clock(afe);
return ret;
}
regmap_update_bits(afe->regmap, ASYS_TOP_CON,
AUDIO_TOP_CON0_A1SYS_A2SYS_ON,
AUDIO_TOP_CON0_A1SYS_A2SYS_ON);
regmap_update_bits(afe->regmap, AFE_DAC_CON0,
AFE_DAC_CON0_AFE_ON,
AFE_DAC_CON0_AFE_ON);
regmap_write(afe->regmap, PWR2_TOP_CON,
PWR2_TOP_CON_INIT_VAL);
regmap_write(afe->regmap, PWR1_ASM_CON1,
PWR1_ASM_CON1_INIT_VAL);
regmap_write(afe->regmap, PWR2_ASM_CON1,
PWR2_ASM_CON1_INIT_VAL);
return 0;
}
void mt2701_afe_disable_clock(struct mtk_base_afe *afe)
{
mt2701_turn_off_afe_clock(afe);
mt2701_turn_off_a1sys_clock(afe);
mt2701_turn_off_a2sys_clock(afe);
regmap_update_bits(afe->regmap, ASYS_TOP_CON,
AUDIO_TOP_CON0_A1SYS_A2SYS_ON, 0);
regmap_update_bits(afe->regmap, AFE_DAC_CON0,
AFE_DAC_CON0_AFE_ON, 0);
}
int mt2701_turn_on_a1sys_clock(struct mtk_base_afe *afe)
{
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int ret = 0;
/* Set Mux */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_AUD_MUX1_SEL], ret);
goto A1SYS_CLK_AUD_MUX1_SEL_ERR;
}
ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL],
afe_priv->clocks[MT2701_AUD_AUD1PLL_98M]);
if (ret) {
dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__,
aud_clks[MT2701_AUD_AUD_MUX1_SEL],
aud_clks[MT2701_AUD_AUD1PLL_98M], ret);
goto A1SYS_CLK_AUD_MUX1_SEL_ERR;
}
/* Set Divider */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__,
aud_clks[MT2701_AUD_AUD_MUX1_DIV],
ret);
goto A1SYS_CLK_AUD_MUX1_DIV_ERR;
}
ret = clk_set_rate(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV],
MT2701_AUD_AUD_MUX1_DIV_RATE);
if (ret) {
dev_err(afe->dev, "%s clk_set_parent %s-%d fail %d\n", __func__,
aud_clks[MT2701_AUD_AUD_MUX1_DIV],
MT2701_AUD_AUD_MUX1_DIV_RATE, ret);
goto A1SYS_CLK_AUD_MUX1_DIV_ERR;
}
/* Enable clock gate */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_48K_TIMING]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_AUD_48K_TIMING], ret);
goto A1SYS_CLK_AUD_48K_ERR;
}
/* Enable infra audio */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_INFRA_SYS_AUDIO], ret);
goto A1SYS_CLK_INFRA_ERR;
}
return 0;
A1SYS_CLK_INFRA_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]);
A1SYS_CLK_AUD_48K_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_48K_TIMING]);
A1SYS_CLK_AUD_MUX1_DIV_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV]);
A1SYS_CLK_AUD_MUX1_SEL_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]);
return ret;
}
void mt2701_turn_off_a1sys_clock(struct mtk_base_afe *afe)
{
struct mt2701_afe_private *afe_priv = afe->platform_priv;
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]);
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_48K_TIMING]);
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV]);
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]);
}
int mt2701_turn_on_a2sys_clock(struct mtk_base_afe *afe)
{
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int ret = 0;
/* Set Mux */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_AUD_MUX2_SEL], ret);
goto A2SYS_CLK_AUD_MUX2_SEL_ERR;
}
ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL],
afe_priv->clocks[MT2701_AUD_AUD2PLL_90M]);
if (ret) {
dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__,
aud_clks[MT2701_AUD_AUD_MUX2_SEL],
aud_clks[MT2701_AUD_AUD2PLL_90M], ret);
goto A2SYS_CLK_AUD_MUX2_SEL_ERR;
}
/* Set Divider */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_AUD_MUX2_DIV], ret);
goto A2SYS_CLK_AUD_MUX2_DIV_ERR;
}
ret = clk_set_rate(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV],
MT2701_AUD_AUD_MUX2_DIV_RATE);
if (ret) {
dev_err(afe->dev, "%s clk_set_parent %s-%d fail %d\n", __func__,
aud_clks[MT2701_AUD_AUD_MUX2_DIV],
MT2701_AUD_AUD_MUX2_DIV_RATE, ret);
goto A2SYS_CLK_AUD_MUX2_DIV_ERR;
}
/* Enable clock gate */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_44K_TIMING]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_AUD_44K_TIMING], ret);
goto A2SYS_CLK_AUD_44K_ERR;
}
/* Enable infra audio */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_INFRA_SYS_AUDIO], ret);
goto A2SYS_CLK_INFRA_ERR;
}
return 0;
A2SYS_CLK_INFRA_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]);
A2SYS_CLK_AUD_44K_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_44K_TIMING]);
A2SYS_CLK_AUD_MUX2_DIV_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV]);
A2SYS_CLK_AUD_MUX2_SEL_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]);
return ret;
}
void mt2701_turn_off_a2sys_clock(struct mtk_base_afe *afe)
{
struct mt2701_afe_private *afe_priv = afe->platform_priv;
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]);
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_44K_TIMING]);
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV]);
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]);
}
int mt2701_turn_on_afe_clock(struct mtk_base_afe *afe)
{
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int ret;
/* enable INFRA_SYS */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_INFRA_SYS_AUDIO], ret);
goto AFE_AUD_INFRA_ERR;
}
/* Set MT2701_AUD_AUDINTBUS to MT2701_AUD_SYSPLL1_D4 */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUDINTBUS]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_AUDINTBUS], ret);
goto AFE_AUD_AUDINTBUS_ERR;
}
ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_AUDINTBUS],
afe_priv->clocks[MT2701_AUD_SYSPLL1_D4]);
if (ret) {
dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__,
aud_clks[MT2701_AUD_AUDINTBUS],
aud_clks[MT2701_AUD_SYSPLL1_D4], ret);
goto AFE_AUD_AUDINTBUS_ERR;
}
/* Set MT2701_AUD_ASM_H_SEL to MT2701_AUD_UNIVPLL2_D2 */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_ASM_H_SEL]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_ASM_H_SEL], ret);
goto AFE_AUD_ASM_H_ERR;
}
ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_ASM_H_SEL],
afe_priv->clocks[MT2701_AUD_UNIVPLL2_D2]);
if (ret) {
dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__,
aud_clks[MT2701_AUD_ASM_H_SEL],
aud_clks[MT2701_AUD_UNIVPLL2_D2], ret);
goto AFE_AUD_ASM_H_ERR;
}
/* Set MT2701_AUD_ASM_M_SEL to MT2701_AUD_UNIVPLL2_D4 */
ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_ASM_M_SEL]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[MT2701_AUD_ASM_M_SEL], ret);
goto AFE_AUD_ASM_M_ERR;
}
ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_ASM_M_SEL],
afe_priv->clocks[MT2701_AUD_UNIVPLL2_D4]);
if (ret) {
dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__,
aud_clks[MT2701_AUD_ASM_M_SEL],
aud_clks[MT2701_AUD_UNIVPLL2_D4], ret);
goto AFE_AUD_ASM_M_ERR;
}
regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
AUDIO_TOP_CON0_PDN_AFE, 0);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
AUDIO_TOP_CON0_PDN_APLL_CK, 0);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
AUDIO_TOP_CON4_PDN_A1SYS, 0);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
AUDIO_TOP_CON4_PDN_A2SYS, 0);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
AUDIO_TOP_CON4_PDN_AFE_CONN, 0);
return 0;
AFE_AUD_ASM_M_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_M_SEL]);
AFE_AUD_ASM_H_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_H_SEL]);
AFE_AUD_AUDINTBUS_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUDINTBUS]);
AFE_AUD_INFRA_ERR:
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]);
return ret;
}
void mt2701_turn_off_afe_clock(struct mtk_base_afe *afe)
{
struct mt2701_afe_private *afe_priv = afe->platform_priv;
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]);
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUDINTBUS]);
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_H_SEL]);
clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_M_SEL]);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
AUDIO_TOP_CON0_PDN_AFE, AUDIO_TOP_CON0_PDN_AFE);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
AUDIO_TOP_CON0_PDN_APLL_CK,
AUDIO_TOP_CON0_PDN_APLL_CK);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
AUDIO_TOP_CON4_PDN_A1SYS,
AUDIO_TOP_CON4_PDN_A1SYS);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
AUDIO_TOP_CON4_PDN_A2SYS,
AUDIO_TOP_CON4_PDN_A2SYS);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
AUDIO_TOP_CON4_PDN_AFE_CONN,
AUDIO_TOP_CON4_PDN_AFE_CONN);
}
void mt2701_mclk_configuration(struct mtk_base_afe *afe, int id, int domain,
int mclk)
{
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int ret;
int aud_src_div_id = MT2701_AUD_AUD_K1_SRC_DIV + id;
int aud_src_clk_id = MT2701_AUD_AUD_K1_SRC_SEL + id;
/* Set MCLK Kx_SRC_SEL(domain) */
ret = clk_prepare_enable(afe_priv->clocks[aud_src_clk_id]);
if (ret)
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[aud_src_clk_id], ret);
if (domain == 0) {
ret = clk_set_parent(afe_priv->clocks[aud_src_clk_id],
afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]);
if (ret)
dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n",
__func__, aud_clks[aud_src_clk_id],
aud_clks[MT2701_AUD_AUD_MUX1_SEL], ret);
} else {
ret = clk_set_parent(afe_priv->clocks[aud_src_clk_id],
afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]);
if (ret)
dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n",
__func__, aud_clks[aud_src_clk_id],
aud_clks[MT2701_AUD_AUD_MUX2_SEL], ret);
}
clk_disable_unprepare(afe_priv->clocks[aud_src_clk_id]);
/* Set MCLK Kx_SRC_DIV(divider) */
ret = clk_prepare_enable(afe_priv->clocks[aud_src_div_id]);
if (ret)
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
__func__, aud_clks[aud_src_div_id], ret);
ret = clk_set_rate(afe_priv->clocks[aud_src_div_id], mclk);
if (ret)
dev_err(afe->dev, "%s clk_set_rate %s-%d fail %d\n", __func__,
aud_clks[aud_src_div_id], mclk, ret);
clk_disable_unprepare(afe_priv->clocks[aud_src_div_id]);
}
MODULE_DESCRIPTION("MT2701 afe clock control");
MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>");
MODULE_LICENSE("GPL v2");
/*
* mt2701-afe-clock-ctrl.h -- Mediatek 2701 afe clock ctrl definition
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _MT2701_AFE_CLOCK_CTRL_H_
#define _MT2701_AFE_CLOCK_CTRL_H_
struct mtk_base_afe;
int mt2701_init_clock(struct mtk_base_afe *afe);
int mt2701_afe_enable_clock(struct mtk_base_afe *afe);
void mt2701_afe_disable_clock(struct mtk_base_afe *afe);
int mt2701_turn_on_a1sys_clock(struct mtk_base_afe *afe);
void mt2701_turn_off_a1sys_clock(struct mtk_base_afe *afe);
int mt2701_turn_on_a2sys_clock(struct mtk_base_afe *afe);
void mt2701_turn_off_a2sys_clock(struct mtk_base_afe *afe);
int mt2701_turn_on_afe_clock(struct mtk_base_afe *afe);
void mt2701_turn_off_afe_clock(struct mtk_base_afe *afe);
void mt2701_mclk_configuration(struct mtk_base_afe *afe, int id, int domain,
int mclk);
#endif
/*
* mt2701-afe-common.h -- Mediatek 2701 audio driver definitions
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _MT_2701_AFE_COMMON_H_
#define _MT_2701_AFE_COMMON_H_
#include <sound/soc.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include "mt2701-reg.h"
#include "../common/mtk-base-afe.h"
#define MT2701_STREAM_DIR_NUM (SNDRV_PCM_STREAM_LAST + 1)
#define MT2701_PLL_DOMAIN_0_RATE 98304000
#define MT2701_PLL_DOMAIN_1_RATE 90316800
#define MT2701_AUD_AUD_MUX1_DIV_RATE (MT2701_PLL_DOMAIN_0_RATE / 2)
#define MT2701_AUD_AUD_MUX2_DIV_RATE (MT2701_PLL_DOMAIN_1_RATE / 2)
enum {
MT2701_I2S_1,
MT2701_I2S_2,
MT2701_I2S_3,
MT2701_I2S_4,
MT2701_I2S_NUM,
};
enum {
MT2701_MEMIF_DL1,
MT2701_MEMIF_DL2,
MT2701_MEMIF_DL3,
MT2701_MEMIF_DL4,
MT2701_MEMIF_DL5,
MT2701_MEMIF_DL_SINGLE_NUM,
MT2701_MEMIF_DLM = MT2701_MEMIF_DL_SINGLE_NUM,
MT2701_MEMIF_UL1,
MT2701_MEMIF_UL2,
MT2701_MEMIF_UL3,
MT2701_MEMIF_UL4,
MT2701_MEMIF_UL5,
MT2701_MEMIF_DLBT,
MT2701_MEMIF_ULBT,
MT2701_MEMIF_NUM,
MT2701_IO_I2S = MT2701_MEMIF_NUM,
MT2701_IO_2ND_I2S,
MT2701_IO_3RD_I2S,
MT2701_IO_4TH_I2S,
MT2701_IO_5TH_I2S,
MT2701_IO_6TH_I2S,
MT2701_IO_MRG,
};
enum {
MT2701_IRQ_ASYS_START,
MT2701_IRQ_ASYS_IRQ1 = MT2701_IRQ_ASYS_START,
MT2701_IRQ_ASYS_IRQ2,
MT2701_IRQ_ASYS_IRQ3,
MT2701_IRQ_ASYS_END,
};
/* 2701 clock def */
enum audio_system_clock_type {
MT2701_AUD_INFRA_SYS_AUDIO,
MT2701_AUD_AUD_MUX1_SEL,
MT2701_AUD_AUD_MUX2_SEL,
MT2701_AUD_AUD_MUX1_DIV,
MT2701_AUD_AUD_MUX2_DIV,
MT2701_AUD_AUD_48K_TIMING,
MT2701_AUD_AUD_44K_TIMING,
MT2701_AUD_AUDPLL_MUX_SEL,
MT2701_AUD_APLL_SEL,
MT2701_AUD_AUD1PLL_98M,
MT2701_AUD_AUD2PLL_90M,
MT2701_AUD_HADDS2PLL_98M,
MT2701_AUD_HADDS2PLL_294M,
MT2701_AUD_AUDPLL,
MT2701_AUD_AUDPLL_D4,
MT2701_AUD_AUDPLL_D8,
MT2701_AUD_AUDPLL_D16,
MT2701_AUD_AUDPLL_D24,
MT2701_AUD_AUDINTBUS,
MT2701_AUD_CLK_26M,
MT2701_AUD_SYSPLL1_D4,
MT2701_AUD_AUD_K1_SRC_SEL,
MT2701_AUD_AUD_K2_SRC_SEL,
MT2701_AUD_AUD_K3_SRC_SEL,
MT2701_AUD_AUD_K4_SRC_SEL,
MT2701_AUD_AUD_K5_SRC_SEL,
MT2701_AUD_AUD_K6_SRC_SEL,
MT2701_AUD_AUD_K1_SRC_DIV,
MT2701_AUD_AUD_K2_SRC_DIV,
MT2701_AUD_AUD_K3_SRC_DIV,
MT2701_AUD_AUD_K4_SRC_DIV,
MT2701_AUD_AUD_K5_SRC_DIV,
MT2701_AUD_AUD_K6_SRC_DIV,
MT2701_AUD_AUD_I2S1_MCLK,
MT2701_AUD_AUD_I2S2_MCLK,
MT2701_AUD_AUD_I2S3_MCLK,
MT2701_AUD_AUD_I2S4_MCLK,
MT2701_AUD_AUD_I2S5_MCLK,
MT2701_AUD_AUD_I2S6_MCLK,
MT2701_AUD_ASM_M_SEL,
MT2701_AUD_ASM_H_SEL,
MT2701_AUD_UNIVPLL2_D4,
MT2701_AUD_UNIVPLL2_D2,
MT2701_AUD_SYSPLL_D5,
MT2701_CLOCK_NUM
};
static const unsigned int mt2701_afe_backup_list[] = {
AUDIO_TOP_CON0,
AUDIO_TOP_CON4,
AUDIO_TOP_CON5,
ASYS_TOP_CON,
AFE_CONN0,
AFE_CONN1,
AFE_CONN2,
AFE_CONN3,
AFE_CONN15,
AFE_CONN16,
AFE_CONN17,
AFE_CONN18,
AFE_CONN19,
AFE_CONN20,
AFE_CONN21,
AFE_CONN22,
AFE_DAC_CON0,
AFE_MEMIF_PBUF_SIZE,
};
struct snd_pcm_substream;
struct mtk_base_irq_data;
struct mt2701_i2s_data {
int i2s_ctrl_reg;
int i2s_pwn_shift;
int i2s_asrc_fs_shift;
int i2s_asrc_fs_mask;
};
enum mt2701_i2s_dir {
I2S_OUT,
I2S_IN,
I2S_DIR_NUM,
};
struct mt2701_i2s_path {
int dai_id;
int mclk_rate;
int on[I2S_DIR_NUM];
int occupied[I2S_DIR_NUM];
const struct mt2701_i2s_data *i2s_data[2];
};
struct mt2701_afe_private {
struct clk *clocks[MT2701_CLOCK_NUM];
struct mt2701_i2s_path i2s_path[MT2701_I2S_NUM];
bool mrg_enable[MT2701_STREAM_DIR_NUM];
};
#endif
/*
* Mediatek ALSA SoC AFE platform driver for 2701
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
* Ir Lian <ir.lian@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include "mt2701-afe-common.h"
#include "mt2701-afe-clock-ctrl.h"
#include "../common/mtk-afe-platform-driver.h"
#include "../common/mtk-afe-fe-dai.h"
#define AFE_IRQ_STATUS_BITS 0xff
static const struct snd_pcm_hardware mt2701_afe_hardware = {
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE,
.period_bytes_min = 1024,
.period_bytes_max = 1024 * 256,
.periods_min = 4,
.periods_max = 1024,
.buffer_bytes_max = 1024 * 1024 * 16,
.fifo_size = 0,
};
struct mt2701_afe_rate {
unsigned int rate;
unsigned int regvalue;
};
static const struct mt2701_afe_rate mt2701_afe_i2s_rates[] = {
{ .rate = 8000, .regvalue = 0 },
{ .rate = 12000, .regvalue = 1 },
{ .rate = 16000, .regvalue = 2 },
{ .rate = 24000, .regvalue = 3 },
{ .rate = 32000, .regvalue = 4 },
{ .rate = 48000, .regvalue = 5 },
{ .rate = 96000, .regvalue = 6 },
{ .rate = 192000, .regvalue = 7 },
{ .rate = 384000, .regvalue = 8 },
{ .rate = 7350, .regvalue = 16 },
{ .rate = 11025, .regvalue = 17 },
{ .rate = 14700, .regvalue = 18 },
{ .rate = 22050, .regvalue = 19 },
{ .rate = 29400, .regvalue = 20 },
{ .rate = 44100, .regvalue = 21 },
{ .rate = 88200, .regvalue = 22 },
{ .rate = 176400, .regvalue = 23 },
{ .rate = 352800, .regvalue = 24 },
};
static int mt2701_dai_num_to_i2s(struct mtk_base_afe *afe, int num)
{
int val = num - MT2701_IO_I2S;
if (val < 0 || val >= MT2701_I2S_NUM) {
dev_err(afe->dev, "%s, num not available, num %d, val %d\n",
__func__, num, val);
return -EINVAL;
}
return val;
}
static int mt2701_afe_i2s_fs(unsigned int sample_rate)
{
int i;
for (i = 0; i < ARRAY_SIZE(mt2701_afe_i2s_rates); i++)
if (mt2701_afe_i2s_rates[i].rate == sample_rate)
return mt2701_afe_i2s_rates[i].regvalue;
return -EINVAL;
}
static int mt2701_afe_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id);
int clk_num = MT2701_AUD_AUD_I2S1_MCLK + i2s_num;
int ret = 0;
if (i2s_num < 0)
return i2s_num;
/* enable mclk */
ret = clk_prepare_enable(afe_priv->clocks[clk_num]);
if (ret)
dev_err(afe->dev, "Failed to enable mclk for I2S: %d\n",
i2s_num);
return ret;
}
static int mt2701_afe_i2s_path_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai,
int dir_invert)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id);
struct mt2701_i2s_path *i2s_path;
const struct mt2701_i2s_data *i2s_data;
int stream_dir = substream->stream;
if (i2s_num < 0)
return i2s_num;
i2s_path = &afe_priv->i2s_path[i2s_num];
if (dir_invert) {
if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK)
stream_dir = SNDRV_PCM_STREAM_CAPTURE;
else
stream_dir = SNDRV_PCM_STREAM_PLAYBACK;
}
i2s_data = i2s_path->i2s_data[stream_dir];
i2s_path->on[stream_dir]--;
if (i2s_path->on[stream_dir] < 0) {
dev_warn(afe->dev, "i2s_path->on: %d, dir: %d\n",
i2s_path->on[stream_dir], stream_dir);
i2s_path->on[stream_dir] = 0;
}
if (i2s_path->on[stream_dir])
return 0;
/* disable i2s */
regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg,
ASYS_I2S_CON_I2S_EN, 0);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
1 << i2s_data->i2s_pwn_shift,
1 << i2s_data->i2s_pwn_shift);
return 0;
}
static void mt2701_afe_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id);
struct mt2701_i2s_path *i2s_path;
int clk_num = MT2701_AUD_AUD_I2S1_MCLK + i2s_num;
if (i2s_num < 0)
return;
i2s_path = &afe_priv->i2s_path[i2s_num];
if (i2s_path->occupied[substream->stream])
i2s_path->occupied[substream->stream] = 0;
else
goto I2S_UNSTART;
mt2701_afe_i2s_path_shutdown(substream, dai, 0);
/* need to disable i2s-out path when disable i2s-in */
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
mt2701_afe_i2s_path_shutdown(substream, dai, 1);
I2S_UNSTART:
/* disable mclk */
clk_disable_unprepare(afe_priv->clocks[clk_num]);
}
static int mt2701_i2s_path_prepare_enable(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai,
int dir_invert)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id);
struct mt2701_i2s_path *i2s_path;
const struct mt2701_i2s_data *i2s_data;
struct snd_pcm_runtime * const runtime = substream->runtime;
int reg, fs, w_len = 1; /* now we support bck 64bits only */
int stream_dir = substream->stream;
unsigned int mask = 0, val = 0;
if (i2s_num < 0)
return i2s_num;
i2s_path = &afe_priv->i2s_path[i2s_num];
if (dir_invert) {
if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK)
stream_dir = SNDRV_PCM_STREAM_CAPTURE;
else
stream_dir = SNDRV_PCM_STREAM_PLAYBACK;
}
i2s_data = i2s_path->i2s_data[stream_dir];
/* no need to enable if already done */
i2s_path->on[stream_dir]++;
if (i2s_path->on[stream_dir] != 1)
return 0;
fs = mt2701_afe_i2s_fs(runtime->rate);
mask = ASYS_I2S_CON_FS |
ASYS_I2S_CON_I2S_COUPLE_MODE | /* 0 */
ASYS_I2S_CON_I2S_MODE |
ASYS_I2S_CON_WIDE_MODE;
val = ASYS_I2S_CON_FS_SET(fs) |
ASYS_I2S_CON_I2S_MODE |
ASYS_I2S_CON_WIDE_MODE_SET(w_len);
if (stream_dir == SNDRV_PCM_STREAM_CAPTURE) {
mask |= ASYS_I2S_IN_PHASE_FIX;
val |= ASYS_I2S_IN_PHASE_FIX;
}
regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, mask, val);
if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK)
reg = ASMO_TIMING_CON1;
else
reg = ASMI_TIMING_CON1;
regmap_update_bits(afe->regmap, reg,
i2s_data->i2s_asrc_fs_mask
<< i2s_data->i2s_asrc_fs_shift,
fs << i2s_data->i2s_asrc_fs_shift);
/* enable i2s */
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
1 << i2s_data->i2s_pwn_shift,
0 << i2s_data->i2s_pwn_shift);
/* reset i2s hw status before enable */
regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg,
ASYS_I2S_CON_RESET, ASYS_I2S_CON_RESET);
udelay(1);
regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg,
ASYS_I2S_CON_RESET, 0);
udelay(1);
regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg,
ASYS_I2S_CON_I2S_EN, ASYS_I2S_CON_I2S_EN);
return 0;
}
static int mt2701_afe_i2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int clk_domain;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id);
struct mt2701_i2s_path *i2s_path;
int mclk_rate;
if (i2s_num < 0)
return i2s_num;
i2s_path = &afe_priv->i2s_path[i2s_num];
mclk_rate = i2s_path->mclk_rate;
if (i2s_path->occupied[substream->stream])
return -EBUSY;
i2s_path->occupied[substream->stream] = 1;
if (MT2701_PLL_DOMAIN_0_RATE % mclk_rate == 0) {
clk_domain = 0;
} else if (MT2701_PLL_DOMAIN_1_RATE % mclk_rate == 0) {
clk_domain = 1;
} else {
dev_err(dai->dev, "%s() bad mclk rate %d\n",
__func__, mclk_rate);
return -EINVAL;
}
mt2701_mclk_configuration(afe, i2s_num, clk_domain, mclk_rate);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
mt2701_i2s_path_prepare_enable(substream, dai, 0);
} else {
/* need to enable i2s-out path when enable i2s-in */
/* prepare for another direction "out" */
mt2701_i2s_path_prepare_enable(substream, dai, 1);
/* prepare for "in" */
mt2701_i2s_path_prepare_enable(substream, dai, 0);
}
return 0;
}
static int mt2701_afe_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir)
{
struct mtk_base_afe *afe = dev_get_drvdata(dai->dev);
struct mt2701_afe_private *afe_priv = afe->platform_priv;
int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id);
if (i2s_num < 0)
return i2s_num;
/* mclk */
if (dir == SND_SOC_CLOCK_IN) {
dev_warn(dai->dev,
"%s() warning: mt2701 doesn't support mclk input\n",
__func__);
return -EINVAL;
}
afe_priv->i2s_path[i2s_num].mclk_rate = freq;
return 0;
}
static int mt2701_btmrg_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt2701_afe_private *afe_priv = afe->platform_priv;
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
AUDIO_TOP_CON4_PDN_MRGIF, 0);
afe_priv->mrg_enable[substream->stream] = 1;
return 0;
}
static int mt2701_btmrg_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
int stream_fs;
u32 val, msk;
stream_fs = params_rate(params);
if ((stream_fs != 8000) && (stream_fs != 16000)) {
dev_err(afe->dev, "%s() btmgr not supprt this stream_fs %d\n",
__func__, stream_fs);
return -EINVAL;
}
regmap_update_bits(afe->regmap, AFE_MRGIF_CON,
AFE_MRGIF_CON_I2S_MODE_MASK,
AFE_MRGIF_CON_I2S_MODE_32K);
val = AFE_DAIBT_CON0_BT_FUNC_EN | AFE_DAIBT_CON0_BT_FUNC_RDY
| AFE_DAIBT_CON0_MRG_USE;
msk = val;
if (stream_fs == 16000)
val |= AFE_DAIBT_CON0_BT_WIDE_MODE_EN;
msk |= AFE_DAIBT_CON0_BT_WIDE_MODE_EN;
regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, msk, val);
regmap_update_bits(afe->regmap, AFE_DAIBT_CON0,
AFE_DAIBT_CON0_DAIBT_EN,
AFE_DAIBT_CON0_DAIBT_EN);
regmap_update_bits(afe->regmap, AFE_MRGIF_CON,
AFE_MRGIF_CON_MRG_I2S_EN,
AFE_MRGIF_CON_MRG_I2S_EN);
regmap_update_bits(afe->regmap, AFE_MRGIF_CON,
AFE_MRGIF_CON_MRG_EN,
AFE_MRGIF_CON_MRG_EN);
return 0;
}
static void mt2701_btmrg_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt2701_afe_private *afe_priv = afe->platform_priv;
/* if the other direction stream is not occupied */
if (!afe_priv->mrg_enable[!substream->stream]) {
regmap_update_bits(afe->regmap, AFE_DAIBT_CON0,
AFE_DAIBT_CON0_DAIBT_EN, 0);
regmap_update_bits(afe->regmap, AFE_MRGIF_CON,
AFE_MRGIF_CON_MRG_EN, 0);
regmap_update_bits(afe->regmap, AFE_MRGIF_CON,
AFE_MRGIF_CON_MRG_I2S_EN, 0);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON4,
AUDIO_TOP_CON4_PDN_MRGIF,
AUDIO_TOP_CON4_PDN_MRGIF);
}
afe_priv->mrg_enable[substream->stream] = 0;
}
static int mt2701_simple_fe_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
int stream_dir = substream->stream;
int memif_num = rtd->cpu_dai->id;
struct mtk_base_afe_memif *memif_tmp;
/* can't run single DL & DLM at the same time */
if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) {
memif_tmp = &afe->memif[MT2701_MEMIF_DLM];
if (memif_tmp->substream) {
dev_warn(afe->dev, "%s memif is not available, stream_dir %d, memif_num %d\n",
__func__, stream_dir, memif_num);
return -EBUSY;
}
}
return mtk_afe_fe_startup(substream, dai);
}
static int mt2701_simple_fe_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
int stream_dir = substream->stream;
/* single DL use PAIR_INTERLEAVE */
if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) {
regmap_update_bits(afe->regmap,
AFE_MEMIF_PBUF_SIZE,
AFE_MEMIF_PBUF_SIZE_DLM_MASK,
AFE_MEMIF_PBUF_SIZE_PAIR_INTERLEAVE);
}
return mtk_afe_fe_hw_params(substream, params, dai);
}
static int mt2701_dlm_fe_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe_memif *memif_tmp;
const struct mtk_base_memif_data *memif_data;
int i;
for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) {
memif_tmp = &afe->memif[i];
if (memif_tmp->substream)
return -EBUSY;
}
/* enable agent for all signal DL (due to hw design) */
for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) {
memif_data = afe->memif[i].data;
regmap_update_bits(afe->regmap,
memif_data->agent_disable_reg,
1 << memif_data->agent_disable_shift,
0 << memif_data->agent_disable_shift);
}
return mtk_afe_fe_startup(substream, dai);
}
static void mt2701_dlm_fe_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
const struct mtk_base_memif_data *memif_data;
int i;
for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) {
memif_data = afe->memif[i].data;
regmap_update_bits(afe->regmap,
memif_data->agent_disable_reg,
1 << memif_data->agent_disable_shift,
1 << memif_data->agent_disable_shift);
}
return mtk_afe_fe_shutdown(substream, dai);
}
static int mt2701_dlm_fe_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
int channels = params_channels(params);
regmap_update_bits(afe->regmap,
AFE_MEMIF_PBUF_SIZE,
AFE_MEMIF_PBUF_SIZE_DLM_MASK,
AFE_MEMIF_PBUF_SIZE_FULL_INTERLEAVE);
regmap_update_bits(afe->regmap,
AFE_MEMIF_PBUF_SIZE,
AFE_MEMIF_PBUF_SIZE_DLM_BYTE_MASK,
AFE_MEMIF_PBUF_SIZE_DLM_32BYTES);
regmap_update_bits(afe->regmap,
AFE_MEMIF_PBUF_SIZE,
AFE_MEMIF_PBUF_SIZE_DLM_CH_MASK,
AFE_MEMIF_PBUF_SIZE_DLM_CH(channels));
return mtk_afe_fe_hw_params(substream, params, dai);
}
static int mt2701_dlm_fe_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe_memif *memif_tmp = &afe->memif[MT2701_MEMIF_DL1];
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
regmap_update_bits(afe->regmap, memif_tmp->data->enable_reg,
1 << memif_tmp->data->enable_shift,
1 << memif_tmp->data->enable_shift);
mtk_afe_fe_trigger(substream, cmd, dai);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
mtk_afe_fe_trigger(substream, cmd, dai);
regmap_update_bits(afe->regmap, memif_tmp->data->enable_reg,
1 << memif_tmp->data->enable_shift, 0);
return 0;
default:
return -EINVAL;
}
}
static int mt2701_memif_fs(struct snd_pcm_substream *substream,
unsigned int rate)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
int fs;
if (rtd->cpu_dai->id != MT2701_MEMIF_ULBT)
fs = mt2701_afe_i2s_fs(rate);
else
fs = (rate == 16000 ? 1 : 0);
return fs;
}
static int mt2701_irq_fs(struct snd_pcm_substream *substream, unsigned int rate)
{
return mt2701_afe_i2s_fs(rate);
}
/* FE DAIs */
static const struct snd_soc_dai_ops mt2701_single_memif_dai_ops = {
.startup = mt2701_simple_fe_startup,
.shutdown = mtk_afe_fe_shutdown,
.hw_params = mt2701_simple_fe_hw_params,
.hw_free = mtk_afe_fe_hw_free,
.prepare = mtk_afe_fe_prepare,
.trigger = mtk_afe_fe_trigger,
};
static const struct snd_soc_dai_ops mt2701_dlm_memif_dai_ops = {
.startup = mt2701_dlm_fe_startup,
.shutdown = mt2701_dlm_fe_shutdown,
.hw_params = mt2701_dlm_fe_hw_params,
.hw_free = mtk_afe_fe_hw_free,
.prepare = mtk_afe_fe_prepare,
.trigger = mt2701_dlm_fe_trigger,
};
/* I2S BE DAIs */
static const struct snd_soc_dai_ops mt2701_afe_i2s_ops = {
.startup = mt2701_afe_i2s_startup,
.shutdown = mt2701_afe_i2s_shutdown,
.prepare = mt2701_afe_i2s_prepare,
.set_sysclk = mt2701_afe_i2s_set_sysclk,
};
/* MRG BE DAIs */
static struct snd_soc_dai_ops mt2701_btmrg_ops = {
.startup = mt2701_btmrg_startup,
.shutdown = mt2701_btmrg_shutdown,
.hw_params = mt2701_btmrg_hw_params,
};
static struct snd_soc_dai_driver mt2701_afe_pcm_dais[] = {
/* FE DAIs: memory intefaces to CPU */
{
.name = "PCM_multi",
.id = MT2701_MEMIF_DLM,
.suspend = mtk_afe_dai_suspend,
.resume = mtk_afe_dai_resume,
.playback = {
.stream_name = "DLM",
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.ops = &mt2701_dlm_memif_dai_ops,
},
{
.name = "PCM0",
.id = MT2701_MEMIF_UL1,
.suspend = mtk_afe_dai_suspend,
.resume = mtk_afe_dai_resume,
.capture = {
.stream_name = "UL1",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.ops = &mt2701_single_memif_dai_ops,
},
{
.name = "PCM1",
.id = MT2701_MEMIF_UL2,
.suspend = mtk_afe_dai_suspend,
.resume = mtk_afe_dai_resume,
.capture = {
.stream_name = "UL2",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.ops = &mt2701_single_memif_dai_ops,
},
{
.name = "PCM_BT_DL",
.id = MT2701_MEMIF_DLBT,
.suspend = mtk_afe_dai_suspend,
.resume = mtk_afe_dai_resume,
.playback = {
.stream_name = "DLBT",
.channels_min = 1,
.channels_max = 1,
.rates = (SNDRV_PCM_RATE_8000
| SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &mt2701_single_memif_dai_ops,
},
{
.name = "PCM_BT_UL",
.id = MT2701_MEMIF_ULBT,
.suspend = mtk_afe_dai_suspend,
.resume = mtk_afe_dai_resume,
.capture = {
.stream_name = "ULBT",
.channels_min = 1,
.channels_max = 1,
.rates = (SNDRV_PCM_RATE_8000
| SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &mt2701_single_memif_dai_ops,
},
/* BE DAIs */
{
.name = "I2S0",
.id = MT2701_IO_I2S,
.playback = {
.stream_name = "I2S0 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.capture = {
.stream_name = "I2S0 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.ops = &mt2701_afe_i2s_ops,
.symmetric_rates = 1,
},
{
.name = "I2S1",
.id = MT2701_IO_2ND_I2S,
.playback = {
.stream_name = "I2S1 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.capture = {
.stream_name = "I2S1 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.ops = &mt2701_afe_i2s_ops,
.symmetric_rates = 1,
},
{
.name = "I2S2",
.id = MT2701_IO_3RD_I2S,
.playback = {
.stream_name = "I2S2 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.capture = {
.stream_name = "I2S2 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.ops = &mt2701_afe_i2s_ops,
.symmetric_rates = 1,
},
{
.name = "I2S3",
.id = MT2701_IO_4TH_I2S,
.playback = {
.stream_name = "I2S3 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.capture = {
.stream_name = "I2S3 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE)
},
.ops = &mt2701_afe_i2s_ops,
.symmetric_rates = 1,
},
{
.name = "MRG BT",
.id = MT2701_IO_MRG,
.playback = {
.stream_name = "BT Playback",
.channels_min = 1,
.channels_max = 1,
.rates = (SNDRV_PCM_RATE_8000
| SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "BT Capture",
.channels_min = 1,
.channels_max = 1,
.rates = (SNDRV_PCM_RATE_8000
| SNDRV_PCM_RATE_16000),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &mt2701_btmrg_ops,
.symmetric_rates = 1,
}
};
static const struct snd_kcontrol_new mt2701_afe_o00_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I00 Switch", AFE_CONN0, 0, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o01_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I01 Switch", AFE_CONN1, 1, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o02_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I02 Switch", AFE_CONN2, 2, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o03_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I03 Switch", AFE_CONN3, 3, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o14_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I26 Switch", AFE_CONN14, 26, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o15_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I12 Switch", AFE_CONN15, 12, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o16_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I13 Switch", AFE_CONN16, 13, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o17_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I14 Switch", AFE_CONN17, 14, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o18_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I15 Switch", AFE_CONN18, 15, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o19_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I16 Switch", AFE_CONN19, 16, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o20_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I17 Switch", AFE_CONN20, 17, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o21_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I18 Switch", AFE_CONN21, 18, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o22_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I19 Switch", AFE_CONN22, 19, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o23_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I20 Switch", AFE_CONN23, 20, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o24_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I21 Switch", AFE_CONN24, 21, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_o31_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I35 Switch", AFE_CONN41, 9, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_i02_mix[] = {
SOC_DAPM_SINGLE("I2S0 Switch", SND_SOC_NOPM, 0, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s0[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S0 Out Switch",
ASYS_I2SO1_CON, 26, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s1[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S1 Out Switch",
ASYS_I2SO2_CON, 26, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s2[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S2 Out Switch",
PWR2_TOP_CON, 17, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s3[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S3 Out Switch",
PWR2_TOP_CON, 18, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s4[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S4 Out Switch",
PWR2_TOP_CON, 19, 1, 0),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc0[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Asrc0 out Switch", AUDIO_TOP_CON4, 14, 1,
1),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc1[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Asrc1 out Switch", AUDIO_TOP_CON4, 15, 1,
1),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc2[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Asrc2 out Switch", PWR2_TOP_CON, 6, 1,
1),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc3[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Asrc3 out Switch", PWR2_TOP_CON, 7, 1,
1),
};
static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc4[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Asrc4 out Switch", PWR2_TOP_CON, 8, 1,
1),
};
static const struct snd_soc_dapm_widget mt2701_afe_pcm_widgets[] = {
/* inter-connections */
SND_SOC_DAPM_MIXER("I00", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I01", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I02", SND_SOC_NOPM, 0, 0, mt2701_afe_i02_mix,
ARRAY_SIZE(mt2701_afe_i02_mix)),
SND_SOC_DAPM_MIXER("I03", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I12", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I13", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I14", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I15", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I16", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I17", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I18", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I19", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I26", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I35", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("O00", SND_SOC_NOPM, 0, 0, mt2701_afe_o00_mix,
ARRAY_SIZE(mt2701_afe_o00_mix)),
SND_SOC_DAPM_MIXER("O01", SND_SOC_NOPM, 0, 0, mt2701_afe_o01_mix,
ARRAY_SIZE(mt2701_afe_o01_mix)),
SND_SOC_DAPM_MIXER("O02", SND_SOC_NOPM, 0, 0, mt2701_afe_o02_mix,
ARRAY_SIZE(mt2701_afe_o02_mix)),
SND_SOC_DAPM_MIXER("O03", SND_SOC_NOPM, 0, 0, mt2701_afe_o03_mix,
ARRAY_SIZE(mt2701_afe_o03_mix)),
SND_SOC_DAPM_MIXER("O14", SND_SOC_NOPM, 0, 0, mt2701_afe_o14_mix,
ARRAY_SIZE(mt2701_afe_o14_mix)),
SND_SOC_DAPM_MIXER("O15", SND_SOC_NOPM, 0, 0, mt2701_afe_o15_mix,
ARRAY_SIZE(mt2701_afe_o15_mix)),
SND_SOC_DAPM_MIXER("O16", SND_SOC_NOPM, 0, 0, mt2701_afe_o16_mix,
ARRAY_SIZE(mt2701_afe_o16_mix)),
SND_SOC_DAPM_MIXER("O17", SND_SOC_NOPM, 0, 0, mt2701_afe_o17_mix,
ARRAY_SIZE(mt2701_afe_o17_mix)),
SND_SOC_DAPM_MIXER("O18", SND_SOC_NOPM, 0, 0, mt2701_afe_o18_mix,
ARRAY_SIZE(mt2701_afe_o18_mix)),
SND_SOC_DAPM_MIXER("O19", SND_SOC_NOPM, 0, 0, mt2701_afe_o19_mix,
ARRAY_SIZE(mt2701_afe_o19_mix)),
SND_SOC_DAPM_MIXER("O20", SND_SOC_NOPM, 0, 0, mt2701_afe_o20_mix,
ARRAY_SIZE(mt2701_afe_o20_mix)),
SND_SOC_DAPM_MIXER("O21", SND_SOC_NOPM, 0, 0, mt2701_afe_o21_mix,
ARRAY_SIZE(mt2701_afe_o21_mix)),
SND_SOC_DAPM_MIXER("O22", SND_SOC_NOPM, 0, 0, mt2701_afe_o22_mix,
ARRAY_SIZE(mt2701_afe_o22_mix)),
SND_SOC_DAPM_MIXER("O31", SND_SOC_NOPM, 0, 0, mt2701_afe_o31_mix,
ARRAY_SIZE(mt2701_afe_o31_mix)),
SND_SOC_DAPM_MIXER("I12I13", SND_SOC_NOPM, 0, 0,
mt2701_afe_multi_ch_out_i2s0,
ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s0)),
SND_SOC_DAPM_MIXER("I14I15", SND_SOC_NOPM, 0, 0,
mt2701_afe_multi_ch_out_i2s1,
ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s1)),
SND_SOC_DAPM_MIXER("I16I17", SND_SOC_NOPM, 0, 0,
mt2701_afe_multi_ch_out_i2s2,
ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s2)),
SND_SOC_DAPM_MIXER("I18I19", SND_SOC_NOPM, 0, 0,
mt2701_afe_multi_ch_out_i2s3,
ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s3)),
SND_SOC_DAPM_MIXER("ASRC_O0", SND_SOC_NOPM, 0, 0,
mt2701_afe_multi_ch_out_asrc0,
ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc0)),
SND_SOC_DAPM_MIXER("ASRC_O1", SND_SOC_NOPM, 0, 0,
mt2701_afe_multi_ch_out_asrc1,
ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc1)),
SND_SOC_DAPM_MIXER("ASRC_O2", SND_SOC_NOPM, 0, 0,
mt2701_afe_multi_ch_out_asrc2,
ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc2)),
SND_SOC_DAPM_MIXER("ASRC_O3", SND_SOC_NOPM, 0, 0,
mt2701_afe_multi_ch_out_asrc3,
ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc3)),
};
static const struct snd_soc_dapm_route mt2701_afe_pcm_routes[] = {
{"I12", NULL, "DL1"},
{"I13", NULL, "DL1"},
{"I35", NULL, "DLBT"},
{"I2S0 Playback", NULL, "O15"},
{"I2S0 Playback", NULL, "O16"},
{"I2S1 Playback", NULL, "O17"},
{"I2S1 Playback", NULL, "O18"},
{"I2S2 Playback", NULL, "O19"},
{"I2S2 Playback", NULL, "O20"},
{"I2S3 Playback", NULL, "O21"},
{"I2S3 Playback", NULL, "O22"},
{"BT Playback", NULL, "O31"},
{"UL1", NULL, "O00"},
{"UL1", NULL, "O01"},
{"UL2", NULL, "O02"},
{"UL2", NULL, "O03"},
{"ULBT", NULL, "O14"},
{"I00", NULL, "I2S0 Capture"},
{"I01", NULL, "I2S0 Capture"},
{"I02", NULL, "I2S1 Capture"},
{"I03", NULL, "I2S1 Capture"},
/* I02,03 link to UL2, also need to open I2S0 */
{"I02", "I2S0 Switch", "I2S0 Capture"},
{"I26", NULL, "BT Capture"},
{"ASRC_O0", "Asrc0 out Switch", "DLM"},
{"ASRC_O1", "Asrc1 out Switch", "DLM"},
{"ASRC_O2", "Asrc2 out Switch", "DLM"},
{"ASRC_O3", "Asrc3 out Switch", "DLM"},
{"I12I13", "Multich I2S0 Out Switch", "ASRC_O0"},
{"I14I15", "Multich I2S1 Out Switch", "ASRC_O1"},
{"I16I17", "Multich I2S2 Out Switch", "ASRC_O2"},
{"I18I19", "Multich I2S3 Out Switch", "ASRC_O3"},
{ "I12", NULL, "I12I13" },
{ "I13", NULL, "I12I13" },
{ "I14", NULL, "I14I15" },
{ "I15", NULL, "I14I15" },
{ "I16", NULL, "I16I17" },
{ "I17", NULL, "I16I17" },
{ "I18", NULL, "I18I19" },
{ "I19", NULL, "I18I19" },
{ "O00", "I00 Switch", "I00" },
{ "O01", "I01 Switch", "I01" },
{ "O02", "I02 Switch", "I02" },
{ "O03", "I03 Switch", "I03" },
{ "O14", "I26 Switch", "I26" },
{ "O15", "I12 Switch", "I12" },
{ "O16", "I13 Switch", "I13" },
{ "O17", "I14 Switch", "I14" },
{ "O18", "I15 Switch", "I15" },
{ "O19", "I16 Switch", "I16" },
{ "O20", "I17 Switch", "I17" },
{ "O21", "I18 Switch", "I18" },
{ "O22", "I19 Switch", "I19" },
{ "O31", "I35 Switch", "I35" },
};
static const struct snd_soc_component_driver mt2701_afe_pcm_dai_component = {
.name = "mt2701-afe-pcm-dai",
.dapm_widgets = mt2701_afe_pcm_widgets,
.num_dapm_widgets = ARRAY_SIZE(mt2701_afe_pcm_widgets),
.dapm_routes = mt2701_afe_pcm_routes,
.num_dapm_routes = ARRAY_SIZE(mt2701_afe_pcm_routes),
};
static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = {
{
.name = "DL1",
.id = MT2701_MEMIF_DL1,
.reg_ofs_base = AFE_DL1_BASE,
.reg_ofs_cur = AFE_DL1_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 0,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON3,
.mono_shift = 16,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 1,
.hd_reg = AFE_MEMIF_HD_CON0,
.hd_shift = 0,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 6,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "DL2",
.id = MT2701_MEMIF_DL2,
.reg_ofs_base = AFE_DL2_BASE,
.reg_ofs_cur = AFE_DL2_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 5,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON3,
.mono_shift = 17,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 2,
.hd_reg = AFE_MEMIF_HD_CON0,
.hd_shift = 2,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 7,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "DL3",
.id = MT2701_MEMIF_DL3,
.reg_ofs_base = AFE_DL3_BASE,
.reg_ofs_cur = AFE_DL3_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 10,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON3,
.mono_shift = 18,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 3,
.hd_reg = AFE_MEMIF_HD_CON0,
.hd_shift = 4,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 8,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "DL4",
.id = MT2701_MEMIF_DL4,
.reg_ofs_base = AFE_DL4_BASE,
.reg_ofs_cur = AFE_DL4_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 15,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON3,
.mono_shift = 19,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 4,
.hd_reg = AFE_MEMIF_HD_CON0,
.hd_shift = 6,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 9,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "DL5",
.id = MT2701_MEMIF_DL5,
.reg_ofs_base = AFE_DL5_BASE,
.reg_ofs_cur = AFE_DL5_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 20,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON3,
.mono_shift = 20,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 5,
.hd_reg = AFE_MEMIF_HD_CON0,
.hd_shift = 8,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 10,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "DLM",
.id = MT2701_MEMIF_DLM,
.reg_ofs_base = AFE_DLMCH_BASE,
.reg_ofs_cur = AFE_DLMCH_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 0,
.fs_maskbit = 0x1f,
.mono_reg = -1,
.mono_shift = -1,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 7,
.hd_reg = AFE_MEMIF_PBUF_SIZE,
.hd_shift = 28,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 12,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "UL1",
.id = MT2701_MEMIF_UL1,
.reg_ofs_base = AFE_VUL_BASE,
.reg_ofs_cur = AFE_VUL_CUR,
.fs_reg = AFE_DAC_CON2,
.fs_shift = 0,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON4,
.mono_shift = 0,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 10,
.hd_reg = AFE_MEMIF_HD_CON1,
.hd_shift = 0,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 0,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "UL2",
.id = MT2701_MEMIF_UL2,
.reg_ofs_base = AFE_UL2_BASE,
.reg_ofs_cur = AFE_UL2_CUR,
.fs_reg = AFE_DAC_CON2,
.fs_shift = 5,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON4,
.mono_shift = 2,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 11,
.hd_reg = AFE_MEMIF_HD_CON1,
.hd_shift = 2,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 1,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "UL3",
.id = MT2701_MEMIF_UL3,
.reg_ofs_base = AFE_UL3_BASE,
.reg_ofs_cur = AFE_UL3_CUR,
.fs_reg = AFE_DAC_CON2,
.fs_shift = 10,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON4,
.mono_shift = 4,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 12,
.hd_reg = AFE_MEMIF_HD_CON0,
.hd_shift = 0,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 2,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "UL4",
.id = MT2701_MEMIF_UL4,
.reg_ofs_base = AFE_UL4_BASE,
.reg_ofs_cur = AFE_UL4_CUR,
.fs_reg = AFE_DAC_CON2,
.fs_shift = 15,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON4,
.mono_shift = 6,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 13,
.hd_reg = AFE_MEMIF_HD_CON0,
.hd_shift = 6,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 3,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "UL5",
.id = MT2701_MEMIF_UL5,
.reg_ofs_base = AFE_UL5_BASE,
.reg_ofs_cur = AFE_UL5_CUR,
.fs_reg = AFE_DAC_CON2,
.fs_shift = 20,
.mono_reg = AFE_DAC_CON4,
.mono_shift = 8,
.fs_maskbit = 0x1f,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 14,
.hd_reg = AFE_MEMIF_HD_CON0,
.hd_shift = 8,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 4,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "DLBT",
.id = MT2701_MEMIF_DLBT,
.reg_ofs_base = AFE_ARB1_BASE,
.reg_ofs_cur = AFE_ARB1_CUR,
.fs_reg = AFE_DAC_CON3,
.fs_shift = 10,
.fs_maskbit = 0x1f,
.mono_reg = AFE_DAC_CON3,
.mono_shift = 22,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 8,
.hd_reg = AFE_MEMIF_HD_CON0,
.hd_shift = 14,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 13,
.msb_reg = -1,
.msb_shift = -1,
},
{
.name = "ULBT",
.id = MT2701_MEMIF_ULBT,
.reg_ofs_base = AFE_DAI_BASE,
.reg_ofs_cur = AFE_DAI_CUR,
.fs_reg = AFE_DAC_CON2,
.fs_shift = 30,
.fs_maskbit = 0x1,
.mono_reg = -1,
.mono_shift = -1,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 17,
.hd_reg = AFE_MEMIF_HD_CON1,
.hd_shift = 20,
.agent_disable_reg = AUDIO_TOP_CON5,
.agent_disable_shift = 16,
.msb_reg = -1,
.msb_shift = -1,
},
};
static const struct mtk_base_irq_data irq_data[MT2701_IRQ_ASYS_END] = {
{
.id = MT2701_IRQ_ASYS_IRQ1,
.irq_cnt_reg = ASYS_IRQ1_CON,
.irq_cnt_shift = 0,
.irq_cnt_maskbit = 0xffffff,
.irq_fs_reg = ASYS_IRQ1_CON,
.irq_fs_shift = 24,
.irq_fs_maskbit = 0x1f,
.irq_en_reg = ASYS_IRQ1_CON,
.irq_en_shift = 31,
.irq_clr_reg = ASYS_IRQ_CLR,
.irq_clr_shift = 0,
},
{
.id = MT2701_IRQ_ASYS_IRQ2,
.irq_cnt_reg = ASYS_IRQ2_CON,
.irq_cnt_shift = 0,
.irq_cnt_maskbit = 0xffffff,
.irq_fs_reg = ASYS_IRQ2_CON,
.irq_fs_shift = 24,
.irq_fs_maskbit = 0x1f,
.irq_en_reg = ASYS_IRQ2_CON,
.irq_en_shift = 31,
.irq_clr_reg = ASYS_IRQ_CLR,
.irq_clr_shift = 1,
},
{
.id = MT2701_IRQ_ASYS_IRQ3,
.irq_cnt_reg = ASYS_IRQ3_CON,
.irq_cnt_shift = 0,
.irq_cnt_maskbit = 0xffffff,
.irq_fs_reg = ASYS_IRQ3_CON,
.irq_fs_shift = 24,
.irq_fs_maskbit = 0x1f,
.irq_en_reg = ASYS_IRQ3_CON,
.irq_en_shift = 31,
.irq_clr_reg = ASYS_IRQ_CLR,
.irq_clr_shift = 2,
}
};
static const struct mt2701_i2s_data mt2701_i2s_data[MT2701_I2S_NUM][2] = {
{
{
.i2s_ctrl_reg = ASYS_I2SO1_CON,
.i2s_pwn_shift = 6,
.i2s_asrc_fs_shift = 0,
.i2s_asrc_fs_mask = 0x1f,
},
{
.i2s_ctrl_reg = ASYS_I2SIN1_CON,
.i2s_pwn_shift = 0,
.i2s_asrc_fs_shift = 0,
.i2s_asrc_fs_mask = 0x1f,
},
},
{
{
.i2s_ctrl_reg = ASYS_I2SO2_CON,
.i2s_pwn_shift = 7,
.i2s_asrc_fs_shift = 5,
.i2s_asrc_fs_mask = 0x1f,
},
{
.i2s_ctrl_reg = ASYS_I2SIN2_CON,
.i2s_pwn_shift = 1,
.i2s_asrc_fs_shift = 5,
.i2s_asrc_fs_mask = 0x1f,
},
},
{
{
.i2s_ctrl_reg = ASYS_I2SO3_CON,
.i2s_pwn_shift = 8,
.i2s_asrc_fs_shift = 10,
.i2s_asrc_fs_mask = 0x1f,
},
{
.i2s_ctrl_reg = ASYS_I2SIN3_CON,
.i2s_pwn_shift = 2,
.i2s_asrc_fs_shift = 10,
.i2s_asrc_fs_mask = 0x1f,
},
},
{
{
.i2s_ctrl_reg = ASYS_I2SO4_CON,
.i2s_pwn_shift = 9,
.i2s_asrc_fs_shift = 15,
.i2s_asrc_fs_mask = 0x1f,
},
{
.i2s_ctrl_reg = ASYS_I2SIN4_CON,
.i2s_pwn_shift = 3,
.i2s_asrc_fs_shift = 15,
.i2s_asrc_fs_mask = 0x1f,
},
},
};
static const struct regmap_config mt2701_afe_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = AFE_END_ADDR,
.cache_type = REGCACHE_NONE,
};
static irqreturn_t mt2701_asys_isr(int irq_id, void *dev)
{
int id;
struct mtk_base_afe *afe = dev;
struct mtk_base_afe_memif *memif;
struct mtk_base_afe_irq *irq;
u32 status;
regmap_read(afe->regmap, ASYS_IRQ_STATUS, &status);
regmap_write(afe->regmap, ASYS_IRQ_CLR, status);
for (id = 0; id < MT2701_MEMIF_NUM; ++id) {
memif = &afe->memif[id];
if (memif->irq_usage < 0)
continue;
irq = &afe->irqs[memif->irq_usage];
if (status & 1 << (irq->irq_data->irq_clr_shift))
snd_pcm_period_elapsed(memif->substream);
}
return IRQ_HANDLED;
}
static int mt2701_afe_runtime_suspend(struct device *dev)
{
struct mtk_base_afe *afe = dev_get_drvdata(dev);
mt2701_afe_disable_clock(afe);
return 0;
}
static int mt2701_afe_runtime_resume(struct device *dev)
{
struct mtk_base_afe *afe = dev_get_drvdata(dev);
return mt2701_afe_enable_clock(afe);
}
static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev)
{
int ret, i;
unsigned int irq_id;
struct mtk_base_afe *afe;
struct mt2701_afe_private *afe_priv;
struct resource *res;
struct device *dev;
ret = 0;
afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL);
if (!afe)
return -ENOMEM;
afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv),
GFP_KERNEL);
if (!afe->platform_priv)
return -ENOMEM;
afe_priv = afe->platform_priv;
afe->dev = &pdev->dev;
dev = afe->dev;
irq_id = platform_get_irq(pdev, 0);
if (!irq_id) {
dev_err(dev, "%s no irq found\n", dev->of_node->name);
return -ENXIO;
}
ret = devm_request_irq(dev, irq_id, mt2701_asys_isr,
IRQF_TRIGGER_NONE, "asys-isr", (void *)afe);
if (ret) {
dev_err(dev, "could not request_irq for asys-isr\n");
return ret;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
afe->base_addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(afe->base_addr))
return PTR_ERR(afe->base_addr);
afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr,
&mt2701_afe_regmap_config);
if (IS_ERR(afe->regmap))
return PTR_ERR(afe->regmap);
mutex_init(&afe->irq_alloc_lock);
/* memif initialize */
afe->memif_size = MT2701_MEMIF_NUM;
afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif),
GFP_KERNEL);
if (!afe->memif)
return -ENOMEM;
for (i = 0; i < afe->memif_size; i++) {
afe->memif[i].data = &memif_data[i];
afe->memif[i].irq_usage = -1;
}
/* irq initialize */
afe->irqs_size = MT2701_IRQ_ASYS_END;
afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs),
GFP_KERNEL);
if (!afe->irqs)
return -ENOMEM;
for (i = 0; i < afe->irqs_size; i++)
afe->irqs[i].irq_data = &irq_data[i];
/* I2S initialize */
for (i = 0; i < MT2701_I2S_NUM; i++) {
afe_priv->i2s_path[i].i2s_data[I2S_OUT]
= &mt2701_i2s_data[i][I2S_OUT];
afe_priv->i2s_path[i].i2s_data[I2S_IN]
= &mt2701_i2s_data[i][I2S_IN];
}
afe->mtk_afe_hardware = &mt2701_afe_hardware;
afe->memif_fs = mt2701_memif_fs;
afe->irq_fs = mt2701_irq_fs;
afe->reg_back_up_list = mt2701_afe_backup_list;
afe->reg_back_up_list_num = ARRAY_SIZE(mt2701_afe_backup_list);
afe->runtime_resume = mt2701_afe_runtime_resume;
afe->runtime_suspend = mt2701_afe_runtime_suspend;
/* initial audio related clock */
ret = mt2701_init_clock(afe);
if (ret) {
dev_err(dev, "init clock error\n");
return ret;
}
platform_set_drvdata(pdev, afe);
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev))
goto err_pm_disable;
ret = snd_soc_register_platform(&pdev->dev, &mtk_afe_pcm_platform);
if (ret) {
dev_warn(dev, "err_platform\n");
goto err_platform;
}
ret = snd_soc_register_component(&pdev->dev,
&mt2701_afe_pcm_dai_component,
mt2701_afe_pcm_dais,
ARRAY_SIZE(mt2701_afe_pcm_dais));
if (ret) {
dev_warn(dev, "err_dai_component\n");
goto err_dai_component;
}
mt2701_afe_runtime_resume(&pdev->dev);
return 0;
err_dai_component:
snd_soc_unregister_component(&pdev->dev);
err_platform:
snd_soc_unregister_platform(&pdev->dev);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
return ret;
}
static int mt2701_afe_pcm_dev_remove(struct platform_device *pdev)
{
struct mtk_base_afe *afe = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
mt2701_afe_runtime_suspend(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
snd_soc_unregister_platform(&pdev->dev);
/* disable afe clock */
mt2701_afe_disable_clock(afe);
return 0;
}
static const struct of_device_id mt2701_afe_pcm_dt_match[] = {
{ .compatible = "mediatek,mt2701-audio", },
{},
};
MODULE_DEVICE_TABLE(of, mt2701_afe_pcm_dt_match);
static const struct dev_pm_ops mt2701_afe_pm_ops = {
SET_RUNTIME_PM_OPS(mt2701_afe_runtime_suspend,
mt2701_afe_runtime_resume, NULL)
};
static struct platform_driver mt2701_afe_pcm_driver = {
.driver = {
.name = "mt2701-audio",
.of_match_table = mt2701_afe_pcm_dt_match,
#ifdef CONFIG_PM
.pm = &mt2701_afe_pm_ops,
#endif
},
.probe = mt2701_afe_pcm_dev_probe,
.remove = mt2701_afe_pcm_dev_remove,
};
module_platform_driver(mt2701_afe_pcm_driver);
MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver for 2701");
MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>");
MODULE_LICENSE("GPL v2");
/*
* mt2701-cs42448.c -- MT2701 CS42448 ALSA SoC machine driver
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Ir Lian <ir.lian@mediatek.com>
* Garlic Tseng <garlic.tseng@mediatek.com>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of_gpio.h>
#include "mt2701-afe-common.h"
struct mt2701_cs42448_private {
int i2s1_in_mux;
int i2s1_in_mux_gpio_sel_1;
int i2s1_in_mux_gpio_sel_2;
};
static const char * const i2sin_mux_switch_text[] = {
"ADC_SDOUT2",
"ADC_SDOUT3",
"I2S_IN_1",
"I2S_IN_2",
};
static const struct soc_enum i2sin_mux_enum =
SOC_ENUM_SINGLE_EXT(4, i2sin_mux_switch_text);
static int mt2701_cs42448_i2sin1_mux_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct mt2701_cs42448_private *priv = snd_soc_card_get_drvdata(card);
ucontrol->value.integer.value[0] = priv->i2s1_in_mux;
return 0;
}
static int mt2701_cs42448_i2sin1_mux_set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct mt2701_cs42448_private *priv = snd_soc_card_get_drvdata(card);
if (ucontrol->value.integer.value[0] == priv->i2s1_in_mux)
return 0;
switch (ucontrol->value.integer.value[0]) {
case 0:
gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 0);
gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 0);
break;
case 1:
gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 1);
gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 0);
break;
case 2:
gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 0);
gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 1);
break;
case 3:
gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 1);
gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 1);
break;
default:
dev_warn(card->dev, "%s invalid setting\n", __func__);
}
priv->i2s1_in_mux = ucontrol->value.integer.value[0];
return 0;
}
static const struct snd_soc_dapm_widget
mt2701_cs42448_asoc_card_dapm_widgets[] = {
SND_SOC_DAPM_LINE("Line Out Jack", NULL),
SND_SOC_DAPM_MIC("AMIC", NULL),
SND_SOC_DAPM_LINE("Tuner In", NULL),
SND_SOC_DAPM_LINE("Satellite Tuner In", NULL),
SND_SOC_DAPM_LINE("AUX In", NULL),
};
static const struct snd_kcontrol_new mt2701_cs42448_controls[] = {
SOC_DAPM_PIN_SWITCH("Line Out Jack"),
SOC_DAPM_PIN_SWITCH("AMIC"),
SOC_DAPM_PIN_SWITCH("Tuner In"),
SOC_DAPM_PIN_SWITCH("Satellite Tuner In"),
SOC_DAPM_PIN_SWITCH("AUX In"),
SOC_ENUM_EXT("I2SIN1_MUX_Switch", i2sin_mux_enum,
mt2701_cs42448_i2sin1_mux_get,
mt2701_cs42448_i2sin1_mux_set),
};
static const unsigned int mt2701_cs42448_sampling_rates[] = {48000};
static struct snd_pcm_hw_constraint_list mt2701_cs42448_constraints_rates = {
.count = ARRAY_SIZE(mt2701_cs42448_sampling_rates),
.list = mt2701_cs42448_sampling_rates,
.mask = 0,
};
static int mt2701_cs42448_fe_ops_startup(struct snd_pcm_substream *substream)
{
int err;
err = snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&mt2701_cs42448_constraints_rates);
if (err < 0) {
dev_err(substream->pcm->card->dev,
"%s snd_pcm_hw_constraint_list failed: 0x%x\n",
__func__, err);
return err;
}
return 0;
}
static struct snd_soc_ops mt2701_cs42448_48k_fe_ops = {
.startup = mt2701_cs42448_fe_ops_startup,
};
static int mt2701_cs42448_be_ops_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
unsigned int mclk_rate;
unsigned int rate = params_rate(params);
unsigned int div_mclk_over_bck = rate > 192000 ? 2 : 4;
unsigned int div_bck_over_lrck = 64;
mclk_rate = rate * div_bck_over_lrck * div_mclk_over_bck;
/* mt2701 mclk */
snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, SND_SOC_CLOCK_OUT);
/* codec mclk */
snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, SND_SOC_CLOCK_IN);
return 0;
}
static struct snd_soc_ops mt2701_cs42448_be_ops = {
.hw_params = mt2701_cs42448_be_ops_hw_params
};
enum {
DAI_LINK_FE_MULTI_CH_OUT,
DAI_LINK_FE_PCM0_IN,
DAI_LINK_FE_PCM1_IN,
DAI_LINK_FE_BT_OUT,
DAI_LINK_FE_BT_IN,
DAI_LINK_BE_I2S0,
DAI_LINK_BE_I2S1,
DAI_LINK_BE_I2S2,
DAI_LINK_BE_I2S3,
DAI_LINK_BE_MRG_BT,
};
static struct snd_soc_dai_link mt2701_cs42448_dai_links[] = {
/* FE */
[DAI_LINK_FE_MULTI_CH_OUT] = {
.name = "mt2701-cs42448-multi-ch-out",
.stream_name = "mt2701-cs42448-multi-ch-out",
.cpu_dai_name = "PCM_multi",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.ops = &mt2701_cs42448_48k_fe_ops,
.dynamic = 1,
.dpcm_playback = 1,
},
[DAI_LINK_FE_PCM0_IN] = {
.name = "mt2701-cs42448-pcm0",
.stream_name = "mt2701-cs42448-pcm0-data-UL",
.cpu_dai_name = "PCM0",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.ops = &mt2701_cs42448_48k_fe_ops,
.dynamic = 1,
.dpcm_capture = 1,
},
[DAI_LINK_FE_PCM1_IN] = {
.name = "mt2701-cs42448-pcm1-data-UL",
.stream_name = "mt2701-cs42448-pcm1-data-UL",
.cpu_dai_name = "PCM1",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.ops = &mt2701_cs42448_48k_fe_ops,
.dynamic = 1,
.dpcm_capture = 1,
},
[DAI_LINK_FE_BT_OUT] = {
.name = "mt2701-cs42448-pcm-BT-out",
.stream_name = "mt2701-cs42448-pcm-BT",
.cpu_dai_name = "PCM_BT_DL",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.dynamic = 1,
.dpcm_playback = 1,
},
[DAI_LINK_FE_BT_IN] = {
.name = "mt2701-cs42448-pcm-BT-in",
.stream_name = "mt2701-cs42448-pcm-BT",
.cpu_dai_name = "PCM_BT_UL",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.dynamic = 1,
.dpcm_capture = 1,
},
/* BE */
[DAI_LINK_BE_I2S0] = {
.name = "mt2701-cs42448-I2S0",
.cpu_dai_name = "I2S0",
.no_pcm = 1,
.codec_dai_name = "cs42448",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS
| SND_SOC_DAIFMT_GATED,
.ops = &mt2701_cs42448_be_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
[DAI_LINK_BE_I2S1] = {
.name = "mt2701-cs42448-I2S1",
.cpu_dai_name = "I2S1",
.no_pcm = 1,
.codec_dai_name = "cs42448",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS
| SND_SOC_DAIFMT_GATED,
.ops = &mt2701_cs42448_be_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
[DAI_LINK_BE_I2S2] = {
.name = "mt2701-cs42448-I2S2",
.cpu_dai_name = "I2S2",
.no_pcm = 1,
.codec_dai_name = "cs42448",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS
| SND_SOC_DAIFMT_GATED,
.ops = &mt2701_cs42448_be_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
[DAI_LINK_BE_I2S3] = {
.name = "mt2701-cs42448-I2S3",
.cpu_dai_name = "I2S3",
.no_pcm = 1,
.codec_dai_name = "cs42448",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS
| SND_SOC_DAIFMT_GATED,
.ops = &mt2701_cs42448_be_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
[DAI_LINK_BE_MRG_BT] = {
.name = "mt2701-cs42448-MRG-BT",
.cpu_dai_name = "MRG BT",
.no_pcm = 1,
.codec_dai_name = "bt-sco-pcm-wb",
.dpcm_playback = 1,
.dpcm_capture = 1,
},
};
static struct snd_soc_card mt2701_cs42448_soc_card = {
.name = "mt2701-cs42448",
.owner = THIS_MODULE,
.dai_link = mt2701_cs42448_dai_links,
.num_links = ARRAY_SIZE(mt2701_cs42448_dai_links),
.controls = mt2701_cs42448_controls,
.num_controls = ARRAY_SIZE(mt2701_cs42448_controls),
.dapm_widgets = mt2701_cs42448_asoc_card_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(mt2701_cs42448_asoc_card_dapm_widgets),
};
static int mt2701_cs42448_machine_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &mt2701_cs42448_soc_card;
int ret;
int i;
struct device_node *platform_node, *codec_node, *codec_node_bt_mrg;
struct mt2701_cs42448_private *priv =
devm_kzalloc(&pdev->dev, sizeof(struct mt2701_cs42448_private),
GFP_KERNEL);
struct device *dev = &pdev->dev;
if (!priv)
return -ENOMEM;
platform_node = of_parse_phandle(pdev->dev.of_node,
"mediatek,platform", 0);
if (!platform_node) {
dev_err(&pdev->dev, "Property 'platform' missing or invalid\n");
return -EINVAL;
}
for (i = 0; i < card->num_links; i++) {
if (mt2701_cs42448_dai_links[i].platform_name)
continue;
mt2701_cs42448_dai_links[i].platform_of_node = platform_node;
}
card->dev = dev;
codec_node = of_parse_phandle(pdev->dev.of_node,
"mediatek,audio-codec", 0);
if (!codec_node) {
dev_err(&pdev->dev,
"Property 'audio-codec' missing or invalid\n");
return -EINVAL;
}
for (i = 0; i < card->num_links; i++) {
if (mt2701_cs42448_dai_links[i].codec_name)
continue;
mt2701_cs42448_dai_links[i].codec_of_node = codec_node;
}
codec_node_bt_mrg = of_parse_phandle(pdev->dev.of_node,
"mediatek,audio-codec-bt-mrg", 0);
if (!codec_node_bt_mrg) {
dev_err(&pdev->dev,
"Property 'audio-codec-bt-mrg' missing or invalid\n");
return -EINVAL;
}
mt2701_cs42448_dai_links[DAI_LINK_BE_MRG_BT].codec_of_node
= codec_node_bt_mrg;
ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
if (ret) {
dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret);
return ret;
}
priv->i2s1_in_mux_gpio_sel_1 =
of_get_named_gpio(dev->of_node, "i2s1-in-sel-gpio1", 0);
if (gpio_is_valid(priv->i2s1_in_mux_gpio_sel_1)) {
ret = devm_gpio_request(dev, priv->i2s1_in_mux_gpio_sel_1,
"i2s1_in_mux_gpio_sel_1");
if (ret)
dev_warn(&pdev->dev, "%s devm_gpio_request fail %d\n",
__func__, ret);
gpio_direction_output(priv->i2s1_in_mux_gpio_sel_1, 0);
}
priv->i2s1_in_mux_gpio_sel_2 =
of_get_named_gpio(dev->of_node, "i2s1-in-sel-gpio2", 0);
if (gpio_is_valid(priv->i2s1_in_mux_gpio_sel_2)) {
ret = devm_gpio_request(dev, priv->i2s1_in_mux_gpio_sel_2,
"i2s1_in_mux_gpio_sel_2");
if (ret)
dev_warn(&pdev->dev, "%s devm_gpio_request fail2 %d\n",
__func__, ret);
gpio_direction_output(priv->i2s1_in_mux_gpio_sel_2, 0);
}
snd_soc_card_set_drvdata(card, priv);
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n",
__func__, ret);
return ret;
}
#ifdef CONFIG_OF
static const struct of_device_id mt2701_cs42448_machine_dt_match[] = {
{.compatible = "mediatek,mt2701-cs42448-machine",},
{}
};
#endif
static struct platform_driver mt2701_cs42448_machine = {
.driver = {
.name = "mt2701-cs42448",
#ifdef CONFIG_OF
.of_match_table = mt2701_cs42448_machine_dt_match,
#endif
},
.probe = mt2701_cs42448_machine_probe,
};
module_platform_driver(mt2701_cs42448_machine);
/* Module information */
MODULE_DESCRIPTION("MT2701 CS42448 ALSA SoC machine driver");
MODULE_AUTHOR("Ir Lian <ir.lian@mediatek.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("mt2701 cs42448 soc card");
/*
* mt2701-reg.h -- Mediatek 2701 audio driver reg definition
*
* Copyright (c) 2016 MediaTek Inc.
* Author: Garlic Tseng <garlic.tseng@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _MT2701_REG_H_
#define _MT2701_REG_H_
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include "mt2701-afe-common.h"
/*****************************************************************************
* R E G I S T E R D E F I N I T I O N
*****************************************************************************/
#define AUDIO_TOP_CON0 0x0000
#define AUDIO_TOP_CON4 0x0010
#define AUDIO_TOP_CON5 0x0014
#define AFE_DAIBT_CON0 0x001c
#define AFE_MRGIF_CON 0x003c
#define ASMI_TIMING_CON1 0x0100
#define ASMO_TIMING_CON1 0x0104
#define PWR1_ASM_CON1 0x0108
#define ASYS_TOP_CON 0x0600
#define ASYS_I2SIN1_CON 0x0604
#define ASYS_I2SIN2_CON 0x0608
#define ASYS_I2SIN3_CON 0x060c
#define ASYS_I2SIN4_CON 0x0610
#define ASYS_I2SIN5_CON 0x0614
#define ASYS_I2SO1_CON 0x061C
#define ASYS_I2SO2_CON 0x0620
#define ASYS_I2SO3_CON 0x0624
#define ASYS_I2SO4_CON 0x0628
#define ASYS_I2SO5_CON 0x062c
#define PWR2_TOP_CON 0x0634
#define AFE_CONN0 0x06c0
#define AFE_CONN1 0x06c4
#define AFE_CONN2 0x06c8
#define AFE_CONN3 0x06cc
#define AFE_CONN14 0x06f8
#define AFE_CONN15 0x06fc
#define AFE_CONN16 0x0700
#define AFE_CONN17 0x0704
#define AFE_CONN18 0x0708
#define AFE_CONN19 0x070c
#define AFE_CONN20 0x0710
#define AFE_CONN21 0x0714
#define AFE_CONN22 0x0718
#define AFE_CONN23 0x071c
#define AFE_CONN24 0x0720
#define AFE_CONN41 0x0764
#define ASYS_IRQ1_CON 0x0780
#define ASYS_IRQ2_CON 0x0784
#define ASYS_IRQ3_CON 0x0788
#define ASYS_IRQ_CLR 0x07c0
#define ASYS_IRQ_STATUS 0x07c4
#define PWR2_ASM_CON1 0x1070
#define AFE_DAC_CON0 0x1200
#define AFE_DAC_CON1 0x1204
#define AFE_DAC_CON2 0x1208
#define AFE_DAC_CON3 0x120c
#define AFE_DAC_CON4 0x1210
#define AFE_MEMIF_HD_CON1 0x121c
#define AFE_MEMIF_PBUF_SIZE 0x1238
#define AFE_MEMIF_HD_CON0 0x123c
#define AFE_DL1_BASE 0x1240
#define AFE_DL1_CUR 0x1244
#define AFE_DL2_BASE 0x1250
#define AFE_DL2_CUR 0x1254
#define AFE_DL3_BASE 0x1260
#define AFE_DL3_CUR 0x1264
#define AFE_DL4_BASE 0x1270
#define AFE_DL4_CUR 0x1274
#define AFE_DL5_BASE 0x1280
#define AFE_DL5_CUR 0x1284
#define AFE_DLMCH_BASE 0x12a0
#define AFE_DLMCH_CUR 0x12a4
#define AFE_ARB1_BASE 0x12b0
#define AFE_ARB1_CUR 0x12b4
#define AFE_VUL_BASE 0x1300
#define AFE_VUL_CUR 0x130c
#define AFE_UL2_BASE 0x1310
#define AFE_UL2_END 0x1318
#define AFE_UL2_CUR 0x131c
#define AFE_UL3_BASE 0x1320
#define AFE_UL3_END 0x1328
#define AFE_UL3_CUR 0x132c
#define AFE_UL4_BASE 0x1330
#define AFE_UL4_END 0x1338
#define AFE_UL4_CUR 0x133c
#define AFE_UL5_BASE 0x1340
#define AFE_UL5_END 0x1348
#define AFE_UL5_CUR 0x134c
#define AFE_DAI_BASE 0x1370
#define AFE_DAI_CUR 0x137c
/* AUDIO_TOP_CON0 (0x0000) */
#define AUDIO_TOP_CON0_A1SYS_A2SYS_ON (0x3 << 0)
#define AUDIO_TOP_CON0_PDN_AFE (0x1 << 2)
#define AUDIO_TOP_CON0_PDN_APLL_CK (0x1 << 23)
/* AUDIO_TOP_CON4 (0x0010) */
#define AUDIO_TOP_CON4_I2SO1_PWN (0x1 << 6)
#define AUDIO_TOP_CON4_PDN_A1SYS (0x1 << 21)
#define AUDIO_TOP_CON4_PDN_A2SYS (0x1 << 22)
#define AUDIO_TOP_CON4_PDN_AFE_CONN (0x1 << 23)
#define AUDIO_TOP_CON4_PDN_MRGIF (0x1 << 25)
/* AFE_DAIBT_CON0 (0x001c) */
#define AFE_DAIBT_CON0_DAIBT_EN (0x1 << 0)
#define AFE_DAIBT_CON0_BT_FUNC_EN (0x1 << 1)
#define AFE_DAIBT_CON0_BT_FUNC_RDY (0x1 << 3)
#define AFE_DAIBT_CON0_BT_WIDE_MODE_EN (0x1 << 9)
#define AFE_DAIBT_CON0_MRG_USE (0x1 << 12)
/* PWR1_ASM_CON1 (0x0108) */
#define PWR1_ASM_CON1_INIT_VAL (0x492)
/* AFE_MRGIF_CON (0x003c) */
#define AFE_MRGIF_CON_MRG_EN (0x1 << 0)
#define AFE_MRGIF_CON_MRG_I2S_EN (0x1 << 16)
#define AFE_MRGIF_CON_I2S_MODE_MASK (0xf << 20)
#define AFE_MRGIF_CON_I2S_MODE_32K (0x4 << 20)
/* ASYS_I2SO1_CON (0x061c) */
#define ASYS_I2SO1_CON_FS (0x1f << 8)
#define ASYS_I2SO1_CON_FS_SET(x) ((x) << 8)
#define ASYS_I2SO1_CON_MULTI_CH (0x1 << 16)
#define ASYS_I2SO1_CON_SIDEGEN (0x1 << 30)
#define ASYS_I2SO1_CON_I2S_EN (0x1 << 0)
/* 0:EIAJ 1:I2S */
#define ASYS_I2SO1_CON_I2S_MODE (0x1 << 3)
#define ASYS_I2SO1_CON_WIDE_MODE (0x1 << 1)
#define ASYS_I2SO1_CON_WIDE_MODE_SET(x) ((x) << 1)
/* PWR2_TOP_CON (0x0634) */
#define PWR2_TOP_CON_INIT_VAL (0xffe1ffff)
/* ASYS_IRQ_CLR (0x07c0) */
#define ASYS_IRQ_CLR_ALL (0xffffffff)
/* PWR2_ASM_CON1 (0x1070) */
#define PWR2_ASM_CON1_INIT_VAL (0x492492)
/* AFE_DAC_CON0 (0x1200) */
#define AFE_DAC_CON0_AFE_ON (0x1 << 0)
/* AFE_MEMIF_PBUF_SIZE (0x1238) */
#define AFE_MEMIF_PBUF_SIZE_DLM_MASK (0x1 << 29)
#define AFE_MEMIF_PBUF_SIZE_PAIR_INTERLEAVE (0x0 << 29)
#define AFE_MEMIF_PBUF_SIZE_FULL_INTERLEAVE (0x1 << 29)
#define DLMCH_BIT_WIDTH_MASK (0x1 << 28)
#define AFE_MEMIF_PBUF_SIZE_DLM_CH_MASK (0xf << 24)
#define AFE_MEMIF_PBUF_SIZE_DLM_CH(x) ((x) << 24)
#define AFE_MEMIF_PBUF_SIZE_DLM_BYTE_MASK (0x3 << 12)
#define AFE_MEMIF_PBUF_SIZE_DLM_32BYTES (0x1 << 12)
/* I2S in/out register bit control */
#define ASYS_I2S_CON_FS (0x1f << 8)
#define ASYS_I2S_CON_FS_SET(x) ((x) << 8)
#define ASYS_I2S_CON_RESET (0x1 << 30)
#define ASYS_I2S_CON_I2S_EN (0x1 << 0)
#define ASYS_I2S_CON_I2S_COUPLE_MODE (0x1 << 17)
/* 0:EIAJ 1:I2S */
#define ASYS_I2S_CON_I2S_MODE (0x1 << 3)
#define ASYS_I2S_CON_WIDE_MODE (0x1 << 1)
#define ASYS_I2S_CON_WIDE_MODE_SET(x) ((x) << 1)
#define ASYS_I2S_IN_PHASE_FIX (0x1 << 31)
#define AFE_END_ADDR 0x15e0
#endif
# MTK Platform Support
obj-$(CONFIG_SND_SOC_MT8173) += mt8173-afe-pcm.o
# Machine support
obj-$(CONFIG_SND_SOC_MT8173_MAX98090) += mt8173-max98090.o
obj-$(CONFIG_SND_SOC_MT8173_RT5650) += mt8173-rt5650.o
obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5514) += mt8173-rt5650-rt5514.o
obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5676) += mt8173-rt5650-rt5676.o
/*
* mtk_afe_common.h -- Mediatek audio driver common definitions
* mt8173_afe_common.h -- Mediatek 8173 audio driver common definitions
*
* Copyright (c) 2015 MediaTek Inc.
* Author: Koro Chen <koro.chen@mediatek.com>
......@@ -17,85 +17,57 @@
* GNU General Public License for more details.
*/
#ifndef _MTK_AFE_COMMON_H_
#define _MTK_AFE_COMMON_H_
#ifndef _MT8173_AFE_COMMON_H_
#define _MT8173_AFE_COMMON_H_
#include <linux/clk.h>
#include <linux/regmap.h>
enum {
MTK_AFE_MEMIF_DL1,
MTK_AFE_MEMIF_DL2,
MTK_AFE_MEMIF_VUL,
MTK_AFE_MEMIF_DAI,
MTK_AFE_MEMIF_AWB,
MTK_AFE_MEMIF_MOD_DAI,
MTK_AFE_MEMIF_HDMI,
MTK_AFE_MEMIF_NUM,
MTK_AFE_IO_MOD_PCM1 = MTK_AFE_MEMIF_NUM,
MTK_AFE_IO_MOD_PCM2,
MTK_AFE_IO_PMIC,
MTK_AFE_IO_I2S,
MTK_AFE_IO_2ND_I2S,
MTK_AFE_IO_HW_GAIN1,
MTK_AFE_IO_HW_GAIN2,
MTK_AFE_IO_MRG_O,
MTK_AFE_IO_MRG_I,
MTK_AFE_IO_DAIBT,
MTK_AFE_IO_HDMI,
MT8173_AFE_MEMIF_DL1,
MT8173_AFE_MEMIF_DL2,
MT8173_AFE_MEMIF_VUL,
MT8173_AFE_MEMIF_DAI,
MT8173_AFE_MEMIF_AWB,
MT8173_AFE_MEMIF_MOD_DAI,
MT8173_AFE_MEMIF_HDMI,
MT8173_AFE_MEMIF_NUM,
MT8173_AFE_IO_MOD_PCM1 = MT8173_AFE_MEMIF_NUM,
MT8173_AFE_IO_MOD_PCM2,
MT8173_AFE_IO_PMIC,
MT8173_AFE_IO_I2S,
MT8173_AFE_IO_2ND_I2S,
MT8173_AFE_IO_HW_GAIN1,
MT8173_AFE_IO_HW_GAIN2,
MT8173_AFE_IO_MRG_O,
MT8173_AFE_IO_MRG_I,
MT8173_AFE_IO_DAIBT,
MT8173_AFE_IO_HDMI,
};
enum {
MTK_AFE_IRQ_1,
MTK_AFE_IRQ_2,
MTK_AFE_IRQ_3,
MTK_AFE_IRQ_4,
MTK_AFE_IRQ_5,
MTK_AFE_IRQ_6,
MTK_AFE_IRQ_7,
MTK_AFE_IRQ_8,
MTK_AFE_IRQ_NUM,
MT8173_AFE_IRQ_DL1,
MT8173_AFE_IRQ_DL2,
MT8173_AFE_IRQ_VUL,
MT8173_AFE_IRQ_DAI,
MT8173_AFE_IRQ_AWB,
MT8173_AFE_IRQ_MOD_DAI,
MT8173_AFE_IRQ_HDMI,
MT8173_AFE_IRQ_NUM,
};
enum {
MTK_CLK_INFRASYS_AUD,
MTK_CLK_TOP_PDN_AUD,
MTK_CLK_TOP_PDN_AUD_BUS,
MTK_CLK_I2S0_M,
MTK_CLK_I2S1_M,
MTK_CLK_I2S2_M,
MTK_CLK_I2S3_M,
MTK_CLK_I2S3_B,
MTK_CLK_BCK0,
MTK_CLK_BCK1,
MTK_CLK_NUM
};
struct mtk_afe;
struct snd_pcm_substream;
struct mtk_afe_memif_data {
int id;
const char *name;
int reg_ofs_base;
int reg_ofs_cur;
int fs_shift;
int mono_shift;
int enable_shift;
int irq_reg_cnt;
int irq_cnt_shift;
int irq_en_shift;
int irq_fs_shift;
int irq_clr_shift;
int msb_shift;
};
struct mtk_afe_memif {
unsigned int phys_buf_addr;
int buffer_size;
struct snd_pcm_substream *substream;
const struct mtk_afe_memif_data *data;
const struct mtk_afe_irq_data *irqdata;
MT8173_CLK_INFRASYS_AUD,
MT8173_CLK_TOP_PDN_AUD,
MT8173_CLK_TOP_PDN_AUD_BUS,
MT8173_CLK_I2S0_M,
MT8173_CLK_I2S1_M,
MT8173_CLK_I2S2_M,
MT8173_CLK_I2S3_M,
MT8173_CLK_I2S3_B,
MT8173_CLK_BCK0,
MT8173_CLK_BCK1,
MT8173_CLK_NUM
};
#endif
/*
* Mediatek ALSA SoC AFE platform driver
* Mediatek 8173 ALSA SoC AFE platform driver
*
* Copyright (c) 2015 MediaTek Inc.
* Author: Koro Chen <koro.chen@mediatek.com>
......@@ -24,7 +24,10 @@
#include <linux/dma-mapping.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include "mtk-afe-common.h"
#include "mt8173-afe-common.h"
#include "../common/mtk-base-afe.h"
#include "../common/mtk-afe-platform-driver.h"
#include "../common/mtk-afe-fe-dai.h"
/*****************************************************************************
* R E G I S T E R D E F I N I T I O N
......@@ -81,7 +84,6 @@
#define AFE_TDM_CON1 0x0548
#define AFE_TDM_CON2 0x054c
#define AFE_BASE_END_OFFSET 8
#define AFE_IRQ_STATUS_BITS 0xff
/* AUDIO_TOP_CON0 (0x0000) */
......@@ -135,7 +137,7 @@ enum afe_tdm_ch_start {
AFE_TDM_CH_ZERO,
};
static const unsigned int mtk_afe_backup_list[] = {
static const unsigned int mt8173_afe_backup_list[] = {
AUDIO_TOP_CON0,
AFE_CONN1,
AFE_CONN2,
......@@ -152,18 +154,11 @@ static const unsigned int mtk_afe_backup_list[] = {
AFE_DAC_CON0,
};
struct mtk_afe {
/* address for ioremap audio hardware register */
void __iomem *base_addr;
struct device *dev;
struct regmap *regmap;
struct mtk_afe_memif memif[MTK_AFE_MEMIF_NUM];
struct clk *clocks[MTK_CLK_NUM];
unsigned int backup_regs[ARRAY_SIZE(mtk_afe_backup_list)];
bool suspended;
struct mt8173_afe_private {
struct clk *clocks[MT8173_CLK_NUM];
};
static const struct snd_pcm_hardware mtk_afe_hardware = {
static const struct snd_pcm_hardware mt8173_afe_hardware = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID),
.buffer_bytes_max = 256 * 1024,
......@@ -174,59 +169,12 @@ static const struct snd_pcm_hardware mtk_afe_hardware = {
.fifo_size = 0,
};
static snd_pcm_uframes_t mtk_afe_pcm_pointer
(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
unsigned int hw_ptr;
int ret;
ret = regmap_read(afe->regmap, memif->data->reg_ofs_cur, &hw_ptr);
if (ret || hw_ptr == 0) {
dev_err(afe->dev, "%s hw_ptr err\n", __func__);
hw_ptr = memif->phys_buf_addr;
}
return bytes_to_frames(substream->runtime,
hw_ptr - memif->phys_buf_addr);
}
static const struct snd_pcm_ops mtk_afe_pcm_ops = {
.ioctl = snd_pcm_lib_ioctl,
.pointer = mtk_afe_pcm_pointer,
};
static int mtk_afe_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
size_t size;
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
size = mtk_afe_hardware.buffer_bytes_max;
return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
card->dev, size, size);
}
static void mtk_afe_pcm_free(struct snd_pcm *pcm)
{
snd_pcm_lib_preallocate_free_for_all(pcm);
}
static const struct snd_soc_platform_driver mtk_afe_pcm_platform = {
.ops = &mtk_afe_pcm_ops,
.pcm_new = mtk_afe_pcm_new,
.pcm_free = mtk_afe_pcm_free,
};
struct mtk_afe_rate {
struct mt8173_afe_rate {
unsigned int rate;
unsigned int regvalue;
};
static const struct mtk_afe_rate mtk_afe_i2s_rates[] = {
static const struct mt8173_afe_rate mt8173_afe_i2s_rates[] = {
{ .rate = 8000, .regvalue = 0 },
{ .rate = 11025, .regvalue = 1 },
{ .rate = 12000, .regvalue = 2 },
......@@ -242,21 +190,21 @@ static const struct mtk_afe_rate mtk_afe_i2s_rates[] = {
{ .rate = 192000, .regvalue = 14 },
};
static int mtk_afe_i2s_fs(unsigned int sample_rate)
static int mt8173_afe_i2s_fs(unsigned int sample_rate)
{
int i;
for (i = 0; i < ARRAY_SIZE(mtk_afe_i2s_rates); i++)
if (mtk_afe_i2s_rates[i].rate == sample_rate)
return mtk_afe_i2s_rates[i].regvalue;
for (i = 0; i < ARRAY_SIZE(mt8173_afe_i2s_rates); i++)
if (mt8173_afe_i2s_rates[i].rate == sample_rate)
return mt8173_afe_i2s_rates[i].regvalue;
return -EINVAL;
}
static int mtk_afe_set_i2s(struct mtk_afe *afe, unsigned int rate)
static int mt8173_afe_set_i2s(struct mtk_base_afe *afe, unsigned int rate)
{
unsigned int val;
int fs = mtk_afe_i2s_fs(rate);
int fs = mt8173_afe_i2s_fs(rate);
if (fs < 0)
return -EINVAL;
......@@ -281,7 +229,7 @@ static int mtk_afe_set_i2s(struct mtk_afe *afe, unsigned int rate)
return 0;
}
static void mtk_afe_set_i2s_enable(struct mtk_afe *afe, bool enable)
static void mt8173_afe_set_i2s_enable(struct mtk_base_afe *afe, bool enable)
{
unsigned int val;
......@@ -296,7 +244,7 @@ static void mtk_afe_set_i2s_enable(struct mtk_afe *afe, bool enable)
regmap_update_bits(afe->regmap, AFE_I2S_CON1, 0x1, enable);
}
static int mtk_afe_dais_enable_clks(struct mtk_afe *afe,
static int mt8173_afe_dais_enable_clks(struct mtk_base_afe *afe,
struct clk *m_ck, struct clk *b_ck)
{
int ret;
......@@ -319,7 +267,7 @@ static int mtk_afe_dais_enable_clks(struct mtk_afe *afe,
return 0;
}
static int mtk_afe_dais_set_clks(struct mtk_afe *afe,
static int mt8173_afe_dais_set_clks(struct mtk_base_afe *afe,
struct clk *m_ck, unsigned int mck_rate,
struct clk *b_ck, unsigned int bck_rate)
{
......@@ -343,7 +291,7 @@ static int mtk_afe_dais_set_clks(struct mtk_afe *afe,
return 0;
}
static void mtk_afe_dais_disable_clks(struct mtk_afe *afe,
static void mt8173_afe_dais_disable_clks(struct mtk_base_afe *afe,
struct clk *m_ck, struct clk *b_ck)
{
if (m_ck)
......@@ -352,101 +300,100 @@ static void mtk_afe_dais_disable_clks(struct mtk_afe *afe,
clk_disable_unprepare(b_ck);
}
static int mtk_afe_i2s_startup(struct snd_pcm_substream *substream,
static int mt8173_afe_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
if (dai->active)
return 0;
mtk_afe_dais_enable_clks(afe, afe->clocks[MTK_CLK_I2S1_M], NULL);
mtk_afe_dais_enable_clks(afe, afe->clocks[MTK_CLK_I2S2_M], NULL);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M, 0);
return 0;
}
static void mtk_afe_i2s_shutdown(struct snd_pcm_substream *substream,
static void mt8173_afe_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
if (dai->active)
return;
mtk_afe_set_i2s_enable(afe, false);
mt8173_afe_set_i2s_enable(afe, false);
regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M,
AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M);
mtk_afe_dais_disable_clks(afe, afe->clocks[MTK_CLK_I2S1_M], NULL);
mtk_afe_dais_disable_clks(afe, afe->clocks[MTK_CLK_I2S2_M], NULL);
}
static int mtk_afe_i2s_prepare(struct snd_pcm_substream *substream,
static int mt8173_afe_i2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime * const runtime = substream->runtime;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt8173_afe_private *afe_priv = afe->platform_priv;
int ret;
mtk_afe_dais_set_clks(afe,
afe->clocks[MTK_CLK_I2S1_M], runtime->rate * 256,
NULL, 0);
mtk_afe_dais_set_clks(afe,
afe->clocks[MTK_CLK_I2S2_M], runtime->rate * 256,
NULL, 0);
mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S1_M],
runtime->rate * 256, NULL, 0);
mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S2_M],
runtime->rate * 256, NULL, 0);
/* config I2S */
ret = mtk_afe_set_i2s(afe, substream->runtime->rate);
ret = mt8173_afe_set_i2s(afe, substream->runtime->rate);
if (ret)
return ret;
mtk_afe_set_i2s_enable(afe, true);
mt8173_afe_set_i2s_enable(afe, true);
return 0;
}
static int mtk_afe_hdmi_startup(struct snd_pcm_substream *substream,
static int mt8173_afe_hdmi_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt8173_afe_private *afe_priv = afe->platform_priv;
if (dai->active)
return 0;
mtk_afe_dais_enable_clks(afe, afe->clocks[MTK_CLK_I2S3_M],
afe->clocks[MTK_CLK_I2S3_B]);
mt8173_afe_dais_enable_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M],
afe_priv->clocks[MT8173_CLK_I2S3_B]);
return 0;
}
static void mtk_afe_hdmi_shutdown(struct snd_pcm_substream *substream,
static void mt8173_afe_hdmi_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt8173_afe_private *afe_priv = afe->platform_priv;
if (dai->active)
return;
mtk_afe_dais_disable_clks(afe, afe->clocks[MTK_CLK_I2S3_M],
afe->clocks[MTK_CLK_I2S3_B]);
mt8173_afe_dais_disable_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M],
afe_priv->clocks[MT8173_CLK_I2S3_B]);
}
static int mtk_afe_hdmi_prepare(struct snd_pcm_substream *substream,
static int mt8173_afe_hdmi_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime * const runtime = substream->runtime;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mt8173_afe_private *afe_priv = afe->platform_priv;
unsigned int val;
mtk_afe_dais_set_clks(afe,
afe->clocks[MTK_CLK_I2S3_M], runtime->rate * 128,
afe->clocks[MTK_CLK_I2S3_B],
mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M],
runtime->rate * 128,
afe_priv->clocks[MT8173_CLK_I2S3_B],
runtime->rate * runtime->channels * 32);
val = AFE_TDM_CON1_BCK_INV |
......@@ -498,11 +445,11 @@ static int mtk_afe_hdmi_prepare(struct snd_pcm_substream *substream,
return 0;
}
static int mtk_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd,
static int mt8173_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
dev_info(afe->dev, "%s cmd=%d %s\n", __func__, cmd, dai->name);
......@@ -514,10 +461,14 @@ static int mtk_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd,
/* set connections: O30~O37: L/R/LS/RS/C/LFE/CH7/CH8 */
regmap_write(afe->regmap, AFE_HDMI_CONN0,
AFE_HDMI_CONN0_O30_I30 | AFE_HDMI_CONN0_O31_I31 |
AFE_HDMI_CONN0_O32_I34 | AFE_HDMI_CONN0_O33_I35 |
AFE_HDMI_CONN0_O34_I32 | AFE_HDMI_CONN0_O35_I33 |
AFE_HDMI_CONN0_O36_I36 | AFE_HDMI_CONN0_O37_I37);
AFE_HDMI_CONN0_O30_I30 |
AFE_HDMI_CONN0_O31_I31 |
AFE_HDMI_CONN0_O32_I34 |
AFE_HDMI_CONN0_O33_I35 |
AFE_HDMI_CONN0_O34_I32 |
AFE_HDMI_CONN0_O35_I33 |
AFE_HDMI_CONN0_O36_I36 |
AFE_HDMI_CONN0_O37_I37);
/* enable Out control */
regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0, 0x1, 0x1);
......@@ -537,284 +488,65 @@ static int mtk_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd,
regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF,
AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF);
return 0;
default:
return -EINVAL;
}
}
static int mtk_afe_dais_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
static int mt8173_memif_fs(struct snd_pcm_substream *substream,
unsigned int rate)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct snd_pcm_runtime *runtime = substream->runtime;
struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
int ret;
memif->substream = substream;
snd_soc_set_runtime_hwparams(substream, &mtk_afe_hardware);
/*
* Capture cannot use ping-pong buffer since hw_ptr at IRQ may be
* smaller than period_size due to AFE's internal buffer.
* This easily leads to overrun when avail_min is period_size.
* One more period can hold the possible unread buffer.
*/
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
ret = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIODS,
3,
mtk_afe_hardware.periods_max);
if (ret < 0) {
dev_err(afe->dev, "hw_constraint_minmax failed\n");
return ret;
}
}
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n");
return ret;
}
static void mtk_afe_dais_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
memif->substream = NULL;
}
static int mtk_afe_dais_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
int msb_at_bit33 = 0;
int ret;
dev_dbg(afe->dev,
"%s period = %u, rate= %u, channels=%u\n",
__func__, params_period_size(params), params_rate(params),
params_channels(params));
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (ret < 0)
return ret;
struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
int fs;
msb_at_bit33 = upper_32_bits(substream->runtime->dma_addr) ? 1 : 0;
memif->phys_buf_addr = lower_32_bits(substream->runtime->dma_addr);
memif->buffer_size = substream->runtime->dma_bytes;
/* start */
regmap_write(afe->regmap,
memif->data->reg_ofs_base, memif->phys_buf_addr);
/* end */
regmap_write(afe->regmap,
memif->data->reg_ofs_base + AFE_BASE_END_OFFSET,
memif->phys_buf_addr + memif->buffer_size - 1);
/* set MSB to 33-bit */
regmap_update_bits(afe->regmap, AFE_MEMIF_MSB,
1 << memif->data->msb_shift,
msb_at_bit33 << memif->data->msb_shift);
/* set channel */
if (memif->data->mono_shift >= 0) {
unsigned int mono = (params_channels(params) == 1) ? 1 : 0;
regmap_update_bits(afe->regmap, AFE_DAC_CON1,
1 << memif->data->mono_shift,
mono << memif->data->mono_shift);
}
/* set rate */
if (memif->data->fs_shift < 0)
return 0;
if (memif->data->id == MTK_AFE_MEMIF_DAI ||
memif->data->id == MTK_AFE_MEMIF_MOD_DAI) {
unsigned int val;
switch (params_rate(params)) {
if (memif->data->id == MT8173_AFE_MEMIF_DAI ||
memif->data->id == MT8173_AFE_MEMIF_MOD_DAI) {
switch (rate) {
case 8000:
val = 0;
fs = 0;
break;
case 16000:
val = 1;
fs = 1;
break;
case 32000:
val = 2;
fs = 2;
break;
default:
return -EINVAL;
}
if (memif->data->id == MTK_AFE_MEMIF_DAI)
regmap_update_bits(afe->regmap, AFE_DAC_CON0,
0x3 << memif->data->fs_shift,
val << memif->data->fs_shift);
else
regmap_update_bits(afe->regmap, AFE_DAC_CON1,
0x3 << memif->data->fs_shift,
val << memif->data->fs_shift);
} else {
int fs = mtk_afe_i2s_fs(params_rate(params));
if (fs < 0)
return -EINVAL;
regmap_update_bits(afe->regmap, AFE_DAC_CON1,
0xf << memif->data->fs_shift,
fs << memif->data->fs_shift);
fs = mt8173_afe_i2s_fs(rate);
}
return 0;
return fs;
}
static int mtk_afe_dais_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
static int mt8173_irq_fs(struct snd_pcm_substream *substream, unsigned int rate)
{
return snd_pcm_lib_free_pages(substream);
return mt8173_afe_i2s_fs(rate);
}
static int mtk_afe_dais_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime * const runtime = substream->runtime;
struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform);
struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];
unsigned int counter = runtime->period_size;
dev_info(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
if (memif->data->enable_shift >= 0)
regmap_update_bits(afe->regmap, AFE_DAC_CON0,
1 << memif->data->enable_shift,
1 << memif->data->enable_shift);
/* set irq counter */
regmap_update_bits(afe->regmap,
memif->data->irq_reg_cnt,
0x3ffff << memif->data->irq_cnt_shift,
counter << memif->data->irq_cnt_shift);
/* set irq fs */
if (memif->data->irq_fs_shift >= 0) {
int fs = mtk_afe_i2s_fs(runtime->rate);
if (fs < 0)
return -EINVAL;
regmap_update_bits(afe->regmap,
AFE_IRQ_MCU_CON,
0xf << memif->data->irq_fs_shift,
fs << memif->data->irq_fs_shift);
}
/* enable interrupt */
regmap_update_bits(afe->regmap, AFE_IRQ_MCU_CON,
1 << memif->data->irq_en_shift,
1 << memif->data->irq_en_shift);
return 0;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
if (memif->data->enable_shift >= 0)
regmap_update_bits(afe->regmap, AFE_DAC_CON0,
1 << memif->data->enable_shift, 0);
/* disable interrupt */
regmap_update_bits(afe->regmap, AFE_IRQ_MCU_CON,
1 << memif->data->irq_en_shift,
0 << memif->data->irq_en_shift);
/* and clear pending IRQ */
regmap_write(afe->regmap, AFE_IRQ_CLR,
1 << memif->data->irq_clr_shift);
return 0;
default:
return -EINVAL;
}
}
/* FE DAIs */
static const struct snd_soc_dai_ops mtk_afe_dai_ops = {
.startup = mtk_afe_dais_startup,
.shutdown = mtk_afe_dais_shutdown,
.hw_params = mtk_afe_dais_hw_params,
.hw_free = mtk_afe_dais_hw_free,
.trigger = mtk_afe_dais_trigger,
};
/* BE DAIs */
static const struct snd_soc_dai_ops mtk_afe_i2s_ops = {
.startup = mtk_afe_i2s_startup,
.shutdown = mtk_afe_i2s_shutdown,
.prepare = mtk_afe_i2s_prepare,
static const struct snd_soc_dai_ops mt8173_afe_i2s_ops = {
.startup = mt8173_afe_i2s_startup,
.shutdown = mt8173_afe_i2s_shutdown,
.prepare = mt8173_afe_i2s_prepare,
};
static const struct snd_soc_dai_ops mtk_afe_hdmi_ops = {
.startup = mtk_afe_hdmi_startup,
.shutdown = mtk_afe_hdmi_shutdown,
.prepare = mtk_afe_hdmi_prepare,
.trigger = mtk_afe_hdmi_trigger,
static const struct snd_soc_dai_ops mt8173_afe_hdmi_ops = {
.startup = mt8173_afe_hdmi_startup,
.shutdown = mt8173_afe_hdmi_shutdown,
.prepare = mt8173_afe_hdmi_prepare,
.trigger = mt8173_afe_hdmi_trigger,
};
static int mtk_afe_runtime_suspend(struct device *dev);
static int mtk_afe_runtime_resume(struct device *dev);
static int mtk_afe_dai_suspend(struct snd_soc_dai *dai)
{
struct mtk_afe *afe = snd_soc_dai_get_drvdata(dai);
int i;
dev_dbg(afe->dev, "%s\n", __func__);
if (pm_runtime_status_suspended(afe->dev) || afe->suspended)
return 0;
for (i = 0; i < ARRAY_SIZE(mtk_afe_backup_list); i++)
regmap_read(afe->regmap, mtk_afe_backup_list[i],
&afe->backup_regs[i]);
afe->suspended = true;
mtk_afe_runtime_suspend(afe->dev);
return 0;
}
static int mtk_afe_dai_resume(struct snd_soc_dai *dai)
{
struct mtk_afe *afe = snd_soc_dai_get_drvdata(dai);
int i = 0;
dev_dbg(afe->dev, "%s\n", __func__);
if (pm_runtime_status_suspended(afe->dev) || !afe->suspended)
return 0;
mtk_afe_runtime_resume(afe->dev);
for (i = 0; i < ARRAY_SIZE(mtk_afe_backup_list); i++)
regmap_write(afe->regmap, mtk_afe_backup_list[i],
afe->backup_regs[i]);
afe->suspended = false;
return 0;
}
static struct snd_soc_dai_driver mtk_afe_pcm_dais[] = {
static struct snd_soc_dai_driver mt8173_afe_pcm_dais[] = {
/* FE DAIs: memory intefaces to CPU */
{
.name = "DL1", /* downlink 1 */
.id = MTK_AFE_MEMIF_DL1,
.id = MT8173_AFE_MEMIF_DL1,
.suspend = mtk_afe_dai_suspend,
.resume = mtk_afe_dai_resume,
.playback = {
......@@ -824,10 +556,10 @@ static struct snd_soc_dai_driver mtk_afe_pcm_dais[] = {
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &mtk_afe_dai_ops,
.ops = &mtk_afe_fe_ops,
}, {
.name = "VUL", /* voice uplink */
.id = MTK_AFE_MEMIF_VUL,
.id = MT8173_AFE_MEMIF_VUL,
.suspend = mtk_afe_dai_suspend,
.resume = mtk_afe_dai_resume,
.capture = {
......@@ -837,11 +569,11 @@ static struct snd_soc_dai_driver mtk_afe_pcm_dais[] = {
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &mtk_afe_dai_ops,
.ops = &mtk_afe_fe_ops,
}, {
/* BE DAIs */
.name = "I2S",
.id = MTK_AFE_IO_I2S,
.id = MT8173_AFE_IO_I2S,
.playback = {
.stream_name = "I2S Playback",
.channels_min = 1,
......@@ -856,16 +588,16 @@ static struct snd_soc_dai_driver mtk_afe_pcm_dais[] = {
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &mtk_afe_i2s_ops,
.ops = &mt8173_afe_i2s_ops,
.symmetric_rates = 1,
},
};
static struct snd_soc_dai_driver mtk_afe_hdmi_dais[] = {
static struct snd_soc_dai_driver mt8173_afe_hdmi_dais[] = {
/* FE DAIs */
{
.name = "HDMI",
.id = MTK_AFE_MEMIF_HDMI,
.id = MT8173_AFE_MEMIF_HDMI,
.suspend = mtk_afe_dai_suspend,
.resume = mtk_afe_dai_resume,
.playback = {
......@@ -878,11 +610,11 @@ static struct snd_soc_dai_driver mtk_afe_hdmi_dais[] = {
SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &mtk_afe_dai_ops,
.ops = &mtk_afe_fe_ops,
}, {
/* BE DAIs */
.name = "HDMIO",
.id = MTK_AFE_IO_HDMI,
.id = MT8173_AFE_IO_HDMI,
.playback = {
.stream_name = "HDMIO Playback",
.channels_min = 2,
......@@ -893,29 +625,29 @@ static struct snd_soc_dai_driver mtk_afe_hdmi_dais[] = {
SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &mtk_afe_hdmi_ops,
.ops = &mt8173_afe_hdmi_ops,
},
};
static const struct snd_kcontrol_new mtk_afe_o03_mix[] = {
static const struct snd_kcontrol_new mt8173_afe_o03_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I05 Switch", AFE_CONN1, 21, 1, 0),
};
static const struct snd_kcontrol_new mtk_afe_o04_mix[] = {
static const struct snd_kcontrol_new mt8173_afe_o04_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I06 Switch", AFE_CONN2, 6, 1, 0),
};
static const struct snd_kcontrol_new mtk_afe_o09_mix[] = {
static const struct snd_kcontrol_new mt8173_afe_o09_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I03 Switch", AFE_CONN3, 0, 1, 0),
SOC_DAPM_SINGLE_AUTODISABLE("I17 Switch", AFE_CONN7, 30, 1, 0),
};
static const struct snd_kcontrol_new mtk_afe_o10_mix[] = {
static const struct snd_kcontrol_new mt8173_afe_o10_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I04 Switch", AFE_CONN3, 3, 1, 0),
SOC_DAPM_SINGLE_AUTODISABLE("I18 Switch", AFE_CONN8, 0, 1, 0),
};
static const struct snd_soc_dapm_widget mtk_afe_pcm_widgets[] = {
static const struct snd_soc_dapm_widget mt8173_afe_pcm_widgets[] = {
/* inter-connections */
SND_SOC_DAPM_MIXER("I03", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I04", SND_SOC_NOPM, 0, 0, NULL, 0),
......@@ -925,16 +657,16 @@ static const struct snd_soc_dapm_widget mtk_afe_pcm_widgets[] = {
SND_SOC_DAPM_MIXER("I18", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("O03", SND_SOC_NOPM, 0, 0,
mtk_afe_o03_mix, ARRAY_SIZE(mtk_afe_o03_mix)),
mt8173_afe_o03_mix, ARRAY_SIZE(mt8173_afe_o03_mix)),
SND_SOC_DAPM_MIXER("O04", SND_SOC_NOPM, 0, 0,
mtk_afe_o04_mix, ARRAY_SIZE(mtk_afe_o04_mix)),
mt8173_afe_o04_mix, ARRAY_SIZE(mt8173_afe_o04_mix)),
SND_SOC_DAPM_MIXER("O09", SND_SOC_NOPM, 0, 0,
mtk_afe_o09_mix, ARRAY_SIZE(mtk_afe_o09_mix)),
mt8173_afe_o09_mix, ARRAY_SIZE(mt8173_afe_o09_mix)),
SND_SOC_DAPM_MIXER("O10", SND_SOC_NOPM, 0, 0,
mtk_afe_o10_mix, ARRAY_SIZE(mtk_afe_o10_mix)),
mt8173_afe_o10_mix, ARRAY_SIZE(mt8173_afe_o10_mix)),
};
static const struct snd_soc_dapm_route mtk_afe_pcm_routes[] = {
static const struct snd_soc_dapm_route mt8173_afe_pcm_routes[] = {
{"I05", NULL, "DL1"},
{"I06", NULL, "DL1"},
{"I2S Playback", NULL, "O03"},
......@@ -953,140 +685,257 @@ static const struct snd_soc_dapm_route mtk_afe_pcm_routes[] = {
{ "O10", "I04 Switch", "I04" },
};
static const struct snd_soc_dapm_route mtk_afe_hdmi_routes[] = {
static const struct snd_soc_dapm_route mt8173_afe_hdmi_routes[] = {
{"HDMIO Playback", NULL, "HDMI"},
};
static const struct snd_soc_component_driver mtk_afe_pcm_dai_component = {
.name = "mtk-afe-pcm-dai",
.dapm_widgets = mtk_afe_pcm_widgets,
.num_dapm_widgets = ARRAY_SIZE(mtk_afe_pcm_widgets),
.dapm_routes = mtk_afe_pcm_routes,
.num_dapm_routes = ARRAY_SIZE(mtk_afe_pcm_routes),
static const struct snd_soc_component_driver mt8173_afe_pcm_dai_component = {
.name = "mt8173-afe-pcm-dai",
.dapm_widgets = mt8173_afe_pcm_widgets,
.num_dapm_widgets = ARRAY_SIZE(mt8173_afe_pcm_widgets),
.dapm_routes = mt8173_afe_pcm_routes,
.num_dapm_routes = ARRAY_SIZE(mt8173_afe_pcm_routes),
};
static const struct snd_soc_component_driver mtk_afe_hdmi_dai_component = {
.name = "mtk-afe-hdmi-dai",
.dapm_routes = mtk_afe_hdmi_routes,
.num_dapm_routes = ARRAY_SIZE(mtk_afe_hdmi_routes),
static const struct snd_soc_component_driver mt8173_afe_hdmi_dai_component = {
.name = "mt8173-afe-hdmi-dai",
.dapm_routes = mt8173_afe_hdmi_routes,
.num_dapm_routes = ARRAY_SIZE(mt8173_afe_hdmi_routes),
};
static const char *aud_clks[MTK_CLK_NUM] = {
[MTK_CLK_INFRASYS_AUD] = "infra_sys_audio_clk",
[MTK_CLK_TOP_PDN_AUD] = "top_pdn_audio",
[MTK_CLK_TOP_PDN_AUD_BUS] = "top_pdn_aud_intbus",
[MTK_CLK_I2S0_M] = "i2s0_m",
[MTK_CLK_I2S1_M] = "i2s1_m",
[MTK_CLK_I2S2_M] = "i2s2_m",
[MTK_CLK_I2S3_M] = "i2s3_m",
[MTK_CLK_I2S3_B] = "i2s3_b",
[MTK_CLK_BCK0] = "bck0",
[MTK_CLK_BCK1] = "bck1",
static const char *aud_clks[MT8173_CLK_NUM] = {
[MT8173_CLK_INFRASYS_AUD] = "infra_sys_audio_clk",
[MT8173_CLK_TOP_PDN_AUD] = "top_pdn_audio",
[MT8173_CLK_TOP_PDN_AUD_BUS] = "top_pdn_aud_intbus",
[MT8173_CLK_I2S0_M] = "i2s0_m",
[MT8173_CLK_I2S1_M] = "i2s1_m",
[MT8173_CLK_I2S2_M] = "i2s2_m",
[MT8173_CLK_I2S3_M] = "i2s3_m",
[MT8173_CLK_I2S3_B] = "i2s3_b",
[MT8173_CLK_BCK0] = "bck0",
[MT8173_CLK_BCK1] = "bck1",
};
static const struct mtk_afe_memif_data memif_data[MTK_AFE_MEMIF_NUM] = {
static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = {
{
.name = "DL1",
.id = MTK_AFE_MEMIF_DL1,
.id = MT8173_AFE_MEMIF_DL1,
.reg_ofs_base = AFE_DL1_BASE,
.reg_ofs_cur = AFE_DL1_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 0,
.fs_maskbit = 0xf,
.mono_reg = AFE_DAC_CON1,
.mono_shift = 21,
.hd_reg = -1,
.hd_shift = -1,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 1,
.irq_reg_cnt = AFE_IRQ_CNT1,
.irq_cnt_shift = 0,
.irq_en_shift = 0,
.irq_fs_shift = 4,
.irq_clr_shift = 0,
.msb_reg = AFE_MEMIF_MSB,
.msb_shift = 0,
.agent_disable_reg = -1,
.agent_disable_shift = -1,
}, {
.name = "DL2",
.id = MTK_AFE_MEMIF_DL2,
.id = MT8173_AFE_MEMIF_DL2,
.reg_ofs_base = AFE_DL2_BASE,
.reg_ofs_cur = AFE_DL2_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 4,
.fs_maskbit = 0xf,
.mono_reg = AFE_DAC_CON1,
.mono_shift = 22,
.hd_reg = -1,
.hd_shift = -1,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 2,
.irq_reg_cnt = AFE_IRQ_CNT1,
.irq_cnt_shift = 20,
.irq_en_shift = 2,
.irq_fs_shift = 16,
.irq_clr_shift = 2,
.msb_reg = AFE_MEMIF_MSB,
.msb_shift = 1,
.agent_disable_reg = -1,
.agent_disable_shift = -1,
}, {
.name = "VUL",
.id = MTK_AFE_MEMIF_VUL,
.id = MT8173_AFE_MEMIF_VUL,
.reg_ofs_base = AFE_VUL_BASE,
.reg_ofs_cur = AFE_VUL_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 16,
.fs_maskbit = 0xf,
.mono_reg = AFE_DAC_CON1,
.mono_shift = 27,
.hd_reg = -1,
.hd_shift = -1,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 3,
.irq_reg_cnt = AFE_IRQ_CNT2,
.irq_cnt_shift = 0,
.irq_en_shift = 1,
.irq_fs_shift = 8,
.irq_clr_shift = 1,
.msb_reg = AFE_MEMIF_MSB,
.msb_shift = 6,
.agent_disable_reg = -1,
.agent_disable_shift = -1,
}, {
.name = "DAI",
.id = MTK_AFE_MEMIF_DAI,
.id = MT8173_AFE_MEMIF_DAI,
.reg_ofs_base = AFE_DAI_BASE,
.reg_ofs_cur = AFE_DAI_CUR,
.fs_reg = AFE_DAC_CON0,
.fs_shift = 24,
.fs_maskbit = 0x3,
.mono_reg = -1,
.mono_shift = -1,
.hd_reg = -1,
.hd_shift = -1,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 4,
.irq_reg_cnt = AFE_IRQ_CNT2,
.irq_cnt_shift = 20,
.irq_en_shift = 3,
.irq_fs_shift = 20,
.irq_clr_shift = 3,
.msb_reg = AFE_MEMIF_MSB,
.msb_shift = 5,
.agent_disable_reg = -1,
.agent_disable_shift = -1,
}, {
.name = "AWB",
.id = MTK_AFE_MEMIF_AWB,
.id = MT8173_AFE_MEMIF_AWB,
.reg_ofs_base = AFE_AWB_BASE,
.reg_ofs_cur = AFE_AWB_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 12,
.fs_maskbit = 0xf,
.mono_reg = AFE_DAC_CON1,
.mono_shift = 24,
.hd_reg = -1,
.hd_shift = -1,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 6,
.irq_reg_cnt = AFE_IRQ_CNT7,
.irq_cnt_shift = 0,
.irq_en_shift = 14,
.irq_fs_shift = 24,
.irq_clr_shift = 6,
.msb_reg = AFE_MEMIF_MSB,
.msb_shift = 3,
.agent_disable_reg = -1,
.agent_disable_shift = -1,
}, {
.name = "MOD_DAI",
.id = MTK_AFE_MEMIF_MOD_DAI,
.id = MT8173_AFE_MEMIF_MOD_DAI,
.reg_ofs_base = AFE_MOD_PCM_BASE,
.reg_ofs_cur = AFE_MOD_PCM_CUR,
.fs_reg = AFE_DAC_CON1,
.fs_shift = 30,
.fs_maskbit = 0x3,
.mono_reg = AFE_DAC_CON1,
.mono_shift = 30,
.hd_reg = -1,
.hd_shift = -1,
.enable_reg = AFE_DAC_CON0,
.enable_shift = 7,
.irq_reg_cnt = AFE_IRQ_CNT2,
.irq_cnt_shift = 20,
.irq_en_shift = 3,
.irq_fs_shift = 20,
.irq_clr_shift = 3,
.msb_reg = AFE_MEMIF_MSB,
.msb_shift = 4,
.agent_disable_reg = -1,
.agent_disable_shift = -1,
}, {
.name = "HDMI",
.id = MTK_AFE_MEMIF_HDMI,
.id = MT8173_AFE_MEMIF_HDMI,
.reg_ofs_base = AFE_HDMI_OUT_BASE,
.reg_ofs_cur = AFE_HDMI_OUT_CUR,
.fs_reg = -1,
.fs_shift = -1,
.fs_maskbit = -1,
.mono_reg = -1,
.mono_shift = -1,
.hd_reg = -1,
.hd_shift = -1,
.enable_reg = -1,
.enable_shift = -1,
.irq_reg_cnt = AFE_IRQ_CNT5,
.msb_reg = AFE_MEMIF_MSB,
.msb_shift = 8,
.agent_disable_reg = -1,
.agent_disable_shift = -1,
},
};
static const struct mtk_base_irq_data irq_data[MT8173_AFE_IRQ_NUM] = {
{
.id = MT8173_AFE_IRQ_DL1,
.irq_cnt_reg = AFE_IRQ_CNT1,
.irq_cnt_shift = 0,
.irq_cnt_maskbit = 0x3ffff,
.irq_en_reg = AFE_IRQ_MCU_CON,
.irq_en_shift = 0,
.irq_fs_reg = AFE_IRQ_MCU_CON,
.irq_fs_shift = 4,
.irq_fs_maskbit = 0xf,
.irq_clr_reg = AFE_IRQ_CLR,
.irq_clr_shift = 0,
}, {
.id = MT8173_AFE_IRQ_DL2,
.irq_cnt_reg = AFE_IRQ_CNT1,
.irq_cnt_shift = 20,
.irq_cnt_maskbit = 0x3ffff,
.irq_en_reg = AFE_IRQ_MCU_CON,
.irq_en_shift = 2,
.irq_fs_reg = AFE_IRQ_MCU_CON,
.irq_fs_shift = 16,
.irq_fs_maskbit = 0xf,
.irq_clr_reg = AFE_IRQ_CLR,
.irq_clr_shift = 2,
}, {
.id = MT8173_AFE_IRQ_VUL,
.irq_cnt_reg = AFE_IRQ_CNT2,
.irq_cnt_shift = 0,
.irq_cnt_maskbit = 0x3ffff,
.irq_en_reg = AFE_IRQ_MCU_CON,
.irq_en_shift = 1,
.irq_fs_reg = AFE_IRQ_MCU_CON,
.irq_fs_shift = 8,
.irq_fs_maskbit = 0xf,
.irq_clr_reg = AFE_IRQ_CLR,
.irq_clr_shift = 1,
}, {
.id = MT8173_AFE_IRQ_DAI,
.irq_cnt_reg = AFE_IRQ_CNT2,
.irq_cnt_shift = 20,
.irq_cnt_maskbit = 0x3ffff,
.irq_en_reg = AFE_IRQ_MCU_CON,
.irq_en_shift = 3,
.irq_fs_reg = AFE_IRQ_MCU_CON,
.irq_fs_shift = 20,
.irq_fs_maskbit = 0xf,
.irq_clr_reg = AFE_IRQ_CLR,
.irq_clr_shift = 3,
}, {
.id = MT8173_AFE_IRQ_AWB,
.irq_cnt_reg = AFE_IRQ_CNT7,
.irq_cnt_shift = 0,
.irq_cnt_maskbit = 0x3ffff,
.irq_en_reg = AFE_IRQ_MCU_CON,
.irq_en_shift = 14,
.irq_fs_reg = AFE_IRQ_MCU_CON,
.irq_fs_shift = 24,
.irq_fs_maskbit = 0xf,
.irq_clr_reg = AFE_IRQ_CLR,
.irq_clr_shift = 6,
}, {
.id = MT8173_AFE_IRQ_DAI,
.irq_cnt_reg = AFE_IRQ_CNT2,
.irq_cnt_shift = 20,
.irq_cnt_maskbit = 0x3ffff,
.irq_en_reg = AFE_IRQ_MCU_CON,
.irq_en_shift = 3,
.irq_fs_reg = AFE_IRQ_MCU_CON,
.irq_fs_shift = 20,
.irq_fs_maskbit = 0xf,
.irq_clr_reg = AFE_IRQ_CLR,
.irq_clr_shift = 3,
}, {
.id = MT8173_AFE_IRQ_HDMI,
.irq_cnt_reg = AFE_IRQ_CNT5,
.irq_cnt_shift = 0,
.irq_cnt_maskbit = 0x3ffff,
.irq_en_reg = AFE_IRQ_MCU_CON,
.irq_en_shift = 12,
.irq_fs_reg = -1,
.irq_fs_shift = -1,
.irq_fs_maskbit = -1,
.irq_clr_reg = AFE_IRQ_CLR,
.irq_clr_shift = 4,
.msb_shift = 8,
},
};
static const struct regmap_config mtk_afe_regmap_config = {
static const struct regmap_config mt8173_afe_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
......@@ -1094,9 +943,9 @@ static const struct regmap_config mtk_afe_regmap_config = {
.cache_type = REGCACHE_NONE,
};
static irqreturn_t mtk_afe_irq_handler(int irq, void *dev_id)
static irqreturn_t mt8173_afe_irq_handler(int irq, void *dev_id)
{
struct mtk_afe *afe = dev_id;
struct mtk_base_afe *afe = dev_id;
unsigned int reg_value;
int i, ret;
......@@ -1107,10 +956,16 @@ static irqreturn_t mtk_afe_irq_handler(int irq, void *dev_id)
goto err_irq;
}
for (i = 0; i < MTK_AFE_MEMIF_NUM; i++) {
struct mtk_afe_memif *memif = &afe->memif[i];
for (i = 0; i < MT8173_AFE_MEMIF_NUM; i++) {
struct mtk_base_afe_memif *memif = &afe->memif[i];
struct mtk_base_afe_irq *irq;
if (memif->irq_usage < 0)
continue;
irq = &afe->irqs[memif->irq_usage];
if (!(reg_value & (1 << memif->data->irq_clr_shift)))
if (!(reg_value & (1 << irq->irq_data->irq_clr_shift)))
continue;
snd_pcm_period_elapsed(memif->substream);
......@@ -1118,14 +973,16 @@ static irqreturn_t mtk_afe_irq_handler(int irq, void *dev_id)
err_irq:
/* clear irq */
regmap_write(afe->regmap, AFE_IRQ_CLR, reg_value & AFE_IRQ_STATUS_BITS);
regmap_write(afe->regmap, AFE_IRQ_CLR,
reg_value & AFE_IRQ_STATUS_BITS);
return IRQ_HANDLED;
}
static int mtk_afe_runtime_suspend(struct device *dev)
static int mt8173_afe_runtime_suspend(struct device *dev)
{
struct mtk_afe *afe = dev_get_drvdata(dev);
struct mtk_base_afe *afe = dev_get_drvdata(dev);
struct mt8173_afe_private *afe_priv = afe->platform_priv;
/* disable AFE */
regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0);
......@@ -1134,38 +991,47 @@ static int mtk_afe_runtime_suspend(struct device *dev)
regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
AUD_TCON0_PDN_AFE, AUD_TCON0_PDN_AFE);
clk_disable_unprepare(afe->clocks[MTK_CLK_BCK0]);
clk_disable_unprepare(afe->clocks[MTK_CLK_BCK1]);
clk_disable_unprepare(afe->clocks[MTK_CLK_TOP_PDN_AUD]);
clk_disable_unprepare(afe->clocks[MTK_CLK_TOP_PDN_AUD_BUS]);
clk_disable_unprepare(afe->clocks[MTK_CLK_INFRASYS_AUD]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK1]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]);
return 0;
}
static int mtk_afe_runtime_resume(struct device *dev)
static int mt8173_afe_runtime_resume(struct device *dev)
{
struct mtk_afe *afe = dev_get_drvdata(dev);
struct mtk_base_afe *afe = dev_get_drvdata(dev);
struct mt8173_afe_private *afe_priv = afe->platform_priv;
int ret;
ret = clk_prepare_enable(afe->clocks[MTK_CLK_INFRASYS_AUD]);
ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]);
if (ret)
return ret;
ret = clk_prepare_enable(afe->clocks[MTK_CLK_TOP_PDN_AUD_BUS]);
ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]);
if (ret)
goto err_infra;
ret = clk_prepare_enable(afe->clocks[MTK_CLK_TOP_PDN_AUD]);
ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]);
if (ret)
goto err_top_aud_bus;
ret = clk_prepare_enable(afe->clocks[MTK_CLK_BCK0]);
ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_BCK0]);
if (ret)
goto err_top_aud;
ret = clk_prepare_enable(afe->clocks[MTK_CLK_BCK1]);
ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_BCK1]);
if (ret)
goto err_bck0;
ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S1_M]);
if (ret)
goto err_i2s1_m;
ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S2_M]);
if (ret)
goto err_i2s2_m;
/* enable AFE clk */
regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_AFE, 0);
......@@ -1181,39 +1047,45 @@ static int mtk_afe_runtime_resume(struct device *dev)
regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1);
return 0;
err_i2s1_m:
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]);
err_i2s2_m:
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]);
err_bck0:
clk_disable_unprepare(afe->clocks[MTK_CLK_BCK0]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]);
err_top_aud:
clk_disable_unprepare(afe->clocks[MTK_CLK_TOP_PDN_AUD]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]);
err_top_aud_bus:
clk_disable_unprepare(afe->clocks[MTK_CLK_TOP_PDN_AUD_BUS]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]);
err_infra:
clk_disable_unprepare(afe->clocks[MTK_CLK_INFRASYS_AUD]);
clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]);
return ret;
}
static int mtk_afe_init_audio_clk(struct mtk_afe *afe)
static int mt8173_afe_init_audio_clk(struct mtk_base_afe *afe)
{
size_t i;
struct mt8173_afe_private *afe_priv = afe->platform_priv;
for (i = 0; i < ARRAY_SIZE(aud_clks); i++) {
afe->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]);
if (IS_ERR(afe->clocks[i])) {
afe_priv->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]);
if (IS_ERR(afe_priv->clocks[i])) {
dev_err(afe->dev, "%s devm_clk_get %s fail\n",
__func__, aud_clks[i]);
return PTR_ERR(afe->clocks[i]);
return PTR_ERR(afe_priv->clocks[i]);
}
}
clk_set_rate(afe->clocks[MTK_CLK_BCK0], 22579200); /* 22M */
clk_set_rate(afe->clocks[MTK_CLK_BCK1], 24576000); /* 24M */
clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK0], 22579200); /* 22M */
clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK1], 24576000); /* 24M */
return 0;
}
static int mtk_afe_pcm_dev_probe(struct platform_device *pdev)
static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev)
{
int ret, i;
unsigned int irq_id;
struct mtk_afe *afe;
struct mtk_base_afe *afe;
struct mt8173_afe_private *afe_priv;
struct resource *res;
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(33));
......@@ -1224,6 +1096,12 @@ static int mtk_afe_pcm_dev_probe(struct platform_device *pdev)
if (!afe)
return -ENOMEM;
afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv),
GFP_KERNEL);
afe_priv = afe->platform_priv;
if (!afe_priv)
return -ENOMEM;
afe->dev = &pdev->dev;
irq_id = platform_get_irq(pdev, 0);
......@@ -1231,7 +1109,7 @@ static int mtk_afe_pcm_dev_probe(struct platform_device *pdev)
dev_err(afe->dev, "np %s no irq\n", afe->dev->of_node->name);
return -ENXIO;
}
ret = devm_request_irq(afe->dev, irq_id, mtk_afe_irq_handler,
ret = devm_request_irq(afe->dev, irq_id, mt8173_afe_irq_handler,
0, "Afe_ISR_Handle", (void *)afe);
if (ret) {
dev_err(afe->dev, "could not request_irq\n");
......@@ -1244,48 +1122,75 @@ static int mtk_afe_pcm_dev_probe(struct platform_device *pdev)
return PTR_ERR(afe->base_addr);
afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr,
&mtk_afe_regmap_config);
&mt8173_afe_regmap_config);
if (IS_ERR(afe->regmap))
return PTR_ERR(afe->regmap);
/* initial audio related clock */
ret = mtk_afe_init_audio_clk(afe);
ret = mt8173_afe_init_audio_clk(afe);
if (ret) {
dev_err(afe->dev, "mtk_afe_init_audio_clk fail\n");
dev_err(afe->dev, "mt8173_afe_init_audio_clk fail\n");
return ret;
}
for (i = 0; i < MTK_AFE_MEMIF_NUM; i++)
/* memif % irq initialize*/
afe->memif_size = MT8173_AFE_MEMIF_NUM;
afe->memif = devm_kcalloc(afe->dev, afe->memif_size,
sizeof(*afe->memif), GFP_KERNEL);
if (!afe->memif)
return -ENOMEM;
afe->irqs_size = MT8173_AFE_IRQ_NUM;
afe->irqs = devm_kcalloc(afe->dev, afe->irqs_size,
sizeof(*afe->irqs), GFP_KERNEL);
if (!afe->irqs)
return -ENOMEM;
for (i = 0; i < afe->irqs_size; i++) {
afe->memif[i].data = &memif_data[i];
afe->irqs[i].irq_data = &irq_data[i];
afe->irqs[i].irq_occupyed = true;
afe->memif[i].irq_usage = i;
afe->memif[i].const_irq = 1;
}
afe->mtk_afe_hardware = &mt8173_afe_hardware;
afe->memif_fs = mt8173_memif_fs;
afe->irq_fs = mt8173_irq_fs;
platform_set_drvdata(pdev, afe);
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev)) {
ret = mtk_afe_runtime_resume(&pdev->dev);
ret = mt8173_afe_runtime_resume(&pdev->dev);
if (ret)
goto err_pm_disable;
}
afe->reg_back_up_list = mt8173_afe_backup_list;
afe->reg_back_up_list_num = ARRAY_SIZE(mt8173_afe_backup_list);
afe->runtime_resume = mt8173_afe_runtime_resume;
afe->runtime_suspend = mt8173_afe_runtime_suspend;
ret = snd_soc_register_platform(&pdev->dev, &mtk_afe_pcm_platform);
if (ret)
goto err_pm_disable;
ret = snd_soc_register_component(&pdev->dev,
&mtk_afe_pcm_dai_component,
mtk_afe_pcm_dais,
ARRAY_SIZE(mtk_afe_pcm_dais));
&mt8173_afe_pcm_dai_component,
mt8173_afe_pcm_dais,
ARRAY_SIZE(mt8173_afe_pcm_dais));
if (ret)
goto err_platform;
ret = snd_soc_register_component(&pdev->dev,
&mtk_afe_hdmi_dai_component,
mtk_afe_hdmi_dais,
ARRAY_SIZE(mtk_afe_hdmi_dais));
&mt8173_afe_hdmi_dai_component,
mt8173_afe_hdmi_dais,
ARRAY_SIZE(mt8173_afe_hdmi_dais));
if (ret)
goto err_comp;
dev_info(&pdev->dev, "MTK AFE driver initialized.\n");
dev_info(&pdev->dev, "MT8173 AFE driver initialized.\n");
return 0;
err_comp:
......@@ -1297,38 +1202,38 @@ static int mtk_afe_pcm_dev_probe(struct platform_device *pdev)
return ret;
}
static int mtk_afe_pcm_dev_remove(struct platform_device *pdev)
static int mt8173_afe_pcm_dev_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
mtk_afe_runtime_suspend(&pdev->dev);
mt8173_afe_runtime_suspend(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static const struct of_device_id mtk_afe_pcm_dt_match[] = {
static const struct of_device_id mt8173_afe_pcm_dt_match[] = {
{ .compatible = "mediatek,mt8173-afe-pcm", },
{ }
};
MODULE_DEVICE_TABLE(of, mtk_afe_pcm_dt_match);
MODULE_DEVICE_TABLE(of, mt8173_afe_pcm_dt_match);
static const struct dev_pm_ops mtk_afe_pm_ops = {
SET_RUNTIME_PM_OPS(mtk_afe_runtime_suspend, mtk_afe_runtime_resume,
NULL)
static const struct dev_pm_ops mt8173_afe_pm_ops = {
SET_RUNTIME_PM_OPS(mt8173_afe_runtime_suspend,
mt8173_afe_runtime_resume, NULL)
};
static struct platform_driver mtk_afe_pcm_driver = {
static struct platform_driver mt8173_afe_pcm_driver = {
.driver = {
.name = "mtk-afe-pcm",
.of_match_table = mtk_afe_pcm_dt_match,
.pm = &mtk_afe_pm_ops,
.name = "mt8173-afe-pcm",
.of_match_table = mt8173_afe_pcm_dt_match,
.pm = &mt8173_afe_pm_ops,
},
.probe = mtk_afe_pcm_dev_probe,
.remove = mtk_afe_pcm_dev_remove,
.probe = mt8173_afe_pcm_dev_probe,
.remove = mt8173_afe_pcm_dev_remove,
};
module_platform_driver(mtk_afe_pcm_driver);
module_platform_driver(mt8173_afe_pcm_driver);
MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver");
MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>");
......
......@@ -18,7 +18,7 @@
#include <sound/soc.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include "../codecs/max98090.h"
#include "../../codecs/max98090.h"
static struct snd_soc_jack mt8173_max98090_jack;
......
......@@ -19,7 +19,7 @@
#include <linux/of_gpio.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include "../codecs/rt5645.h"
#include "../../codecs/rt5645.h"
#define MCLK_FOR_CODECS 12288000
......
......@@ -19,8 +19,8 @@
#include <linux/of_gpio.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include "../codecs/rt5645.h"
#include "../codecs/rt5677.h"
#include "../../codecs/rt5645.h"
#include "../../codecs/rt5677.h"
#define MCLK_FOR_CODECS 12288000
......
......@@ -19,10 +19,24 @@
#include <linux/of_gpio.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include "../codecs/rt5645.h"
#include "../../codecs/rt5645.h"
#define MCLK_FOR_CODECS 12288000
enum mt8173_rt5650_mclk {
MT8173_RT5650_MCLK_EXTERNAL = 0,
MT8173_RT5650_MCLK_INTERNAL,
};
struct mt8173_rt5650_platform_data {
enum mt8173_rt5650_mclk pll_from;
/* 0 = external oscillator; 1 = internal source from mt8173 */
};
static struct mt8173_rt5650_platform_data mt8173_rt5650_priv = {
.pll_from = MT8173_RT5650_MCLK_EXTERNAL,
};
static const struct snd_soc_dapm_widget mt8173_rt5650_widgets[] = {
SND_SOC_DAPM_SPK("Speaker", NULL),
SND_SOC_DAPM_MIC("Int Mic", NULL),
......@@ -54,13 +68,29 @@ static int mt8173_rt5650_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
unsigned int mclk_clock;
int i, ret;
switch (mt8173_rt5650_priv.pll_from) {
case MT8173_RT5650_MCLK_EXTERNAL:
/* mclk = 12.288M */
mclk_clock = MCLK_FOR_CODECS;
break;
case MT8173_RT5650_MCLK_INTERNAL:
/* mclk = sampling rate*256 */
mclk_clock = params_rate(params) * 256;
break;
default:
/* mclk = 12.288M */
mclk_clock = MCLK_FOR_CODECS;
break;
}
for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
/* pll from mclk 12.288M */
ret = snd_soc_dai_set_pll(codec_dai, 0, 0, MCLK_FOR_CODECS,
/* pll from mclk */
ret = snd_soc_dai_set_pll(codec_dai, 0, 0, mclk_clock,
params_rate(params) * 512);
if (ret)
return ret;
......@@ -139,7 +169,9 @@ static struct snd_soc_dai_link_component mt8173_rt5650_codecs[] = {
enum {
DAI_LINK_PLAYBACK,
DAI_LINK_CAPTURE,
DAI_LINK_HDMI,
DAI_LINK_CODEC_I2S,
DAI_LINK_HDMI_I2S,
};
/* Digital audio interface glue - connects codec <---> CPU */
......@@ -165,6 +197,16 @@ static struct snd_soc_dai_link mt8173_rt5650_dais[] = {
.dynamic = 1,
.dpcm_capture = 1,
},
[DAI_LINK_HDMI] = {
.name = "HDMI",
.stream_name = "HDMI PCM",
.cpu_dai_name = "HDMI",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dynamic = 1,
.dpcm_playback = 1,
},
/* Back End DAI links */
[DAI_LINK_CODEC_I2S] = {
.name = "Codec",
......@@ -180,6 +222,13 @@ static struct snd_soc_dai_link mt8173_rt5650_dais[] = {
.dpcm_playback = 1,
.dpcm_capture = 1,
},
[DAI_LINK_HDMI_I2S] = {
.name = "HDMI BE",
.cpu_dai_name = "HDMIO",
.no_pcm = 1,
.codec_dai_name = "i2s-hifi",
.dpcm_playback = 1,
},
};
static struct snd_soc_card mt8173_rt5650_card = {
......@@ -243,6 +292,24 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev)
mt8173_rt5650_codecs[1].dai_name = codec_capture_dai;
}
if (device_property_present(&pdev->dev, "mediatek,mclk")) {
ret = device_property_read_u32(&pdev->dev,
"mediatek,mclk",
&mt8173_rt5650_priv.pll_from);
if (ret) {
dev_err(&pdev->dev,
"%s snd_soc_register_card fail %d\n",
__func__, ret);
}
}
mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codec_of_node =
of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1);
if (!mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codec_of_node) {
dev_err(&pdev->dev,
"Property 'audio-codec' missing or invalid\n");
return -EINVAL;
}
card->dev = &pdev->dev;
platform_set_drvdata(pdev, card);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment