diff options
Diffstat (limited to '')
-rw-r--r-- | sound/soc/amd/acp-config.c | 163 | ||||
-rw-r--r-- | sound/soc/amd/acp-da7219-max98357a.c | 791 | ||||
-rw-r--r-- | sound/soc/amd/acp-es8336.c | 320 | ||||
-rw-r--r-- | sound/soc/amd/acp-pcm-dma.c | 1444 | ||||
-rw-r--r-- | sound/soc/amd/acp-rt5645.c | 206 | ||||
-rw-r--r-- | sound/soc/amd/acp.h | 223 | ||||
-rw-r--r-- | sound/soc/amd/acp/Kconfig | 81 | ||||
-rw-r--r-- | sound/soc/amd/acp/Makefile | 32 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-i2s.c | 559 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-legacy-mach.c | 165 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-mach-common.c | 915 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-mach.h | 66 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-pci.c | 181 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-pdm.c | 193 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-platform.c | 322 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-rembrandt.c | 401 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-renoir.c | 348 | ||||
-rw-r--r-- | sound/soc/amd/acp/acp-sof-mach.c | 176 | ||||
-rw-r--r-- | sound/soc/amd/acp/amd.h | 253 | ||||
-rw-r--r-- | sound/soc/amd/acp/chip_offset_byte.h | 132 | ||||
-rw-r--r-- | sound/soc/amd/acp3x-rt5682-max9836.c | 545 |
21 files changed, 7516 insertions, 0 deletions
diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c new file mode 100644 index 000000000..0932473b6 --- /dev/null +++ b/sound/soc/amd/acp-config.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* ACP machine configuration module */ + +#include <linux/acpi.h> +#include <linux/bits.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <linux/pci.h> + +#include "../sof/amd/acp.h" +#include "mach-config.h" + +static int acp_quirk_data; + +static const struct config_entry config_table[] = { + { + .flags = FLAG_AMD_SOF, + .device = ACP_PCI_DEV_ID, + .dmi_table = (const struct dmi_system_id []) { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AMD"), + DMI_MATCH(DMI_PRODUCT_NAME, "Majolica-CZN"), + }, + }, + {} + }, + }, + { + .flags = FLAG_AMD_SOF, + .device = ACP_PCI_DEV_ID, + .dmi_table = (const struct dmi_system_id []) { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Google"), + }, + }, + {} + }, + }, +}; + +int snd_amd_acp_find_config(struct pci_dev *pci) +{ + const struct config_entry *table = config_table; + u16 device = pci->device; + int i; + + /* Do not enable FLAGS on older platforms with Rev id zero */ + if (!pci->revision) + return 0; + + for (i = 0; i < ARRAY_SIZE(config_table); i++, table++) { + if (table->device != device) + continue; + if (table->dmi_table && !dmi_check_system(table->dmi_table)) + continue; + acp_quirk_data = table->flags; + return table->flags; + } + + return 0; +} +EXPORT_SYMBOL(snd_amd_acp_find_config); + +static struct snd_soc_acpi_codecs amp_rt1019 = { + .num_codecs = 1, + .codecs = {"10EC1019"} +}; + +static struct snd_soc_acpi_codecs amp_max = { + .num_codecs = 1, + .codecs = {"MX98360A"} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_amd_sof_machines[] = { + { + .id = "10EC5682", + .drv_name = "rt5682-rt1019", + .pdata = (void *)&acp_quirk_data, + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_rt1019, + .fw_filename = "sof-rn.ri", + .sof_tplg_filename = "sof-rn-rt5682-rt1019.tplg", + }, + { + .id = "10EC5682", + .drv_name = "rt5682-max", + .pdata = (void *)&acp_quirk_data, + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_max, + .fw_filename = "sof-rn.ri", + .sof_tplg_filename = "sof-rn-rt5682-max98360.tplg", + }, + { + .id = "RTL5682", + .drv_name = "rt5682s-max", + .pdata = (void *)&acp_quirk_data, + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_max, + .fw_filename = "sof-rn.ri", + .sof_tplg_filename = "sof-rn-rt5682-max98360.tplg", + }, + { + .id = "RTL5682", + .drv_name = "rt5682s-rt1019", + .pdata = (void *)&acp_quirk_data, + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_rt1019, + .fw_filename = "sof-rn.ri", + .sof_tplg_filename = "sof-rn-rt5682-rt1019.tplg", + }, + { + .id = "AMDI1019", + .drv_name = "renoir-dsp", + .pdata = (void *)&acp_quirk_data, + .fw_filename = "sof-rn.ri", + .sof_tplg_filename = "sof-acp.tplg", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_sof_machines); + +struct snd_soc_acpi_mach snd_soc_acpi_amd_rmb_sof_machines[] = { + { + .id = "AMDI1019", + .drv_name = "rmb-dsp", + .pdata = &acp_quirk_data, + .fw_filename = "sof-rmb.ri", + .sof_tplg_filename = "sof-acp-rmb.tplg", + }, + { + .id = "10508825", + .drv_name = "nau8825-max", + .pdata = &acp_quirk_data, + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_max, + .fw_filename = "sof-rmb.ri", + .sof_tplg_filename = "sof-rmb-nau8825-max98360.tplg", + }, + { + .id = "RTL5682", + .drv_name = "rt5682s-hs-rt1019", + .pdata = &acp_quirk_data, + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_rt1019, + .fw_filename = "sof-rmb.ri", + .sof_tplg_filename = "sof-rmb-rt5682s-rt1019.tplg", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_rmb_sof_machines); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c new file mode 100644 index 000000000..ef1b4cefc --- /dev/null +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: MIT +// +// Machine driver for AMD ACP Audio engine using DA7219, RT5682 & MAX98357 codec +// +//Copyright 2017-2021 Advanced Micro Devices, Inc. + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/acpi.h> + +#include "acp.h" +#include "../codecs/da7219.h" +#include "../codecs/da7219-aad.h" +#include "../codecs/rt5682.h" + +#define CZ_PLAT_CLK 48000000 +#define DUAL_CHANNEL 2 +#define RT5682_PLL_FREQ (48000 * 512) + +static struct snd_soc_jack cz_jack; +static struct clk *da7219_dai_wclk; +static struct clk *da7219_dai_bclk; +static struct clk *rt5682_dai_wclk; +static struct clk *rt5682_dai_bclk; + +void *acp_soc_is_rltk_max(struct device *dev); + +static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, + CZ_PLAT_CLK, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL, + CZ_PLAT_CLK, DA7219_PLL_FREQ_OUT_98304); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + da7219_dai_wclk = devm_clk_get(component->dev, "da7219-dai-wclk"); + if (IS_ERR(da7219_dai_wclk)) + return PTR_ERR(da7219_dai_wclk); + + da7219_dai_bclk = devm_clk_get(component->dev, "da7219-dai-bclk"); + if (IS_ERR(da7219_dai_bclk)) + return PTR_ERR(da7219_dai_bclk); + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &cz_jack); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + da7219_aad_jack_det(component, &cz_jack); + + return 0; +} + +static int da7219_clk_enable(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + + /* + * Set wclk to 48000 because the rate constraint of this driver is + * 48000. ADAU7002 spec: "The ADAU7002 requires a BCLK rate that is + * minimum of 64x the LRCLK sample rate." DA7219 is the only clk + * source so for all codecs we have to limit bclk to 64X lrclk. + */ + clk_set_rate(da7219_dai_wclk, 48000); + clk_set_rate(da7219_dai_bclk, 48000 * 64); + ret = clk_prepare_enable(da7219_dai_bclk); + if (ret < 0) { + dev_err(rtd->dev, "can't enable master clock %d\n", ret); + return ret; + } + + return ret; +} + +static void da7219_clk_disable(void) +{ + clk_disable_unprepare(da7219_dai_bclk); +} + +static int cz_rt5682_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + dev_info(codec_dai->dev, "codec dai name = %s\n", codec_dai->name); + + /* Set codec sysclk */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL2, + RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, + "Failed to set rt5682 SYSCLK: %d\n", ret); + return ret; + } + /* set codec PLL */ + ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL2, RT5682_PLL2_S_MCLK, + CZ_PLAT_CLK, RT5682_PLL_FREQ); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set rt5682 PLL: %d\n", ret); + return ret; + } + + rt5682_dai_wclk = devm_clk_get(component->dev, "rt5682-dai-wclk"); + if (IS_ERR(rt5682_dai_wclk)) + return PTR_ERR(rt5682_dai_wclk); + + rt5682_dai_bclk = devm_clk_get(component->dev, "rt5682-dai-bclk"); + if (IS_ERR(rt5682_dai_bclk)) + return PTR_ERR(rt5682_dai_bclk); + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &cz_jack); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + ret = snd_soc_component_set_jack(component, &cz_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + return 0; +} + +static int rt5682_clk_enable(struct snd_pcm_substream *substream) +{ + int ret; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + + /* + * Set wclk to 48000 because the rate constraint of this driver is + * 48000. ADAU7002 spec: "The ADAU7002 requires a BCLK rate that is + * minimum of 64x the LRCLK sample rate." RT5682 is the only clk + * source so for all codecs we have to limit bclk to 64X lrclk. + */ + ret = clk_set_rate(rt5682_dai_wclk, 48000); + if (ret) { + dev_err(rtd->dev, "Error setting wclk rate: %d\n", ret); + return ret; + } + ret = clk_set_rate(rt5682_dai_bclk, 48000 * 64); + if (ret) { + dev_err(rtd->dev, "Error setting bclk rate: %d\n", ret); + return ret; + } + ret = clk_prepare_enable(rt5682_dai_wclk); + if (ret < 0) { + dev_err(rtd->dev, "can't enable wclk %d\n", ret); + return ret; + } + return ret; +} + +static void rt5682_clk_disable(void) +{ + clk_disable_unprepare(rt5682_dai_wclk); +} + +static const unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int cz_da7219_play_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->play_i2s_instance = I2S_SP_INSTANCE; + return da7219_clk_enable(substream); +} + +static int cz_da7219_cap_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->cap_i2s_instance = I2S_SP_INSTANCE; + machine->capture_channel = CAP_CHANNEL1; + return da7219_clk_enable(substream); +} + +static int cz_max_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->play_i2s_instance = I2S_BT_INSTANCE; + return da7219_clk_enable(substream); +} + +static int cz_dmic0_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->cap_i2s_instance = I2S_BT_INSTANCE; + return da7219_clk_enable(substream); +} + +static int cz_dmic1_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->cap_i2s_instance = I2S_SP_INSTANCE; + machine->capture_channel = CAP_CHANNEL0; + return da7219_clk_enable(substream); +} + +static void cz_da7219_shutdown(struct snd_pcm_substream *substream) +{ + da7219_clk_disable(); +} + +static int cz_rt5682_play_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->play_i2s_instance = I2S_SP_INSTANCE; + return rt5682_clk_enable(substream); +} + +static int cz_rt5682_cap_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->cap_i2s_instance = I2S_SP_INSTANCE; + machine->capture_channel = CAP_CHANNEL1; + return rt5682_clk_enable(substream); +} + +static int cz_rt5682_max_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->play_i2s_instance = I2S_BT_INSTANCE; + return rt5682_clk_enable(substream); +} + +static int cz_rt5682_dmic0_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->cap_i2s_instance = I2S_BT_INSTANCE; + return rt5682_clk_enable(substream); +} + +static int cz_rt5682_dmic1_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->cap_i2s_instance = I2S_SP_INSTANCE; + machine->capture_channel = CAP_CHANNEL0; + return rt5682_clk_enable(substream); +} + +static void cz_rt5682_shutdown(struct snd_pcm_substream *substream) +{ + rt5682_clk_disable(); +} + +static const struct snd_soc_ops cz_da7219_play_ops = { + .startup = cz_da7219_play_startup, + .shutdown = cz_da7219_shutdown, +}; + +static const struct snd_soc_ops cz_da7219_cap_ops = { + .startup = cz_da7219_cap_startup, + .shutdown = cz_da7219_shutdown, +}; + +static const struct snd_soc_ops cz_max_play_ops = { + .startup = cz_max_startup, + .shutdown = cz_da7219_shutdown, +}; + +static const struct snd_soc_ops cz_dmic0_cap_ops = { + .startup = cz_dmic0_startup, + .shutdown = cz_da7219_shutdown, +}; + +static const struct snd_soc_ops cz_dmic1_cap_ops = { + .startup = cz_dmic1_startup, + .shutdown = cz_da7219_shutdown, +}; + +static const struct snd_soc_ops cz_rt5682_play_ops = { + .startup = cz_rt5682_play_startup, + .shutdown = cz_rt5682_shutdown, +}; + +static const struct snd_soc_ops cz_rt5682_cap_ops = { + .startup = cz_rt5682_cap_startup, + .shutdown = cz_rt5682_shutdown, +}; + +static const struct snd_soc_ops cz_rt5682_max_play_ops = { + .startup = cz_rt5682_max_startup, + .shutdown = cz_rt5682_shutdown, +}; + +static const struct snd_soc_ops cz_rt5682_dmic0_cap_ops = { + .startup = cz_rt5682_dmic0_startup, + .shutdown = cz_rt5682_shutdown, +}; + +static const struct snd_soc_ops cz_rt5682_dmic1_cap_ops = { + .startup = cz_rt5682_dmic1_startup, + .shutdown = cz_rt5682_shutdown, +}; + +SND_SOC_DAILINK_DEF(designware1, + DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.1.auto"))); +SND_SOC_DAILINK_DEF(designware2, + DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.2.auto"))); +SND_SOC_DAILINK_DEF(designware3, + DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.3.auto"))); + +SND_SOC_DAILINK_DEF(dlgs, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-DLGS7219:00", "da7219-hifi"))); +SND_SOC_DAILINK_DEF(rt5682, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", "rt5682-aif1"))); +SND_SOC_DAILINK_DEF(mx, + DAILINK_COMP_ARRAY(COMP_CODEC("MX98357A:00", "HiFi"))); +SND_SOC_DAILINK_DEF(adau, + DAILINK_COMP_ARRAY(COMP_CODEC("ADAU7002:00", "adau7002-hifi"))); + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_audio_dma.0.auto"))); + +static struct snd_soc_dai_link cz_dai_7219_98357[] = { + { + .name = "amd-da7219-play", + .stream_name = "Playback", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .init = cz_da7219_init, + .dpcm_playback = 1, + .stop_dma_first = 1, + .ops = &cz_da7219_play_ops, + SND_SOC_DAILINK_REG(designware1, dlgs, platform), + }, + { + .name = "amd-da7219-cap", + .stream_name = "Capture", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .dpcm_capture = 1, + .stop_dma_first = 1, + .ops = &cz_da7219_cap_ops, + SND_SOC_DAILINK_REG(designware2, dlgs, platform), + }, + { + .name = "amd-max98357-play", + .stream_name = "HiFi Playback", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .dpcm_playback = 1, + .stop_dma_first = 1, + .ops = &cz_max_play_ops, + SND_SOC_DAILINK_REG(designware3, mx, platform), + }, + { + /* C panel DMIC */ + .name = "dmic0", + .stream_name = "DMIC0 Capture", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .dpcm_capture = 1, + .stop_dma_first = 1, + .ops = &cz_dmic0_cap_ops, + SND_SOC_DAILINK_REG(designware3, adau, platform), + }, + { + /* A/B panel DMIC */ + .name = "dmic1", + .stream_name = "DMIC1 Capture", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .dpcm_capture = 1, + .stop_dma_first = 1, + .ops = &cz_dmic1_cap_ops, + SND_SOC_DAILINK_REG(designware2, adau, platform), + }, +}; + +static struct snd_soc_dai_link cz_dai_5682_98357[] = { + { + .name = "amd-rt5682-play", + .stream_name = "Playback", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .init = cz_rt5682_init, + .dpcm_playback = 1, + .stop_dma_first = 1, + .ops = &cz_rt5682_play_ops, + SND_SOC_DAILINK_REG(designware1, rt5682, platform), + }, + { + .name = "amd-rt5682-cap", + .stream_name = "Capture", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .dpcm_capture = 1, + .stop_dma_first = 1, + .ops = &cz_rt5682_cap_ops, + SND_SOC_DAILINK_REG(designware2, rt5682, platform), + }, + { + .name = "amd-max98357-play", + .stream_name = "HiFi Playback", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .dpcm_playback = 1, + .stop_dma_first = 1, + .ops = &cz_rt5682_max_play_ops, + SND_SOC_DAILINK_REG(designware3, mx, platform), + }, + { + /* C panel DMIC */ + .name = "dmic0", + .stream_name = "DMIC0 Capture", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .dpcm_capture = 1, + .stop_dma_first = 1, + .ops = &cz_rt5682_dmic0_cap_ops, + SND_SOC_DAILINK_REG(designware3, adau, platform), + }, + { + /* A/B panel DMIC */ + .name = "dmic1", + .stream_name = "DMIC1 Capture", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .dpcm_capture = 1, + .stop_dma_first = 1, + .ops = &cz_rt5682_dmic1_cap_ops, + SND_SOC_DAILINK_REG(designware2, adau, platform), + }, +}; + +static const struct snd_soc_dapm_widget cz_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), +}; + +static const struct snd_soc_dapm_route cz_audio_route[] = { + {"Headphones", NULL, "HPL"}, + {"Headphones", NULL, "HPR"}, + {"MIC", NULL, "Headset Mic"}, + {"Speakers", NULL, "Speaker"}, + {"PDM_DAT", NULL, "Int Mic"}, +}; + +static const struct snd_soc_dapm_route cz_rt5682_audio_route[] = { + {"Headphones", NULL, "HPOL"}, + {"Headphones", NULL, "HPOR"}, + {"IN1P", NULL, "Headset Mic"}, + {"Speakers", NULL, "Speaker"}, + {"PDM_DAT", NULL, "Int Mic"}, +}; + +static const struct snd_kcontrol_new cz_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static struct snd_soc_card cz_card = { + .name = "acpd7219m98357", + .owner = THIS_MODULE, + .dai_link = cz_dai_7219_98357, + .num_links = ARRAY_SIZE(cz_dai_7219_98357), + .dapm_widgets = cz_widgets, + .num_dapm_widgets = ARRAY_SIZE(cz_widgets), + .dapm_routes = cz_audio_route, + .num_dapm_routes = ARRAY_SIZE(cz_audio_route), + .controls = cz_mc_controls, + .num_controls = ARRAY_SIZE(cz_mc_controls), +}; + +static struct snd_soc_card cz_rt5682_card = { + .name = "acpr5682m98357", + .owner = THIS_MODULE, + .dai_link = cz_dai_5682_98357, + .num_links = ARRAY_SIZE(cz_dai_5682_98357), + .dapm_widgets = cz_widgets, + .num_dapm_widgets = ARRAY_SIZE(cz_widgets), + .dapm_routes = cz_rt5682_audio_route, + .controls = cz_mc_controls, + .num_controls = ARRAY_SIZE(cz_mc_controls), +}; + +void *acp_soc_is_rltk_max(struct device *dev) +{ + const struct acpi_device_id *match; + + match = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!match) + return NULL; + return (void *)match->driver_data; +} + +static struct regulator_consumer_supply acp_da7219_supplies[] = { + REGULATOR_SUPPLY("VDD", "i2c-DLGS7219:00"), + REGULATOR_SUPPLY("VDDMIC", "i2c-DLGS7219:00"), + REGULATOR_SUPPLY("VDDIO", "i2c-DLGS7219:00"), + REGULATOR_SUPPLY("IOVDD", "ADAU7002:00"), +}; + +static struct regulator_init_data acp_da7219_data = { + .constraints = { + .always_on = 1, + }, + .num_consumer_supplies = ARRAY_SIZE(acp_da7219_supplies), + .consumer_supplies = acp_da7219_supplies, +}; + +static struct regulator_config acp_da7219_cfg = { + .init_data = &acp_da7219_data, +}; + +static struct regulator_ops acp_da7219_ops = { +}; + +static const struct regulator_desc acp_da7219_desc = { + .name = "reg-fixed-1.8V", + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &acp_da7219_ops, + .fixed_uV = 1800000, /* 1.8V */ + .n_voltages = 1, +}; + +static int cz_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card; + struct acp_platform_info *machine; + struct regulator_dev *rdev; + struct device *dev = &pdev->dev; + + card = (struct snd_soc_card *)acp_soc_is_rltk_max(dev); + if (!card) + return -ENODEV; + if (!strcmp(card->name, "acpd7219m98357")) { + acp_da7219_cfg.dev = &pdev->dev; + rdev = devm_regulator_register(&pdev->dev, &acp_da7219_desc, + &acp_da7219_cfg); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "Failed to register regulator: %d\n", + (int)PTR_ERR(rdev)); + return -EINVAL; + } + } + + machine = devm_kzalloc(&pdev->dev, sizeof(struct acp_platform_info), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + return dev_err_probe(&pdev->dev, ret, + "devm_snd_soc_register_card(%s) failed\n", + card->name); + } + acp_bt_uart_enable = !device_property_read_bool(&pdev->dev, + "bt-pad-enable"); + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cz_audio_acpi_match[] = { + { "AMD7219", (unsigned long)&cz_card }, + { "AMDI5682", (unsigned long)&cz_rt5682_card}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, cz_audio_acpi_match); +#endif + +static struct platform_driver cz_pcm_driver = { + .driver = { + .name = "cz-da7219-max98357a", + .acpi_match_table = ACPI_PTR(cz_audio_acpi_match), + .pm = &snd_soc_pm_ops, + }, + .probe = cz_probe, +}; + +module_platform_driver(cz_pcm_driver); + +MODULE_AUTHOR("akshu.agrawal@amd.com"); +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("DA7219, RT5682 & MAX98357A audio support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp-es8336.c b/sound/soc/amd/acp-es8336.c new file mode 100644 index 000000000..89499542c --- /dev/null +++ b/sound/soc/amd/acp-es8336.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Machine driver for AMD Stoney platform using ES8336 Codec + * + * Copyright 2022 Advanced Micro Devices, Inc. + */ + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include <linux/gpio.h> +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/machine.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/acpi.h> + +#include "acp.h" + +#define DUAL_CHANNEL 2 +#define DRV_NAME "acp2x_mach" +#define ST_JADEITE 1 +#define ES8336_PLL_FREQ (48000 * 256) + +static unsigned long acp2x_machine_id; +static struct snd_soc_jack st_jack; +static struct device *codec_dev; +static struct gpio_desc *gpio_pa; + +static int sof_es8316_speaker_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpiod_set_value_cansleep(gpio_pa, true); + else + gpiod_set_value_cansleep(gpio_pa, false); + + return 0; +} + +static struct snd_soc_jack_pin st_es8316_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int st_es8336_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_card *card; + struct snd_soc_component *codec; + + codec = asoc_rtd_to_codec(rtd, 0)->component; + card = rtd->card; + + ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0, + &st_jack, st_es8316_jack_pins, + ARRAY_SIZE(st_es8316_jack_pins)); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + snd_jack_set_key(st_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + ret = snd_soc_component_set_jack(codec, &st_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + return 0; +} + +static const unsigned int st_channels[] = { + DUAL_CHANNEL, +}; + +static const unsigned int st_rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list st_constraints_rates = { + .count = ARRAY_SIZE(st_rates), + .list = st_rates, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list st_constraints_channels = { + .count = ARRAY_SIZE(st_channels), + .list = st_channels, + .mask = 0, +}; + +static int st_es8336_codec_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_card *card; + struct acp_platform_info *machine; + struct snd_soc_dai *codec_dai; + int ret; + + runtime = substream->runtime; + rtd = asoc_substream_to_rtd(substream); + card = rtd->card; + machine = snd_soc_card_get_drvdata(card); + codec_dai = asoc_rtd_to_codec(rtd, 0); + ret = snd_soc_dai_set_sysclk(codec_dai, 0, ES8336_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &st_constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &st_constraints_rates); + + machine->play_i2s_instance = I2S_MICSP_INSTANCE; + machine->cap_i2s_instance = I2S_MICSP_INSTANCE; + machine->capture_channel = CAP_CHANNEL0; + return 0; +} + +static const struct snd_soc_ops st_es8336_ops = { + .startup = st_es8336_codec_startup, +}; + +SND_SOC_DAILINK_DEF(designware1, + DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.2.auto"))); +SND_SOC_DAILINK_DEF(codec, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8336:00", "ES8316 HiFi"))); +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_audio_dma.1.auto"))); + +static struct snd_soc_dai_link st_dai_es8336[] = { + { + .name = "amdes8336", + .stream_name = "ES8336 HiFi Play", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .stop_dma_first = 1, + .dpcm_capture = 1, + .dpcm_playback = 1, + .init = st_es8336_init, + .ops = &st_es8336_ops, + SND_SOC_DAILINK_REG(designware1, codec, platform), + }, +}; + +static const struct snd_soc_dapm_widget st_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + + SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0, + sof_es8316_speaker_power_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +}; + +static const struct snd_soc_dapm_route st_audio_route[] = { + {"Speaker", NULL, "HPOL"}, + {"Speaker", NULL, "HPOR"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"MIC1", NULL, "Headset Mic"}, + {"MIC2", NULL, "Internal Mic"}, + {"Speaker", NULL, "Speaker Power"}, +}; + +static const struct snd_kcontrol_new st_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), +}; + +static const struct acpi_gpio_params pa_enable_gpio = { 0, 0, false }; +static const struct acpi_gpio_mapping acpi_es8336_gpios[] = { + { "pa-enable-gpios", &pa_enable_gpio, 1 }, + { } +}; + +static int st_es8336_late_probe(struct snd_soc_card *card) +{ + struct acpi_device *adev; + int ret; + + adev = acpi_dev_get_first_match_dev("ESSX8336", NULL, -1); + if (!adev) + return -ENODEV; + + codec_dev = acpi_get_first_physical_node(adev); + acpi_dev_put(adev); + if (!codec_dev) + dev_err(card->dev, "can not find codec dev\n"); + + ret = devm_acpi_dev_add_driver_gpios(codec_dev, acpi_es8336_gpios); + if (ret) + dev_warn(card->dev, "Failed to add driver gpios\n"); + + gpio_pa = gpiod_get_optional(codec_dev, "pa-enable", GPIOD_OUT_LOW); + if (IS_ERR(gpio_pa)) { + ret = dev_err_probe(card->dev, PTR_ERR(gpio_pa), + "could not get pa-enable GPIO\n"); + put_device(codec_dev); + return ret; + } + return 0; +} + +static struct snd_soc_card st_card = { + .name = "acpes8336", + .owner = THIS_MODULE, + .dai_link = st_dai_es8336, + .num_links = ARRAY_SIZE(st_dai_es8336), + .dapm_widgets = st_widgets, + .num_dapm_widgets = ARRAY_SIZE(st_widgets), + .dapm_routes = st_audio_route, + .num_dapm_routes = ARRAY_SIZE(st_audio_route), + .controls = st_mc_controls, + .num_controls = ARRAY_SIZE(st_mc_controls), + .late_probe = st_es8336_late_probe, +}; + +static int st_es8336_quirk_cb(const struct dmi_system_id *id) +{ + acp2x_machine_id = ST_JADEITE; + return 1; +} + +static const struct dmi_system_id st_es8336_quirk_table[] = { + { + .callback = st_es8336_quirk_cb, + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMD"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jadeite"), + }, + }, + { + .callback = st_es8336_quirk_cb, + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "IP3 Technology CO.,Ltd."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ASN1D"), + }, + }, + { + .callback = st_es8336_quirk_cb, + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Standard"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ASN10"), + }, + }, + {} +}; + +static int st_es8336_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card; + struct acp_platform_info *machine; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct acp_platform_info), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + dmi_check_system(st_es8336_quirk_table); + switch (acp2x_machine_id) { + case ST_JADEITE: + card = &st_card; + st_card.dev = &pdev->dev; + break; + default: + return -ENODEV; + } + + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + ret = devm_snd_soc_register_card(&pdev->dev, &st_card); + if (ret) { + return dev_err_probe(&pdev->dev, ret, + "devm_snd_soc_register_card(%s) failed\n", + card->name); + } + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id st_audio_acpi_match[] = { + {"AMDI8336", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, st_audio_acpi_match); +#endif + +static struct platform_driver st_mach_driver = { + .driver = { + .name = "st-es8316", + .acpi_match_table = ACPI_PTR(st_audio_acpi_match), + .pm = &snd_soc_pm_ops, + }, + .probe = st_es8336_probe, +}; + +module_platform_driver(st_mach_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("st-es8316 audio support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c new file mode 100644 index 000000000..198358d28 --- /dev/null +++ b/sound/soc/amd/acp-pcm-dma.c @@ -0,0 +1,1444 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AMD ALSA SoC PCM Driver for ACP 2.x + * + * Copyright 2014-2015 Advanced Micro Devices, Inc. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/sizes.h> +#include <linux/pm_runtime.h> + +#include <sound/soc.h> +#include <drm/amd_asic_type.h> +#include "acp.h" + +#define DRV_NAME "acp_audio_dma" + +#define PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 2 +#define PLAYBACK_MAX_PERIOD_SIZE 16384 +#define PLAYBACK_MIN_PERIOD_SIZE 1024 +#define CAPTURE_MIN_NUM_PERIODS 2 +#define CAPTURE_MAX_NUM_PERIODS 2 +#define CAPTURE_MAX_PERIOD_SIZE 16384 +#define CAPTURE_MIN_PERIOD_SIZE 1024 + +#define MAX_BUFFER (PLAYBACK_MAX_PERIOD_SIZE * PLAYBACK_MAX_NUM_PERIODS) +#define MIN_BUFFER MAX_BUFFER + +#define ST_PLAYBACK_MAX_PERIOD_SIZE 4096 +#define ST_CAPTURE_MAX_PERIOD_SIZE ST_PLAYBACK_MAX_PERIOD_SIZE +#define ST_MAX_BUFFER (ST_PLAYBACK_MAX_PERIOD_SIZE * PLAYBACK_MAX_NUM_PERIODS) +#define ST_MIN_BUFFER ST_MAX_BUFFER + +#define DRV_NAME "acp_audio_dma" +bool acp_bt_uart_enable = true; +EXPORT_SYMBOL(acp_bt_uart_enable); + +static const struct snd_pcm_hardware acp_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .rate_min = 8000, + .rate_max = 96000, + .buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp_st_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .rate_min = 8000, + .rate_max = 96000, + .buffer_bytes_max = ST_MAX_BUFFER, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = ST_PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp_st_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .buffer_bytes_max = ST_MAX_BUFFER, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = ST_CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, +}; + +static u32 acp_reg_read(void __iomem *acp_mmio, u32 reg) +{ + return readl(acp_mmio + (reg * 4)); +} + +static void acp_reg_write(u32 val, void __iomem *acp_mmio, u32 reg) +{ + writel(val, acp_mmio + (reg * 4)); +} + +/* + * Configure a given dma channel parameters - enable/disable, + * number of descriptors, priority + */ +static void config_acp_dma_channel(void __iomem *acp_mmio, u8 ch_num, + u16 dscr_strt_idx, u16 num_dscrs, + enum acp_dma_priority_level priority_level) +{ + u32 dma_ctrl; + + /* disable the channel run field */ + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK; + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + + /* program a DMA channel with first descriptor to be processed. */ + acp_reg_write((ACP_DMA_DSCR_STRT_IDX_0__DMAChDscrStrtIdx_MASK + & dscr_strt_idx), + acp_mmio, mmACP_DMA_DSCR_STRT_IDX_0 + ch_num); + + /* + * program a DMA channel with the number of descriptors to be + * processed in the transfer + */ + acp_reg_write(ACP_DMA_DSCR_CNT_0__DMAChDscrCnt_MASK & num_dscrs, + acp_mmio, mmACP_DMA_DSCR_CNT_0 + ch_num); + + /* set DMA channel priority */ + acp_reg_write(priority_level, acp_mmio, mmACP_DMA_PRIO_0 + ch_num); +} + +/* Initialize a dma descriptor in SRAM based on descriptor information passed */ +static void config_dma_descriptor_in_sram(void __iomem *acp_mmio, + u16 descr_idx, + acp_dma_dscr_transfer_t *descr_info) +{ + u32 sram_offset; + + sram_offset = (descr_idx * sizeof(acp_dma_dscr_transfer_t)); + + /* program the source base address. */ + acp_reg_write(sram_offset, acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + acp_reg_write(descr_info->src, acp_mmio, mmACP_SRBM_Targ_Idx_Data); + /* program the destination base address. */ + acp_reg_write(sram_offset + 4, acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + acp_reg_write(descr_info->dest, acp_mmio, mmACP_SRBM_Targ_Idx_Data); + + /* program the number of bytes to be transferred for this descriptor. */ + acp_reg_write(sram_offset + 8, acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + acp_reg_write(descr_info->xfer_val, acp_mmio, mmACP_SRBM_Targ_Idx_Data); +} + +static void pre_config_reset(void __iomem *acp_mmio, u16 ch_num) +{ + u32 dma_ctrl; + int ret; + + /* clear the reset bit */ + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK; + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + /* check the reset bit before programming configuration registers */ + ret = readl_poll_timeout(acp_mmio + ((mmACP_DMA_CNTL_0 + ch_num) * 4), + dma_ctrl, + !(dma_ctrl & ACP_DMA_CNTL_0__DMAChRst_MASK), + 100, ACP_DMA_RESET_TIME); + if (ret < 0) + pr_err("Failed to clear reset of channel : %d\n", ch_num); +} + +/* + * Initialize the DMA descriptor information for transfer between + * system memory <-> ACP SRAM + */ +static void set_acp_sysmem_dma_descriptors(void __iomem *acp_mmio, + u32 size, int direction, + u32 pte_offset, u16 ch, + u32 sram_bank, u16 dma_dscr_idx, + u32 asic_type) +{ + u16 i; + acp_dma_dscr_transfer_t dmadscr[NUM_DSCRS_PER_CHANNEL]; + + for (i = 0; i < NUM_DSCRS_PER_CHANNEL; i++) { + dmadscr[i].xfer_val = 0; + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + dma_dscr_idx = dma_dscr_idx + i; + dmadscr[i].dest = sram_bank + (i * (size / 2)); + dmadscr[i].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + + (pte_offset * SZ_4K) + (i * (size / 2)); + switch (asic_type) { + case CHIP_STONEY: + dmadscr[i].xfer_val |= + (ACP_DMA_ATTR_DAGB_GARLIC_TO_SHAREDMEM << 16) | + (size / 2); + break; + default: + dmadscr[i].xfer_val |= + (ACP_DMA_ATTR_DAGB_ONION_TO_SHAREDMEM << 16) | + (size / 2); + } + } else { + dma_dscr_idx = dma_dscr_idx + i; + dmadscr[i].src = sram_bank + (i * (size / 2)); + dmadscr[i].dest = + ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + + (pte_offset * SZ_4K) + (i * (size / 2)); + switch (asic_type) { + case CHIP_STONEY: + dmadscr[i].xfer_val |= + (ACP_DMA_ATTR_SHARED_MEM_TO_DAGB_GARLIC << 16) | + (size / 2); + break; + default: + dmadscr[i].xfer_val |= + (ACP_DMA_ATTR_SHAREDMEM_TO_DAGB_ONION << 16) | + (size / 2); + } + } + config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx, + &dmadscr[i]); + } + pre_config_reset(acp_mmio, ch); + config_acp_dma_channel(acp_mmio, ch, + dma_dscr_idx - 1, + NUM_DSCRS_PER_CHANNEL, + ACP_DMA_PRIORITY_LEVEL_NORMAL); +} + +/* + * Initialize the DMA descriptor information for transfer between + * ACP SRAM <-> I2S + */ +static void set_acp_to_i2s_dma_descriptors(void __iomem *acp_mmio, u32 size, + int direction, u32 sram_bank, + u16 destination, u16 ch, + u16 dma_dscr_idx, u32 asic_type) +{ + u16 i; + acp_dma_dscr_transfer_t dmadscr[NUM_DSCRS_PER_CHANNEL]; + + for (i = 0; i < NUM_DSCRS_PER_CHANNEL; i++) { + dmadscr[i].xfer_val = 0; + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + dma_dscr_idx = dma_dscr_idx + i; + dmadscr[i].src = sram_bank + (i * (size / 2)); + /* dmadscr[i].dest is unused by hardware. */ + dmadscr[i].dest = 0; + dmadscr[i].xfer_val |= BIT(22) | (destination << 16) | + (size / 2); + } else { + dma_dscr_idx = dma_dscr_idx + i; + /* dmadscr[i].src is unused by hardware. */ + dmadscr[i].src = 0; + dmadscr[i].dest = + sram_bank + (i * (size / 2)); + dmadscr[i].xfer_val |= BIT(22) | + (destination << 16) | (size / 2); + } + config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx, + &dmadscr[i]); + } + pre_config_reset(acp_mmio, ch); + /* Configure the DMA channel with the above descriptor */ + config_acp_dma_channel(acp_mmio, ch, dma_dscr_idx - 1, + NUM_DSCRS_PER_CHANNEL, + ACP_DMA_PRIORITY_LEVEL_NORMAL); +} + +/* Create page table entries in ACP SRAM for the allocated memory */ +static void acp_pte_config(void __iomem *acp_mmio, dma_addr_t addr, + u16 num_of_pages, u32 pte_offset) +{ + u16 page_idx; + u32 low; + u32 high; + u32 offset; + + offset = ACP_DAGB_GRP_SRBM_SRAM_BASE_OFFSET + (pte_offset * 8); + for (page_idx = 0; page_idx < (num_of_pages); page_idx++) { + /* Load the low address of page int ACP SRAM through SRBM */ + acp_reg_write((offset + (page_idx * 8)), + acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + + low = lower_32_bits(addr); + high = upper_32_bits(addr); + + acp_reg_write(low, acp_mmio, mmACP_SRBM_Targ_Idx_Data); + + /* Load the High address of page int ACP SRAM through SRBM */ + acp_reg_write((offset + (page_idx * 8) + 4), + acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + + /* page enable in ACP */ + high |= BIT(31); + acp_reg_write(high, acp_mmio, mmACP_SRBM_Targ_Idx_Data); + + /* Move to next physically contiguous page */ + addr += PAGE_SIZE; + } +} + +static void config_acp_dma(void __iomem *acp_mmio, + struct audio_substream_data *rtd, + u32 asic_type) +{ + u16 ch_acp_sysmem, ch_acp_i2s; + + acp_pte_config(acp_mmio, rtd->dma_addr, rtd->num_of_pages, + rtd->pte_offset); + + if (rtd->direction == SNDRV_PCM_STREAM_PLAYBACK) { + ch_acp_sysmem = rtd->ch1; + ch_acp_i2s = rtd->ch2; + } else { + ch_acp_i2s = rtd->ch1; + ch_acp_sysmem = rtd->ch2; + } + /* Configure System memory <-> ACP SRAM DMA descriptors */ + set_acp_sysmem_dma_descriptors(acp_mmio, rtd->size, + rtd->direction, rtd->pte_offset, + ch_acp_sysmem, rtd->sram_bank, + rtd->dma_dscr_idx_1, asic_type); + /* Configure ACP SRAM <-> I2S DMA descriptors */ + set_acp_to_i2s_dma_descriptors(acp_mmio, rtd->size, + rtd->direction, rtd->sram_bank, + rtd->destination, ch_acp_i2s, + rtd->dma_dscr_idx_2, asic_type); +} + +static void acp_dma_cap_channel_enable(void __iomem *acp_mmio, + u16 cap_channel) +{ + u32 val, ch_reg, imr_reg, res_reg; + + switch (cap_channel) { + case CAP_CHANNEL1: + ch_reg = mmACP_I2SMICSP_RER1; + res_reg = mmACP_I2SMICSP_RCR1; + imr_reg = mmACP_I2SMICSP_IMR1; + break; + case CAP_CHANNEL0: + default: + ch_reg = mmACP_I2SMICSP_RER0; + res_reg = mmACP_I2SMICSP_RCR0; + imr_reg = mmACP_I2SMICSP_IMR0; + break; + } + val = acp_reg_read(acp_mmio, + mmACP_I2S_16BIT_RESOLUTION_EN); + if (val & ACP_I2S_MIC_16BIT_RESOLUTION_EN) { + acp_reg_write(0x0, acp_mmio, ch_reg); + /* Set 16bit resolution on capture */ + acp_reg_write(0x2, acp_mmio, res_reg); + } + val = acp_reg_read(acp_mmio, imr_reg); + val &= ~ACP_I2SMICSP_IMR1__I2SMICSP_RXDAM_MASK; + val &= ~ACP_I2SMICSP_IMR1__I2SMICSP_RXFOM_MASK; + acp_reg_write(val, acp_mmio, imr_reg); + acp_reg_write(0x1, acp_mmio, ch_reg); +} + +static void acp_dma_cap_channel_disable(void __iomem *acp_mmio, + u16 cap_channel) +{ + u32 val, ch_reg, imr_reg; + + switch (cap_channel) { + case CAP_CHANNEL1: + imr_reg = mmACP_I2SMICSP_IMR1; + ch_reg = mmACP_I2SMICSP_RER1; + break; + case CAP_CHANNEL0: + default: + imr_reg = mmACP_I2SMICSP_IMR0; + ch_reg = mmACP_I2SMICSP_RER0; + break; + } + val = acp_reg_read(acp_mmio, imr_reg); + val |= ACP_I2SMICSP_IMR1__I2SMICSP_RXDAM_MASK; + val |= ACP_I2SMICSP_IMR1__I2SMICSP_RXFOM_MASK; + acp_reg_write(val, acp_mmio, imr_reg); + acp_reg_write(0x0, acp_mmio, ch_reg); +} + +/* Start a given DMA channel transfer */ +static void acp_dma_start(void __iomem *acp_mmio, u16 ch_num, bool is_circular) +{ + u32 dma_ctrl; + + /* read the dma control register and disable the channel run field */ + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + + /* Invalidating the DAGB cache */ + acp_reg_write(1, acp_mmio, mmACP_DAGB_ATU_CTRL); + + /* + * configure the DMA channel and start the DMA transfer + * set dmachrun bit to start the transfer and enable the + * interrupt on completion of the dma transfer + */ + dma_ctrl |= ACP_DMA_CNTL_0__DMAChRun_MASK; + + switch (ch_num) { + case ACP_TO_I2S_DMA_CH_NUM: + case I2S_TO_ACP_DMA_CH_NUM: + case ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM: + case I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM: + case ACP_TO_I2S_DMA_MICSP_INSTANCE_CH_NUM: + dma_ctrl |= ACP_DMA_CNTL_0__DMAChIOCEn_MASK; + break; + default: + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK; + break; + } + + /* enable for ACP to SRAM DMA channel */ + if (is_circular == true) + dma_ctrl |= ACP_DMA_CNTL_0__Circular_DMA_En_MASK; + else + dma_ctrl &= ~ACP_DMA_CNTL_0__Circular_DMA_En_MASK; + + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); +} + +/* Stop a given DMA channel transfer */ +static int acp_dma_stop(void __iomem *acp_mmio, u8 ch_num) +{ + u32 dma_ctrl; + u32 dma_ch_sts; + u32 count = ACP_DMA_RESET_TIME; + + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + + /* + * clear the dma control register fields before writing zero + * in reset bit + */ + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK; + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK; + + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + dma_ch_sts = acp_reg_read(acp_mmio, mmACP_DMA_CH_STS); + + if (dma_ch_sts & BIT(ch_num)) { + /* + * set the reset bit for this channel to stop the dma + * transfer + */ + dma_ctrl |= ACP_DMA_CNTL_0__DMAChRst_MASK; + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + } + + /* check the channel status bit for some time and return the status */ + while (true) { + dma_ch_sts = acp_reg_read(acp_mmio, mmACP_DMA_CH_STS); + if (!(dma_ch_sts & BIT(ch_num))) { + /* + * clear the reset flag after successfully stopping + * the dma transfer and break from the loop + */ + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK; + + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + + ch_num); + break; + } + if (--count == 0) { + pr_err("Failed to stop ACP DMA channel : %d\n", ch_num); + return -ETIMEDOUT; + } + udelay(100); + } + return 0; +} + +static void acp_set_sram_bank_state(void __iomem *acp_mmio, u16 bank, + bool power_on) +{ + u32 val, req_reg, sts_reg, sts_reg_mask; + u32 loops = 1000; + + if (bank < 32) { + req_reg = mmACP_MEM_SHUT_DOWN_REQ_LO; + sts_reg = mmACP_MEM_SHUT_DOWN_STS_LO; + sts_reg_mask = 0xFFFFFFFF; + + } else { + bank -= 32; + req_reg = mmACP_MEM_SHUT_DOWN_REQ_HI; + sts_reg = mmACP_MEM_SHUT_DOWN_STS_HI; + sts_reg_mask = 0x0000FFFF; + } + + val = acp_reg_read(acp_mmio, req_reg); + if (val & (1 << bank)) { + /* bank is in off state */ + if (power_on == true) + /* request to on */ + val &= ~(1 << bank); + else + /* request to off */ + return; + } else { + /* bank is in on state */ + if (power_on == false) + /* request to off */ + val |= 1 << bank; + else + /* request to on */ + return; + } + acp_reg_write(val, acp_mmio, req_reg); + + while (acp_reg_read(acp_mmio, sts_reg) != sts_reg_mask) { + if (!loops--) { + pr_err("ACP SRAM bank %d state change failed\n", bank); + break; + } + cpu_relax(); + } +} + +/* Initialize and bring ACP hardware to default state. */ +static int acp_init(void __iomem *acp_mmio, u32 asic_type) +{ + u16 bank; + u32 val, count, sram_pte_offset; + + /* Assert Soft reset of ACP */ + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + + val |= ACP_SOFT_RESET__SoftResetAud_MASK; + acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET); + + count = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (true) { + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + if (ACP_SOFT_RESET__SoftResetAudDone_MASK == + (val & ACP_SOFT_RESET__SoftResetAudDone_MASK)) + break; + if (--count == 0) { + pr_err("Failed to reset ACP\n"); + return -ETIMEDOUT; + } + udelay(100); + } + + /* Enable clock to ACP and wait until the clock is enabled */ + val = acp_reg_read(acp_mmio, mmACP_CONTROL); + val = val | ACP_CONTROL__ClkEn_MASK; + acp_reg_write(val, acp_mmio, mmACP_CONTROL); + + count = ACP_CLOCK_EN_TIME_OUT_VALUE; + + while (true) { + val = acp_reg_read(acp_mmio, mmACP_STATUS); + if (val & (u32)0x1) + break; + if (--count == 0) { + pr_err("Failed to reset ACP\n"); + return -ETIMEDOUT; + } + udelay(100); + } + + /* Deassert the SOFT RESET flags */ + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + val &= ~ACP_SOFT_RESET__SoftResetAud_MASK; + acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET); + + /* For BT instance change pins from UART to BT */ + if (!acp_bt_uart_enable) { + val = acp_reg_read(acp_mmio, mmACP_BT_UART_PAD_SEL); + val |= ACP_BT_UART_PAD_SELECT_MASK; + acp_reg_write(val, acp_mmio, mmACP_BT_UART_PAD_SEL); + } + + /* initialize Onion control DAGB register */ + acp_reg_write(ACP_ONION_CNTL_DEFAULT, acp_mmio, + mmACP_AXI2DAGB_ONION_CNTL); + + /* initialize Garlic control DAGB registers */ + acp_reg_write(ACP_GARLIC_CNTL_DEFAULT, acp_mmio, + mmACP_AXI2DAGB_GARLIC_CNTL); + + sram_pte_offset = ACP_DAGB_GRP_SRAM_BASE_ADDRESS | + ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBSnoopSel_MASK | + ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBTargetMemSel_MASK | + ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBGrpEnable_MASK; + acp_reg_write(sram_pte_offset, acp_mmio, mmACP_DAGB_BASE_ADDR_GRP_1); + acp_reg_write(ACP_PAGE_SIZE_4K_ENABLE, acp_mmio, + mmACP_DAGB_PAGE_SIZE_GRP_1); + + acp_reg_write(ACP_SRAM_BASE_ADDRESS, acp_mmio, + mmACP_DMA_DESC_BASE_ADDR); + + /* Num of descriptors in SRAM 0x4, means 256 descriptors;(64 * 4) */ + acp_reg_write(0x4, acp_mmio, mmACP_DMA_DESC_MAX_NUM_DSCR); + acp_reg_write(ACP_EXTERNAL_INTR_CNTL__DMAIOCMask_MASK, + acp_mmio, mmACP_EXTERNAL_INTR_CNTL); + + /* + * When ACP_TILE_P1 is turned on, all SRAM banks get turned on. + * Now, turn off all of them. This can't be done in 'poweron' of + * ACP pm domain, as this requires ACP to be initialized. + * For Stoney, Memory gating is disabled,i.e SRAM Banks + * won't be turned off. The default state for SRAM banks is ON. + * Setting SRAM bank state code skipped for STONEY platform. + */ + if (asic_type != CHIP_STONEY) { + for (bank = 1; bank < 48; bank++) + acp_set_sram_bank_state(acp_mmio, bank, false); + } + return 0; +} + +/* Deinitialize ACP */ +static int acp_deinit(void __iomem *acp_mmio) +{ + u32 val; + u32 count; + + /* Assert Soft reset of ACP */ + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + + val |= ACP_SOFT_RESET__SoftResetAud_MASK; + acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET); + + count = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (true) { + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + if (ACP_SOFT_RESET__SoftResetAudDone_MASK == + (val & ACP_SOFT_RESET__SoftResetAudDone_MASK)) + break; + if (--count == 0) { + pr_err("Failed to reset ACP\n"); + return -ETIMEDOUT; + } + udelay(100); + } + /* Disable ACP clock */ + val = acp_reg_read(acp_mmio, mmACP_CONTROL); + val &= ~ACP_CONTROL__ClkEn_MASK; + acp_reg_write(val, acp_mmio, mmACP_CONTROL); + + count = ACP_CLOCK_EN_TIME_OUT_VALUE; + + while (true) { + val = acp_reg_read(acp_mmio, mmACP_STATUS); + if (!(val & (u32)0x1)) + break; + if (--count == 0) { + pr_err("Failed to reset ACP\n"); + return -ETIMEDOUT; + } + udelay(100); + } + return 0; +} + +/* ACP DMA irq handler routine for playback, capture usecases */ +static irqreturn_t dma_irq_handler(int irq, void *arg) +{ + u16 dscr_idx; + u32 intr_flag, ext_intr_status; + struct audio_drv_data *irq_data; + void __iomem *acp_mmio; + struct device *dev = arg; + bool valid_irq = false; + + irq_data = dev_get_drvdata(dev); + acp_mmio = irq_data->acp_mmio; + + ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT); + intr_flag = (((ext_intr_status & + ACP_EXTERNAL_INTR_STAT__DMAIOCStat_MASK) >> + ACP_EXTERNAL_INTR_STAT__DMAIOCStat__SHIFT)); + + if ((intr_flag & BIT(ACP_TO_I2S_DMA_CH_NUM)) != 0) { + valid_irq = true; + snd_pcm_period_elapsed(irq_data->play_i2ssp_stream); + acp_reg_write((intr_flag & BIT(ACP_TO_I2S_DMA_CH_NUM)) << 16, + acp_mmio, mmACP_EXTERNAL_INTR_STAT); + } + + if ((intr_flag & BIT(ACP_TO_I2S_DMA_MICSP_INSTANCE_CH_NUM)) != 0) { + valid_irq = true; + snd_pcm_period_elapsed(irq_data->play_i2s_micsp_stream); + acp_reg_write((intr_flag & BIT(ACP_TO_I2S_DMA_MICSP_INSTANCE_CH_NUM)) << 16, + acp_mmio, mmACP_EXTERNAL_INTR_STAT); + } + + if ((intr_flag & BIT(ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM)) != 0) { + valid_irq = true; + snd_pcm_period_elapsed(irq_data->play_i2sbt_stream); + acp_reg_write((intr_flag & + BIT(ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM)) << 16, + acp_mmio, mmACP_EXTERNAL_INTR_STAT); + } + + if ((intr_flag & BIT(I2S_TO_ACP_DMA_CH_NUM)) != 0) { + valid_irq = true; + if (acp_reg_read(acp_mmio, mmACP_DMA_CUR_DSCR_14) == + CAPTURE_START_DMA_DESCR_CH15) + dscr_idx = CAPTURE_END_DMA_DESCR_CH14; + else + dscr_idx = CAPTURE_START_DMA_DESCR_CH14; + config_acp_dma_channel(acp_mmio, ACP_TO_SYSRAM_CH_NUM, dscr_idx, + 1, 0); + acp_dma_start(acp_mmio, ACP_TO_SYSRAM_CH_NUM, false); + + snd_pcm_period_elapsed(irq_data->capture_i2ssp_stream); + acp_reg_write((intr_flag & BIT(I2S_TO_ACP_DMA_CH_NUM)) << 16, + acp_mmio, mmACP_EXTERNAL_INTR_STAT); + } + + if ((intr_flag & BIT(I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM)) != 0) { + valid_irq = true; + if (acp_reg_read(acp_mmio, mmACP_DMA_CUR_DSCR_10) == + CAPTURE_START_DMA_DESCR_CH11) + dscr_idx = CAPTURE_END_DMA_DESCR_CH10; + else + dscr_idx = CAPTURE_START_DMA_DESCR_CH10; + config_acp_dma_channel(acp_mmio, + ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM, + dscr_idx, 1, 0); + acp_dma_start(acp_mmio, ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM, + false); + + snd_pcm_period_elapsed(irq_data->capture_i2sbt_stream); + acp_reg_write((intr_flag & + BIT(I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM)) << 16, + acp_mmio, mmACP_EXTERNAL_INTR_STAT); + } + + if (valid_irq) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static int acp_dma_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + u16 bank; + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_drv_data *intr_data = dev_get_drvdata(component->dev); + struct audio_substream_data *adata = + kzalloc(sizeof(struct audio_substream_data), GFP_KERNEL); + if (!adata) + return -ENOMEM; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (intr_data->asic_type) { + case CHIP_STONEY: + runtime->hw = acp_st_pcm_hardware_playback; + break; + default: + runtime->hw = acp_pcm_hardware_playback; + } + } else { + switch (intr_data->asic_type) { + case CHIP_STONEY: + runtime->hw = acp_st_pcm_hardware_capture; + break; + default: + runtime->hw = acp_pcm_hardware_capture; + } + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(component->dev, "set integer constraint failed\n"); + kfree(adata); + return ret; + } + + adata->acp_mmio = intr_data->acp_mmio; + runtime->private_data = adata; + + /* + * Enable ACP irq, when neither playback or capture streams are + * active by the time when a new stream is being opened. + * This enablement is not required for another stream, if current + * stream is not closed + */ + if (!intr_data->play_i2ssp_stream && !intr_data->capture_i2ssp_stream && + !intr_data->play_i2sbt_stream && !intr_data->capture_i2sbt_stream && + !intr_data->play_i2s_micsp_stream) + acp_reg_write(1, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * For Stoney, Memory gating is disabled,i.e SRAM Banks + * won't be turned off. The default state for SRAM banks is ON. + * Setting SRAM bank state code skipped for STONEY platform. + */ + if (intr_data->asic_type != CHIP_STONEY) { + for (bank = 1; bank <= 4; bank++) + acp_set_sram_bank_state(intr_data->acp_mmio, + bank, true); + } + } else { + if (intr_data->asic_type != CHIP_STONEY) { + for (bank = 5; bank <= 8; bank++) + acp_set_sram_bank_state(intr_data->acp_mmio, + bank, true); + } + } + + return 0; +} + +static int acp_dma_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + uint64_t size; + u32 val = 0; + struct snd_pcm_runtime *runtime; + struct audio_substream_data *rtd; + struct snd_soc_pcm_runtime *prtd = asoc_substream_to_rtd(substream); + struct audio_drv_data *adata = dev_get_drvdata(component->dev); + struct snd_soc_card *card = prtd->card; + struct acp_platform_info *pinfo = snd_soc_card_get_drvdata(card); + + runtime = substream->runtime; + rtd = runtime->private_data; + + if (WARN_ON(!rtd)) + return -EINVAL; + + if (pinfo) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + rtd->i2s_instance = pinfo->play_i2s_instance; + } else { + rtd->i2s_instance = pinfo->cap_i2s_instance; + rtd->capture_channel = pinfo->capture_channel; + } + } + if (adata->asic_type == CHIP_STONEY) { + val = acp_reg_read(adata->acp_mmio, + mmACP_I2S_16BIT_RESOLUTION_EN); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + val |= ACP_I2S_BT_16BIT_RESOLUTION_EN; + break; + case I2S_MICSP_INSTANCE: + val |= ACP_I2S_MICSP_16BIT_RESOLUTION_EN; + break; + case I2S_SP_INSTANCE: + default: + val |= ACP_I2S_SP_16BIT_RESOLUTION_EN; + } + } else { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + val |= ACP_I2S_BT_16BIT_RESOLUTION_EN; + break; + case I2S_MICSP_INSTANCE: + case I2S_SP_INSTANCE: + default: + val |= ACP_I2S_MIC_16BIT_RESOLUTION_EN; + } + } + acp_reg_write(val, adata->acp_mmio, + mmACP_I2S_16BIT_RESOLUTION_EN); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + rtd->pte_offset = ACP_ST_BT_PLAYBACK_PTE_OFFSET; + rtd->ch1 = SYSRAM_TO_ACP_BT_INSTANCE_CH_NUM; + rtd->ch2 = ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM; + rtd->sram_bank = ACP_SRAM_BANK_3_ADDRESS; + rtd->destination = TO_BLUETOOTH; + rtd->dma_dscr_idx_1 = PLAYBACK_START_DMA_DESCR_CH8; + rtd->dma_dscr_idx_2 = PLAYBACK_START_DMA_DESCR_CH9; + rtd->byte_cnt_high_reg_offset = + mmACP_I2S_BT_TRANSMIT_BYTE_CNT_HIGH; + rtd->byte_cnt_low_reg_offset = + mmACP_I2S_BT_TRANSMIT_BYTE_CNT_LOW; + adata->play_i2sbt_stream = substream; + break; + case I2S_MICSP_INSTANCE: + switch (adata->asic_type) { + case CHIP_STONEY: + rtd->pte_offset = ACP_ST_PLAYBACK_PTE_OFFSET; + break; + default: + rtd->pte_offset = ACP_PLAYBACK_PTE_OFFSET; + } + rtd->ch1 = SYSRAM_TO_ACP_MICSP_INSTANCE_CH_NUM; + rtd->ch2 = ACP_TO_I2S_DMA_MICSP_INSTANCE_CH_NUM; + rtd->sram_bank = ACP_SRAM_BANK_1_ADDRESS; + rtd->destination = TO_ACP_I2S_2; + rtd->dma_dscr_idx_1 = PLAYBACK_START_DMA_DESCR_CH4; + rtd->dma_dscr_idx_2 = PLAYBACK_START_DMA_DESCR_CH5; + rtd->byte_cnt_high_reg_offset = + mmACP_I2S_MICSP_TRANSMIT_BYTE_CNT_HIGH; + rtd->byte_cnt_low_reg_offset = + mmACP_I2S_MICSP_TRANSMIT_BYTE_CNT_LOW; + + adata->play_i2s_micsp_stream = substream; + break; + case I2S_SP_INSTANCE: + default: + switch (adata->asic_type) { + case CHIP_STONEY: + rtd->pte_offset = ACP_ST_PLAYBACK_PTE_OFFSET; + break; + default: + rtd->pte_offset = ACP_PLAYBACK_PTE_OFFSET; + } + rtd->ch1 = SYSRAM_TO_ACP_CH_NUM; + rtd->ch2 = ACP_TO_I2S_DMA_CH_NUM; + rtd->sram_bank = ACP_SRAM_BANK_1_ADDRESS; + rtd->destination = TO_ACP_I2S_1; + rtd->dma_dscr_idx_1 = PLAYBACK_START_DMA_DESCR_CH12; + rtd->dma_dscr_idx_2 = PLAYBACK_START_DMA_DESCR_CH13; + rtd->byte_cnt_high_reg_offset = + mmACP_I2S_TRANSMIT_BYTE_CNT_HIGH; + rtd->byte_cnt_low_reg_offset = + mmACP_I2S_TRANSMIT_BYTE_CNT_LOW; + adata->play_i2ssp_stream = substream; + } + } else { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + rtd->pte_offset = ACP_ST_BT_CAPTURE_PTE_OFFSET; + rtd->ch1 = I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM; + rtd->ch2 = ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM; + rtd->sram_bank = ACP_SRAM_BANK_4_ADDRESS; + rtd->destination = FROM_BLUETOOTH; + rtd->dma_dscr_idx_1 = CAPTURE_START_DMA_DESCR_CH10; + rtd->dma_dscr_idx_2 = CAPTURE_START_DMA_DESCR_CH11; + rtd->byte_cnt_high_reg_offset = + mmACP_I2S_BT_RECEIVE_BYTE_CNT_HIGH; + rtd->byte_cnt_low_reg_offset = + mmACP_I2S_BT_RECEIVE_BYTE_CNT_LOW; + rtd->dma_curr_dscr = mmACP_DMA_CUR_DSCR_11; + adata->capture_i2sbt_stream = substream; + break; + case I2S_MICSP_INSTANCE: + case I2S_SP_INSTANCE: + default: + rtd->pte_offset = ACP_CAPTURE_PTE_OFFSET; + rtd->ch1 = I2S_TO_ACP_DMA_CH_NUM; + rtd->ch2 = ACP_TO_SYSRAM_CH_NUM; + switch (adata->asic_type) { + case CHIP_STONEY: + rtd->pte_offset = ACP_ST_CAPTURE_PTE_OFFSET; + rtd->sram_bank = ACP_SRAM_BANK_2_ADDRESS; + break; + default: + rtd->pte_offset = ACP_CAPTURE_PTE_OFFSET; + rtd->sram_bank = ACP_SRAM_BANK_5_ADDRESS; + } + rtd->destination = FROM_ACP_I2S_1; + rtd->dma_dscr_idx_1 = CAPTURE_START_DMA_DESCR_CH14; + rtd->dma_dscr_idx_2 = CAPTURE_START_DMA_DESCR_CH15; + rtd->byte_cnt_high_reg_offset = + mmACP_I2S_RECEIVED_BYTE_CNT_HIGH; + rtd->byte_cnt_low_reg_offset = + mmACP_I2S_RECEIVED_BYTE_CNT_LOW; + rtd->dma_curr_dscr = mmACP_DMA_CUR_DSCR_15; + adata->capture_i2ssp_stream = substream; + } + } + + size = params_buffer_bytes(params); + + acp_set_sram_bank_state(rtd->acp_mmio, 0, true); + /* Save for runtime private data */ + rtd->dma_addr = runtime->dma_addr; + rtd->order = get_order(size); + + /* Fill the page table entries in ACP SRAM */ + rtd->size = size; + rtd->num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; + rtd->direction = substream->stream; + + config_acp_dma(rtd->acp_mmio, rtd, adata->asic_type); + return 0; +} + +static u64 acp_get_byte_count(struct audio_substream_data *rtd) +{ + union acp_dma_count byte_count; + + byte_count.bcount.high = acp_reg_read(rtd->acp_mmio, + rtd->byte_cnt_high_reg_offset); + byte_count.bcount.low = acp_reg_read(rtd->acp_mmio, + rtd->byte_cnt_low_reg_offset); + return byte_count.bytescount; +} + +static snd_pcm_uframes_t acp_dma_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + u32 buffersize; + u32 pos = 0; + u64 bytescount = 0; + u16 dscr; + u32 period_bytes, delay; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + struct audio_drv_data *adata = dev_get_drvdata(component->dev); + + if (!rtd) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + period_bytes = frames_to_bytes(runtime, runtime->period_size); + bytescount = acp_get_byte_count(rtd); + if (bytescount >= rtd->bytescount) + bytescount -= rtd->bytescount; + if (bytescount < period_bytes) { + pos = 0; + } else { + dscr = acp_reg_read(rtd->acp_mmio, rtd->dma_curr_dscr); + if (dscr == rtd->dma_dscr_idx_1) + pos = period_bytes; + else + pos = 0; + } + if (bytescount > 0) { + delay = do_div(bytescount, period_bytes); + adata->delay += bytes_to_frames(runtime, delay); + } + } else { + buffersize = frames_to_bytes(runtime, runtime->buffer_size); + bytescount = acp_get_byte_count(rtd); + if (bytescount > rtd->bytescount) + bytescount -= rtd->bytescount; + pos = do_div(bytescount, buffersize); + } + return bytes_to_frames(runtime, pos); +} + +static snd_pcm_sframes_t acp_dma_delay(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct audio_drv_data *adata = dev_get_drvdata(component->dev); + snd_pcm_sframes_t delay = adata->delay; + + adata->delay = 0; + + return delay; +} + +static int acp_dma_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + u16 ch_acp_sysmem, ch_acp_i2s; + + if (!rtd) + return -EINVAL; + + if (rtd->direction == SNDRV_PCM_STREAM_PLAYBACK) { + ch_acp_sysmem = rtd->ch1; + ch_acp_i2s = rtd->ch2; + } else { + ch_acp_i2s = rtd->ch1; + ch_acp_sysmem = rtd->ch2; + } + config_acp_dma_channel(rtd->acp_mmio, + ch_acp_sysmem, + rtd->dma_dscr_idx_1, + NUM_DSCRS_PER_CHANNEL, 0); + config_acp_dma_channel(rtd->acp_mmio, + ch_acp_i2s, + rtd->dma_dscr_idx_2, + NUM_DSCRS_PER_CHANNEL, 0); + return 0; +} + +static int acp_dma_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + int ret; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + + if (!rtd) + return -EINVAL; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + rtd->bytescount = acp_get_byte_count(rtd); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (rtd->capture_channel == CAP_CHANNEL0) { + acp_dma_cap_channel_disable(rtd->acp_mmio, + CAP_CHANNEL1); + acp_dma_cap_channel_enable(rtd->acp_mmio, + CAP_CHANNEL0); + } + if (rtd->capture_channel == CAP_CHANNEL1) { + acp_dma_cap_channel_disable(rtd->acp_mmio, + CAP_CHANNEL0); + acp_dma_cap_channel_enable(rtd->acp_mmio, + CAP_CHANNEL1); + } + acp_dma_start(rtd->acp_mmio, rtd->ch1, true); + } else { + acp_dma_start(rtd->acp_mmio, rtd->ch1, true); + acp_dma_start(rtd->acp_mmio, rtd->ch2, true); + } + ret = 0; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + acp_dma_stop(rtd->acp_mmio, rtd->ch2); + ret = acp_dma_stop(rtd->acp_mmio, rtd->ch1); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static int acp_dma_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct audio_drv_data *adata = dev_get_drvdata(component->dev); + struct device *parent = component->dev->parent; + + switch (adata->asic_type) { + case CHIP_STONEY: + snd_pcm_set_managed_buffer_all(rtd->pcm, + SNDRV_DMA_TYPE_DEV, + parent, + ST_MIN_BUFFER, + ST_MAX_BUFFER); + break; + default: + snd_pcm_set_managed_buffer_all(rtd->pcm, + SNDRV_DMA_TYPE_DEV, + parent, + MIN_BUFFER, + MAX_BUFFER); + break; + } + return 0; +} + +static int acp_dma_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + u16 bank; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + struct audio_drv_data *adata = dev_get_drvdata(component->dev); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + adata->play_i2sbt_stream = NULL; + break; + case I2S_MICSP_INSTANCE: + adata->play_i2s_micsp_stream = NULL; + break; + case I2S_SP_INSTANCE: + default: + adata->play_i2ssp_stream = NULL; + /* + * For Stoney, Memory gating is disabled,i.e SRAM Banks + * won't be turned off. The default state for SRAM banks + * is ON.Setting SRAM bank state code skipped for STONEY + * platform. Added condition checks for Carrizo platform + * only. + */ + if (adata->asic_type != CHIP_STONEY) { + for (bank = 1; bank <= 4; bank++) + acp_set_sram_bank_state(adata->acp_mmio, + bank, false); + } + } + } else { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + adata->capture_i2sbt_stream = NULL; + break; + case I2S_MICSP_INSTANCE: + case I2S_SP_INSTANCE: + default: + adata->capture_i2ssp_stream = NULL; + if (adata->asic_type != CHIP_STONEY) { + for (bank = 5; bank <= 8; bank++) + acp_set_sram_bank_state(adata->acp_mmio, + bank, false); + } + } + } + + /* + * Disable ACP irq, when the current stream is being closed and + * another stream is also not active. + */ + if (!adata->play_i2ssp_stream && !adata->capture_i2ssp_stream && + !adata->play_i2sbt_stream && !adata->capture_i2sbt_stream && + !adata->play_i2s_micsp_stream) + acp_reg_write(0, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + kfree(rtd); + return 0; +} + +static const struct snd_soc_component_driver acp_asoc_platform = { + .name = DRV_NAME, + .open = acp_dma_open, + .close = acp_dma_close, + .hw_params = acp_dma_hw_params, + .trigger = acp_dma_trigger, + .pointer = acp_dma_pointer, + .delay = acp_dma_delay, + .prepare = acp_dma_prepare, + .pcm_construct = acp_dma_new, +}; + +static int acp_audio_probe(struct platform_device *pdev) +{ + int status, irq; + struct audio_drv_data *audio_drv_data; + const u32 *pdata = pdev->dev.platform_data; + + if (!pdata) { + dev_err(&pdev->dev, "Missing platform data\n"); + return -ENODEV; + } + + audio_drv_data = devm_kzalloc(&pdev->dev, sizeof(struct audio_drv_data), + GFP_KERNEL); + if (!audio_drv_data) + return -ENOMEM; + + audio_drv_data->acp_mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(audio_drv_data->acp_mmio)) + return PTR_ERR(audio_drv_data->acp_mmio); + + /* + * The following members gets populated in device 'open' + * function. Till then interrupts are disabled in 'acp_init' + * and device doesn't generate any interrupts. + */ + + audio_drv_data->play_i2ssp_stream = NULL; + audio_drv_data->capture_i2ssp_stream = NULL; + audio_drv_data->play_i2sbt_stream = NULL; + audio_drv_data->capture_i2sbt_stream = NULL; + audio_drv_data->play_i2s_micsp_stream = NULL; + + audio_drv_data->asic_type = *pdata; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -ENODEV; + + status = devm_request_irq(&pdev->dev, irq, dma_irq_handler, + 0, "ACP_IRQ", &pdev->dev); + if (status) { + dev_err(&pdev->dev, "ACP IRQ request failed\n"); + return status; + } + + dev_set_drvdata(&pdev->dev, audio_drv_data); + + /* Initialize the ACP */ + status = acp_init(audio_drv_data->acp_mmio, audio_drv_data->asic_type); + if (status) { + dev_err(&pdev->dev, "ACP Init failed status:%d\n", status); + return status; + } + + status = devm_snd_soc_register_component(&pdev->dev, + &acp_asoc_platform, NULL, 0); + if (status != 0) { + dev_err(&pdev->dev, "Fail to register ALSA platform device\n"); + return status; + } + + pm_runtime_set_autosuspend_delay(&pdev->dev, 10000); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return status; +} + +static int acp_audio_remove(struct platform_device *pdev) +{ + int status; + struct audio_drv_data *adata = dev_get_drvdata(&pdev->dev); + + status = acp_deinit(adata->acp_mmio); + if (status) + dev_err(&pdev->dev, "ACP Deinit failed status:%d\n", status); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int acp_pcm_resume(struct device *dev) +{ + u16 bank; + int status; + struct audio_substream_data *rtd; + struct audio_drv_data *adata = dev_get_drvdata(dev); + + status = acp_init(adata->acp_mmio, adata->asic_type); + if (status) { + dev_err(dev, "ACP Init failed status:%d\n", status); + return status; + } + + if (adata->play_i2ssp_stream && adata->play_i2ssp_stream->runtime) { + /* + * For Stoney, Memory gating is disabled,i.e SRAM Banks + * won't be turned off. The default state for SRAM banks is ON. + * Setting SRAM bank state code skipped for STONEY platform. + */ + if (adata->asic_type != CHIP_STONEY) { + for (bank = 1; bank <= 4; bank++) + acp_set_sram_bank_state(adata->acp_mmio, bank, + true); + } + rtd = adata->play_i2ssp_stream->runtime->private_data; + config_acp_dma(adata->acp_mmio, rtd, adata->asic_type); + } + if (adata->capture_i2ssp_stream && + adata->capture_i2ssp_stream->runtime) { + if (adata->asic_type != CHIP_STONEY) { + for (bank = 5; bank <= 8; bank++) + acp_set_sram_bank_state(adata->acp_mmio, bank, + true); + } + rtd = adata->capture_i2ssp_stream->runtime->private_data; + config_acp_dma(adata->acp_mmio, rtd, adata->asic_type); + } + if (adata->asic_type != CHIP_CARRIZO) { + if (adata->play_i2s_micsp_stream && + adata->play_i2s_micsp_stream->runtime) { + rtd = adata->play_i2s_micsp_stream->runtime->private_data; + config_acp_dma(adata->acp_mmio, rtd, adata->asic_type); + } + if (adata->play_i2sbt_stream && + adata->play_i2sbt_stream->runtime) { + rtd = adata->play_i2sbt_stream->runtime->private_data; + config_acp_dma(adata->acp_mmio, rtd, adata->asic_type); + } + if (adata->capture_i2sbt_stream && + adata->capture_i2sbt_stream->runtime) { + rtd = adata->capture_i2sbt_stream->runtime->private_data; + config_acp_dma(adata->acp_mmio, rtd, adata->asic_type); + } + } + acp_reg_write(1, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + return 0; +} + +static int acp_pcm_runtime_suspend(struct device *dev) +{ + int status; + struct audio_drv_data *adata = dev_get_drvdata(dev); + + status = acp_deinit(adata->acp_mmio); + if (status) + dev_err(dev, "ACP Deinit failed status:%d\n", status); + acp_reg_write(0, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + return 0; +} + +static int acp_pcm_runtime_resume(struct device *dev) +{ + int status; + struct audio_drv_data *adata = dev_get_drvdata(dev); + + status = acp_init(adata->acp_mmio, adata->asic_type); + if (status) { + dev_err(dev, "ACP Init failed status:%d\n", status); + return status; + } + acp_reg_write(1, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + return 0; +} + +static const struct dev_pm_ops acp_pm_ops = { + .resume = acp_pcm_resume, + .runtime_suspend = acp_pcm_runtime_suspend, + .runtime_resume = acp_pcm_runtime_resume, +}; + +static struct platform_driver acp_dma_driver = { + .probe = acp_audio_probe, + .remove = acp_audio_remove, + .driver = { + .name = DRV_NAME, + .pm = &acp_pm_ops, + }, +}; + +module_platform_driver(acp_dma_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com"); +MODULE_DESCRIPTION("AMD ACP PCM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:"DRV_NAME); diff --git a/sound/soc/amd/acp-rt5645.c b/sound/soc/amd/acp-rt5645.c new file mode 100644 index 000000000..532aa98a2 --- /dev/null +++ b/sound/soc/amd/acp-rt5645.c @@ -0,0 +1,206 @@ +/* + * Machine driver for AMD ACP Audio engine using Realtek RT5645 codec + * + * Copyright 2017 Advanced Micro Devices, Inc. + * + * This file is modified from rt288 machine driver + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + */ + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/acpi.h> + +#include "../codecs/rt5645.h" + +#define CZ_PLAT_CLK 24000000 + +static struct snd_soc_jack cz_jack; + +static int cz_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK, + CZ_PLAT_CLK, params_rate(params) * 512); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1, + params_rate(params) * 512, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + return ret; +} + +static int cz_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_card *card; + struct snd_soc_component *codec; + + codec = asoc_rtd_to_codec(rtd, 0)->component; + card = rtd->card; + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &cz_jack); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + rt5645_set_jack_detect(codec, &cz_jack, &cz_jack, &cz_jack); + + return 0; +} + +static const struct snd_soc_ops cz_aif1_ops = { + .hw_params = cz_aif1_hw_params, +}; + +SND_SOC_DAILINK_DEF(designware1, + DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.1.auto"))); +SND_SOC_DAILINK_DEF(designware2, + DAILINK_COMP_ARRAY(COMP_CPU("designware-i2s.2.auto"))); + +SND_SOC_DAILINK_DEF(codec, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5650:00", "rt5645-aif1"))); + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_audio_dma.0.auto"))); + +static struct snd_soc_dai_link cz_dai_rt5650[] = { + { + .name = "amd-rt5645-play", + .stream_name = "RT5645_AIF1", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .init = cz_init, + .ops = &cz_aif1_ops, + SND_SOC_DAILINK_REG(designware1, codec, platform), + }, + { + .name = "amd-rt5645-cap", + .stream_name = "RT5645_AIF1", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .ops = &cz_aif1_ops, + SND_SOC_DAILINK_REG(designware2, codec, platform), + }, +}; + +static const struct snd_soc_dapm_widget cz_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), +}; + +static const struct snd_soc_dapm_route cz_audio_route[] = { + {"Headphones", NULL, "HPOL"}, + {"Headphones", NULL, "HPOR"}, + {"RECMIXL", NULL, "Headset Mic"}, + {"RECMIXR", NULL, "Headset Mic"}, + {"Speakers", NULL, "SPOL"}, + {"Speakers", NULL, "SPOR"}, + {"DMIC L2", NULL, "Int Mic"}, + {"DMIC R2", NULL, "Int Mic"}, +}; + +static const struct snd_kcontrol_new cz_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static struct snd_soc_card cz_card = { + .name = "acprt5650", + .owner = THIS_MODULE, + .dai_link = cz_dai_rt5650, + .num_links = ARRAY_SIZE(cz_dai_rt5650), + .dapm_widgets = cz_widgets, + .num_dapm_widgets = ARRAY_SIZE(cz_widgets), + .dapm_routes = cz_audio_route, + .num_dapm_routes = ARRAY_SIZE(cz_audio_route), + .controls = cz_mc_controls, + .num_controls = ARRAY_SIZE(cz_mc_controls), +}; + +static int cz_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card; + + card = &cz_card; + cz_card.dev = &pdev->dev; + platform_set_drvdata(pdev, card); + ret = devm_snd_soc_register_card(&pdev->dev, &cz_card); + if (ret) { + dev_err(&pdev->dev, + "devm_snd_soc_register_card(%s) failed: %d\n", + cz_card.name, ret); + return ret; + } + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cz_audio_acpi_match[] = { + { "AMDI1002", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, cz_audio_acpi_match); +#endif + +static struct platform_driver cz_pcm_driver = { + .driver = { + .name = "cz-rt5645", + .acpi_match_table = ACPI_PTR(cz_audio_acpi_match), + .pm = &snd_soc_pm_ops, + }, + .probe = cz_probe, +}; + +module_platform_driver(cz_pcm_driver); + +MODULE_AUTHOR("akshu.agrawal@amd.com"); +MODULE_DESCRIPTION("cz-rt5645 audio support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp.h b/sound/soc/amd/acp.h new file mode 100644 index 000000000..b29bef90f --- /dev/null +++ b/sound/soc/amd/acp.h @@ -0,0 +1,223 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ACP_HW_H +#define __ACP_HW_H + +#include "include/acp_2_2_d.h" +#include "include/acp_2_2_sh_mask.h" + +#define ACP_PAGE_SIZE_4K_ENABLE 0x02 + +#define ACP_PLAYBACK_PTE_OFFSET 10 +#define ACP_CAPTURE_PTE_OFFSET 0 + +/* Playback and Capture Offset for Stoney */ +#define ACP_ST_PLAYBACK_PTE_OFFSET 0x04 +#define ACP_ST_CAPTURE_PTE_OFFSET 0x00 +#define ACP_ST_BT_PLAYBACK_PTE_OFFSET 0x08 +#define ACP_ST_BT_CAPTURE_PTE_OFFSET 0x0c + +#define ACP_GARLIC_CNTL_DEFAULT 0x00000FB4 +#define ACP_ONION_CNTL_DEFAULT 0x00000FB4 + +#define ACP_PHYSICAL_BASE 0x14000 + +/* + * In case of I2S SP controller instance, Stoney uses SRAM bank 1 for + * playback and SRAM Bank 2 for capture where as in case of BT I2S + * Instance, Stoney uses SRAM Bank 3 for playback & SRAM Bank 4 will + * be used for capture. Carrizo uses I2S SP controller instance. SRAM Banks + * 1, 2, 3, 4 will be used for playback & SRAM Banks 5, 6, 7, 8 will be used + * for capture scenario. + */ +#define ACP_SRAM_BANK_1_ADDRESS 0x4002000 +#define ACP_SRAM_BANK_2_ADDRESS 0x4004000 +#define ACP_SRAM_BANK_3_ADDRESS 0x4006000 +#define ACP_SRAM_BANK_4_ADDRESS 0x4008000 +#define ACP_SRAM_BANK_5_ADDRESS 0x400A000 + +#define ACP_DMA_RESET_TIME 10000 +#define ACP_CLOCK_EN_TIME_OUT_VALUE 0x000000FF +#define ACP_SOFT_RESET_DONE_TIME_OUT_VALUE 0x000000FF +#define ACP_DMA_COMPLETE_TIME_OUT_VALUE 0x000000FF + +#define ACP_SRAM_BASE_ADDRESS 0x4000000 +#define ACP_DAGB_GRP_SRAM_BASE_ADDRESS 0x4001000 +#define ACP_DAGB_GRP_SRBM_SRAM_BASE_OFFSET 0x1000 +#define ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS 0x00000000 +#define ACP_INTERNAL_APERTURE_WINDOW_4_ADDRESS 0x01800000 + +#define TO_ACP_I2S_1 0x2 +#define TO_ACP_I2S_2 0x4 +#define TO_BLUETOOTH 0x3 +#define FROM_ACP_I2S_1 0xa +#define FROM_ACP_I2S_2 0xb +#define FROM_BLUETOOTH 0xb + +#define I2S_SP_INSTANCE 0x01 +#define I2S_BT_INSTANCE 0x02 +#define I2S_MICSP_INSTANCE 0x03 +#define CAP_CHANNEL0 0x00 +#define CAP_CHANNEL1 0x01 + +#define ACP_TILE_ON_MASK 0x03 +#define ACP_TILE_OFF_MASK 0x02 +#define ACP_TILE_ON_RETAIN_REG_MASK 0x1f +#define ACP_TILE_OFF_RETAIN_REG_MASK 0x20 + +#define ACP_TILE_P1_MASK 0x3e +#define ACP_TILE_P2_MASK 0x3d +#define ACP_TILE_DSP0_MASK 0x3b +#define ACP_TILE_DSP1_MASK 0x37 + +#define ACP_TILE_DSP2_MASK 0x2f +/* Playback DMA channels */ +#define SYSRAM_TO_ACP_CH_NUM 12 +#define ACP_TO_I2S_DMA_CH_NUM 13 + +/* Capture DMA channels */ +#define I2S_TO_ACP_DMA_CH_NUM 14 +#define ACP_TO_SYSRAM_CH_NUM 15 + +/* Playback DMA Channels for I2S BT instance */ +#define SYSRAM_TO_ACP_BT_INSTANCE_CH_NUM 8 +#define ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM 9 + +/* Capture DMA Channels for I2S BT Instance */ +#define I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM 10 +#define ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM 11 + +/* Playback DMA channels for I2S MICSP instance */ +#define SYSRAM_TO_ACP_MICSP_INSTANCE_CH_NUM 4 +#define ACP_TO_I2S_DMA_MICSP_INSTANCE_CH_NUM 5 + +#define NUM_DSCRS_PER_CHANNEL 2 + +#define PLAYBACK_START_DMA_DESCR_CH12 0 +#define PLAYBACK_END_DMA_DESCR_CH12 1 +#define PLAYBACK_START_DMA_DESCR_CH13 2 +#define PLAYBACK_END_DMA_DESCR_CH13 3 + +#define CAPTURE_START_DMA_DESCR_CH14 4 +#define CAPTURE_END_DMA_DESCR_CH14 5 +#define CAPTURE_START_DMA_DESCR_CH15 6 +#define CAPTURE_END_DMA_DESCR_CH15 7 + +/* I2S BT Instance DMA Descriptors */ +#define PLAYBACK_START_DMA_DESCR_CH8 8 +#define PLAYBACK_END_DMA_DESCR_CH8 9 +#define PLAYBACK_START_DMA_DESCR_CH9 10 +#define PLAYBACK_END_DMA_DESCR_CH9 11 + +#define CAPTURE_START_DMA_DESCR_CH10 12 +#define CAPTURE_END_DMA_DESCR_CH10 13 +#define CAPTURE_START_DMA_DESCR_CH11 14 +#define CAPTURE_END_DMA_DESCR_CH11 15 + +/* I2S MICSP Instance DMA Descriptors */ +#define PLAYBACK_START_DMA_DESCR_CH4 0 +#define PLAYBACK_END_DMA_DESCR_CH4 1 +#define PLAYBACK_START_DMA_DESCR_CH5 2 +#define PLAYBACK_END_DMA_DESCR_CH5 3 + +#define mmACP_I2S_16BIT_RESOLUTION_EN 0x5209 +#define ACP_I2S_MIC_16BIT_RESOLUTION_EN 0x01 +#define ACP_I2S_MICSP_16BIT_RESOLUTION_EN 0x01 +#define ACP_I2S_SP_16BIT_RESOLUTION_EN 0x02 +#define ACP_I2S_BT_16BIT_RESOLUTION_EN 0x04 +#define ACP_BT_UART_PAD_SELECT_MASK 0x1 + +enum acp_dma_priority_level { + /* 0x0 Specifies the DMA channel is given normal priority */ + ACP_DMA_PRIORITY_LEVEL_NORMAL = 0x0, + /* 0x1 Specifies the DMA channel is given high priority */ + ACP_DMA_PRIORITY_LEVEL_HIGH = 0x1, + ACP_DMA_PRIORITY_LEVEL_FORCESIZE = 0xFF +}; + +struct audio_substream_data { + dma_addr_t dma_addr; + unsigned int order; + u16 num_of_pages; + u16 i2s_instance; + u16 capture_channel; + u16 direction; + u16 ch1; + u16 ch2; + u16 destination; + u16 dma_dscr_idx_1; + u16 dma_dscr_idx_2; + u32 pte_offset; + u32 sram_bank; + u32 byte_cnt_high_reg_offset; + u32 byte_cnt_low_reg_offset; + u32 dma_curr_dscr; + uint64_t size; + u64 bytescount; + void __iomem *acp_mmio; +}; + +struct audio_drv_data { + struct snd_pcm_substream *play_i2ssp_stream; + struct snd_pcm_substream *capture_i2ssp_stream; + struct snd_pcm_substream *play_i2sbt_stream; + struct snd_pcm_substream *capture_i2sbt_stream; + struct snd_pcm_substream *play_i2s_micsp_stream; + void __iomem *acp_mmio; + u32 asic_type; + snd_pcm_sframes_t delay; +}; + +/* + * this structure used for platform data transfer between machine driver + * and dma driver + */ +struct acp_platform_info { + u16 play_i2s_instance; + u16 cap_i2s_instance; + u16 capture_channel; +}; + +union acp_dma_count { + struct { + u32 low; + u32 high; + } bcount; + u64 bytescount; +}; + +enum { + ACP_TILE_P1 = 0, + ACP_TILE_P2, + ACP_TILE_DSP0, + ACP_TILE_DSP1, + ACP_TILE_DSP2, +}; + +enum { + ACP_DMA_ATTR_SHAREDMEM_TO_DAGB_ONION = 0x0, + ACP_DMA_ATTR_SHARED_MEM_TO_DAGB_GARLIC = 0x1, + ACP_DMA_ATTR_DAGB_ONION_TO_SHAREDMEM = 0x8, + ACP_DMA_ATTR_DAGB_GARLIC_TO_SHAREDMEM = 0x9, + ACP_DMA_ATTR_FORCE_SIZE = 0xF +}; + +typedef struct acp_dma_dscr_transfer { + /* Specifies the source memory location for the DMA data transfer. */ + u32 src; + /* + * Specifies the destination memory location to where the data will + * be transferred. + */ + u32 dest; + /* + * Specifies the number of bytes need to be transferred + * from source to destination memory.Transfer direction & IOC enable + */ + u32 xfer_val; + /* Reserved for future use */ + u32 reserved; +} acp_dma_dscr_transfer_t; + +extern bool acp_bt_uart_enable; + +#endif /*__ACP_HW_H */ diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig new file mode 100644 index 000000000..ce0037810 --- /dev/null +++ b/sound/soc/amd/acp/Kconfig @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +# This file is provided under a dual BSD/GPLv2 license. When using or +# redistributing this file, you may do so under either license. +# +# Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. +# + +config SND_SOC_AMD_ACP_COMMON + tristate "AMD Audio ACP Common support" + select SND_AMD_ACP_CONFIG + depends on X86 && PCI + help + This option enables common modules for Audio-Coprocessor i.e. ACP + IP block on AMD platforms. + +if SND_SOC_AMD_ACP_COMMON + +config SND_SOC_AMD_ACP_PDM + tristate + +config SND_SOC_AMD_ACP_I2S + tristate + +config SND_SOC_AMD_ACP_PCM + tristate + select SND_SOC_ACPI if ACPI + +config SND_SOC_AMD_ACP_PCI + tristate "AMD ACP PCI Driver Support" + depends on X86 && PCI + help + This options enables generic PCI driver for ACP device. + +config SND_AMD_ASOC_RENOIR + tristate "AMD ACP ASOC Renoir Support" + select SND_SOC_AMD_ACP_PCM + select SND_SOC_AMD_ACP_I2S + select SND_SOC_AMD_ACP_PDM + depends on X86 && PCI + help + This option enables Renoir I2S support on AMD platform. + +config SND_AMD_ASOC_REMBRANDT + tristate "AMD ACP ASOC Rembrandt Support" + select SND_SOC_AMD_ACP_PCM + select SND_SOC_AMD_ACP_I2S + select SND_SOC_AMD_ACP_PDM + depends on X86 && PCI + help + This option enables Rembrandt I2S support on AMD platform. + Say Y if you want to enable AUDIO on Rembrandt + If unsure select "N". + +config SND_SOC_AMD_MACH_COMMON + tristate + depends on X86 && PCI && I2C + select CLK_FIXED_FCH + select SND_SOC_RT5682_I2C + select SND_SOC_DMIC + select SND_SOC_RT1019 + select SND_SOC_MAX98357A + select SND_SOC_RT5682S + select SND_SOC_NAU8825 + help + This option enables common Machine driver module for ACP. + +config SND_SOC_AMD_LEGACY_MACH + tristate "AMD Legacy Machine Driver Support" + depends on X86 && PCI && I2C + select SND_SOC_AMD_MACH_COMMON + help + This option enables legacy sound card support for ACP audio. + +config SND_SOC_AMD_SOF_MACH + tristate "AMD SOF Machine Driver Support" + depends on X86 && PCI && I2C + select SND_SOC_AMD_MACH_COMMON + help + This option enables SOF sound card support for ACP audio. + +endif # SND_SOC_AMD_ACP_COMMON diff --git a/sound/soc/amd/acp/Makefile b/sound/soc/amd/acp/Makefile new file mode 100644 index 000000000..d9abb0ee5 --- /dev/null +++ b/sound/soc/amd/acp/Makefile @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +# This file is provided under a dual BSD/GPLv2 license. When using or +# redistributing this file, you may do so under either license. +# +# Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + +#common acp driver +snd-acp-pcm-objs := acp-platform.o +snd-acp-i2s-objs := acp-i2s.o +snd-acp-pdm-objs := acp-pdm.o +snd-acp-pci-objs := acp-pci.o + +#platform specific driver +snd-acp-renoir-objs := acp-renoir.o +snd-acp-rembrandt-objs := acp-rembrandt.o + +#machine specific driver +snd-acp-mach-objs := acp-mach-common.o +snd-acp-legacy-mach-objs := acp-legacy-mach.o +snd-acp-sof-mach-objs := acp-sof-mach.o + +obj-$(CONFIG_SND_SOC_AMD_ACP_PCM) += snd-acp-pcm.o +obj-$(CONFIG_SND_SOC_AMD_ACP_I2S) += snd-acp-i2s.o +obj-$(CONFIG_SND_SOC_AMD_ACP_PDM) += snd-acp-pdm.o +obj-$(CONFIG_SND_SOC_AMD_ACP_PCI) += snd-acp-pci.o + +obj-$(CONFIG_SND_AMD_ASOC_RENOIR) += snd-acp-renoir.o +obj-$(CONFIG_SND_AMD_ASOC_REMBRANDT) += snd-acp-rembrandt.o + +obj-$(CONFIG_SND_SOC_AMD_MACH_COMMON) += snd-acp-mach.o +obj-$(CONFIG_SND_SOC_AMD_LEGACY_MACH) += snd-acp-legacy-mach.o +obj-$(CONFIG_SND_SOC_AMD_SOF_MACH) += snd-acp-sof-mach.o diff --git a/sound/soc/amd/acp/acp-i2s.c b/sound/soc/amd/acp/acp-i2s.c new file mode 100644 index 000000000..ac416572d --- /dev/null +++ b/sound/soc/amd/acp/acp-i2s.c @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * Generic Hardware interface for ACP Audio I2S controller + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <linux/dma-mapping.h> + +#include "amd.h" + +#define DRV_NAME "acp_i2s_playcap" + +static int acp_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct acp_dev_data *adata = snd_soc_dai_get_drvdata(cpu_dai); + int mode; + + mode = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + switch (mode) { + case SND_SOC_DAIFMT_I2S: + adata->tdm_mode = TDM_DISABLE; + break; + case SND_SOC_DAIFMT_DSP_A: + adata->tdm_mode = TDM_ENABLE; + break; + default: + return -EINVAL; + } + return 0; +} + +static int acp_i2s_set_tdm_slot(struct snd_soc_dai *dai, u32 tx_mask, u32 rx_mask, + int slots, int slot_width) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = snd_soc_dai_get_drvdata(dai); + struct acp_stream *stream; + int slot_len; + + switch (slot_width) { + case SLOT_WIDTH_8: + slot_len = 8; + break; + case SLOT_WIDTH_16: + slot_len = 16; + break; + case SLOT_WIDTH_24: + slot_len = 24; + break; + case SLOT_WIDTH_32: + slot_len = 0; + break; + default: + dev_err(dev, "Unsupported bitdepth %d\n", slot_width); + return -EINVAL; + } + + spin_lock_irq(&adata->acp_lock); + list_for_each_entry(stream, &adata->stream_list, list) { + if (tx_mask && stream->dir == SNDRV_PCM_STREAM_PLAYBACK) + adata->tdm_tx_fmt[stream->dai_id - 1] = + FRM_LEN | (slots << 15) | (slot_len << 18); + else if (rx_mask && stream->dir == SNDRV_PCM_STREAM_CAPTURE) + adata->tdm_rx_fmt[stream->dai_id - 1] = + FRM_LEN | (slots << 15) | (slot_len << 18); + } + spin_unlock_irq(&adata->acp_lock); + return 0; +} + +static int acp_i2s_hwparams(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata; + struct acp_resource *rsrc; + u32 val; + u32 xfer_resolution; + u32 reg_val, fmt_reg, tdm_fmt; + u32 lrclk_div_val, bclk_div_val; + + adata = snd_soc_dai_get_drvdata(dai); + rsrc = adata->rsrc; + + /* These values are as per Hardware Spec */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S8: + xfer_resolution = 0x0; + break; + case SNDRV_PCM_FORMAT_S16_LE: + xfer_resolution = 0x02; + break; + case SNDRV_PCM_FORMAT_S24_LE: + xfer_resolution = 0x04; + break; + case SNDRV_PCM_FORMAT_S32_LE: + xfer_resolution = 0x05; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_ITER; + fmt_reg = ACP_BTTDM_TXFRMT; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_ITER; + fmt_reg = ACP_I2STDM_TXFRMT; + break; + case I2S_HS_INSTANCE: + reg_val = ACP_HSTDM_ITER; + fmt_reg = ACP_HSTDM_TXFRMT; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } else { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_IRER; + fmt_reg = ACP_BTTDM_RXFRMT; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_IRER; + fmt_reg = ACP_I2STDM_RXFRMT; + break; + case I2S_HS_INSTANCE: + reg_val = ACP_HSTDM_IRER; + fmt_reg = ACP_HSTDM_RXFRMT; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } + + val = readl(adata->acp_base + reg_val); + val &= ~ACP3x_ITER_IRER_SAMP_LEN_MASK; + val = val | (xfer_resolution << 3); + writel(val, adata->acp_base + reg_val); + + if (adata->tdm_mode) { + val = readl(adata->acp_base + reg_val); + writel(val | BIT(1), adata->acp_base + reg_val); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tdm_fmt = adata->tdm_tx_fmt[dai->driver->id - 1]; + else + tdm_fmt = adata->tdm_rx_fmt[dai->driver->id - 1]; + writel(tdm_fmt, adata->acp_base + fmt_reg); + } + + if (rsrc->soc_mclk) { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + switch (params_rate(params)) { + case 8000: + bclk_div_val = 768; + break; + case 16000: + bclk_div_val = 384; + break; + case 24000: + bclk_div_val = 256; + break; + case 32000: + bclk_div_val = 192; + break; + case 44100: + case 48000: + bclk_div_val = 128; + break; + case 88200: + case 96000: + bclk_div_val = 64; + break; + case 192000: + bclk_div_val = 32; + break; + default: + return -EINVAL; + } + lrclk_div_val = 32; + break; + case SNDRV_PCM_FORMAT_S32_LE: + switch (params_rate(params)) { + case 8000: + bclk_div_val = 384; + break; + case 16000: + bclk_div_val = 192; + break; + case 24000: + bclk_div_val = 128; + break; + case 32000: + bclk_div_val = 96; + break; + case 44100: + case 48000: + bclk_div_val = 64; + break; + case 88200: + case 96000: + bclk_div_val = 32; + break; + case 192000: + bclk_div_val = 16; + break; + default: + return -EINVAL; + } + lrclk_div_val = 64; + break; + default: + return -EINVAL; + } + adata->lrclk_div = lrclk_div_val; + adata->bclk_div = bclk_div_val; + } + return 0; +} + +static int acp_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) +{ + struct acp_stream *stream = substream->runtime->private_data; + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_resource *rsrc = adata->rsrc; + u32 val, period_bytes, reg_val, ier_val, water_val, buf_size, buf_reg; + + period_bytes = frames_to_bytes(substream->runtime, substream->runtime->period_size); + buf_size = frames_to_bytes(substream->runtime, substream->runtime->buffer_size); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + stream->bytescount = acp_get_byte_count(adata, stream->dai_id, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + water_val = ACP_BT_TX_INTR_WATERMARK_SIZE; + reg_val = ACP_BTTDM_ITER; + ier_val = ACP_BTTDM_IER; + buf_reg = ACP_BT_TX_RINGBUFSIZE; + break; + case I2S_SP_INSTANCE: + water_val = ACP_I2S_TX_INTR_WATERMARK_SIZE; + reg_val = ACP_I2STDM_ITER; + ier_val = ACP_I2STDM_IER; + buf_reg = ACP_I2S_TX_RINGBUFSIZE; + break; + case I2S_HS_INSTANCE: + water_val = ACP_HS_TX_INTR_WATERMARK_SIZE; + reg_val = ACP_HSTDM_ITER; + ier_val = ACP_HSTDM_IER; + buf_reg = ACP_HS_TX_RINGBUFSIZE; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } else { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + water_val = ACP_BT_RX_INTR_WATERMARK_SIZE; + reg_val = ACP_BTTDM_IRER; + ier_val = ACP_BTTDM_IER; + buf_reg = ACP_BT_RX_RINGBUFSIZE; + break; + case I2S_SP_INSTANCE: + water_val = ACP_I2S_RX_INTR_WATERMARK_SIZE; + reg_val = ACP_I2STDM_IRER; + ier_val = ACP_I2STDM_IER; + buf_reg = ACP_I2S_RX_RINGBUFSIZE; + break; + case I2S_HS_INSTANCE: + water_val = ACP_HS_RX_INTR_WATERMARK_SIZE; + reg_val = ACP_HSTDM_IRER; + ier_val = ACP_HSTDM_IER; + buf_reg = ACP_HS_RX_RINGBUFSIZE; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } + writel(period_bytes, adata->acp_base + water_val); + writel(buf_size, adata->acp_base + buf_reg); + val = readl(adata->acp_base + reg_val); + val = val | BIT(0); + writel(val, adata->acp_base + reg_val); + writel(1, adata->acp_base + ier_val); + if (rsrc->soc_mclk) + acp_set_i2s_clk(adata, dai->driver->id); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_ITER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_ITER; + break; + case I2S_HS_INSTANCE: + reg_val = ACP_HSTDM_ITER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + + } else { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_IRER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_IRER; + break; + case I2S_HS_INSTANCE: + reg_val = ACP_HSTDM_IRER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } + val = readl(adata->acp_base + reg_val); + val = val & ~BIT(0); + writel(val, adata->acp_base + reg_val); + + if (!(readl(adata->acp_base + ACP_BTTDM_ITER) & BIT(0)) && + !(readl(adata->acp_base + ACP_BTTDM_IRER) & BIT(0))) + writel(0, adata->acp_base + ACP_BTTDM_IER); + if (!(readl(adata->acp_base + ACP_I2STDM_ITER) & BIT(0)) && + !(readl(adata->acp_base + ACP_I2STDM_IRER) & BIT(0))) + writel(0, adata->acp_base + ACP_I2STDM_IER); + if (!(readl(adata->acp_base + ACP_HSTDM_ITER) & BIT(0)) && + !(readl(adata->acp_base + ACP_HSTDM_IRER) & BIT(0))) + writel(0, adata->acp_base + ACP_HSTDM_IER); + return 0; + default: + return -EINVAL; + } + + return 0; +} + +static int acp_i2s_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_resource *rsrc = adata->rsrc; + struct acp_stream *stream = substream->runtime->private_data; + u32 reg_dma_size = 0, reg_fifo_size = 0, reg_fifo_addr = 0; + u32 phy_addr = 0, acp_fifo_addr = 0, ext_int_ctrl; + unsigned int dir = substream->stream; + + switch (dai->driver->id) { + case I2S_SP_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + reg_dma_size = ACP_I2S_TX_DMA_SIZE; + acp_fifo_addr = rsrc->sram_pte_offset + + SP_PB_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_I2S_TX_FIFOADDR; + reg_fifo_size = ACP_I2S_TX_FIFOSIZE; + + phy_addr = I2S_SP_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_I2S_TX_RINGBUFADDR); + } else { + reg_dma_size = ACP_I2S_RX_DMA_SIZE; + acp_fifo_addr = rsrc->sram_pte_offset + + SP_CAPT_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_I2S_RX_FIFOADDR; + reg_fifo_size = ACP_I2S_RX_FIFOSIZE; + phy_addr = I2S_SP_RX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_I2S_RX_RINGBUFADDR); + } + break; + case I2S_BT_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + reg_dma_size = ACP_BT_TX_DMA_SIZE; + acp_fifo_addr = rsrc->sram_pte_offset + + BT_PB_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_BT_TX_FIFOADDR; + reg_fifo_size = ACP_BT_TX_FIFOSIZE; + + phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_BT_TX_RINGBUFADDR); + } else { + reg_dma_size = ACP_BT_RX_DMA_SIZE; + acp_fifo_addr = rsrc->sram_pte_offset + + BT_CAPT_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_BT_RX_FIFOADDR; + reg_fifo_size = ACP_BT_RX_FIFOSIZE; + + phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_BT_RX_RINGBUFADDR); + } + break; + case I2S_HS_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + reg_dma_size = ACP_HS_TX_DMA_SIZE; + acp_fifo_addr = rsrc->sram_pte_offset + + HS_PB_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_HS_TX_FIFOADDR; + reg_fifo_size = ACP_HS_TX_FIFOSIZE; + + phy_addr = I2S_HS_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_HS_TX_RINGBUFADDR); + } else { + reg_dma_size = ACP_HS_RX_DMA_SIZE; + acp_fifo_addr = rsrc->sram_pte_offset + + HS_CAPT_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_HS_RX_FIFOADDR; + reg_fifo_size = ACP_HS_RX_FIFOSIZE; + + phy_addr = I2S_HS_RX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_HS_RX_RINGBUFADDR); + } + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + + writel(DMA_SIZE, adata->acp_base + reg_dma_size); + writel(acp_fifo_addr, adata->acp_base + reg_fifo_addr); + writel(FIFO_SIZE, adata->acp_base + reg_fifo_size); + + ext_int_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, rsrc->irqp_used)); + ext_int_ctrl |= BIT(I2S_RX_THRESHOLD(rsrc->offset)) | + BIT(BT_RX_THRESHOLD(rsrc->offset)) | + BIT(I2S_TX_THRESHOLD(rsrc->offset)) | + BIT(BT_TX_THRESHOLD(rsrc->offset)) | + BIT(HS_RX_THRESHOLD(rsrc->offset)) | + BIT(HS_TX_THRESHOLD(rsrc->offset)); + + writel(ext_int_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, rsrc->irqp_used)); + + return 0; +} + +static int acp_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct acp_stream *stream = substream->runtime->private_data; + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_resource *rsrc = adata->rsrc; + unsigned int dir = substream->stream; + unsigned int irq_bit = 0; + + switch (dai->driver->id) { + case I2S_SP_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + irq_bit = BIT(I2S_TX_THRESHOLD(rsrc->offset)); + stream->pte_offset = ACP_SRAM_SP_PB_PTE_OFFSET; + stream->fifo_offset = SP_PB_FIFO_ADDR_OFFSET; + } else { + irq_bit = BIT(I2S_RX_THRESHOLD(rsrc->offset)); + stream->pte_offset = ACP_SRAM_SP_CP_PTE_OFFSET; + stream->fifo_offset = SP_CAPT_FIFO_ADDR_OFFSET; + } + break; + case I2S_BT_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + irq_bit = BIT(BT_TX_THRESHOLD(rsrc->offset)); + stream->pte_offset = ACP_SRAM_BT_PB_PTE_OFFSET; + stream->fifo_offset = BT_PB_FIFO_ADDR_OFFSET; + } else { + irq_bit = BIT(BT_RX_THRESHOLD(rsrc->offset)); + stream->pte_offset = ACP_SRAM_BT_CP_PTE_OFFSET; + stream->fifo_offset = BT_CAPT_FIFO_ADDR_OFFSET; + } + break; + case I2S_HS_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + irq_bit = BIT(HS_TX_THRESHOLD(rsrc->offset)); + stream->pte_offset = ACP_SRAM_HS_PB_PTE_OFFSET; + stream->fifo_offset = HS_PB_FIFO_ADDR_OFFSET; + } else { + irq_bit = BIT(HS_RX_THRESHOLD(rsrc->offset)); + stream->pte_offset = ACP_SRAM_HS_CP_PTE_OFFSET; + stream->fifo_offset = HS_CAPT_FIFO_ADDR_OFFSET; + } + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + + /* Save runtime dai configuration in stream */ + stream->id = dai->driver->id + dir; + stream->dai_id = dai->driver->id; + stream->irq_bit = irq_bit; + stream->dir = substream->stream; + + return 0; +} + +const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .startup = acp_i2s_startup, + .hw_params = acp_i2s_hwparams, + .prepare = acp_i2s_prepare, + .trigger = acp_i2s_trigger, + .set_fmt = acp_i2s_set_fmt, + .set_tdm_slot = acp_i2s_set_tdm_slot, +}; +EXPORT_SYMBOL_NS_GPL(asoc_acp_cpu_dai_ops, SND_SOC_ACP_COMMON); + +int asoc_acp_i2s_probe(struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_resource *rsrc = adata->rsrc; + unsigned int val; + + if (!adata->acp_base) { + dev_err(dev, "I2S base is NULL\n"); + return -EINVAL; + } + + val = readl(adata->acp_base + rsrc->i2s_pin_cfg_offset); + if (val != rsrc->i2s_mode) { + dev_err(dev, "I2S Mode not supported val %x\n", val); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(asoc_acp_i2s_probe, SND_SOC_ACP_COMMON); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS(DRV_NAME); diff --git a/sound/soc/amd/acp/acp-legacy-mach.c b/sound/soc/amd/acp/acp-legacy-mach.c new file mode 100644 index 000000000..1f4878ff7 --- /dev/null +++ b/sound/soc/amd/acp/acp-legacy-mach.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * Machine Driver Legacy Support for ACP HW block + */ + +#include <sound/core.h> +#include <sound/pcm_params.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> + +#include "acp-mach.h" + +static struct acp_card_drvdata rt5682_rt1019_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682, + .amp_codec_id = RT1019, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata rt5682s_max_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682S, + .amp_codec_id = MAX98360A, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata rt5682s_rt1019_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682S, + .amp_codec_id = RT1019, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata max_nau8825_data = { + .hs_cpu_id = I2S_HS, + .amp_cpu_id = I2S_HS, + .dmic_cpu_id = DMIC, + .hs_codec_id = NAU8825, + .amp_codec_id = MAX98360A, + .dmic_codec_id = DMIC, + .soc_mclk = true, + .platform = REMBRANDT, +}; + +static struct acp_card_drvdata rt5682s_rt1019_rmb_data = { + .hs_cpu_id = I2S_HS, + .amp_cpu_id = I2S_HS, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682S, + .amp_codec_id = RT1019, + .dmic_codec_id = DMIC, + .soc_mclk = true, + .platform = REMBRANDT, +}; + +static const struct snd_kcontrol_new acp_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), + +}; + +static const struct snd_soc_dapm_widget acp_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static int acp_asoc_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = NULL; + struct device *dev = &pdev->dev; + int ret; + + if (!pdev->id_entry) + return -EINVAL; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = pdev->id_entry->name; + card->dapm_widgets = acp_widgets; + card->num_dapm_widgets = ARRAY_SIZE(acp_widgets); + card->controls = acp_controls; + card->num_controls = ARRAY_SIZE(acp_controls); + card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data; + + acp_legacy_dai_links_create(card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, + "devm_snd_soc_register_card(%s) failed: %d\n", + card->name, ret); + return ret; + } + + return 0; +} + +static const struct platform_device_id board_ids[] = { + { + .name = "acp3xalc56821019", + .driver_data = (kernel_ulong_t)&rt5682_rt1019_data, + }, + { + .name = "acp3xalc5682sm98360", + .driver_data = (kernel_ulong_t)&rt5682s_max_data, + }, + { + .name = "acp3xalc5682s1019", + .driver_data = (kernel_ulong_t)&rt5682s_rt1019_data, + }, + { + .name = "rmb-nau8825-max", + .driver_data = (kernel_ulong_t)&max_nau8825_data, + }, + { + .name = "rmb-rt5682s-rt1019", + .driver_data = (kernel_ulong_t)&rt5682s_rt1019_rmb_data, + }, + { } +}; +static struct platform_driver acp_asoc_audio = { + .driver = { + .pm = &snd_soc_pm_ops, + .name = "acp_mach", + }, + .probe = acp_asoc_probe, + .id_table = board_ids, +}; + +module_platform_driver(acp_asoc_audio); + +MODULE_IMPORT_NS(SND_SOC_AMD_MACH); +MODULE_DESCRIPTION("ACP chrome audio support"); +MODULE_ALIAS("platform:acp3xalc56821019"); +MODULE_ALIAS("platform:acp3xalc5682sm98360"); +MODULE_ALIAS("platform:acp3xalc5682s1019"); +MODULE_ALIAS("platform:rmb-nau8825-max"); +MODULE_ALIAS("platform:rmb-rt5682s-rt1019"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-mach-common.c new file mode 100644 index 000000000..4c69cb6e3 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach-common.c @@ -0,0 +1,915 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// Vijendar Mukunda <Vijendar.Mukunda@amd.com> +// + +/* + * Machine Driver Interface for ACP HW block + */ + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/soc.h> +#include <linux/input.h> +#include <linux/module.h> + +#include "../../codecs/rt5682.h" +#include "../../codecs/rt1019.h" +#include "../../codecs/rt5682s.h" +#include "../../codecs/nau8825.h" +#include "acp-mach.h" + +#define PCO_PLAT_CLK 48000000 +#define RT5682_PLL_FREQ (48000 * 512) +#define DUAL_CHANNEL 2 +#define FOUR_CHANNEL 4 + +static struct snd_soc_jack pco_jack; + +static const unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int acp_clk_enable(struct acp_card_drvdata *drvdata) +{ + clk_set_rate(drvdata->wclk, 48000); + clk_set_rate(drvdata->bclk, 48000 * 64); + + return clk_prepare_enable(drvdata->wclk); +} + +/* Declare RT5682 codec components */ +SND_SOC_DAILINK_DEF(rt5682, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", "rt5682-aif1"))); + +static const struct snd_soc_dapm_route rt5682_map[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + { "IN1P", NULL, "Headset Mic" }, +}; + +/* Define card ops for RT5682 CODEC */ +static int acp_card_rt5682_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + int ret; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + if (drvdata->hs_codec_id != RT5682) + return -EINVAL; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL2, RT5682_PLL2_S_MCLK, + PCO_PLAT_CLK, RT5682_PLL_FREQ); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec PLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL2, + RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec SYSCLK: %d\n", ret); + return ret; + } + + /* Set tdm/i2s1 master bclk ratio */ + ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set rt5682 tdm bclk ratio: %d\n", ret); + return ret; + } + + drvdata->wclk = clk_get(component->dev, "rt5682-dai-wclk"); + drvdata->bclk = clk_get(component->dev, "rt5682-dai-bclk"); + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &pco_jack); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, &pco_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return snd_soc_dapm_add_routes(&rtd->card->dapm, rt5682_map, ARRAY_SIZE(rt5682_map)); +} + +static int acp_card_hs_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + unsigned int fmt; + + if (drvdata->soc_mclk) + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP; + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + if (!drvdata->soc_mclk) { + ret = acp_clk_enable(drvdata); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to enable HS clk: %d\n", ret); + return ret; + } + } + + return ret; +} + +static void acp_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + + if (!drvdata->soc_mclk) + clk_disable_unprepare(drvdata->wclk); +} + +static const struct snd_soc_ops acp_card_rt5682_ops = { + .startup = acp_card_hs_startup, + .shutdown = acp_card_shutdown, +}; + +/* Define RT5682S CODEC component*/ +SND_SOC_DAILINK_DEF(rt5682s, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-RTL5682:00", "rt5682s-aif1"))); + +static const struct snd_soc_dapm_route rt5682s_map[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + { "IN1P", NULL, "Headset Mic" }, +}; + +static int acp_card_rt5682s_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + unsigned int fmt; + int ret; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + if (drvdata->hs_codec_id != RT5682S) + return -EINVAL; + + if (drvdata->soc_mclk) + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP; + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, RT5682S_PLL2, RT5682S_PLL_S_MCLK, + PCO_PLAT_CLK, RT5682_PLL_FREQ); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec PLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682S_SCLK_S_PLL2, + RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec SYSCLK: %d\n", ret); + return ret; + } + + /* Set tdm/i2s1 master bclk ratio */ + ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set rt5682 tdm bclk ratio: %d\n", ret); + return ret; + } + + if (!drvdata->soc_mclk) { + drvdata->wclk = clk_get(component->dev, "rt5682-dai-wclk"); + drvdata->bclk = clk_get(component->dev, "rt5682-dai-bclk"); + } + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &pco_jack); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, &pco_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return snd_soc_dapm_add_routes(&rtd->card->dapm, rt5682s_map, ARRAY_SIZE(rt5682s_map)); +} + +static const struct snd_soc_ops acp_card_rt5682s_ops = { + .startup = acp_card_hs_startup, + .shutdown = acp_card_shutdown, +}; + +static const unsigned int dmic_channels[] = { + DUAL_CHANNEL, FOUR_CHANNEL, +}; + +static const struct snd_pcm_hw_constraint_list dmic_constraints_channels = { + .count = ARRAY_SIZE(dmic_channels), + .list = dmic_channels, + .mask = 0, +}; + +static int acp_card_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &dmic_constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops acp_card_dmic_ops = { + .startup = acp_card_dmic_startup, +}; + +/* Declare RT1019 codec components */ +SND_SOC_DAILINK_DEF(rt1019, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC1019:00", "rt1019-aif"), + COMP_CODEC("i2c-10EC1019:01", "rt1019-aif"))); + +static const struct snd_soc_dapm_route rt1019_map_lr[] = { + { "Left Spk", NULL, "Left SPO" }, + { "Right Spk", NULL, "Right SPO" }, +}; + +static struct snd_soc_codec_conf rt1019_conf[] = { + { + .dlc = COMP_CODEC_CONF("i2c-10EC1019:01"), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF("i2c-10EC1019:00"), + .name_prefix = "Right", + }, +}; + +static int acp_card_rt1019_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + + if (drvdata->amp_codec_id != RT1019) + return -EINVAL; + + return snd_soc_dapm_add_routes(&rtd->card->dapm, rt1019_map_lr, + ARRAY_SIZE(rt1019_map_lr)); +} + +static int acp_card_rt1019_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_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai; + int srate, i, ret = 0; + + srate = params_rate(params); + + if (drvdata->amp_codec_id != RT1019) + return -EINVAL; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + if (strcmp(codec_dai->name, "rt1019-aif")) + continue; + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT1019_PLL_S_BCLK, + 64 * srate, 256 * srate); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT1019_SCLK_S_PLL, + 256 * srate, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + } + + return 0; +} + +static int acp_card_amp_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + int ret = 0; + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + if (!drvdata->soc_mclk) { + ret = acp_clk_enable(drvdata); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to enable AMP clk: %d\n", ret); + return ret; + } + } + return ret; +} + +static const struct snd_soc_ops acp_card_rt1019_ops = { + .startup = acp_card_amp_startup, + .shutdown = acp_card_shutdown, + .hw_params = acp_card_rt1019_hw_params, +}; + +/* Declare Maxim codec components */ +SND_SOC_DAILINK_DEF(max98360a, + DAILINK_COMP_ARRAY(COMP_CODEC("MX98360A:00", "HiFi"))); + +static const struct snd_soc_dapm_route max98360a_map[] = { + {"Spk", NULL, "Speaker"}, +}; + +static int acp_card_maxim_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + + if (drvdata->amp_codec_id != MAX98360A) + return -EINVAL; + + return snd_soc_dapm_add_routes(&rtd->card->dapm, max98360a_map, + ARRAY_SIZE(max98360a_map)); +} + +static const struct snd_soc_ops acp_card_maxim_ops = { + .startup = acp_card_amp_startup, + .shutdown = acp_card_shutdown, +}; + +/* Declare nau8825 codec components */ +SND_SOC_DAILINK_DEF(nau8825, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10508825:00", "nau8825-hifi"))); + +static const struct snd_soc_dapm_route nau8825_map[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, +}; + +static int acp_card_nau8825_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + unsigned int fmt; + int ret; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + if (drvdata->hs_codec_id != NAU8825) + return -EINVAL; + + if (drvdata->soc_mclk) + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP; + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &pco_jack); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, &pco_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return snd_soc_dapm_add_routes(&rtd->card->dapm, nau8825_map, ARRAY_SIZE(nau8825_map)); +} + +static int acp_nau8825_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_FS, + (48000 * 256), SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, params_rate(params), + params_rate(params) * 256); + if (ret < 0) { + dev_err(rtd->dev, "can't set FLL: %d\n", ret); + return ret; + } + + return ret; +} + +static int acp_nau8825_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + return 0; +} + +static const struct snd_soc_ops acp_card_nau8825_ops = { + .startup = acp_nau8825_startup, + .hw_params = acp_nau8825_hw_params, +}; + +/* Declare DMIC codec components */ +SND_SOC_DAILINK_DEF(dmic_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi"))); + +/* Declare ACP CPU components */ +static struct snd_soc_dai_link_component dummy_codec[] = { + { + .name = "snd-soc-dummy", + .dai_name = "snd-soc-dummy-dai", + } +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + .name = "acp_asoc_renoir.0", + } +}; + +static struct snd_soc_dai_link_component platform_rmb_component[] = { + { + .name = "acp_asoc_rembrandt.0", + } +}; + +static struct snd_soc_dai_link_component sof_component[] = { + { + .name = "0000:04:00.5", + } +}; + +SND_SOC_DAILINK_DEF(i2s_sp, + DAILINK_COMP_ARRAY(COMP_CPU("acp-i2s-sp"))); +SND_SOC_DAILINK_DEF(i2s_hs, + DAILINK_COMP_ARRAY(COMP_CPU("acp-i2s-hs"))); +SND_SOC_DAILINK_DEF(sof_sp, + DAILINK_COMP_ARRAY(COMP_CPU("acp-sof-sp"))); +SND_SOC_DAILINK_DEF(sof_hs, + DAILINK_COMP_ARRAY(COMP_CPU("acp-sof-hs"))); +SND_SOC_DAILINK_DEF(sof_dmic, + DAILINK_COMP_ARRAY(COMP_CPU("acp-sof-dmic"))); +SND_SOC_DAILINK_DEF(pdm_dmic, + DAILINK_COMP_ARRAY(COMP_CPU("acp-pdm-dmic"))); + +int acp_sofdsp_dai_links_create(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *links; + struct device *dev = card->dev; + struct acp_card_drvdata *drv_data = card->drvdata; + int i = 0, num_links = 0; + + if (drv_data->hs_cpu_id) + num_links++; + if (drv_data->amp_cpu_id) + num_links++; + if (drv_data->dmic_cpu_id) + num_links++; + + links = devm_kcalloc(dev, num_links, sizeof(struct snd_soc_dai_link), GFP_KERNEL); + if (!links) + return -ENOMEM; + + if (drv_data->hs_cpu_id == I2S_SP) { + links[i].name = "acp-headset-codec"; + links[i].id = HEADSET_BE_ID; + links[i].cpus = sof_sp; + links[i].num_cpus = ARRAY_SIZE(sof_sp); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + if (!drv_data->hs_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->hs_codec_id == RT5682) { + links[i].codecs = rt5682; + links[i].num_codecs = ARRAY_SIZE(rt5682); + links[i].init = acp_card_rt5682_init; + links[i].ops = &acp_card_rt5682_ops; + } + if (drv_data->hs_codec_id == RT5682S) { + links[i].codecs = rt5682s; + links[i].num_codecs = ARRAY_SIZE(rt5682s); + links[i].init = acp_card_rt5682s_init; + links[i].ops = &acp_card_rt5682s_ops; + } + i++; + } + + if (drv_data->hs_cpu_id == I2S_HS) { + links[i].name = "acp-headset-codec"; + links[i].id = HEADSET_BE_ID; + links[i].cpus = sof_hs; + links[i].num_cpus = ARRAY_SIZE(sof_hs); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + if (!drv_data->hs_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->hs_codec_id == NAU8825) { + links[i].codecs = nau8825; + links[i].num_codecs = ARRAY_SIZE(nau8825); + links[i].init = acp_card_nau8825_init; + links[i].ops = &acp_card_nau8825_ops; + } + if (drv_data->hs_codec_id == RT5682S) { + links[i].codecs = rt5682s; + links[i].num_codecs = ARRAY_SIZE(rt5682s); + links[i].init = acp_card_rt5682s_init; + links[i].ops = &acp_card_rt5682s_ops; + } + i++; + } + + if (drv_data->amp_cpu_id == I2S_SP) { + links[i].name = "acp-amp-codec"; + links[i].id = AMP_BE_ID; + links[i].cpus = sof_sp; + links[i].num_cpus = ARRAY_SIZE(sof_sp); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_playback = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + if (!drv_data->amp_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->amp_codec_id == RT1019) { + links[i].codecs = rt1019; + links[i].num_codecs = ARRAY_SIZE(rt1019); + links[i].ops = &acp_card_rt1019_ops; + links[i].init = acp_card_rt1019_init; + card->codec_conf = rt1019_conf; + card->num_configs = ARRAY_SIZE(rt1019_conf); + } + if (drv_data->amp_codec_id == MAX98360A) { + links[i].codecs = max98360a; + links[i].num_codecs = ARRAY_SIZE(max98360a); + links[i].ops = &acp_card_maxim_ops; + links[i].init = acp_card_maxim_init; + } + i++; + } + + if (drv_data->amp_cpu_id == I2S_HS) { + links[i].name = "acp-amp-codec"; + links[i].id = AMP_BE_ID; + links[i].cpus = sof_hs; + links[i].num_cpus = ARRAY_SIZE(sof_hs); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_playback = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + if (!drv_data->amp_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->amp_codec_id == MAX98360A) { + links[i].codecs = max98360a; + links[i].num_codecs = ARRAY_SIZE(max98360a); + links[i].ops = &acp_card_maxim_ops; + links[i].init = acp_card_maxim_init; + } + if (drv_data->amp_codec_id == RT1019) { + links[i].codecs = rt1019; + links[i].num_codecs = ARRAY_SIZE(rt1019); + links[i].ops = &acp_card_rt1019_ops; + links[i].init = acp_card_rt1019_init; + card->codec_conf = rt1019_conf; + card->num_configs = ARRAY_SIZE(rt1019_conf); + } + i++; + } + + if (drv_data->dmic_cpu_id == DMIC) { + links[i].name = "acp-dmic-codec"; + links[i].id = DMIC_BE_ID; + links[i].codecs = dmic_codec; + links[i].num_codecs = ARRAY_SIZE(dmic_codec); + links[i].cpus = sof_dmic; + links[i].num_cpus = ARRAY_SIZE(sof_dmic); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_capture = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + } + + card->dai_link = links; + card->num_links = num_links; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_sofdsp_dai_links_create, SND_SOC_AMD_MACH); + +int acp_legacy_dai_links_create(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *links; + struct device *dev = card->dev; + struct acp_card_drvdata *drv_data = card->drvdata; + int i = 0, num_links = 0; + + if (drv_data->hs_cpu_id) + num_links++; + if (drv_data->amp_cpu_id) + num_links++; + if (drv_data->dmic_cpu_id) + num_links++; + + links = devm_kcalloc(dev, num_links, sizeof(struct snd_soc_dai_link), GFP_KERNEL); + if (!links) + return -ENOMEM; + + if (drv_data->hs_cpu_id == I2S_SP) { + links[i].name = "acp-headset-codec"; + links[i].id = HEADSET_BE_ID; + links[i].cpus = i2s_sp; + links[i].num_cpus = ARRAY_SIZE(i2s_sp); + links[i].platforms = platform_component; + links[i].num_platforms = ARRAY_SIZE(platform_component); + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + if (!drv_data->hs_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->hs_codec_id == RT5682) { + links[i].codecs = rt5682; + links[i].num_codecs = ARRAY_SIZE(rt5682); + links[i].init = acp_card_rt5682_init; + links[i].ops = &acp_card_rt5682_ops; + } + if (drv_data->hs_codec_id == RT5682S) { + links[i].codecs = rt5682s; + links[i].num_codecs = ARRAY_SIZE(rt5682s); + links[i].init = acp_card_rt5682s_init; + links[i].ops = &acp_card_rt5682s_ops; + } + i++; + } + + if (drv_data->hs_cpu_id == I2S_HS) { + links[i].name = "acp-headset-codec"; + links[i].id = HEADSET_BE_ID; + links[i].cpus = i2s_hs; + links[i].num_cpus = ARRAY_SIZE(i2s_hs); + if (drv_data->platform == REMBRANDT) { + links[i].platforms = platform_rmb_component; + links[i].num_platforms = ARRAY_SIZE(platform_rmb_component); + } else { + links[i].platforms = platform_component; + links[i].num_platforms = ARRAY_SIZE(platform_component); + } + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + if (!drv_data->hs_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->hs_codec_id == NAU8825) { + links[i].codecs = nau8825; + links[i].num_codecs = ARRAY_SIZE(nau8825); + links[i].init = acp_card_nau8825_init; + links[i].ops = &acp_card_nau8825_ops; + } + if (drv_data->hs_codec_id == RT5682S) { + links[i].codecs = rt5682s; + links[i].num_codecs = ARRAY_SIZE(rt5682s); + links[i].init = acp_card_rt5682s_init; + links[i].ops = &acp_card_rt5682s_ops; + } + i++; + } + + if (drv_data->amp_cpu_id == I2S_SP) { + links[i].name = "acp-amp-codec"; + links[i].id = AMP_BE_ID; + links[i].cpus = i2s_sp; + links[i].num_cpus = ARRAY_SIZE(i2s_sp); + links[i].platforms = platform_component; + links[i].num_platforms = ARRAY_SIZE(platform_component); + links[i].dpcm_playback = 1; + if (!drv_data->amp_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->amp_codec_id == RT1019) { + links[i].codecs = rt1019; + links[i].num_codecs = ARRAY_SIZE(rt1019); + links[i].ops = &acp_card_rt1019_ops; + links[i].init = acp_card_rt1019_init; + card->codec_conf = rt1019_conf; + card->num_configs = ARRAY_SIZE(rt1019_conf); + } + if (drv_data->amp_codec_id == MAX98360A) { + links[i].codecs = max98360a; + links[i].num_codecs = ARRAY_SIZE(max98360a); + links[i].ops = &acp_card_maxim_ops; + links[i].init = acp_card_maxim_init; + } + i++; + } + + if (drv_data->amp_cpu_id == I2S_HS) { + links[i].name = "acp-amp-codec"; + links[i].id = AMP_BE_ID; + links[i].cpus = i2s_hs; + links[i].num_cpus = ARRAY_SIZE(i2s_hs); + if (drv_data->platform == REMBRANDT) { + links[i].platforms = platform_rmb_component; + links[i].num_platforms = ARRAY_SIZE(platform_rmb_component); + } else { + links[i].platforms = platform_component; + links[i].num_platforms = ARRAY_SIZE(platform_component); + } + links[i].dpcm_playback = 1; + if (!drv_data->amp_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->amp_codec_id == MAX98360A) { + links[i].codecs = max98360a; + links[i].num_codecs = ARRAY_SIZE(max98360a); + links[i].ops = &acp_card_maxim_ops; + links[i].init = acp_card_maxim_init; + } + if (drv_data->amp_codec_id == RT1019) { + links[i].codecs = rt1019; + links[i].num_codecs = ARRAY_SIZE(rt1019); + links[i].ops = &acp_card_rt1019_ops; + links[i].init = acp_card_rt1019_init; + card->codec_conf = rt1019_conf; + card->num_configs = ARRAY_SIZE(rt1019_conf); + } + i++; + } + + if (drv_data->dmic_cpu_id == DMIC) { + links[i].name = "acp-dmic-codec"; + links[i].id = DMIC_BE_ID; + if (drv_data->dmic_codec_id == DMIC) { + links[i].codecs = dmic_codec; + links[i].num_codecs = ARRAY_SIZE(dmic_codec); + } else { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + links[i].cpus = pdm_dmic; + links[i].num_cpus = ARRAY_SIZE(pdm_dmic); + if (drv_data->platform == REMBRANDT) { + links[i].platforms = platform_rmb_component; + links[i].num_platforms = ARRAY_SIZE(platform_rmb_component); + } else { + links[i].platforms = platform_component; + links[i].num_platforms = ARRAY_SIZE(platform_component); + } + links[i].ops = &acp_card_dmic_ops; + links[i].dpcm_capture = 1; + } + + card->dai_link = links; + card->num_links = num_links; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_legacy_dai_links_create, SND_SOC_AMD_MACH); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/acp-mach.h b/sound/soc/amd/acp/acp-mach.h new file mode 100644 index 000000000..20583ef90 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ +#ifndef __ACP_MACH_H +#define __ACP_MACH_H + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/input.h> +#include <linux/module.h> +#include <sound/soc.h> + +enum be_id { + HEADSET_BE_ID = 0, + AMP_BE_ID, + DMIC_BE_ID, +}; + +enum cpu_endpoints { + NONE = 0, + I2S_HS, + I2S_SP, + I2S_BT, + DMIC, +}; + +enum codec_endpoints { + DUMMY = 0, + RT5682, + RT1019, + MAX98360A, + RT5682S, + NAU8825, +}; + +enum platform_end_point { + RENOIR = 0, + REMBRANDT, +}; + +struct acp_card_drvdata { + unsigned int hs_cpu_id; + unsigned int amp_cpu_id; + unsigned int dmic_cpu_id; + unsigned int hs_codec_id; + unsigned int amp_codec_id; + unsigned int dmic_codec_id; + unsigned int dai_fmt; + unsigned int platform; + struct clk *wclk; + struct clk *bclk; + bool soc_mclk; +}; + +int acp_sofdsp_dai_links_create(struct snd_soc_card *card); +int acp_legacy_dai_links_create(struct snd_soc_card *card); + +#endif diff --git a/sound/soc/amd/acp/acp-pci.c b/sound/soc/amd/acp/acp-pci.c new file mode 100644 index 000000000..a0c84cd07 --- /dev/null +++ b/sound/soc/amd/acp/acp-pci.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Advanced Micro Devices, Inc. All rights reserved. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * Generic PCI interface for ACP device + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/module.h> + +#include "amd.h" +#include "../mach-config.h" + +#define DRV_NAME "acp_pci" + +#define ACP3x_REG_START 0x1240000 +#define ACP3x_REG_END 0x125C000 + +static struct platform_device *dmic_dev; +static struct platform_device *pdev; + +static const struct resource acp_res[] = { + { + .start = 0, + .end = ACP3x_REG_END - ACP3x_REG_START, + .name = "acp_mem", + .flags = IORESOURCE_MEM, + }, + { + .start = 0, + .end = 0, + .name = "acp_dai_irq", + .flags = IORESOURCE_IRQ, + }, +}; + +static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + struct platform_device_info pdevinfo; + struct device *dev = &pci->dev; + const struct resource *res_acp; + struct acp_chip_info *chip; + struct resource *res; + unsigned int flag, addr, num_res, i; + int ret; + + flag = snd_amd_acp_find_config(pci); + if (flag != FLAG_AMD_LEGACY) + return -ENODEV; + + chip = devm_kzalloc(&pci->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + if (pci_enable_device(pci)) + return dev_err_probe(&pci->dev, -ENODEV, + "pci_enable_device failed\n"); + + ret = pci_request_regions(pci, "AMD ACP3x audio"); + if (ret < 0) { + dev_err(&pci->dev, "pci_request_regions failed\n"); + ret = -ENOMEM; + goto disable_pci; + } + + pci_set_master(pci); + + res_acp = acp_res; + num_res = ARRAY_SIZE(acp_res); + + switch (pci->revision) { + case 0x01: + chip->name = "acp_asoc_renoir"; + chip->acp_rev = ACP3X_DEV; + break; + case 0x6f: + chip->name = "acp_asoc_rembrandt"; + chip->acp_rev = ACP6X_DEV; + break; + default: + dev_err(dev, "Unsupported device revision:0x%x\n", pci->revision); + ret = -EINVAL; + goto release_regions; + } + + dmic_dev = platform_device_register_data(dev, "dmic-codec", PLATFORM_DEVID_NONE, NULL, 0); + if (IS_ERR(dmic_dev)) { + dev_err(dev, "failed to create DMIC device\n"); + ret = PTR_ERR(dmic_dev); + goto release_regions; + } + + addr = pci_resource_start(pci, 0); + chip->base = devm_ioremap(&pci->dev, addr, pci_resource_len(pci, 0)); + if (!chip->base) { + ret = -ENOMEM; + goto unregister_dmic_dev; + } + + res = devm_kcalloc(&pci->dev, num_res, sizeof(struct resource), GFP_KERNEL); + if (!res) { + ret = -ENOMEM; + goto unregister_dmic_dev; + } + + for (i = 0; i < num_res; i++, res_acp++) { + res[i].name = res_acp->name; + res[i].flags = res_acp->flags; + res[i].start = addr + res_acp->start; + res[i].end = addr + res_acp->end; + if (res_acp->flags == IORESOURCE_IRQ) { + res[i].start = pci->irq; + res[i].end = res[i].start; + } + } + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + + pdevinfo.name = chip->name; + pdevinfo.id = 0; + pdevinfo.parent = &pci->dev; + pdevinfo.num_res = num_res; + pdevinfo.res = &res[0]; + pdevinfo.data = chip; + pdevinfo.size_data = sizeof(*chip); + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + dev_err(&pci->dev, "cannot register %s device\n", pdevinfo.name); + ret = PTR_ERR(pdev); + goto unregister_dmic_dev; + } + + return ret; + +unregister_dmic_dev: + platform_device_unregister(dmic_dev); +release_regions: + pci_release_regions(pci); +disable_pci: + pci_disable_device(pci); + + return ret; +}; + +static void acp_pci_remove(struct pci_dev *pci) +{ + if (dmic_dev) + platform_device_unregister(dmic_dev); + if (pdev) + platform_device_unregister(pdev); +} + +/* PCI IDs */ +static const struct pci_device_id acp_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID)}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, acp_pci_ids); + +/* pci_driver definition */ +static struct pci_driver snd_amd_acp_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = acp_pci_ids, + .probe = acp_pci_probe, + .remove = acp_pci_remove, +}; +module_pci_driver(snd_amd_acp_pci_driver); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS(DRV_NAME); diff --git a/sound/soc/amd/acp/acp-pdm.c b/sound/soc/amd/acp/acp-pdm.c new file mode 100644 index 000000000..f8030b79a --- /dev/null +++ b/sound/soc/amd/acp/acp-pdm.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// Vijendar Mukunda <Vijendar.Mukunda@amd.com> +// + +/* + * Generic Hardware interface for ACP Audio PDM controller + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include "amd.h" + +#define DRV_NAME "acp-pdm" + +#define PDM_DMA_STAT 0x10 +#define PDM_DMA_INTR_MASK 0x10000 +#define PDM_DEC_64 0x2 +#define PDM_CLK_FREQ_MASK 0x07 +#define PDM_MISC_CTRL_MASK 0x10 +#define PDM_ENABLE 0x01 +#define PDM_DISABLE 0x00 +#define DMA_EN_MASK 0x02 +#define DELAY_US 5 +#define PDM_TIMEOUT 1000 +#define ACP_REGION2_OFFSET 0x02000000 + +static int acp_dmic_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct acp_stream *stream = substream->runtime->private_data; + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + u32 physical_addr, size_dmic, period_bytes; + unsigned int dmic_ctrl; + + /* Enable default DMIC clk */ + writel(PDM_CLK_FREQ_MASK, adata->acp_base + ACP_WOV_CLK_CTRL); + dmic_ctrl = readl(adata->acp_base + ACP_WOV_MISC_CTRL); + dmic_ctrl |= PDM_MISC_CTRL_MASK; + writel(dmic_ctrl, adata->acp_base + ACP_WOV_MISC_CTRL); + + period_bytes = frames_to_bytes(substream->runtime, + substream->runtime->period_size); + size_dmic = frames_to_bytes(substream->runtime, + substream->runtime->buffer_size); + + physical_addr = stream->reg_offset + MEM_WINDOW_START; + + /* Init DMIC Ring buffer */ + writel(physical_addr, adata->acp_base + ACP_WOV_RX_RINGBUFADDR); + writel(size_dmic, adata->acp_base + ACP_WOV_RX_RINGBUFSIZE); + writel(period_bytes, adata->acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE); + writel(0x01, adata->acp_base + ACPAXI2AXI_ATU_CTRL); + + return 0; +} + +static int acp_dmic_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + unsigned int dma_enable; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dma_enable = readl(adata->acp_base + ACP_WOV_PDM_DMA_ENABLE); + if (!(dma_enable & DMA_EN_MASK)) { + writel(PDM_ENABLE, adata->acp_base + ACP_WOV_PDM_ENABLE); + writel(PDM_ENABLE, adata->acp_base + ACP_WOV_PDM_DMA_ENABLE); + } + + ret = readl_poll_timeout_atomic(adata->acp_base + ACP_WOV_PDM_DMA_ENABLE, + dma_enable, (dma_enable & DMA_EN_MASK), + DELAY_US, PDM_TIMEOUT); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dma_enable = readl(adata->acp_base + ACP_WOV_PDM_DMA_ENABLE); + if ((dma_enable & DMA_EN_MASK)) { + writel(PDM_DISABLE, adata->acp_base + ACP_WOV_PDM_ENABLE); + writel(PDM_DISABLE, adata->acp_base + ACP_WOV_PDM_DMA_ENABLE); + + } + + ret = readl_poll_timeout_atomic(adata->acp_base + ACP_WOV_PDM_DMA_ENABLE, + dma_enable, !(dma_enable & DMA_EN_MASK), + DELAY_US, PDM_TIMEOUT); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int acp_dmic_hwparams(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hwparams, struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + unsigned int channels, ch_mask; + + channels = params_channels(hwparams); + switch (channels) { + case 2: + ch_mask = 0; + break; + case 4: + ch_mask = 1; + break; + case 6: + ch_mask = 2; + break; + default: + dev_err(dev, "Invalid channels %d\n", channels); + return -EINVAL; + } + + if (params_format(hwparams) != SNDRV_PCM_FORMAT_S32_LE) { + dev_err(dai->dev, "Invalid format:%d\n", params_format(hwparams)); + return -EINVAL; + } + + writel(ch_mask, adata->acp_base + ACP_WOV_PDM_NO_OF_CHANNELS); + writel(PDM_DEC_64, adata->acp_base + ACP_WOV_PDM_DECIMATION_FACTOR); + + return 0; +} + +static int acp_dmic_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct acp_stream *stream = substream->runtime->private_data; + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + u32 ext_int_ctrl; + + stream->dai_id = DMIC_INSTANCE; + stream->irq_bit = BIT(PDM_DMA_STAT); + stream->pte_offset = ACP_SRAM_PDM_PTE_OFFSET; + stream->reg_offset = ACP_REGION2_OFFSET; + + /* Enable DMIC Interrupts */ + ext_int_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, 0)); + ext_int_ctrl |= PDM_DMA_INTR_MASK; + writel(ext_int_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, 0)); + + return 0; +} + +static void acp_dmic_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + u32 ext_int_ctrl; + + /* Disable DMIC interrupts */ + ext_int_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, 0)); + ext_int_ctrl &= ~PDM_DMA_INTR_MASK; + writel(ext_int_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, 0)); +} + +const struct snd_soc_dai_ops acp_dmic_dai_ops = { + .prepare = acp_dmic_prepare, + .hw_params = acp_dmic_hwparams, + .trigger = acp_dmic_dai_trigger, + .startup = acp_dmic_dai_startup, + .shutdown = acp_dmic_dai_shutdown, +}; +EXPORT_SYMBOL_NS_GPL(acp_dmic_dai_ops, SND_SOC_ACP_COMMON); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS(DRV_NAME); diff --git a/sound/soc/amd/acp/acp-platform.c b/sound/soc/amd/acp/acp-platform.c new file mode 100644 index 000000000..447612a7a --- /dev/null +++ b/sound/soc/amd/acp/acp-platform.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * Generic interface for ACP audio blck PCM component + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> + +#include "amd.h" + +#define DRV_NAME "acp_i2s_dma" + +static const struct snd_pcm_hardware acp_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .rate_min = 8000, + .rate_max = 96000, + .buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, +}; + +int acp_machine_select(struct acp_dev_data *adata) +{ + struct snd_soc_acpi_mach *mach; + int size; + + size = sizeof(*adata->machines); + mach = snd_soc_acpi_find_machine(adata->machines); + if (!mach) { + dev_err(adata->dev, "warning: No matching ASoC machine driver found\n"); + return -EINVAL; + } + + adata->mach_dev = platform_device_register_data(adata->dev, mach->drv_name, + PLATFORM_DEVID_NONE, mach, size); + if (IS_ERR(adata->mach_dev)) + dev_warn(adata->dev, "Unable to register Machine device\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_machine_select, SND_SOC_ACP_COMMON); + +static irqreturn_t i2s_irq_handler(int irq, void *data) +{ + struct acp_dev_data *adata = data; + struct acp_resource *rsrc = adata->rsrc; + struct acp_stream *stream; + u16 i2s_flag = 0; + u32 ext_intr_stat, ext_intr_stat1; + + if (!adata) + return IRQ_NONE; + + if (adata->rsrc->no_of_ctrls == 2) + ext_intr_stat1 = readl(ACP_EXTERNAL_INTR_STAT(adata, (rsrc->irqp_used - 1))); + + ext_intr_stat = readl(ACP_EXTERNAL_INTR_STAT(adata, rsrc->irqp_used)); + + spin_lock(&adata->acp_lock); + list_for_each_entry(stream, &adata->stream_list, list) { + if (ext_intr_stat & stream->irq_bit) { + writel(stream->irq_bit, + ACP_EXTERNAL_INTR_STAT(adata, rsrc->irqp_used)); + snd_pcm_period_elapsed(stream->substream); + i2s_flag = 1; + } + if (adata->rsrc->no_of_ctrls == 2) { + if (ext_intr_stat1 & stream->irq_bit) { + writel(stream->irq_bit, ACP_EXTERNAL_INTR_STAT(adata, + (rsrc->irqp_used - 1))); + snd_pcm_period_elapsed(stream->substream); + i2s_flag = 1; + } + } + } + spin_unlock(&adata->acp_lock); + if (i2s_flag) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static void config_pte_for_stream(struct acp_dev_data *adata, struct acp_stream *stream) +{ + struct acp_resource *rsrc = adata->rsrc; + u32 pte_reg, pte_size, reg_val; + + /* Use ATU base Group5 */ + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_5; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5; + stream->reg_offset = 0x02000000; + + /* Group Enable */ + reg_val = rsrc->sram_pte_offset; + writel(reg_val | BIT(31), adata->acp_base + pte_reg); + writel(PAGE_SIZE_4K_ENABLE, adata->acp_base + pte_size); + writel(0x01, adata->acp_base + ACPAXI2AXI_ATU_CTRL); +} + +static void config_acp_dma(struct acp_dev_data *adata, struct acp_stream *stream, int size) +{ + struct snd_pcm_substream *substream = stream->substream; + struct acp_resource *rsrc = adata->rsrc; + dma_addr_t addr = substream->dma_buffer.addr; + int num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); + u32 low, high, val; + u16 page_idx; + + val = stream->pte_offset; + + for (page_idx = 0; page_idx < num_pages; page_idx++) { + /* Load the low address of page int ACP SRAM through SRBM */ + low = lower_32_bits(addr); + high = upper_32_bits(addr); + writel(low, adata->acp_base + rsrc->scratch_reg_offset + val); + high |= BIT(31); + writel(high, adata->acp_base + rsrc->scratch_reg_offset + val + 4); + + /* Move to next physically contiguous page */ + val += 8; + addr += PAGE_SIZE; + } +} + +static int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream; + int ret; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->substream = substream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = acp_pcm_hardware_playback; + else + runtime->hw = acp_pcm_hardware_capture; + + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(component->dev, "set integer constraint failed\n"); + kfree(stream); + return ret; + } + runtime->private_data = stream; + + writel(1, ACP_EXTERNAL_INTR_ENB(adata)); + + spin_lock_irq(&adata->acp_lock); + list_add_tail(&stream->list, &adata->stream_list); + spin_unlock_irq(&adata->acp_lock); + + return ret; +} + +static int acp_dma_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct acp_dev_data *adata = snd_soc_component_get_drvdata(component); + struct acp_stream *stream = substream->runtime->private_data; + u64 size = params_buffer_bytes(params); + + /* Configure ACP DMA block with params */ + config_pte_for_stream(adata, stream); + config_acp_dma(adata, stream, size); + + return 0; +} + +static snd_pcm_uframes_t acp_dma_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct device *dev = component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream = substream->runtime->private_data; + u32 pos, buffersize; + u64 bytescount; + + buffersize = frames_to_bytes(substream->runtime, + substream->runtime->buffer_size); + + bytescount = acp_get_byte_count(adata, stream->dai_id, substream->stream); + + if (bytescount > stream->bytescount) + bytescount -= stream->bytescount; + + pos = do_div(bytescount, buffersize); + + return bytes_to_frames(substream->runtime, pos); +} + +static int acp_dma_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct device *parent = component->dev->parent; + + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, + parent, MIN_BUFFER, MAX_BUFFER); + return 0; +} + +static int acp_dma_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct device *dev = component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream = substream->runtime->private_data; + + /* Remove entry from list */ + spin_lock_irq(&adata->acp_lock); + list_del(&stream->list); + spin_unlock_irq(&adata->acp_lock); + kfree(stream); + + return 0; +} + +static const struct snd_soc_component_driver acp_pcm_component = { + .name = DRV_NAME, + .open = acp_dma_open, + .close = acp_dma_close, + .hw_params = acp_dma_hw_params, + .pointer = acp_dma_pointer, + .pcm_construct = acp_dma_new, + .legacy_dai_naming = 1, +}; + +int acp_platform_register(struct device *dev) +{ + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct snd_soc_dai_driver; + unsigned int status; + + status = devm_request_irq(dev, adata->i2s_irq, i2s_irq_handler, + IRQF_SHARED, "ACP_I2S_IRQ", adata); + if (status) { + dev_err(dev, "ACP I2S IRQ request failed\n"); + return status; + } + + status = devm_snd_soc_register_component(dev, &acp_pcm_component, + adata->dai_driver, + adata->num_dai); + if (status) { + dev_err(dev, "Fail to register acp i2s component\n"); + return status; + } + + INIT_LIST_HEAD(&adata->stream_list); + spin_lock_init(&adata->acp_lock); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_platform_register, SND_SOC_ACP_COMMON); + +int acp_platform_unregister(struct device *dev) +{ + struct acp_dev_data *adata = dev_get_drvdata(dev); + + if (adata->mach_dev) + platform_device_unregister(adata->mach_dev); + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_platform_unregister, SND_SOC_ACP_COMMON); + +MODULE_DESCRIPTION("AMD ACP PCM Driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS(DRV_NAME); diff --git a/sound/soc/amd/acp/acp-rembrandt.c b/sound/soc/amd/acp/acp-rembrandt.c new file mode 100644 index 000000000..2b57c0ca4 --- /dev/null +++ b/sound/soc/amd/acp/acp-rembrandt.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// V sujith kumar Reddy <Vsujithkumar.Reddy@amd.com> +/* + * Hardware interface for Renoir ACP block + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <linux/dma-mapping.h> + +#include "amd.h" + +#define DRV_NAME "acp_asoc_rembrandt" + +#define ACP6X_PGFSM_CONTROL 0x1024 +#define ACP6X_PGFSM_STATUS 0x1028 + +#define ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK 0x00010001 + +#define ACP_PGFSM_CNTL_POWER_ON_MASK 0x01 +#define ACP_PGFSM_CNTL_POWER_OFF_MASK 0x00 +#define ACP_PGFSM_STATUS_MASK 0x03 +#define ACP_POWERED_ON 0x00 +#define ACP_POWER_ON_IN_PROGRESS 0x01 +#define ACP_POWERED_OFF 0x02 +#define ACP_POWER_OFF_IN_PROGRESS 0x03 + +#define ACP_ERROR_MASK 0x20000000 +#define ACP_EXT_INTR_STAT_CLEAR_MASK 0xFFFFFFFF + + +static int rmb_acp_init(void __iomem *base); +static int rmb_acp_deinit(void __iomem *base); + +static struct acp_resource rsrc = { + .offset = 0, + .no_of_ctrls = 2, + .irqp_used = 1, + .soc_mclk = true, + .irq_reg_offset = 0x1a00, + .i2s_pin_cfg_offset = 0x1440, + .i2s_mode = 0x0a, + .scratch_reg_offset = 0x12800, + .sram_pte_offset = 0x03802800, +}; + +static struct snd_soc_acpi_codecs amp_rt1019 = { + .num_codecs = 1, + .codecs = {"10EC1019"} +}; + +static struct snd_soc_acpi_codecs amp_max = { + .num_codecs = 1, + .codecs = {"MX98360A"} +}; + +static struct snd_soc_acpi_mach snd_soc_acpi_amd_rmb_acp_machines[] = { + { + .id = "10508825", + .drv_name = "rmb-nau8825-max", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_max, + }, + { + .id = "AMDI0007", + .drv_name = "rembrandt-acp", + }, + { + .id = "RTL5682", + .drv_name = "rmb-rt5682s-rt1019", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_rt1019, + }, + {}, +}; + +static struct snd_soc_dai_driver acp_rmb_dai[] = { +{ + .name = "acp-i2s-sp", + .id = I2S_SP_INSTANCE, + .playback = { + .stream_name = "I2S SP Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S SP Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &asoc_acp_cpu_dai_ops, + .probe = &asoc_acp_i2s_probe, +}, +{ + .name = "acp-i2s-bt", + .id = I2S_BT_INSTANCE, + .playback = { + .stream_name = "I2S BT Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S BT Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &asoc_acp_cpu_dai_ops, + .probe = &asoc_acp_i2s_probe, +}, +{ + .name = "acp-i2s-hs", + .id = I2S_HS_INSTANCE, + .playback = { + .stream_name = "I2S HS Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S HS Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &asoc_acp_cpu_dai_ops, + .probe = &asoc_acp_i2s_probe, +}, +{ + .name = "acp-pdm-dmic", + .id = DMIC_INSTANCE, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &acp_dmic_dai_ops, +}, +}; + +static int acp6x_power_on(void __iomem *base) +{ + u32 val; + int timeout; + + val = readl(base + ACP6X_PGFSM_STATUS); + + if (val == ACP_POWERED_ON) + return 0; + + if ((val & ACP_PGFSM_STATUS_MASK) != + ACP_POWER_ON_IN_PROGRESS) + writel(ACP_PGFSM_CNTL_POWER_ON_MASK, + base + ACP6X_PGFSM_CONTROL); + timeout = 0; + while (++timeout < 500) { + val = readl(base + ACP6X_PGFSM_STATUS); + if (!val) + return 0; + udelay(1); + } + return -ETIMEDOUT; +} + +static int acp6x_power_off(void __iomem *base) +{ + u32 val; + int timeout; + + writel(ACP_PGFSM_CNTL_POWER_OFF_MASK, + base + ACP6X_PGFSM_CONTROL); + timeout = 0; + while (++timeout < 500) { + val = readl(base + ACP6X_PGFSM_STATUS); + if ((val & ACP_PGFSM_STATUS_MASK) == ACP_POWERED_OFF) + return 0; + udelay(1); + } + return -ETIMEDOUT; +} + +static int acp6x_reset(void __iomem *base) +{ + u32 val; + int timeout; + + writel(1, base + ACP_SOFT_RESET); + timeout = 0; + while (++timeout < 500) { + val = readl(base + ACP_SOFT_RESET); + if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK) + break; + cpu_relax(); + } + writel(0, base + ACP_SOFT_RESET); + timeout = 0; + while (++timeout < 500) { + val = readl(base + ACP_SOFT_RESET); + if (!val) + return 0; + cpu_relax(); + } + return -ETIMEDOUT; +} + +static void acp6x_enable_interrupts(struct acp_dev_data *adata) +{ + struct acp_resource *rsrc = adata->rsrc; + u32 ext_intr_ctrl; + + writel(0x01, ACP_EXTERNAL_INTR_ENB(adata)); + ext_intr_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, rsrc->irqp_used)); + ext_intr_ctrl |= ACP_ERROR_MASK; + writel(ext_intr_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, rsrc->irqp_used)); +} + +static void acp6x_disable_interrupts(struct acp_dev_data *adata) +{ + struct acp_resource *rsrc = adata->rsrc; + + writel(ACP_EXT_INTR_STAT_CLEAR_MASK, + ACP_EXTERNAL_INTR_STAT(adata, rsrc->irqp_used)); + writel(0x00, ACP_EXTERNAL_INTR_ENB(adata)); +} + +static int rmb_acp_init(void __iomem *base) +{ + int ret; + + /* power on */ + ret = acp6x_power_on(base); + if (ret) { + pr_err("ACP power on failed\n"); + return ret; + } + writel(0x01, base + ACP_CONTROL); + + /* Reset */ + ret = acp6x_reset(base); + if (ret) { + pr_err("ACP reset failed\n"); + return ret; + } + + return 0; +} + +static int rmb_acp_deinit(void __iomem *base) +{ + int ret = 0; + + /* Reset */ + ret = acp6x_reset(base); + if (ret) { + pr_err("ACP reset failed\n"); + return ret; + } + + writel(0x00, base + ACP_CONTROL); + + /* power off */ + ret = acp6x_power_off(base); + if (ret) { + pr_err("ACP power off failed\n"); + return ret; + } + + return 0; +} + +static int rembrandt_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acp_chip_info *chip; + struct acp_dev_data *adata; + struct resource *res; + + chip = dev_get_platdata(&pdev->dev); + if (!chip || !chip->base) { + dev_err(&pdev->dev, "ACP chip data is NULL\n"); + return -ENODEV; + } + + if (chip->acp_rev != ACP6X_DEV) { + dev_err(&pdev->dev, "Un-supported ACP Revision %d\n", chip->acp_rev); + return -ENODEV; + } + + rmb_acp_init(chip->base); + + adata = devm_kzalloc(dev, sizeof(struct acp_dev_data), GFP_KERNEL); + if (!adata) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acp_mem"); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); + return -ENODEV; + } + + adata->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!adata->acp_base) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "acp_dai_irq"); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n"); + return -ENODEV; + } + + adata->i2s_irq = res->start; + adata->dev = dev; + adata->dai_driver = acp_rmb_dai; + adata->num_dai = ARRAY_SIZE(acp_rmb_dai); + adata->rsrc = &rsrc; + + adata->machines = snd_soc_acpi_amd_rmb_acp_machines; + acp_machine_select(adata); + + dev_set_drvdata(dev, adata); + acp6x_enable_interrupts(adata); + acp_platform_register(dev); + + return 0; +} + +static int rembrandt_audio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_chip_info *chip; + + chip = dev_get_platdata(&pdev->dev); + if (!chip || !chip->base) { + dev_err(&pdev->dev, "ACP chip data is NULL\n"); + return -ENODEV; + } + + rmb_acp_deinit(chip->base); + + acp6x_disable_interrupts(adata); + acp_platform_unregister(dev); + return 0; +} + +static struct platform_driver rembrandt_driver = { + .probe = rembrandt_audio_probe, + .remove = rembrandt_audio_remove, + .driver = { + .name = "acp_asoc_rembrandt", + }, +}; + +module_platform_driver(rembrandt_driver); + +MODULE_DESCRIPTION("AMD ACP Rembrandt Driver"); +MODULE_IMPORT_NS(SND_SOC_ACP_COMMON); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/acp/acp-renoir.c b/sound/soc/amd/acp/acp-renoir.c new file mode 100644 index 000000000..2a89a0d2e --- /dev/null +++ b/sound/soc/amd/acp/acp-renoir.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * Hardware interface for Renoir ACP block + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <linux/dma-mapping.h> + +#include "amd.h" + +#define DRV_NAME "acp_asoc_renoir" + +#define ACP_SOFT_RST_DONE_MASK 0x00010001 + +#define ACP_PWR_ON_MASK 0x01 +#define ACP_PWR_OFF_MASK 0x00 +#define ACP_PGFSM_STAT_MASK 0x03 +#define ACP_POWERED_ON 0x00 +#define ACP_PWR_ON_IN_PROGRESS 0x01 +#define ACP_POWERED_OFF 0x02 +#define DELAY_US 5 +#define ACP_TIMEOUT 500 + +#define ACP_ERROR_MASK 0x20000000 +#define ACP_EXT_INTR_STAT_CLEAR_MASK 0xFFFFFFFF + +static struct acp_resource rsrc = { + .offset = 20, + .no_of_ctrls = 1, + .irqp_used = 0, + .irq_reg_offset = 0x1800, + .i2s_pin_cfg_offset = 0x1400, + .i2s_mode = 0x04, + .scratch_reg_offset = 0x12800, + .sram_pte_offset = 0x02052800, +}; + +static struct snd_soc_acpi_codecs amp_rt1019 = { + .num_codecs = 1, + .codecs = {"10EC1019"} +}; + +static struct snd_soc_acpi_codecs amp_max = { + .num_codecs = 1, + .codecs = {"MX98360A"} +}; + +static struct snd_soc_acpi_mach snd_soc_acpi_amd_acp_machines[] = { + { + .id = "10EC5682", + .drv_name = "acp3xalc56821019", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_rt1019, + }, + { + .id = "RTL5682", + .drv_name = "acp3xalc5682sm98360", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_max, + }, + { + .id = "RTL5682", + .drv_name = "acp3xalc5682s1019", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_rt1019, + }, + { + .id = "AMDI1019", + .drv_name = "renoir-acp", + }, + {}, +}; + +static struct snd_soc_dai_driver acp_renoir_dai[] = { +{ + .name = "acp-i2s-sp", + .id = I2S_SP_INSTANCE, + .playback = { + .stream_name = "I2S SP Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S SP Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &asoc_acp_cpu_dai_ops, + .probe = &asoc_acp_i2s_probe, +}, +{ + .name = "acp-i2s-bt", + .id = I2S_BT_INSTANCE, + .playback = { + .stream_name = "I2S BT Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S BT Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &asoc_acp_cpu_dai_ops, + .probe = &asoc_acp_i2s_probe, +}, +{ + .name = "acp-pdm-dmic", + .id = DMIC_INSTANCE, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &acp_dmic_dai_ops, +}, +}; + +static int acp3x_power_on(void __iomem *base) +{ + u32 val; + + val = readl(base + ACP_PGFSM_STATUS); + + if (val == ACP_POWERED_ON) + return 0; + + if ((val & ACP_PGFSM_STAT_MASK) != ACP_PWR_ON_IN_PROGRESS) + writel(ACP_PWR_ON_MASK, base + ACP_PGFSM_CONTROL); + + return readl_poll_timeout(base + ACP_PGFSM_STATUS, val, !val, DELAY_US, ACP_TIMEOUT); +} + +static int acp3x_power_off(void __iomem *base) +{ + u32 val; + + writel(ACP_PWR_OFF_MASK, base + ACP_PGFSM_CONTROL); + + return readl_poll_timeout(base + ACP_PGFSM_STATUS, val, + (val & ACP_PGFSM_STAT_MASK) == ACP_POWERED_OFF, + DELAY_US, ACP_TIMEOUT); +} + +static int acp3x_reset(void __iomem *base) +{ + u32 val; + int ret; + + writel(1, base + ACP_SOFT_RESET); + + ret = readl_poll_timeout(base + ACP_SOFT_RESET, val, val & ACP_SOFT_RST_DONE_MASK, + DELAY_US, ACP_TIMEOUT); + if (ret) + return ret; + + writel(0, base + ACP_SOFT_RESET); + + return readl_poll_timeout(base + ACP_SOFT_RESET, val, !val, DELAY_US, ACP_TIMEOUT); +} + +static void acp3x_enable_interrupts(struct acp_dev_data *adata) +{ + struct acp_resource *rsrc = adata->rsrc; + u32 ext_intr_ctrl; + + writel(0x01, ACP_EXTERNAL_INTR_ENB(adata)); + ext_intr_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, rsrc->irqp_used)); + ext_intr_ctrl |= ACP_ERROR_MASK; + writel(ext_intr_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, rsrc->irqp_used)); +} + +static void acp3x_disable_interrupts(struct acp_dev_data *adata) +{ + struct acp_resource *rsrc = adata->rsrc; + + writel(ACP_EXT_INTR_STAT_CLEAR_MASK, + ACP_EXTERNAL_INTR_STAT(adata, rsrc->irqp_used)); + writel(0x00, ACP_EXTERNAL_INTR_ENB(adata)); +} + +static int rn_acp_init(void __iomem *base) +{ + int ret; + + /* power on */ + ret = acp3x_power_on(base); + if (ret) + return ret; + + writel(0x01, base + ACP_CONTROL); + + /* Reset */ + ret = acp3x_reset(base); + if (ret) + return ret; + + return 0; +} + +static int rn_acp_deinit(void __iomem *base) +{ + int ret = 0; + + /* Reset */ + ret = acp3x_reset(base); + if (ret) + return ret; + + writel(0x00, base + ACP_CONTROL); + + /* power off */ + ret = acp3x_power_off(base); + if (ret) + return ret; + + return 0; +} +static int renoir_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acp_chip_info *chip; + struct acp_dev_data *adata; + struct resource *res; + int ret; + + chip = dev_get_platdata(&pdev->dev); + if (!chip || !chip->base) { + dev_err(&pdev->dev, "ACP chip data is NULL\n"); + return -ENODEV; + } + + if (chip->acp_rev != ACP3X_DEV) { + dev_err(&pdev->dev, "Un-supported ACP Revision %d\n", chip->acp_rev); + return -ENODEV; + } + + ret = rn_acp_init(chip->base); + if (ret) { + dev_err(&pdev->dev, "ACP Init failed\n"); + return -EINVAL; + } + + adata = devm_kzalloc(dev, sizeof(struct acp_dev_data), GFP_KERNEL); + if (!adata) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acp_mem"); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); + return -ENODEV; + } + + adata->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!adata->acp_base) + return -ENOMEM; + + ret = platform_get_irq_byname(pdev, "acp_dai_irq"); + if (ret < 0) + return ret; + adata->i2s_irq = ret; + + adata->dev = dev; + adata->dai_driver = acp_renoir_dai; + adata->num_dai = ARRAY_SIZE(acp_renoir_dai); + adata->rsrc = &rsrc; + + adata->machines = snd_soc_acpi_amd_acp_machines; + acp_machine_select(adata); + + dev_set_drvdata(dev, adata); + acp3x_enable_interrupts(adata); + acp_platform_register(dev); + + return 0; +} + +static int renoir_audio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_chip_info *chip; + int ret; + + chip = dev_get_platdata(&pdev->dev); + + acp3x_disable_interrupts(adata); + + ret = rn_acp_deinit(chip->base); + if (ret) + dev_err(&pdev->dev, "ACP de-init Failed (%pe)\n", ERR_PTR(ret)); + + acp_platform_unregister(dev); + return 0; +} + +static struct platform_driver renoir_driver = { + .probe = renoir_audio_probe, + .remove = renoir_audio_remove, + .driver = { + .name = "acp_asoc_renoir", + }, +}; + +module_platform_driver(renoir_driver); + +MODULE_DESCRIPTION("AMD ACP Renoir Driver"); +MODULE_IMPORT_NS(SND_SOC_ACP_COMMON); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/acp/acp-sof-mach.c b/sound/soc/amd/acp/acp-sof-mach.c new file mode 100644 index 000000000..f19f064a7 --- /dev/null +++ b/sound/soc/amd/acp/acp-sof-mach.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * SOF Machine Driver Support for ACP HW block + */ + +#include <sound/core.h> +#include <sound/pcm_params.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> + +#include "acp-mach.h" + +static struct acp_card_drvdata sof_rt5682_rt1019_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682, + .amp_codec_id = RT1019, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata sof_rt5682_max_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682, + .amp_codec_id = MAX98360A, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata sof_rt5682s_rt1019_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682S, + .amp_codec_id = RT1019, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata sof_rt5682s_max_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682S, + .amp_codec_id = MAX98360A, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata sof_nau8825_data = { + .hs_cpu_id = I2S_HS, + .amp_cpu_id = I2S_HS, + .dmic_cpu_id = DMIC, + .hs_codec_id = NAU8825, + .amp_codec_id = MAX98360A, + .dmic_codec_id = DMIC, + .soc_mclk = true, +}; + +static struct acp_card_drvdata sof_rt5682s_hs_rt1019_data = { + .hs_cpu_id = I2S_HS, + .amp_cpu_id = I2S_HS, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682S, + .amp_codec_id = RT1019, + .dmic_codec_id = DMIC, + .soc_mclk = true, +}; + +static const struct snd_kcontrol_new acp_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static const struct snd_soc_dapm_widget acp_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static int acp_sof_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = NULL; + struct device *dev = &pdev->dev; + int ret; + + if (!pdev->id_entry) + return -EINVAL; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = pdev->id_entry->name; + card->dapm_widgets = acp_widgets; + card->num_dapm_widgets = ARRAY_SIZE(acp_widgets); + card->controls = acp_controls; + card->num_controls = ARRAY_SIZE(acp_controls); + card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data; + + acp_sofdsp_dai_links_create(card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, + "devm_snd_soc_register_card(%s) failed: %d\n", + card->name, ret); + return ret; + } + + return 0; +} + +static const struct platform_device_id board_ids[] = { + { + .name = "rt5682-rt1019", + .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data + }, + { + .name = "rt5682-max", + .driver_data = (kernel_ulong_t)&sof_rt5682_max_data + }, + { + .name = "rt5682s-max", + .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data + }, + { + .name = "rt5682s-rt1019", + .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data + }, + { + .name = "nau8825-max", + .driver_data = (kernel_ulong_t)&sof_nau8825_data + }, + { + .name = "rt5682s-hs-rt1019", + .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data + }, + { } +}; +static struct platform_driver acp_asoc_audio = { + .driver = { + .name = "sof_mach", + .pm = &snd_soc_pm_ops, + }, + .probe = acp_sof_probe, + .id_table = board_ids, +}; + +module_platform_driver(acp_asoc_audio); + +MODULE_IMPORT_NS(SND_SOC_AMD_MACH); +MODULE_DESCRIPTION("ACP chrome SOF audio support"); +MODULE_ALIAS("platform:rt5682-rt1019"); +MODULE_ALIAS("platform:rt5682-max"); +MODULE_ALIAS("platform:rt5682s-max"); +MODULE_ALIAS("platform:rt5682s-rt1019"); +MODULE_ALIAS("platform:nau8825-max"); +MODULE_ALIAS("platform:rt5682s-hs-rt1019"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/amd.h b/sound/soc/amd/acp/amd.h new file mode 100644 index 000000000..12a176a50 --- /dev/null +++ b/sound/soc/amd/acp/amd.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ + +#ifndef __AMD_ACP_H +#define __AMD_ACP_H + +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dai.h> + +#include "chip_offset_byte.h" + +#define ACP3X_DEV 3 +#define ACP6X_DEV 6 + +#define DMIC_INSTANCE 0x00 +#define I2S_SP_INSTANCE 0x01 +#define I2S_BT_INSTANCE 0x02 +#define I2S_HS_INSTANCE 0x03 + +#define MEM_WINDOW_START 0x4080000 + +#define ACP_I2S_REG_START 0x1242400 +#define ACP_I2S_REG_END 0x1242810 +#define ACP3x_I2STDM_REG_START 0x1242400 +#define ACP3x_I2STDM_REG_END 0x1242410 +#define ACP3x_BT_TDM_REG_START 0x1242800 +#define ACP3x_BT_TDM_REG_END 0x1242810 + +#define THRESHOLD(bit, base) ((bit) + (base)) +#define I2S_RX_THRESHOLD(base) THRESHOLD(7, base) +#define I2S_TX_THRESHOLD(base) THRESHOLD(8, base) +#define BT_TX_THRESHOLD(base) THRESHOLD(6, base) +#define BT_RX_THRESHOLD(base) THRESHOLD(5, base) +#define HS_TX_THRESHOLD(base) THRESHOLD(4, base) +#define HS_RX_THRESHOLD(base) THRESHOLD(3, base) + +#define ACP_SRAM_SP_PB_PTE_OFFSET 0x0 +#define ACP_SRAM_SP_CP_PTE_OFFSET 0x100 +#define ACP_SRAM_BT_PB_PTE_OFFSET 0x200 +#define ACP_SRAM_BT_CP_PTE_OFFSET 0x300 +#define ACP_SRAM_PDM_PTE_OFFSET 0x400 +#define ACP_SRAM_HS_PB_PTE_OFFSET 0x500 +#define ACP_SRAM_HS_CP_PTE_OFFSET 0x600 +#define PAGE_SIZE_4K_ENABLE 0x2 + +#define I2S_SP_TX_MEM_WINDOW_START 0x4000000 +#define I2S_SP_RX_MEM_WINDOW_START 0x4020000 +#define I2S_BT_TX_MEM_WINDOW_START 0x4040000 +#define I2S_BT_RX_MEM_WINDOW_START 0x4060000 +#define I2S_HS_TX_MEM_WINDOW_START 0x40A0000 +#define I2S_HS_RX_MEM_WINDOW_START 0x40C0000 + +#define SP_PB_FIFO_ADDR_OFFSET 0x500 +#define SP_CAPT_FIFO_ADDR_OFFSET 0x700 +#define BT_PB_FIFO_ADDR_OFFSET 0x900 +#define BT_CAPT_FIFO_ADDR_OFFSET 0xB00 +#define HS_PB_FIFO_ADDR_OFFSET 0xD00 +#define HS_CAPT_FIFO_ADDR_OFFSET 0xF00 +#define PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 8 +#define PLAYBACK_MAX_PERIOD_SIZE 8192 +#define PLAYBACK_MIN_PERIOD_SIZE 1024 +#define CAPTURE_MIN_NUM_PERIODS 2 +#define CAPTURE_MAX_NUM_PERIODS 8 +#define CAPTURE_MAX_PERIOD_SIZE 8192 +#define CAPTURE_MIN_PERIOD_SIZE 1024 + +#define MAX_BUFFER 65536 +#define MIN_BUFFER MAX_BUFFER +#define FIFO_SIZE 0x100 +#define DMA_SIZE 0x40 +#define FRM_LEN 0x100 + +#define ACP3x_ITER_IRER_SAMP_LEN_MASK 0x38 + +#define ACP_MAX_STREAM 8 + +#define TDM_ENABLE 1 +#define TDM_DISABLE 0 + +#define SLOT_WIDTH_8 0x8 +#define SLOT_WIDTH_16 0x10 +#define SLOT_WIDTH_24 0x18 +#define SLOT_WIDTH_32 0x20 + +struct acp_chip_info { + char *name; /* Platform name */ + unsigned int acp_rev; /* ACP Revision id */ + void __iomem *base; /* ACP memory PCI base */ +}; + +struct acp_stream { + struct list_head list; + struct snd_pcm_substream *substream; + int irq_bit; + int dai_id; + int id; + int dir; + u64 bytescount; + u32 reg_offset; + u32 pte_offset; + u32 fifo_offset; +}; + +struct acp_resource { + int offset; + int no_of_ctrls; + int irqp_used; + bool soc_mclk; + u32 irq_reg_offset; + u32 i2s_pin_cfg_offset; + int i2s_mode; + u64 scratch_reg_offset; + u64 sram_pte_offset; +}; + +struct acp_dev_data { + char *name; + struct device *dev; + void __iomem *acp_base; + unsigned int i2s_irq; + + bool tdm_mode; + /* SOC specific dais */ + struct snd_soc_dai_driver *dai_driver; + int num_dai; + + struct list_head stream_list; + spinlock_t acp_lock; + + struct snd_soc_acpi_mach *machines; + struct platform_device *mach_dev; + + u32 bclk_div; + u32 lrclk_div; + + struct acp_resource *rsrc; + u32 tdm_tx_fmt[3]; + u32 tdm_rx_fmt[3]; +}; + +union acp_i2stdm_mstrclkgen { + struct { + u32 i2stdm_master_mode : 1; + u32 i2stdm_format_mode : 1; + u32 i2stdm_lrclk_div_val : 9; + u32 i2stdm_bclk_div_val : 11; + u32:10; + } bitfields, bits; + u32 u32_all; +}; + +extern const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops; +extern const struct snd_soc_dai_ops acp_dmic_dai_ops; + +int asoc_acp_i2s_probe(struct snd_soc_dai *dai); +int acp_platform_register(struct device *dev); +int acp_platform_unregister(struct device *dev); + +int acp_machine_select(struct acp_dev_data *adata); + +/* Machine configuration */ +int snd_amd_acp_find_config(struct pci_dev *pci); + +static inline u64 acp_get_byte_count(struct acp_dev_data *adata, int dai_id, int direction) +{ + u64 byte_count = 0, low = 0, high = 0; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai_id) { + case I2S_BT_INSTANCE: + high = readl(adata->acp_base + ACP_BT_TX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_BT_TX_LINEARPOSITIONCNTR_LOW); + break; + case I2S_SP_INSTANCE: + high = readl(adata->acp_base + ACP_I2S_TX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_I2S_TX_LINEARPOSITIONCNTR_LOW); + break; + case I2S_HS_INSTANCE: + high = readl(adata->acp_base + ACP_HS_TX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_HS_TX_LINEARPOSITIONCNTR_LOW); + break; + default: + dev_err(adata->dev, "Invalid dai id %x\n", dai_id); + goto POINTER_RETURN_BYTES; + } + } else { + switch (dai_id) { + case I2S_BT_INSTANCE: + high = readl(adata->acp_base + ACP_BT_RX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_BT_RX_LINEARPOSITIONCNTR_LOW); + break; + case I2S_SP_INSTANCE: + high = readl(adata->acp_base + ACP_I2S_RX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_I2S_RX_LINEARPOSITIONCNTR_LOW); + break; + case I2S_HS_INSTANCE: + high = readl(adata->acp_base + ACP_HS_RX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_HS_RX_LINEARPOSITIONCNTR_LOW); + break; + case DMIC_INSTANCE: + high = readl(adata->acp_base + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW); + break; + default: + dev_err(adata->dev, "Invalid dai id %x\n", dai_id); + goto POINTER_RETURN_BYTES; + } + } + /* Get 64 bit value from two 32 bit registers */ + byte_count = (high << 32) | low; + +POINTER_RETURN_BYTES: + return byte_count; +} + +static inline void acp_set_i2s_clk(struct acp_dev_data *adata, int dai_id) +{ + union acp_i2stdm_mstrclkgen mclkgen; + u32 master_reg; + + switch (dai_id) { + case I2S_SP_INSTANCE: + master_reg = ACP_I2STDM0_MSTRCLKGEN; + break; + case I2S_BT_INSTANCE: + master_reg = ACP_I2STDM1_MSTRCLKGEN; + break; + case I2S_HS_INSTANCE: + master_reg = ACP_I2STDM2_MSTRCLKGEN; + break; + default: + master_reg = ACP_I2STDM0_MSTRCLKGEN; + break; + } + + mclkgen.bits.i2stdm_master_mode = 0x1; + mclkgen.bits.i2stdm_format_mode = 0x00; + + mclkgen.bits.i2stdm_bclk_div_val = adata->bclk_div; + mclkgen.bits.i2stdm_lrclk_div_val = adata->lrclk_div; + writel(mclkgen.u32_all, adata->acp_base + master_reg); +} +#endif diff --git a/sound/soc/amd/acp/chip_offset_byte.h b/sound/soc/amd/acp/chip_offset_byte.h new file mode 100644 index 000000000..ce3948e06 --- /dev/null +++ b/sound/soc/amd/acp/chip_offset_byte.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ + +#ifndef _ACP_IP_OFFSET_HEADER +#define _ACP_IP_OFFSET_HEADER + +#define ACPAXI2AXI_ATU_CTRL 0xC40 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5 0xC20 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_5 0xC24 + +#define ACP_PGFSM_CONTROL 0x141C +#define ACP_PGFSM_STATUS 0x1420 +#define ACP_SOFT_RESET 0x1000 +#define ACP_CONTROL 0x1004 + +#define ACP_EXTERNAL_INTR_REG_ADDR(adata, offset, ctrl) \ + (adata->acp_base + adata->rsrc->irq_reg_offset + offset + (ctrl * 0x04)) + +#define ACP_EXTERNAL_INTR_ENB(adata) ACP_EXTERNAL_INTR_REG_ADDR(adata, 0x0, 0x0) +#define ACP_EXTERNAL_INTR_CNTL(adata, ctrl) ACP_EXTERNAL_INTR_REG_ADDR(adata, 0x4, ctrl) +#define ACP_EXTERNAL_INTR_STAT(adata, ctrl) ACP_EXTERNAL_INTR_REG_ADDR(adata, \ + (0x4 + (adata->rsrc->no_of_ctrls * 0x04)), ctrl) + +/* Registers from ACP_AUDIO_BUFFERS block */ + +#define ACP_I2S_RX_RINGBUFADDR 0x2000 +#define ACP_I2S_RX_RINGBUFSIZE 0x2004 +#define ACP_I2S_RX_LINKPOSITIONCNTR 0x2008 +#define ACP_I2S_RX_FIFOADDR 0x200C +#define ACP_I2S_RX_FIFOSIZE 0x2010 +#define ACP_I2S_RX_DMA_SIZE 0x2014 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_HIGH 0x2018 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_LOW 0x201C +#define ACP_I2S_RX_INTR_WATERMARK_SIZE 0x2020 +#define ACP_I2S_TX_RINGBUFADDR 0x2024 +#define ACP_I2S_TX_RINGBUFSIZE 0x2028 +#define ACP_I2S_TX_LINKPOSITIONCNTR 0x202C +#define ACP_I2S_TX_FIFOADDR 0x2030 +#define ACP_I2S_TX_FIFOSIZE 0x2034 +#define ACP_I2S_TX_DMA_SIZE 0x2038 +#define ACP_I2S_TX_LINEARPOSITIONCNTR_HIGH 0x203C +#define ACP_I2S_TX_LINEARPOSITIONCNTR_LOW 0x2040 +#define ACP_I2S_TX_INTR_WATERMARK_SIZE 0x2044 +#define ACP_BT_RX_RINGBUFADDR 0x2048 +#define ACP_BT_RX_RINGBUFSIZE 0x204C +#define ACP_BT_RX_LINKPOSITIONCNTR 0x2050 +#define ACP_BT_RX_FIFOADDR 0x2054 +#define ACP_BT_RX_FIFOSIZE 0x2058 +#define ACP_BT_RX_DMA_SIZE 0x205C +#define ACP_BT_RX_LINEARPOSITIONCNTR_HIGH 0x2060 +#define ACP_BT_RX_LINEARPOSITIONCNTR_LOW 0x2064 +#define ACP_BT_RX_INTR_WATERMARK_SIZE 0x2068 +#define ACP_BT_TX_RINGBUFADDR 0x206C +#define ACP_BT_TX_RINGBUFSIZE 0x2070 +#define ACP_BT_TX_LINKPOSITIONCNTR 0x2074 +#define ACP_BT_TX_FIFOADDR 0x2078 +#define ACP_BT_TX_FIFOSIZE 0x207C +#define ACP_BT_TX_DMA_SIZE 0x2080 +#define ACP_BT_TX_LINEARPOSITIONCNTR_HIGH 0x2084 +#define ACP_BT_TX_LINEARPOSITIONCNTR_LOW 0x2088 +#define ACP_BT_TX_INTR_WATERMARK_SIZE 0x208C +#define ACP_HS_RX_RINGBUFADDR 0x3A90 +#define ACP_HS_RX_RINGBUFSIZE 0x3A94 +#define ACP_HS_RX_LINKPOSITIONCNTR 0x3A98 +#define ACP_HS_RX_FIFOADDR 0x3A9C +#define ACP_HS_RX_FIFOSIZE 0x3AA0 +#define ACP_HS_RX_DMA_SIZE 0x3AA4 +#define ACP_HS_RX_LINEARPOSITIONCNTR_HIGH 0x3AA8 +#define ACP_HS_RX_LINEARPOSITIONCNTR_LOW 0x3AAC +#define ACP_HS_RX_INTR_WATERMARK_SIZE 0x3AB0 +#define ACP_HS_TX_RINGBUFADDR 0x3AB4 +#define ACP_HS_TX_RINGBUFSIZE 0x3AB8 +#define ACP_HS_TX_LINKPOSITIONCNTR 0x3ABC +#define ACP_HS_TX_FIFOADDR 0x3AC0 +#define ACP_HS_TX_FIFOSIZE 0x3AC4 +#define ACP_HS_TX_DMA_SIZE 0x3AC8 +#define ACP_HS_TX_LINEARPOSITIONCNTR_HIGH 0x3ACC +#define ACP_HS_TX_LINEARPOSITIONCNTR_LOW 0x3AD0 +#define ACP_HS_TX_INTR_WATERMARK_SIZE 0x3AD4 + +#define ACP_I2STDM_IER 0x2400 +#define ACP_I2STDM_IRER 0x2404 +#define ACP_I2STDM_RXFRMT 0x2408 +#define ACP_I2STDM_ITER 0x240C +#define ACP_I2STDM_TXFRMT 0x2410 + +/* Registers from ACP_BT_TDM block */ + +#define ACP_BTTDM_IER 0x2800 +#define ACP_BTTDM_IRER 0x2804 +#define ACP_BTTDM_RXFRMT 0x2808 +#define ACP_BTTDM_ITER 0x280C +#define ACP_BTTDM_TXFRMT 0x2810 + +/* Registers from ACP_HS_TDM block */ +#define ACP_HSTDM_IER 0x2814 +#define ACP_HSTDM_IRER 0x2818 +#define ACP_HSTDM_RXFRMT 0x281C +#define ACP_HSTDM_ITER 0x2820 +#define ACP_HSTDM_TXFRMT 0x2824 + +/* Registers from ACP_WOV_PDM block */ + +#define ACP_WOV_PDM_ENABLE 0x2C04 +#define ACP_WOV_PDM_DMA_ENABLE 0x2C08 +#define ACP_WOV_RX_RINGBUFADDR 0x2C0C +#define ACP_WOV_RX_RINGBUFSIZE 0x2C10 +#define ACP_WOV_RX_LINKPOSITIONCNTR 0x2C14 +#define ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH 0x2C18 +#define ACP_WOV_RX_LINEARPOSITIONCNTR_LOW 0x2C1C +#define ACP_WOV_RX_INTR_WATERMARK_SIZE 0x2C20 +#define ACP_WOV_PDM_FIFO_FLUSH 0x2C24 +#define ACP_WOV_PDM_NO_OF_CHANNELS 0x2C28 +#define ACP_WOV_PDM_DECIMATION_FACTOR 0x2C2C +#define ACP_WOV_PDM_VAD_CTRL 0x2C30 +#define ACP_WOV_BUFFER_STATUS 0x2C58 +#define ACP_WOV_MISC_CTRL 0x2C5C +#define ACP_WOV_CLK_CTRL 0x2C60 +#define ACP_PDM_VAD_DYNAMIC_CLK_GATING_EN 0x2C64 +#define ACP_WOV_ERROR_STATUS_REGISTER 0x2C68 + +#define ACP_I2STDM0_MSTRCLKGEN 0x2414 +#define ACP_I2STDM1_MSTRCLKGEN 0x2418 +#define ACP_I2STDM2_MSTRCLKGEN 0x241C +#endif diff --git a/sound/soc/amd/acp3x-rt5682-max9836.c b/sound/soc/amd/acp3x-rt5682-max9836.c new file mode 100644 index 000000000..0543dda75 --- /dev/null +++ b/sound/soc/amd/acp3x-rt5682-max9836.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Machine driver for AMD ACP Audio engine using DA7219 & MAX98357 codec. +// +//Copyright 2016 Advanced Micro Devices, Inc. + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/io.h> +#include <linux/acpi.h> + +#include "raven/acp3x.h" +#include "../codecs/rt5682.h" +#include "../codecs/rt1015.h" + +#define PCO_PLAT_CLK 48000000 +#define RT5682_PLL_FREQ (48000 * 512) +#define DUAL_CHANNEL 2 + +static struct snd_soc_jack pco_jack; +static struct clk *rt5682_dai_wclk; +static struct clk *rt5682_dai_bclk; +static struct gpio_desc *dmic_sel; +void *soc_is_rltk_max(struct device *dev); + +enum { + RT5682 = 0, + MAX, + EC, +}; + +static int acp3x_5682_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + /* set rt5682 dai fmt */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP); + if (ret < 0) { + dev_err(rtd->card->dev, + "Failed to set rt5682 dai fmt: %d\n", ret); + return ret; + } + + /* set codec PLL */ + ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL2, RT5682_PLL2_S_MCLK, + PCO_PLAT_CLK, RT5682_PLL_FREQ); + if (ret < 0) { + dev_err(rtd->dev, "can't set rt5682 PLL: %d\n", ret); + return ret; + } + + /* Set codec sysclk */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL2, + RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, + "Failed to set rt5682 SYSCLK: %d\n", ret); + return ret; + } + + /* Set tdm/i2s1 master bclk ratio */ + ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); + if (ret < 0) { + dev_err(rtd->dev, + "Failed to set rt5682 tdm bclk ratio: %d\n", ret); + return ret; + } + + rt5682_dai_wclk = clk_get(component->dev, "rt5682-dai-wclk"); + rt5682_dai_bclk = clk_get(component->dev, "rt5682-dai-bclk"); + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &pco_jack); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, &pco_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return ret; +} + +static int rt5682_clk_enable(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + + /* RT5682 will support only 48K output with 48M mclk */ + clk_set_rate(rt5682_dai_wclk, 48000); + clk_set_rate(rt5682_dai_bclk, 48000 * 64); + ret = clk_prepare_enable(rt5682_dai_wclk); + if (ret < 0) { + dev_err(rtd->dev, "can't enable wclk %d\n", ret); + return ret; + } + + return ret; +} + +static int acp3x_1015_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 *codec_dai; + int srate, i, ret; + + ret = 0; + srate = params_rate(params); + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + if (strcmp(codec_dai->name, "rt1015-aif")) + continue; + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT1015_PLL_S_BCLK, + 64 * srate, 256 * srate); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_sysclk(codec_dai, RT1015_SCLK_S_PLL, + 256 * srate, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + } + return ret; +} + +static void rt5682_clk_disable(void) +{ + clk_disable_unprepare(rt5682_dai_wclk); +} + +static const unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int acp3x_5682_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->play_i2s_instance = I2S_SP_INSTANCE; + machine->cap_i2s_instance = I2S_SP_INSTANCE; + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + return rt5682_clk_enable(substream); +} + +static int acp3x_max_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->play_i2s_instance = I2S_BT_INSTANCE; + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + return rt5682_clk_enable(substream); +} + +static int acp3x_ec_dmic0_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct acp3x_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->cap_i2s_instance = I2S_BT_INSTANCE; + snd_soc_dai_set_bclk_ratio(codec_dai, 64); + + return rt5682_clk_enable(substream); +} + +static int dmic_switch; + +static int dmic_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = dmic_switch; + return 0; +} + +static int dmic_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (dmic_sel) { + dmic_switch = ucontrol->value.integer.value[0]; + gpiod_set_value(dmic_sel, dmic_switch); + } + return 0; +} + +static void rt5682_shutdown(struct snd_pcm_substream *substream) +{ + rt5682_clk_disable(); +} + +static const struct snd_soc_ops acp3x_5682_ops = { + .startup = acp3x_5682_startup, + .shutdown = rt5682_shutdown, +}; + +static const struct snd_soc_ops acp3x_max_play_ops = { + .startup = acp3x_max_startup, + .shutdown = rt5682_shutdown, + .hw_params = acp3x_1015_hw_params, +}; + +static const struct snd_soc_ops acp3x_ec_cap0_ops = { + .startup = acp3x_ec_dmic0_startup, + .shutdown = rt5682_shutdown, +}; + +SND_SOC_DAILINK_DEF(acp3x_i2s, + DAILINK_COMP_ARRAY(COMP_CPU("acp3x_i2s_playcap.0"))); +SND_SOC_DAILINK_DEF(acp3x_bt, + DAILINK_COMP_ARRAY(COMP_CPU("acp3x_i2s_playcap.2"))); + +SND_SOC_DAILINK_DEF(rt5682, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", "rt5682-aif1"))); +SND_SOC_DAILINK_DEF(max, + DAILINK_COMP_ARRAY(COMP_CODEC("MX98357A:00", "HiFi"))); +SND_SOC_DAILINK_DEF(rt1015p, + DAILINK_COMP_ARRAY(COMP_CODEC("RTL1015:00", "HiFi"))); +SND_SOC_DAILINK_DEF(rt1015, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC1015:00", "rt1015-aif"), + COMP_CODEC("i2c-10EC1015:01", "rt1015-aif"))); +SND_SOC_DAILINK_DEF(cros_ec, + DAILINK_COMP_ARRAY(COMP_CODEC("GOOG0013:00", "EC Codec I2S RX"))); + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("acp3x_rv_i2s_dma.0"))); + +static struct snd_soc_codec_conf rt1015_conf[] = { + { + .dlc = COMP_CODEC_CONF("i2c-10EC1015:00"), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF("i2c-10EC1015:01"), + .name_prefix = "Right", + }, +}; + +static struct snd_soc_dai_link acp3x_dai[] = { + [RT5682] = { + .name = "acp3x-5682-play", + .stream_name = "Playback", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP, + .init = acp3x_5682_init, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &acp3x_5682_ops, + SND_SOC_DAILINK_REG(acp3x_i2s, rt5682, platform), + }, + [MAX] = { + .name = "acp3x-max98357-play", + .stream_name = "HiFi Playback", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBC_CFC, + .dpcm_playback = 1, + .ops = &acp3x_max_play_ops, + .cpus = acp3x_bt, + .num_cpus = ARRAY_SIZE(acp3x_bt), + .platforms = platform, + .num_platforms = ARRAY_SIZE(platform), + }, + [EC] = { + .name = "acp3x-ec-dmic0-capture", + .stream_name = "Capture DMIC0", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBC_CFC, + .dpcm_capture = 1, + .ops = &acp3x_ec_cap0_ops, + SND_SOC_DAILINK_REG(acp3x_bt, cros_ec, platform), + }, +}; + +static const char * const dmic_mux_text[] = { + "Front Mic", + "Rear Mic", +}; + +static SOC_ENUM_SINGLE_DECL( + acp3x_dmic_enum, SND_SOC_NOPM, 0, dmic_mux_text); + +static const struct snd_kcontrol_new acp3x_dmic_mux_control = + SOC_DAPM_ENUM_EXT("DMIC Select Mux", acp3x_dmic_enum, + dmic_get, dmic_set); + +static const struct snd_soc_dapm_widget acp3x_5682_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MUX("Dmic Mux", SND_SOC_NOPM, 0, 0, + &acp3x_dmic_mux_control), +}; + +static const struct snd_soc_dapm_route acp3x_5682_audio_route[] = { + {"Headphone Jack", NULL, "HPOL"}, + {"Headphone Jack", NULL, "HPOR"}, + {"IN1P", NULL, "Headset Mic"}, + {"Spk", NULL, "Speaker"}, + {"Dmic Mux", "Front Mic", "DMIC"}, + {"Dmic Mux", "Rear Mic", "DMIC"}, +}; + +static const struct snd_kcontrol_new acp3x_5682_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Spk"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static struct snd_soc_card acp3x_5682 = { + .name = "acp3xalc5682m98357", + .owner = THIS_MODULE, + .dai_link = acp3x_dai, + .num_links = ARRAY_SIZE(acp3x_dai), + .dapm_widgets = acp3x_5682_widgets, + .num_dapm_widgets = ARRAY_SIZE(acp3x_5682_widgets), + .dapm_routes = acp3x_5682_audio_route, + .num_dapm_routes = ARRAY_SIZE(acp3x_5682_audio_route), + .controls = acp3x_5682_mc_controls, + .num_controls = ARRAY_SIZE(acp3x_5682_mc_controls), +}; + +static const struct snd_soc_dapm_widget acp3x_1015_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MUX("Dmic Mux", SND_SOC_NOPM, 0, 0, + &acp3x_dmic_mux_control), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static const struct snd_soc_dapm_route acp3x_1015_route[] = { + {"Headphone Jack", NULL, "HPOL"}, + {"Headphone Jack", NULL, "HPOR"}, + {"IN1P", NULL, "Headset Mic"}, + {"Dmic Mux", "Front Mic", "DMIC"}, + {"Dmic Mux", "Rear Mic", "DMIC"}, + {"Left Spk", NULL, "Left SPO"}, + {"Right Spk", NULL, "Right SPO"}, +}; + +static const struct snd_kcontrol_new acp3x_mc_1015_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static struct snd_soc_card acp3x_1015 = { + .name = "acp3xalc56821015", + .owner = THIS_MODULE, + .dai_link = acp3x_dai, + .num_links = ARRAY_SIZE(acp3x_dai), + .dapm_widgets = acp3x_1015_widgets, + .num_dapm_widgets = ARRAY_SIZE(acp3x_1015_widgets), + .dapm_routes = acp3x_1015_route, + .num_dapm_routes = ARRAY_SIZE(acp3x_1015_route), + .codec_conf = rt1015_conf, + .num_configs = ARRAY_SIZE(rt1015_conf), + .controls = acp3x_mc_1015_controls, + .num_controls = ARRAY_SIZE(acp3x_mc_1015_controls), +}; + +static const struct snd_soc_dapm_widget acp3x_1015p_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MUX("Dmic Mux", SND_SOC_NOPM, 0, 0, + &acp3x_dmic_mux_control), + SND_SOC_DAPM_SPK("Speakers", NULL), +}; + +static const struct snd_soc_dapm_route acp3x_1015p_route[] = { + {"Headphone Jack", NULL, "HPOL"}, + {"Headphone Jack", NULL, "HPOR"}, + {"IN1P", NULL, "Headset Mic"}, + {"Dmic Mux", "Front Mic", "DMIC"}, + {"Dmic Mux", "Rear Mic", "DMIC"}, + /* speaker */ + { "Speakers", NULL, "Speaker" }, +}; + +static const struct snd_kcontrol_new acp3x_mc_1015p_controls[] = { + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static struct snd_soc_card acp3x_1015p = { + .name = "acp3xalc56821015p", + .owner = THIS_MODULE, + .dai_link = acp3x_dai, + .num_links = ARRAY_SIZE(acp3x_dai), + .dapm_widgets = acp3x_1015p_widgets, + .num_dapm_widgets = ARRAY_SIZE(acp3x_1015p_widgets), + .dapm_routes = acp3x_1015p_route, + .num_dapm_routes = ARRAY_SIZE(acp3x_1015p_route), + .controls = acp3x_mc_1015p_controls, + .num_controls = ARRAY_SIZE(acp3x_mc_1015p_controls), +}; + +void *soc_is_rltk_max(struct device *dev) +{ + const struct acpi_device_id *match; + + match = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!match) + return NULL; + return (void *)match->driver_data; +} + +static void card_spk_dai_link_present(struct snd_soc_dai_link *links, + const char *card_name) +{ + if (!strcmp(card_name, "acp3xalc56821015")) { + links[1].codecs = rt1015; + links[1].num_codecs = ARRAY_SIZE(rt1015); + } else if (!strcmp(card_name, "acp3xalc56821015p")) { + links[1].codecs = rt1015p; + links[1].num_codecs = ARRAY_SIZE(rt1015p); + } else { + links[1].codecs = max; + links[1].num_codecs = ARRAY_SIZE(max); + } +} + +static int acp3x_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card; + struct acp3x_platform_info *machine; + struct device *dev = &pdev->dev; + + card = (struct snd_soc_card *)soc_is_rltk_max(dev); + if (!card) + return -ENODEV; + + machine = devm_kzalloc(&pdev->dev, sizeof(*machine), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card_spk_dai_link_present(card->dai_link, card->name); + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + + dmic_sel = devm_gpiod_get(&pdev->dev, "dmic", GPIOD_OUT_LOW); + if (IS_ERR(dmic_sel)) { + dev_err(&pdev->dev, "DMIC gpio failed err=%ld\n", + PTR_ERR(dmic_sel)); + return PTR_ERR(dmic_sel); + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + return dev_err_probe(&pdev->dev, ret, + "devm_snd_soc_register_card(%s) failed\n", + card->name); + } + return 0; +} + +static const struct acpi_device_id acp3x_audio_acpi_match[] = { + { "AMDI5682", (unsigned long)&acp3x_5682}, + { "AMDI1015", (unsigned long)&acp3x_1015}, + { "10021015", (unsigned long)&acp3x_1015p}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, acp3x_audio_acpi_match); + +static struct platform_driver acp3x_audio = { + .driver = { + .name = "acp3x-alc5682-max98357", + .acpi_match_table = ACPI_PTR(acp3x_audio_acpi_match), + .pm = &snd_soc_pm_ops, + }, + .probe = acp3x_probe, +}; + +module_platform_driver(acp3x_audio); + +MODULE_AUTHOR("akshu.agrawal@amd.com"); +MODULE_AUTHOR("Vishnuvardhanrao.Ravulapati@amd.com"); +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("ALC5682 ALC1015, ALC1015P & MAX98357 audio support"); +MODULE_LICENSE("GPL v2"); |