diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2019-05-09 17:26:55 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-05-09 17:26:55 +0200 |
commit | e57ccca1ba33e1d92cc3bbf8b6304a46948844b0 (patch) | |
tree | a988a1f7d1d3250f57761dbea365482300a7b3b2 /sound/soc | |
parent | Merge tag 'drm-next-2019-05-09' of git://anongit.freedesktop.org/drm/drm (diff) | |
parent | Merge tag 'asoc-v5.2-5' of git://git.kernel.org/pub/scm/linux/kernel/git/broo... (diff) | |
download | linux-e57ccca1ba33e1d92cc3bbf8b6304a46948844b0.tar.xz linux-e57ccca1ba33e1d92cc3bbf8b6304a46948844b0.zip |
Merge tag 'sound-5.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound updates from Takashi Iwai:
"The most significant changes at this cycle are the Sound Open Firmware
support from Intel for the common DSP framework along with its support
for Intel platforms. It's a door opened to a real "free" firmware (in
the sense of FOSS), and other parties show interests in it.
In addition to SOF, we've got a bunch of updates and fixes as usual.
Some highlights are below.
ALSA core:
- Cleanups and fixes in ALSA timer code to cover some races spotted
by syzkaller
- Cleanups and fixes in ALSA sequencer code to cover some races,
again unsurprisingly, spotted by syzkaller
- Optimize the common page allocation helper with alloc_pages_exact()
ASoC:
- Add SOF core support, as well as Intel SOF platform support
- Generic card driver improvements: support for MCLK/sample rate
ratio and pin switches
- A big set of improvements to TLV320AIC32x4 drivers
- New drivers for Freescale audio mixers, several Intel machines,
several Mediatek machines, Meson G12A, Spreadtrum compressed audio
and DMA devices
HD-audio:
- A few Realtek codec fixes for reducing pop noises
- Quirks for Chromebooks
- Workaround for faulty connection report on AMD/Nvidia HDMI
Others:
- A quirk for Focusrite Scarlett Solo USB-audio
- Add support for MOTU 8pre FireWire
- 24bit sample format support in aloop
- GUS patch format support (finally, over a decade) in native emux
synth code"
* tag 'sound-5.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (375 commits)
ASoC: SOF: Fix unused variable warnings
ALSA: line6: toneport: Fix broken usage of timer for delayed execution
ALSA: aica: Fix a long-time build breakage
ALSA: hda/realtek - Support low power consumption for ALC256
ASoC: stm32: i2s: update pcm hardware constraints
ASoC: codec: hdac_hdmi: no checking monitor in hw_params
ASoC: mediatek: mt6358: save PGA for mixer control
ASoC: mediatek: mt6358: save output volume for mixer controls
ASoC: mediatek: mt6358: initialize setting when ramping volume
ASoC: SOF: core: fix undefined nocodec reference
ASoC: SOF: xtensa: fix undefined references
ASoC: SOF: Propagate sof_get_ctrl_copy_params() error properly
ALSA: hdea/realtek - Headset fixup for System76 Gazelle (gaze14)
ALSA: hda/intel: add CometLake PCI IDs
ALSA: hda/realtek - Support low power consumption for ALC295
ASoC: rockchip: Fix an uninitialized variable compile warning
ASoC: SOF: Fix a compile warning with CONFIG_PCI=n
ASoC: da7219: Fix a compile warning at CONFIG_COMMON_CLK=n
ASoC: sound/soc/sof/: fix kconfig dependency warning
ASoC: stm32: spdifrx: change trace level on iec control
...
Diffstat (limited to 'sound/soc')
234 files changed, 27002 insertions, 2696 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index aa35940f5c50..297be0ca3dbc 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -63,6 +63,7 @@ source "sound/soc/rockchip/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/sirf/Kconfig" +source "sound/soc/sof/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/sprd/Kconfig" source "sound/soc/sti/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 974fb9821e17..d90ce8a32887 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_SND_SOC) += rockchip/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += sirf/ +obj-$(CONFIG_SND_SOC) += sof/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += sprd/ obj-$(CONFIG_SND_SOC) += sti/ diff --git a/sound/soc/adi/axi-i2s.c b/sound/soc/adi/axi-i2s.c index 4c23381727a1..273c543e8ff3 100644 --- a/sound/soc/adi/axi-i2s.c +++ b/sound/soc/adi/axi-i2s.c @@ -43,6 +43,9 @@ struct axi_i2s { struct clk *clk; struct clk *clk_ref; + bool has_capture; + bool has_playback; + struct snd_soc_dai_driver dai_driver; struct snd_dmaengine_dai_dma_data capture_dma_data; @@ -136,8 +139,10 @@ static int axi_i2s_dai_probe(struct snd_soc_dai *dai) { struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); - snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, - &i2s->capture_dma_data); + snd_soc_dai_init_dma_data( + dai, + i2s->has_playback ? &i2s->playback_dma_data : NULL, + i2s->has_capture ? &i2s->capture_dma_data : NULL); return 0; } @@ -151,18 +156,6 @@ static const struct snd_soc_dai_ops axi_i2s_dai_ops = { static struct snd_soc_dai_driver axi_i2s_dai = { .probe = axi_i2s_dai_probe, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE, - }, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE, - }, .ops = &axi_i2s_dai_ops, .symmetric_rates = 1, }; @@ -178,6 +171,19 @@ static const struct regmap_config axi_i2s_regmap_config = { .max_register = AXI_I2S_REG_STATUS, }; +static void axi_i2s_parse_of(struct axi_i2s *i2s, const struct device_node *np) +{ + struct property *dma_names; + const char *dma_name; + + of_property_for_each_string(np, "dma-names", dma_names, dma_name) { + if (strcmp(dma_name, "rx") == 0) + i2s->has_capture = true; + if (strcmp(dma_name, "tx") == 0) + i2s->has_playback = true; + } +} + static int axi_i2s_probe(struct platform_device *pdev) { struct resource *res; @@ -191,6 +197,8 @@ static int axi_i2s_probe(struct platform_device *pdev) platform_set_drvdata(pdev, i2s); + axi_i2s_parse_of(i2s, pdev->dev.of_node); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) @@ -213,13 +221,29 @@ static int axi_i2s_probe(struct platform_device *pdev) if (ret) return ret; - i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO; - i2s->playback_dma_data.addr_width = 4; - i2s->playback_dma_data.maxburst = 1; + if (i2s->has_playback) { + axi_i2s_dai.playback.channels_min = 2; + axi_i2s_dai.playback.channels_max = 2; + axi_i2s_dai.playback.rates = SNDRV_PCM_RATE_KNOT; + axi_i2s_dai.playback.formats = + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE; + + i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO; + i2s->playback_dma_data.addr_width = 4; + i2s->playback_dma_data.maxburst = 1; + } + + if (i2s->has_capture) { + axi_i2s_dai.capture.channels_min = 2; + axi_i2s_dai.capture.channels_max = 2; + axi_i2s_dai.capture.rates = SNDRV_PCM_RATE_KNOT; + axi_i2s_dai.capture.formats = + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE; - i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO; - i2s->capture_dma_data.addr_width = 4; - i2s->capture_dma_data.maxburst = 1; + i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO; + i2s->capture_dma_data.addr_width = 4; + i2s->capture_dma_data.maxburst = 1; + } i2s->ratnum.num = clk_get_rate(i2s->clk_ref) / 2 / AXI_I2S_BITS_PER_FRAME; i2s->ratnum.den_step = 1; @@ -240,6 +264,10 @@ static int axi_i2s_probe(struct platform_device *pdev) if (ret) goto err_clk_disable; + dev_info(&pdev->dev, "probed, capture %s, playback %s\n", + i2s->has_capture ? "enabled" : "disabled", + i2s->has_playback ? "enabled" : "disabled"); + return 0; err_clk_disable: diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index a5daad973ce5..16b0ea3a3d72 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -46,8 +46,9 @@ #define DUAL_CHANNEL 2 static struct snd_soc_jack cz_jack; -static struct clk *da7219_dai_clk; -extern int bt_uart_enable; +static struct clk *da7219_dai_wclk; +static struct clk *da7219_dai_bclk; +extern bool bt_uart_enable; static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) { @@ -72,7 +73,8 @@ static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) return ret; } - da7219_dai_clk = clk_get(component->dev, "da7219-dai-clks"); + da7219_dai_wclk = clk_get(component->dev, "da7219-dai-wclk"); + da7219_dai_bclk = clk_get(component->dev, "da7219-dai-bclk"); ret = snd_soc_card_jack_new(card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_LINEOUT | @@ -94,12 +96,15 @@ static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static int da7219_clk_enable(struct snd_pcm_substream *substream) +static int da7219_clk_enable(struct snd_pcm_substream *substream, + int wclk_rate, int bclk_rate) { int ret = 0; struct snd_soc_pcm_runtime *rtd = substream->private_data; - ret = clk_prepare_enable(da7219_dai_clk); + clk_set_rate(da7219_dai_wclk, wclk_rate); + clk_set_rate(da7219_dai_bclk, bclk_rate); + ret = clk_prepare_enable(da7219_dai_bclk); if (ret < 0) { dev_err(rtd->dev, "can't enable master clock %d\n", ret); return ret; @@ -110,7 +115,7 @@ static int da7219_clk_enable(struct snd_pcm_substream *substream) static void da7219_clk_disable(void) { - clk_disable_unprepare(da7219_dai_clk); + clk_disable_unprepare(da7219_dai_bclk); } static const unsigned int channels[] = { @@ -151,7 +156,7 @@ static int cz_da7219_play_startup(struct snd_pcm_substream *substream) &constraints_rates); machine->play_i2s_instance = I2S_SP_INSTANCE; - return da7219_clk_enable(substream); + return 0; } static int cz_da7219_cap_startup(struct snd_pcm_substream *substream) @@ -173,12 +178,7 @@ static int cz_da7219_cap_startup(struct snd_pcm_substream *substream) machine->cap_i2s_instance = I2S_SP_INSTANCE; machine->capture_channel = CAP_CHANNEL1; - return da7219_clk_enable(substream); -} - -static void cz_da7219_shutdown(struct snd_pcm_substream *substream) -{ - da7219_clk_disable(); + return 0; } static int cz_max_startup(struct snd_pcm_substream *substream) @@ -199,12 +199,7 @@ static int cz_max_startup(struct snd_pcm_substream *substream) &constraints_rates); machine->play_i2s_instance = I2S_BT_INSTANCE; - return da7219_clk_enable(substream); -} - -static void cz_max_shutdown(struct snd_pcm_substream *substream) -{ - da7219_clk_disable(); + return 0; } static int cz_dmic0_startup(struct snd_pcm_substream *substream) @@ -225,7 +220,7 @@ static int cz_dmic0_startup(struct snd_pcm_substream *substream) &constraints_rates); machine->cap_i2s_instance = I2S_BT_INSTANCE; - return da7219_clk_enable(substream); + return 0; } static int cz_dmic1_startup(struct snd_pcm_substream *substream) @@ -247,10 +242,28 @@ static int cz_dmic1_startup(struct snd_pcm_substream *substream) machine->cap_i2s_instance = I2S_SP_INSTANCE; machine->capture_channel = CAP_CHANNEL0; - return da7219_clk_enable(substream); + return 0; } -static void cz_dmic_shutdown(struct snd_pcm_substream *substream) +static int cz_da7219_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int wclk, bclk; + + wclk = params_rate(params); + bclk = wclk * params_channels(params) * + snd_pcm_format_width(params_format(params)); + /* 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. + */ + if (bclk < (wclk * 64)) + bclk = wclk * 64; + return da7219_clk_enable(substream, wclk, bclk); +} + +static void cz_da7219_shutdown(struct snd_pcm_substream *substream) { da7219_clk_disable(); } @@ -258,26 +271,31 @@ static void cz_dmic_shutdown(struct snd_pcm_substream *substream) static const struct snd_soc_ops cz_da7219_play_ops = { .startup = cz_da7219_play_startup, .shutdown = cz_da7219_shutdown, + .hw_params = cz_da7219_params, }; static const struct snd_soc_ops cz_da7219_cap_ops = { .startup = cz_da7219_cap_startup, .shutdown = cz_da7219_shutdown, + .hw_params = cz_da7219_params, }; static const struct snd_soc_ops cz_max_play_ops = { .startup = cz_max_startup, - .shutdown = cz_max_shutdown, + .shutdown = cz_da7219_shutdown, + .hw_params = cz_da7219_params, }; static const struct snd_soc_ops cz_dmic0_cap_ops = { .startup = cz_dmic0_startup, - .shutdown = cz_dmic_shutdown, + .shutdown = cz_da7219_shutdown, + .hw_params = cz_da7219_params, }; static const struct snd_soc_ops cz_dmic1_cap_ops = { .startup = cz_dmic1_startup, - .shutdown = cz_dmic_shutdown, + .shutdown = cz_da7219_shutdown, + .hw_params = cz_da7219_params, }; static struct snd_soc_dai_link cz_dai_7219_98357[] = { diff --git a/sound/soc/amd/raven/acp3x-pcm-dma.c b/sound/soc/amd/raven/acp3x-pcm-dma.c index 1a2e15ff1456..9775bda2a4ca 100644 --- a/sound/soc/amd/raven/acp3x-pcm-dma.c +++ b/sound/soc/amd/raven/acp3x-pcm-dma.c @@ -558,7 +558,7 @@ static int acp3x_dai_i2s_trigger(struct snd_pcm_substream *substream, return ret; } -struct snd_soc_dai_ops acp3x_dai_i2s_ops = { +static struct snd_soc_dai_ops acp3x_dai_i2s_ops = { .hw_params = acp3x_dai_i2s_hwparams, .trigger = acp3x_dai_i2s_trigger, .set_fmt = acp3x_dai_i2s_set_fmt, diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 64f86f0b87e5..c473b9e463ab 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -109,4 +109,18 @@ config SND_SOC_MIKROE_PROTO using I2C over SDA (MPU Data Input) and SCL (MPU Clock Input) pins. Both playback and capture are supported. +config SND_MCHP_SOC_I2S_MCC + tristate "Microchip ASoC driver for boards using I2S MCC" + depends on OF && (ARCH_AT91 || COMPILE_TEST) + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for I2S Multi-Channel ASoC + driver on the following Microchip platforms: + - sam9x60 + + The I2SMCC complies with the Inter-IC Sound (I2S) bus specification + and supports a Time Division Multiplexed (TDM) interface with + external multi-channel audio codecs. + endif diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index 9f41bfa0fea3..1f6890ed3738 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -4,11 +4,13 @@ snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o snd-soc-atmel-i2s-objs := atmel-i2s.o +snd-soc-mchp-i2s-mcc-objs := mchp-i2s-mcc.o obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o obj-$(CONFIG_SND_ATMEL_SOC_DMA) += snd-soc-atmel-pcm-dma.o obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o obj-$(CONFIG_SND_ATMEL_SOC_I2S) += snd-soc-atmel-i2s.o +obj-$(CONFIG_SND_MCHP_SOC_I2S_MCC) += snd-soc-mchp-i2s-mcc.o # AT91 Machine Support snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c new file mode 100644 index 000000000000..86495883ca3f --- /dev/null +++ b/sound/soc/atmel/mchp-i2s-mcc.c @@ -0,0 +1,974 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Driver for Microchip I2S Multi-channel controller +// +// Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries +// +// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com> + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/lcm.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +/* + * ---- I2S Controller Register map ---- + */ +#define MCHP_I2SMCC_CR 0x0000 /* Control Register */ +#define MCHP_I2SMCC_MRA 0x0004 /* Mode Register A */ +#define MCHP_I2SMCC_MRB 0x0008 /* Mode Register B */ +#define MCHP_I2SMCC_SR 0x000C /* Status Register */ +#define MCHP_I2SMCC_IERA 0x0010 /* Interrupt Enable Register A */ +#define MCHP_I2SMCC_IDRA 0x0014 /* Interrupt Disable Register A */ +#define MCHP_I2SMCC_IMRA 0x0018 /* Interrupt Mask Register A */ +#define MCHP_I2SMCC_ISRA 0X001C /* Interrupt Status Register A */ + +#define MCHP_I2SMCC_IERB 0x0020 /* Interrupt Enable Register B */ +#define MCHP_I2SMCC_IDRB 0x0024 /* Interrupt Disable Register B */ +#define MCHP_I2SMCC_IMRB 0x0028 /* Interrupt Mask Register B */ +#define MCHP_I2SMCC_ISRB 0X002C /* Interrupt Status Register B */ + +#define MCHP_I2SMCC_RHR 0x0030 /* Receiver Holding Register */ +#define MCHP_I2SMCC_THR 0x0034 /* Transmitter Holding Register */ + +#define MCHP_I2SMCC_RHL0R 0x0040 /* Receiver Holding Left 0 Register */ +#define MCHP_I2SMCC_RHR0R 0x0044 /* Receiver Holding Right 0 Register */ + +#define MCHP_I2SMCC_RHL1R 0x0048 /* Receiver Holding Left 1 Register */ +#define MCHP_I2SMCC_RHR1R 0x004C /* Receiver Holding Right 1 Register */ + +#define MCHP_I2SMCC_RHL2R 0x0050 /* Receiver Holding Left 2 Register */ +#define MCHP_I2SMCC_RHR2R 0x0054 /* Receiver Holding Right 2 Register */ + +#define MCHP_I2SMCC_RHL3R 0x0058 /* Receiver Holding Left 3 Register */ +#define MCHP_I2SMCC_RHR3R 0x005C /* Receiver Holding Right 3 Register */ + +#define MCHP_I2SMCC_THL0R 0x0060 /* Transmitter Holding Left 0 Register */ +#define MCHP_I2SMCC_THR0R 0x0064 /* Transmitter Holding Right 0 Register */ + +#define MCHP_I2SMCC_THL1R 0x0068 /* Transmitter Holding Left 1 Register */ +#define MCHP_I2SMCC_THR1R 0x006C /* Transmitter Holding Right 1 Register */ + +#define MCHP_I2SMCC_THL2R 0x0070 /* Transmitter Holding Left 2 Register */ +#define MCHP_I2SMCC_THR2R 0x0074 /* Transmitter Holding Right 2 Register */ + +#define MCHP_I2SMCC_THL3R 0x0078 /* Transmitter Holding Left 3 Register */ +#define MCHP_I2SMCC_THR3R 0x007C /* Transmitter Holding Right 3 Register */ + +#define MCHP_I2SMCC_VERSION 0x00FC /* Version Register */ + +/* + * ---- Control Register (Write-only) ---- + */ +#define MCHP_I2SMCC_CR_RXEN BIT(0) /* Receiver Enable */ +#define MCHP_I2SMCC_CR_RXDIS BIT(1) /* Receiver Disable */ +#define MCHP_I2SMCC_CR_CKEN BIT(2) /* Clock Enable */ +#define MCHP_I2SMCC_CR_CKDIS BIT(3) /* Clock Disable */ +#define MCHP_I2SMCC_CR_TXEN BIT(4) /* Transmitter Enable */ +#define MCHP_I2SMCC_CR_TXDIS BIT(5) /* Transmitter Disable */ +#define MCHP_I2SMCC_CR_SWRST BIT(7) /* Software Reset */ + +/* + * ---- Mode Register A (Read/Write) ---- + */ +#define MCHP_I2SMCC_MRA_MODE_MASK GENMASK(0, 0) +#define MCHP_I2SMCC_MRA_MODE_SLAVE (0 << 0) +#define MCHP_I2SMCC_MRA_MODE_MASTER (1 << 0) + +#define MCHP_I2SMCC_MRA_DATALENGTH_MASK GENMASK(3, 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_32_BITS (0 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_24_BITS (1 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_20_BITS (2 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_18_BITS (3 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_16_BITS (4 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_16_BITS_COMPACT (5 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_8_BITS (6 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_8_BITS_COMPACT (7 << 1) + +#define MCHP_I2SMCC_MRA_WIRECFG_MASK GENMASK(5, 4) +#define MCHP_I2SMCC_MRA_WIRECFG_I2S_1_TDM_0 (0 << 4) +#define MCHP_I2SMCC_MRA_WIRECFG_I2S_2_TDM_1 (1 << 4) +#define MCHP_I2SMCC_MRA_WIRECFG_I2S_4_TDM_2 (2 << 4) +#define MCHP_I2SMCC_MRA_WIRECFG_TDM_3 (3 << 4) + +#define MCHP_I2SMCC_MRA_FORMAT_MASK GENMASK(7, 6) +#define MCHP_I2SMCC_MRA_FORMAT_I2S (0 << 6) +#define MCHP_I2SMCC_MRA_FORMAT_LJ (1 << 6) /* Left Justified */ +#define MCHP_I2SMCC_MRA_FORMAT_TDM (2 << 6) +#define MCHP_I2SMCC_MRA_FORMAT_TDMLJ (3 << 6) + +/* Transmitter uses one DMA channel ... */ +/* Left audio samples duplicated to right audio channel */ +#define MCHP_I2SMCC_MRA_RXMONO BIT(8) + +/* I2SDO output of I2SC is internally connected to I2SDI input */ +#define MCHP_I2SMCC_MRA_RXLOOP BIT(9) + +/* Receiver uses one DMA channel ... */ +/* Left audio samples duplicated to right audio channel */ +#define MCHP_I2SMCC_MRA_TXMONO BIT(10) + +/* x sample transmitted when underrun */ +#define MCHP_I2SMCC_MRA_TXSAME_ZERO (0 << 11) /* Zero sample */ +#define MCHP_I2SMCC_MRA_TXSAME_PREVIOUS (1 << 11) /* Previous sample */ + +/* select between peripheral clock and generated clock */ +#define MCHP_I2SMCC_MRA_SRCCLK_PCLK (0 << 12) +#define MCHP_I2SMCC_MRA_SRCCLK_GCLK (1 << 12) + +/* Number of TDM Channels - 1 */ +#define MCHP_I2SMCC_MRA_NBCHAN_MASK GENMASK(15, 13) +#define MCHP_I2SMCC_MRA_NBCHAN(ch) \ + ((((ch) - 1) << 13) & MCHP_I2SMCC_MRA_NBCHAN_MASK) + +/* Selected Clock to I2SMCC Master Clock ratio */ +#define MCHP_I2SMCC_MRA_IMCKDIV_MASK GENMASK(21, 16) +#define MCHP_I2SMCC_MRA_IMCKDIV(div) \ + (((div) << 16) & MCHP_I2SMCC_MRA_IMCKDIV_MASK) + +/* TDM Frame Synchronization */ +#define MCHP_I2SMCC_MRA_TDMFS_MASK GENMASK(23, 22) +#define MCHP_I2SMCC_MRA_TDMFS_SLOT (0 << 22) +#define MCHP_I2SMCC_MRA_TDMFS_HALF (1 << 22) +#define MCHP_I2SMCC_MRA_TDMFS_BIT (2 << 22) + +/* Selected Clock to I2SMC Serial Clock ratio */ +#define MCHP_I2SMCC_MRA_ISCKDIV_MASK GENMASK(29, 24) +#define MCHP_I2SMCC_MRA_ISCKDIV(div) \ + (((div) << 24) & MCHP_I2SMCC_MRA_ISCKDIV_MASK) + +/* Master Clock mode */ +#define MCHP_I2SMCC_MRA_IMCKMODE_MASK GENMASK(30, 30) +/* 0: No master clock generated*/ +#define MCHP_I2SMCC_MRA_IMCKMODE_NONE (0 << 30) +/* 1: master clock generated (internally generated clock drives I2SMCK pin) */ +#define MCHP_I2SMCC_MRA_IMCKMODE_GEN (1 << 30) + +/* Slot Width */ +/* 0: slot is 32 bits wide for DATALENGTH = 18/20/24 bits. */ +/* 1: slot is 24 bits wide for DATALENGTH = 18/20/24 bits. */ +#define MCHP_I2SMCC_MRA_IWS BIT(31) + +/* + * ---- Mode Register B (Read/Write) ---- + */ +/* all enabled I2S left channels are filled first, then I2S right channels */ +#define MCHP_I2SMCC_MRB_CRAMODE_LEFT_FIRST (0 << 0) +/* + * an enabled I2S left channel is filled, then the corresponding right + * channel, until all channels are filled + */ +#define MCHP_I2SMCC_MRB_CRAMODE_REGULAR (1 << 0) + +#define MCHP_I2SMCC_MRB_FIFOEN BIT(1) + +#define MCHP_I2SMCC_MRB_DMACHUNK_MASK GENMASK(9, 8) +#define MCHP_I2SMCC_MRB_DMACHUNK(no_words) \ + (((fls(no_words) - 1) << 8) & MCHP_I2SMCC_MRB_DMACHUNK_MASK) + +#define MCHP_I2SMCC_MRB_CLKSEL_MASK GENMASK(16, 16) +#define MCHP_I2SMCC_MRB_CLKSEL_EXT (0 << 16) +#define MCHP_I2SMCC_MRB_CLKSEL_INT (1 << 16) + +/* + * ---- Status Registers (Read-only) ---- + */ +#define MCHP_I2SMCC_SR_RXEN BIT(0) /* Receiver Enabled */ +#define MCHP_I2SMCC_SR_TXEN BIT(4) /* Transmitter Enabled */ + +/* + * ---- Interrupt Enable/Disable/Mask/Status Registers A ---- + */ +#define MCHP_I2SMCC_INT_TXRDY_MASK(ch) GENMASK((ch) - 1, 0) +#define MCHP_I2SMCC_INT_TXRDYCH(ch) BIT(ch) +#define MCHP_I2SMCC_INT_TXUNF_MASK(ch) GENMASK((ch) + 7, 8) +#define MCHP_I2SMCC_INT_TXUNFCH(ch) BIT((ch) + 8) +#define MCHP_I2SMCC_INT_RXRDY_MASK(ch) GENMASK((ch) + 15, 16) +#define MCHP_I2SMCC_INT_RXRDYCH(ch) BIT((ch) + 16) +#define MCHP_I2SMCC_INT_RXOVF_MASK(ch) GENMASK((ch) + 23, 24) +#define MCHP_I2SMCC_INT_RXOVFCH(ch) BIT((ch) + 24) + +/* + * ---- Interrupt Enable/Disable/Mask/Status Registers B ---- + */ +#define MCHP_I2SMCC_INT_WERR BIT(0) +#define MCHP_I2SMCC_INT_TXFFRDY BIT(8) +#define MCHP_I2SMCC_INT_TXFFEMP BIT(9) +#define MCHP_I2SMCC_INT_RXFFRDY BIT(12) +#define MCHP_I2SMCC_INT_RXFFFUL BIT(13) + +/* + * ---- Version Register (Read-only) ---- + */ +#define MCHP_I2SMCC_VERSION_MASK GENMASK(11, 0) + +#define MCHP_I2SMCC_MAX_CHANNELS 8 +#define MCHP_I2MCC_TDM_SLOT_WIDTH 32 + +static const struct regmap_config mchp_i2s_mcc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = MCHP_I2SMCC_VERSION, +}; + +struct mchp_i2s_mcc_dev { + struct wait_queue_head wq_txrdy; + struct wait_queue_head wq_rxrdy; + struct device *dev; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct snd_dmaengine_dai_dma_data playback; + struct snd_dmaengine_dai_dma_data capture; + unsigned int fmt; + unsigned int sysclk; + unsigned int frame_length; + int tdm_slots; + int channels; + int gclk_use:1; + int gclk_running:1; + int tx_rdy:1; + int rx_rdy:1; +}; + +static irqreturn_t mchp_i2s_mcc_interrupt(int irq, void *dev_id) +{ + struct mchp_i2s_mcc_dev *dev = dev_id; + u32 sra, imra, srb, imrb, pendinga, pendingb, idra = 0; + irqreturn_t ret = IRQ_NONE; + + regmap_read(dev->regmap, MCHP_I2SMCC_IMRA, &imra); + regmap_read(dev->regmap, MCHP_I2SMCC_ISRA, &sra); + pendinga = imra & sra; + + regmap_read(dev->regmap, MCHP_I2SMCC_IMRB, &imrb); + regmap_read(dev->regmap, MCHP_I2SMCC_ISRB, &srb); + pendingb = imrb & srb; + + if (!pendinga && !pendingb) + return IRQ_NONE; + + /* + * Tx/Rx ready interrupts are enabled when stopping only, to assure + * availability and to disable clocks if necessary + */ + idra |= pendinga & (MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels) | + MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)); + if (idra) + ret = IRQ_HANDLED; + + if ((imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) && + (imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) == + (idra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels))) { + dev->tx_rdy = 1; + wake_up_interruptible(&dev->wq_txrdy); + } + if ((imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) && + (imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) == + (idra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels))) { + dev->rx_rdy = 1; + wake_up_interruptible(&dev->wq_rxrdy); + } + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, idra); + + return ret; +} + +static int mchp_i2s_mcc_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s() clk_id=%d freq=%u dir=%d\n", + __func__, clk_id, freq, dir); + + /* We do not need SYSCLK */ + if (dir == SND_SOC_CLOCK_IN) + return 0; + + dev->sysclk = freq; + + return 0; +} + +static int mchp_i2s_mcc_set_bclk_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s() ratio=%u\n", __func__, ratio); + + dev->frame_length = ratio; + + return 0; +} + +static int mchp_i2s_mcc_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s() fmt=%#x\n", __func__, fmt); + + /* We don't support any kind of clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) + return -EINVAL; + + /* We can't generate only FSYNC */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFS) + return -EINVAL; + + /* We can only reconfigure the IP when it's stopped */ + if (fmt & SND_SOC_DAIFMT_CONT) + return -EINVAL; + + dev->fmt = fmt; + + return 0; +} + +static int mchp_i2s_mcc_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, + "%s() tx_mask=0x%08x rx_mask=0x%08x slots=%d width=%d\n", + __func__, tx_mask, rx_mask, slots, slot_width); + + if (slots < 0 || slots > MCHP_I2SMCC_MAX_CHANNELS || + slot_width != MCHP_I2MCC_TDM_SLOT_WIDTH) + return -EINVAL; + + if (slots) { + /* We do not support daisy chain */ + if (rx_mask != GENMASK(slots - 1, 0) || + rx_mask != tx_mask) + return -EINVAL; + } + + dev->tdm_slots = slots; + dev->frame_length = slots * MCHP_I2MCC_TDM_SLOT_WIDTH; + + return 0; +} + +static int mchp_i2s_mcc_clk_get_rate_diff(struct clk *clk, + unsigned long rate, + struct clk **best_clk, + unsigned long *best_rate, + unsigned long *best_diff_rate) +{ + long round_rate; + unsigned int diff_rate; + + round_rate = clk_round_rate(clk, rate); + if (round_rate < 0) + return (int)round_rate; + + diff_rate = abs(rate - round_rate); + if (diff_rate < *best_diff_rate) { + *best_clk = clk; + *best_diff_rate = diff_rate; + *best_rate = rate; + } + + return 0; +} + +static int mchp_i2s_mcc_config_divs(struct mchp_i2s_mcc_dev *dev, + unsigned int bclk, unsigned int *mra) +{ + unsigned long clk_rate; + unsigned long lcm_rate; + unsigned long best_rate = 0; + unsigned long best_diff_rate = ~0; + unsigned int sysclk; + struct clk *best_clk = NULL; + int ret; + + /* For code simplification */ + if (!dev->sysclk) + sysclk = bclk; + else + sysclk = dev->sysclk; + + /* + * MCLK is Selected CLK / (2 * IMCKDIV), + * BCLK is Selected CLK / (2 * ISCKDIV); + * if IMCKDIV or ISCKDIV are 0, MCLK or BCLK = Selected CLK + */ + lcm_rate = lcm(sysclk, bclk); + if ((lcm_rate / sysclk % 2 == 1 && lcm_rate / sysclk > 2) || + (lcm_rate / bclk % 2 == 1 && lcm_rate / bclk > 2)) + lcm_rate *= 2; + + for (clk_rate = lcm_rate; + (clk_rate == sysclk || clk_rate / (sysclk * 2) <= GENMASK(5, 0)) && + (clk_rate == bclk || clk_rate / (bclk * 2) <= GENMASK(5, 0)); + clk_rate += lcm_rate) { + ret = mchp_i2s_mcc_clk_get_rate_diff(dev->gclk, clk_rate, + &best_clk, &best_rate, + &best_diff_rate); + if (ret) { + dev_err(dev->dev, "gclk error for rate %lu: %d", + clk_rate, ret); + } else { + if (!best_diff_rate) { + dev_dbg(dev->dev, "found perfect rate on gclk: %lu\n", + clk_rate); + break; + } + } + + ret = mchp_i2s_mcc_clk_get_rate_diff(dev->pclk, clk_rate, + &best_clk, &best_rate, + &best_diff_rate); + if (ret) { + dev_err(dev->dev, "pclk error for rate %lu: %d", + clk_rate, ret); + } else { + if (!best_diff_rate) { + dev_dbg(dev->dev, "found perfect rate on pclk: %lu\n", + clk_rate); + break; + } + } + } + + /* check if clocks returned only errors */ + if (!best_clk) { + dev_err(dev->dev, "unable to change rate to clocks\n"); + return -EINVAL; + } + + dev_dbg(dev->dev, "source CLK is %s with rate %lu, diff %lu\n", + best_clk == dev->pclk ? "pclk" : "gclk", + best_rate, best_diff_rate); + + /* set the rate */ + ret = clk_set_rate(best_clk, best_rate); + if (ret) { + dev_err(dev->dev, "unable to set rate %lu to %s: %d\n", + best_rate, best_clk == dev->pclk ? "PCLK" : "GCLK", + ret); + return ret; + } + + /* Configure divisors */ + if (dev->sysclk) + *mra |= MCHP_I2SMCC_MRA_IMCKDIV(best_rate / (2 * sysclk)); + *mra |= MCHP_I2SMCC_MRA_ISCKDIV(best_rate / (2 * bclk)); + + if (best_clk == dev->gclk) { + *mra |= MCHP_I2SMCC_MRA_SRCCLK_GCLK; + ret = clk_prepare(dev->gclk); + if (ret < 0) + dev_err(dev->dev, "unable to prepare GCLK: %d\n", ret); + else + dev->gclk_use = 1; + } else { + *mra |= MCHP_I2SMCC_MRA_SRCCLK_PCLK; + dev->gclk_use = 0; + } + + return 0; +} + +static int mchp_i2s_mcc_is_running(struct mchp_i2s_mcc_dev *dev) +{ + u32 sr; + + regmap_read(dev->regmap, MCHP_I2SMCC_SR, &sr); + return !!(sr & (MCHP_I2SMCC_SR_TXEN | MCHP_I2SMCC_SR_RXEN)); +} + +static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + u32 mra = 0; + u32 mrb = 0; + unsigned int channels = params_channels(params); + unsigned int frame_length = dev->frame_length; + unsigned int bclk_rate; + int set_divs = 0; + int ret; + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + dev_dbg(dev->dev, "%s() rate=%u format=%#x width=%u channels=%u\n", + __func__, params_rate(params), params_format(params), + params_width(params), params_channels(params)); + + switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + if (dev->tdm_slots) { + dev_err(dev->dev, "I2S with TDM is not supported\n"); + return -EINVAL; + } + mra |= MCHP_I2SMCC_MRA_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + if (dev->tdm_slots) { + dev_err(dev->dev, "Left-Justified with TDM is not supported\n"); + return -EINVAL; + } + mra |= MCHP_I2SMCC_MRA_FORMAT_LJ; + break; + case SND_SOC_DAIFMT_DSP_A: + mra |= MCHP_I2SMCC_MRA_FORMAT_TDM; + break; + default: + dev_err(dev->dev, "unsupported bus format\n"); + return -EINVAL; + } + + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* cpu is BCLK and LRC master */ + mra |= MCHP_I2SMCC_MRA_MODE_MASTER; + if (dev->sysclk) + mra |= MCHP_I2SMCC_MRA_IMCKMODE_GEN; + set_divs = 1; + break; + case SND_SOC_DAIFMT_CBS_CFM: + /* cpu is BCLK master */ + mrb |= MCHP_I2SMCC_MRB_CLKSEL_INT; + set_divs = 1; + /* fall through */ + case SND_SOC_DAIFMT_CBM_CFM: + /* cpu is slave */ + mra |= MCHP_I2SMCC_MRA_MODE_SLAVE; + if (dev->sysclk) + dev_warn(dev->dev, "Unable to generate MCLK in Slave mode\n"); + break; + default: + dev_err(dev->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + if (dev->fmt & (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J)) { + switch (channels) { + case 1: + if (is_playback) + mra |= MCHP_I2SMCC_MRA_TXMONO; + else + mra |= MCHP_I2SMCC_MRA_RXMONO; + break; + case 2: + break; + default: + dev_err(dev->dev, "unsupported number of audio channels\n"); + return -EINVAL; + } + + if (!frame_length) + frame_length = 2 * params_physical_width(params); + } else if (dev->fmt & SND_SOC_DAIFMT_DSP_A) { + if (dev->tdm_slots) { + if (channels % 2 && channels * 2 <= dev->tdm_slots) { + /* + * Duplicate data for even-numbered channels + * to odd-numbered channels + */ + if (is_playback) + mra |= MCHP_I2SMCC_MRA_TXMONO; + else + mra |= MCHP_I2SMCC_MRA_RXMONO; + } + channels = dev->tdm_slots; + } + + mra |= MCHP_I2SMCC_MRA_NBCHAN(channels); + if (!frame_length) + frame_length = channels * MCHP_I2MCC_TDM_SLOT_WIDTH; + } + + /* + * We must have the same burst size configured + * in the DMA transfer and in out IP + */ + mrb |= MCHP_I2SMCC_MRB_DMACHUNK(channels); + if (is_playback) + dev->playback.maxburst = 1 << (fls(channels) - 1); + else + dev->capture.maxburst = 1 << (fls(channels) - 1); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_8_BITS; + break; + case SNDRV_PCM_FORMAT_S16_LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_16_BITS; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_18_BITS | + MCHP_I2SMCC_MRA_IWS; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_20_BITS | + MCHP_I2SMCC_MRA_IWS; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_24_BITS | + MCHP_I2SMCC_MRA_IWS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_24_BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_32_BITS; + break; + default: + dev_err(dev->dev, "unsupported size/endianness for audio samples\n"); + return -EINVAL; + } + + /* + * If we are already running, the wanted setup must be + * the same with the one that's currently ongoing + */ + if (mchp_i2s_mcc_is_running(dev)) { + u32 mra_cur; + u32 mrb_cur; + + regmap_read(dev->regmap, MCHP_I2SMCC_MRA, &mra_cur); + regmap_read(dev->regmap, MCHP_I2SMCC_MRB, &mrb_cur); + if (mra != mra_cur || mrb != mrb_cur) + return -EINVAL; + + return 0; + } + + /* Save the number of channels to know what interrupts to enable */ + dev->channels = channels; + + if (set_divs) { + bclk_rate = frame_length * params_rate(params); + ret = mchp_i2s_mcc_config_divs(dev, bclk_rate, &mra); + if (ret) { + dev_err(dev->dev, "unable to configure the divisors: %d\n", + ret); + return ret; + } + } + + ret = regmap_write(dev->regmap, MCHP_I2SMCC_MRA, mra); + if (ret < 0) + return ret; + return regmap_write(dev->regmap, MCHP_I2SMCC_MRB, mrb); +} + +static int mchp_i2s_mcc_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + long err; + + if (is_playback) { + err = wait_event_interruptible_timeout(dev->wq_txrdy, + dev->tx_rdy, + msecs_to_jiffies(500)); + } else { + err = wait_event_interruptible_timeout(dev->wq_rxrdy, + dev->rx_rdy, + msecs_to_jiffies(500)); + } + + if (err == 0) { + u32 idra; + + dev_warn_once(dev->dev, "Timeout waiting for %s\n", + is_playback ? "Tx ready" : "Rx ready"); + if (is_playback) + idra = MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels); + else + idra = MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels); + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, idra); + } + + if (!mchp_i2s_mcc_is_running(dev)) { + regmap_write(dev->regmap, MCHP_I2SMCC_CR, MCHP_I2SMCC_CR_CKDIS); + + if (dev->gclk_running) { + clk_disable_unprepare(dev->gclk); + dev->gclk_running = 0; + } + } + + return 0; +} + +static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + u32 cr = 0; + u32 iera = 0; + u32 sr; + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (is_playback) + cr = MCHP_I2SMCC_CR_TXEN | MCHP_I2SMCC_CR_CKEN; + else + cr = MCHP_I2SMCC_CR_RXEN | MCHP_I2SMCC_CR_CKEN; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_read(dev->regmap, MCHP_I2SMCC_SR, &sr); + if (is_playback && (sr & MCHP_I2SMCC_SR_TXEN)) { + cr = MCHP_I2SMCC_CR_TXDIS; + dev->tx_rdy = 0; + /* + * Enable Tx Ready interrupts on all channels + * to assure all data is sent + */ + iera = MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels); + } else if (!is_playback && (sr & MCHP_I2SMCC_SR_RXEN)) { + cr = MCHP_I2SMCC_CR_RXDIS; + dev->rx_rdy = 0; + /* + * Enable Rx Ready interrupts on all channels + * to assure all data is received + */ + iera = MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels); + } + break; + default: + return -EINVAL; + } + + if ((cr & MCHP_I2SMCC_CR_CKEN) && dev->gclk_use && + !dev->gclk_running) { + err = clk_enable(dev->gclk); + if (err) { + dev_err_once(dev->dev, "failed to enable GCLK: %d\n", + err); + } else { + dev->gclk_running = 1; + } + } + + regmap_write(dev->regmap, MCHP_I2SMCC_IERA, iera); + regmap_write(dev->regmap, MCHP_I2SMCC_CR, cr); + + return 0; +} + +static int mchp_i2s_mcc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + /* Software reset the IP if it's not running */ + if (!mchp_i2s_mcc_is_running(dev)) { + return regmap_write(dev->regmap, MCHP_I2SMCC_CR, + MCHP_I2SMCC_CR_SWRST); + } + + return 0; +} + +static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .set_sysclk = mchp_i2s_mcc_set_sysclk, + .set_bclk_ratio = mchp_i2s_mcc_set_bclk_ratio, + .startup = mchp_i2s_mcc_startup, + .trigger = mchp_i2s_mcc_trigger, + .hw_params = mchp_i2s_mcc_hw_params, + .hw_free = mchp_i2s_mcc_hw_free, + .set_fmt = mchp_i2s_mcc_set_dai_fmt, + .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, +}; + +static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + init_waitqueue_head(&dev->wq_txrdy); + init_waitqueue_head(&dev->wq_rxrdy); + + snd_soc_dai_init_dma_data(dai, &dev->playback, &dev->capture); + + return 0; +} + +#define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000 + +#define MCHP_I2SMCC_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mchp_i2s_mcc_dai = { + .probe = mchp_i2s_mcc_dai_probe, + .playback = { + .stream_name = "I2SMCC-Playback", + .channels_min = 1, + .channels_max = 8, + .rates = MCHP_I2SMCC_RATES, + .formats = MCHP_I2SMCC_FORMATS, + }, + .capture = { + .stream_name = "I2SMCC-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = MCHP_I2SMCC_RATES, + .formats = MCHP_I2SMCC_FORMATS, + }, + .ops = &mchp_i2s_mcc_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + .symmetric_channels = 1, +}; + +static const struct snd_soc_component_driver mchp_i2s_mcc_component = { + .name = "mchp-i2s-mcc", +}; + +#ifdef CONFIG_OF +static const struct of_device_id mchp_i2s_mcc_dt_ids[] = { + { + .compatible = "microchip,sam9x60-i2smcc", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mchp_i2s_mcc_dt_ids); +#endif + +static int mchp_i2s_mcc_probe(struct platform_device *pdev) +{ + struct mchp_i2s_mcc_dev *dev; + struct resource *mem; + struct regmap *regmap; + void __iomem *base; + u32 version; + int irq; + int err; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(&pdev->dev, base, + &mchp_i2s_mcc_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_irq(&pdev->dev, irq, mchp_i2s_mcc_interrupt, 0, + dev_name(&pdev->dev), dev); + if (err) + return err; + + dev->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(dev->pclk)) { + err = PTR_ERR(dev->pclk); + dev_err(&pdev->dev, + "failed to get the peripheral clock: %d\n", err); + return err; + } + + /* Get the optional generated clock */ + dev->gclk = devm_clk_get(&pdev->dev, "gclk"); + if (IS_ERR(dev->gclk)) { + if (PTR_ERR(dev->gclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(&pdev->dev, + "generated clock not found: %d\n", err); + dev->gclk = NULL; + } + + dev->dev = &pdev->dev; + dev->regmap = regmap; + platform_set_drvdata(pdev, dev); + + err = clk_prepare_enable(dev->pclk); + if (err) { + dev_err(&pdev->dev, + "failed to enable the peripheral clock: %d\n", err); + return err; + } + + err = devm_snd_soc_register_component(&pdev->dev, + &mchp_i2s_mcc_component, + &mchp_i2s_mcc_dai, 1); + if (err) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + dev->playback.addr = (dma_addr_t)mem->start + MCHP_I2SMCC_THR; + dev->capture.addr = (dma_addr_t)mem->start + MCHP_I2SMCC_RHR; + + err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (err) { + dev_err(&pdev->dev, "failed to register PCM: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + /* Get IP version. */ + regmap_read(dev->regmap, MCHP_I2SMCC_VERSION, &version); + dev_info(&pdev->dev, "hw version: %#lx\n", + version & MCHP_I2SMCC_VERSION_MASK); + + return 0; +} + +static int mchp_i2s_mcc_remove(struct platform_device *pdev) +{ + struct mchp_i2s_mcc_dev *dev = platform_get_drvdata(pdev); + + clk_disable_unprepare(dev->pclk); + + return 0; +} + +static struct platform_driver mchp_i2s_mcc_driver = { + .driver = { + .name = "mchp_i2s_mcc", + .of_match_table = of_match_ptr(mchp_i2s_mcc_dt_ids), + }, + .probe = mchp_i2s_mcc_probe, + .remove = mchp_i2s_mcc_remove, +}; +module_platform_driver(mchp_i2s_mcc_driver); + +MODULE_DESCRIPTION("Microchip I2S Multi-Channel Controller driver"); +MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@microchip.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/atmel/tse850-pcm5142.c b/sound/soc/atmel/tse850-pcm5142.c index 214adcad5419..ae445184614a 100644 --- a/sound/soc/atmel/tse850-pcm5142.c +++ b/sound/soc/atmel/tse850-pcm5142.c @@ -117,8 +117,8 @@ static int tse850_put_mux2(struct snd_kcontrol *kctrl, return snd_soc_dapm_put_enum_double(kctrl, ucontrol); } -int tse850_get_mix(struct snd_kcontrol *kctrl, - struct snd_ctl_elem_value *ucontrol) +static int tse850_get_mix(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); struct snd_soc_card *card = dapm->card; @@ -129,8 +129,8 @@ int tse850_get_mix(struct snd_kcontrol *kctrl, return 0; } -int tse850_put_mix(struct snd_kcontrol *kctrl, - struct snd_ctl_elem_value *ucontrol) +static int tse850_put_mix(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); struct snd_soc_card *card = dapm->card; @@ -151,8 +151,8 @@ int tse850_put_mix(struct snd_kcontrol *kctrl, return 1; } -int tse850_get_ana(struct snd_kcontrol *kctrl, - struct snd_ctl_elem_value *ucontrol) +static int tse850_get_ana(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); struct snd_soc_card *card = dapm->card; @@ -184,8 +184,8 @@ int tse850_get_ana(struct snd_kcontrol *kctrl, return 0; } -int tse850_put_ana(struct snd_kcontrol *kctrl, - struct snd_ctl_elem_value *ucontrol) +static int tse850_put_ana(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); struct snd_soc_card *card = dapm->card; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 667fc1d59e18..8f577258080b 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -94,6 +94,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_JZ4725B_CODEC select SND_SOC_LM4857 if I2C select SND_SOC_LM49453 if I2C + select SND_SOC_LOCHNAGAR_SC if MFD_LOCHNAGAR select SND_SOC_MAX98088 if I2C select SND_SOC_MAX98090 if I2C select SND_SOC_MAX98095 if I2C @@ -179,8 +180,8 @@ config SND_SOC_ALL_CODECS select SND_SOC_TLV320AIC23_SPI if SPI_MASTER select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TLV320AIC31XX if I2C - select SND_SOC_TLV320AIC32X4_I2C if I2C - select SND_SOC_TLV320AIC32X4_SPI if SPI_MASTER + select SND_SOC_TLV320AIC32X4_I2C if I2C && COMMON_CLK + select SND_SOC_TLV320AIC32X4_SPI if SPI_MASTER && COMMON_CLK select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C @@ -688,6 +689,13 @@ config SND_SOC_ISABELLE config SND_SOC_LM49453 tristate +config SND_SOC_LOCHNAGAR_SC + tristate "Lochnagar Sound Card" + depends on MFD_LOCHNAGAR + help + This driver support the sound card functionality of the Cirrus + Logic Lochnagar audio development board. + config SND_SOC_MAX98088 tristate "Maxim MAX98088/9 Low-Power, Stereo Audio Codec" depends on I2C @@ -1097,15 +1105,18 @@ config SND_SOC_TLV320AIC31XX config SND_SOC_TLV320AIC32X4 tristate + depends on COMMON_CLK config SND_SOC_TLV320AIC32X4_I2C tristate "Texas Instruments TLV320AIC32x4 audio CODECs - I2C" depends on I2C + depends on COMMON_CLK select SND_SOC_TLV320AIC32X4 config SND_SOC_TLV320AIC32X4_SPI tristate "Texas Instruments TLV320AIC32x4 audio CODECs - SPI" depends on SPI_MASTER + depends on COMMON_CLK select SND_SOC_TLV320AIC32X4 config SND_SOC_TLV320AIC3X diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index aab2ad95a137..aa7720a7a0aa 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -91,6 +91,7 @@ snd-soc-jz4725b-codec-objs := jz4725b.o snd-soc-l3-objs := l3.o snd-soc-lm4857-objs := lm4857.o snd-soc-lm49453-objs := lm49453.o +snd-soc-lochnagar-sc-objs := lochnagar-sc.o snd-soc-max9759-objs := max9759.o snd-soc-max9768-objs := max9768.o snd-soc-max98088-objs := max98088.o @@ -192,7 +193,7 @@ snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o snd-soc-tlv320aic23-spi-objs := tlv320aic23-spi.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic31xx-objs := tlv320aic31xx.o -snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o +snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o tlv320aic32x4-clk.o snd-soc-tlv320aic32x4-i2c-objs := tlv320aic32x4-i2c.o snd-soc-tlv320aic32x4-spi-objs := tlv320aic32x4-spi.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o @@ -364,6 +365,7 @@ obj-$(CONFIG_SND_SOC_JZ4725B_CODEC) += snd-soc-jz4725b-codec.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o +obj-$(CONFIG_SND_SOC_LOCHNAGAR_SC) += snd-soc-lochnagar-sc.o obj-$(CONFIG_SND_SOC_MAX9759) += snd-soc-max9759.o obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o diff --git a/sound/soc/codecs/cs42l51-i2c.c b/sound/soc/codecs/cs42l51-i2c.c index 4b5731a41876..116221e581ce 100644 --- a/sound/soc/codecs/cs42l51-i2c.c +++ b/sound/soc/codecs/cs42l51-i2c.c @@ -29,18 +29,27 @@ static int cs42l51_i2c_probe(struct i2c_client *i2c, struct regmap_config config; config = cs42l51_regmap; - config.val_bits = 8; - config.reg_bits = 8; return cs42l51_probe(&i2c->dev, devm_regmap_init_i2c(i2c, &config)); } +static int cs42l51_i2c_remove(struct i2c_client *i2c) +{ + return cs42l51_remove(&i2c->dev); +} + +static const struct dev_pm_ops cs42l51_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cs42l51_suspend, cs42l51_resume) +}; + static struct i2c_driver cs42l51_i2c_driver = { .driver = { .name = "cs42l51", .of_match_table = cs42l51_of_match, + .pm = &cs42l51_pm_ops, }, .probe = cs42l51_i2c_probe, + .remove = cs42l51_i2c_remove, .id_table = cs42l51_i2c_id, }; diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c index fd2bd74024c1..991e4ebd7a04 100644 --- a/sound/soc/codecs/cs42l51.c +++ b/sound/soc/codecs/cs42l51.c @@ -30,7 +30,9 @@ #include <sound/initval.h> #include <sound/pcm_params.h> #include <sound/pcm.h> +#include <linux/gpio/consumer.h> #include <linux/regmap.h> +#include <linux/regulator/consumer.h> #include "cs42l51.h" @@ -40,11 +42,21 @@ enum master_slave_mode { MODE_MASTER, }; +static const char * const cs42l51_supply_names[] = { + "VL", + "VD", + "VA", + "VAHP", +}; + struct cs42l51_private { unsigned int mclk; struct clk *mclk_handle; unsigned int audio_mode; /* The mode (I2S or left-justified) */ enum master_slave_mode func; + struct regulator_bulk_data supplies[ARRAY_SIZE(cs42l51_supply_names)]; + struct gpio_desc *reset_gpio; + struct regmap *regmap; }; #define CS42L51_FORMATS ( \ @@ -111,6 +123,7 @@ static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); static const DECLARE_TLV_DB_SCALE(aout_tlv, -10200, 50, 0); static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); +static const DECLARE_TLV_DB_SCALE(adc_boost_tlv, 2000, 2000, 0); static const char *chan_mix[] = { "L R", "L+R", @@ -139,6 +152,8 @@ static const struct snd_kcontrol_new cs42l51_snd_controls[] = { SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0), SOC_DOUBLE_TLV("Mic Boost Volume", CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), + SOC_DOUBLE_TLV("ADC Boost Volume", + CS42L51_MIC_CTL, 5, 6, 1, 0, adc_boost_tlv), SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv), SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv), SOC_ENUM_EXT("PCM channel mixer", @@ -195,7 +210,8 @@ static const struct snd_kcontrol_new cs42l51_adcr_mux_controls = SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum); static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { - SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), + SND_SOC_DAPM_SUPPLY("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1, NULL, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0, cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, @@ -329,6 +345,19 @@ static struct cs42l51_ratios slave_auto_ratios[] = { { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 }, }; +/* + * Master mode mclk/fs ratios. + * Recommended configurations are SSM for 4-50khz and DSM for 50-100kHz ranges + * The table below provides support of following ratios: + * 128: SSM (%128) with div2 disabled + * 256: SSM (%128) with div2 enabled + * In both cases, if sampling rate is above 50kHz, SSM is overridden + * with DSM (%128) configuration + */ +static struct cs42l51_ratios master_ratios[] = { + { 128, CS42L51_SSM_MODE, 0 }, { 256, CS42L51_SSM_MODE, 1 }, +}; + static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { @@ -351,11 +380,13 @@ static int cs42l51_hw_params(struct snd_pcm_substream *substream, unsigned int ratio; struct cs42l51_ratios *ratios = NULL; int nr_ratios = 0; - int intf_ctl, power_ctl, fmt; + int intf_ctl, power_ctl, fmt, mode; switch (cs42l51->func) { case MODE_MASTER: - return -EINVAL; + ratios = master_ratios; + nr_ratios = ARRAY_SIZE(master_ratios); + break; case MODE_SLAVE: ratios = slave_ratios; nr_ratios = ARRAY_SIZE(slave_ratios); @@ -391,7 +422,16 @@ static int cs42l51_hw_params(struct snd_pcm_substream *substream, switch (cs42l51->func) { case MODE_MASTER: intf_ctl |= CS42L51_INTF_CTL_MASTER; - power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); + mode = ratios[i].speed_mode; + /* Force DSM mode if sampling rate is above 50kHz */ + if (rate > 50000) + mode = CS42L51_DSM_MODE; + power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(mode); + /* + * Auto detect mode is not applicable for master mode and has to + * be disabled. Otherwise SPEED[1:0] bits will be ignored. + */ + power_ctl &= ~CS42L51_MIC_POWER_CTL_AUTO; break; case MODE_SLAVE: power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); @@ -464,6 +504,13 @@ static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute) return snd_soc_component_write(component, CS42L51_DAC_OUT_CTL, reg); } +static int cs42l51_of_xlate_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + /* return dai id 0, whatever the endpoint index */ + return 0; +} + static const struct snd_soc_dai_ops cs42l51_dai_ops = { .hw_params = cs42l51_hw_params, .set_sysclk = cs42l51_set_dai_sysclk, @@ -526,13 +573,113 @@ static const struct snd_soc_component_driver soc_component_device_cs42l51 = { .num_dapm_widgets = ARRAY_SIZE(cs42l51_dapm_widgets), .dapm_routes = cs42l51_routes, .num_dapm_routes = ARRAY_SIZE(cs42l51_routes), + .of_xlate_dai_id = cs42l51_of_xlate_dai_id, .idle_bias_on = 1, .use_pmdown_time = 1, .endianness = 1, .non_legacy_dai_naming = 1, }; +static bool cs42l51_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L51_POWER_CTL1: + case CS42L51_MIC_POWER_CTL: + case CS42L51_INTF_CTL: + case CS42L51_MIC_CTL: + case CS42L51_ADC_CTL: + case CS42L51_ADC_INPUT: + case CS42L51_DAC_OUT_CTL: + case CS42L51_DAC_CTL: + case CS42L51_ALC_PGA_CTL: + case CS42L51_ALC_PGB_CTL: + case CS42L51_ADCA_ATT: + case CS42L51_ADCB_ATT: + case CS42L51_ADCA_VOL: + case CS42L51_ADCB_VOL: + case CS42L51_PCMA_VOL: + case CS42L51_PCMB_VOL: + case CS42L51_BEEP_FREQ: + case CS42L51_BEEP_VOL: + case CS42L51_BEEP_CONF: + case CS42L51_TONE_CTL: + case CS42L51_AOUTA_VOL: + case CS42L51_AOUTB_VOL: + case CS42L51_PCM_MIXER: + case CS42L51_LIMIT_THRES_DIS: + case CS42L51_LIMIT_REL: + case CS42L51_LIMIT_ATT: + case CS42L51_ALC_EN: + case CS42L51_ALC_REL: + case CS42L51_ALC_THRES: + case CS42L51_NOISE_CONF: + case CS42L51_CHARGE_FREQ: + return true; + default: + return false; + } +} + +static bool cs42l51_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L51_STATUS: + return true; + default: + return false; + } +} + +static bool cs42l51_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L51_CHIP_REV_ID: + case CS42L51_POWER_CTL1: + case CS42L51_MIC_POWER_CTL: + case CS42L51_INTF_CTL: + case CS42L51_MIC_CTL: + case CS42L51_ADC_CTL: + case CS42L51_ADC_INPUT: + case CS42L51_DAC_OUT_CTL: + case CS42L51_DAC_CTL: + case CS42L51_ALC_PGA_CTL: + case CS42L51_ALC_PGB_CTL: + case CS42L51_ADCA_ATT: + case CS42L51_ADCB_ATT: + case CS42L51_ADCA_VOL: + case CS42L51_ADCB_VOL: + case CS42L51_PCMA_VOL: + case CS42L51_PCMB_VOL: + case CS42L51_BEEP_FREQ: + case CS42L51_BEEP_VOL: + case CS42L51_BEEP_CONF: + case CS42L51_TONE_CTL: + case CS42L51_AOUTA_VOL: + case CS42L51_AOUTB_VOL: + case CS42L51_PCM_MIXER: + case CS42L51_LIMIT_THRES_DIS: + case CS42L51_LIMIT_REL: + case CS42L51_LIMIT_ATT: + case CS42L51_ALC_EN: + case CS42L51_ALC_REL: + case CS42L51_ALC_THRES: + case CS42L51_NOISE_CONF: + case CS42L51_STATUS: + case CS42L51_CHARGE_FREQ: + return true; + default: + return false; + } +} + const struct regmap_config cs42l51_regmap = { + .reg_bits = 8, + .reg_stride = 1, + .val_bits = 8, + .use_single_write = true, + .readable_reg = cs42l51_readable_reg, + .volatile_reg = cs42l51_volatile_reg, + .writeable_reg = cs42l51_writeable_reg, .max_register = CS42L51_CHARGE_FREQ, .cache_type = REGCACHE_RBTREE, }; @@ -542,7 +689,7 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap) { struct cs42l51_private *cs42l51; unsigned int val; - int ret; + int ret, i; if (IS_ERR(regmap)) return PTR_ERR(regmap); @@ -553,6 +700,7 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap) return -ENOMEM; dev_set_drvdata(dev, cs42l51); + cs42l51->regmap = regmap; cs42l51->mclk_handle = devm_clk_get(dev, "MCLK"); if (IS_ERR(cs42l51->mclk_handle)) { @@ -561,6 +709,34 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap) cs42l51->mclk_handle = NULL; } + for (i = 0; i < ARRAY_SIZE(cs42l51->supplies); i++) + cs42l51->supplies[i].supply = cs42l51_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs42l51->supplies), + cs42l51->supplies); + if (ret != 0) { + dev_err(dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs42l51->supplies), + cs42l51->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + cs42l51->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(cs42l51->reset_gpio)) + return PTR_ERR(cs42l51->reset_gpio); + + if (cs42l51->reset_gpio) { + dev_dbg(dev, "Release reset gpio\n"); + gpiod_set_value_cansleep(cs42l51->reset_gpio, 0); + mdelay(2); + } + /* Verify that we have a CS42L51 */ ret = regmap_read(regmap, CS42L51_CHIP_REV_ID, &val); if (ret < 0) { @@ -579,11 +755,50 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap) ret = devm_snd_soc_register_component(dev, &soc_component_device_cs42l51, &cs42l51_dai, 1); + if (ret < 0) + goto error; + + return 0; + error: + regulator_bulk_disable(ARRAY_SIZE(cs42l51->supplies), + cs42l51->supplies); return ret; } EXPORT_SYMBOL_GPL(cs42l51_probe); +int cs42l51_remove(struct device *dev) +{ + struct cs42l51_private *cs42l51 = dev_get_drvdata(dev); + + gpiod_set_value_cansleep(cs42l51->reset_gpio, 1); + + return regulator_bulk_disable(ARRAY_SIZE(cs42l51->supplies), + cs42l51->supplies); +} +EXPORT_SYMBOL_GPL(cs42l51_remove); + +int __maybe_unused cs42l51_suspend(struct device *dev) +{ + struct cs42l51_private *cs42l51 = dev_get_drvdata(dev); + + regcache_cache_only(cs42l51->regmap, true); + regcache_mark_dirty(cs42l51->regmap); + + return 0; +} +EXPORT_SYMBOL_GPL(cs42l51_suspend); + +int __maybe_unused cs42l51_resume(struct device *dev) +{ + struct cs42l51_private *cs42l51 = dev_get_drvdata(dev); + + regcache_cache_only(cs42l51->regmap, false); + + return regcache_sync(cs42l51->regmap); +} +EXPORT_SYMBOL_GPL(cs42l51_resume); + const struct of_device_id cs42l51_of_match[] = { { .compatible = "cirrus,cs42l51", }, { } diff --git a/sound/soc/codecs/cs42l51.h b/sound/soc/codecs/cs42l51.h index 0ca805492ac4..79dee01137c8 100644 --- a/sound/soc/codecs/cs42l51.h +++ b/sound/soc/codecs/cs42l51.h @@ -22,6 +22,9 @@ struct device; extern const struct regmap_config cs42l51_regmap; int cs42l51_probe(struct device *dev, struct regmap *regmap); +int cs42l51_remove(struct device *dev); +int __maybe_unused cs42l51_suspend(struct device *dev); +int __maybe_unused cs42l51_resume(struct device *dev); extern const struct of_device_id cs42l51_of_match[]; #define CS42L51_CHIP_ID 0x1B diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c index 3f7b255587e6..80d672710eae 100644 --- a/sound/soc/codecs/cs43130.c +++ b/sound/soc/codecs/cs43130.c @@ -2322,6 +2322,8 @@ static int cs43130_probe(struct snd_soc_component *component) return ret; cs43130->wq = create_singlethread_workqueue("cs43130_hp"); + if (!cs43130->wq) + return -ENOMEM; INIT_WORK(&cs43130->work, cs43130_imp_meas); } diff --git a/sound/soc/codecs/cs47l24.c b/sound/soc/codecs/cs47l24.c index b16832a6a9af..eebbf02e1c39 100644 --- a/sound/soc/codecs/cs47l24.c +++ b/sound/soc/codecs/cs47l24.c @@ -75,7 +75,9 @@ static int cs47l24_adsp_power_ev(struct snd_soc_dapm_widget *w, v = (v & ARIZONA_SYSCLK_FREQ_MASK) >> ARIZONA_SYSCLK_FREQ_SHIFT; - return wm_adsp2_early_event(w, kcontrol, event, v); + wm_adsp2_set_dspclk(w, v); + + return wm_adsp_early_event(w, kcontrol, event); } static DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index 92d006a5283e..425c11d63e49 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -1305,7 +1305,10 @@ static int da7213_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) /* By default only 64 BCLK per WCLK is supported */ dai_clk_mode |= DA7213_DAI_BCLKS_PER_WCLK_64; - snd_soc_component_write(component, DA7213_DAI_CLK_MODE, dai_clk_mode); + snd_soc_component_update_bits(component, DA7213_DAI_CLK_MODE, + DA7213_DAI_BCLKS_PER_WCLK_MASK | + DA7213_DAI_CLK_POL_MASK | DA7213_DAI_WCLK_POL_MASK, + dai_clk_mode); snd_soc_component_update_bits(component, DA7213_DAI_CTRL, DA7213_DAI_FORMAT_MASK, dai_ctrl); snd_soc_component_write(component, DA7213_DAI_OFFSET, dai_offset); diff --git a/sound/soc/codecs/da7213.h b/sound/soc/codecs/da7213.h index 5a78dba1dcb5..9d31efc3cfe5 100644 --- a/sound/soc/codecs/da7213.h +++ b/sound/soc/codecs/da7213.h @@ -181,7 +181,9 @@ #define DA7213_DAI_BCLKS_PER_WCLK_256 (0x3 << 0) #define DA7213_DAI_BCLKS_PER_WCLK_MASK (0x3 << 0) #define DA7213_DAI_CLK_POL_INV (0x1 << 2) +#define DA7213_DAI_CLK_POL_MASK (0x1 << 2) #define DA7213_DAI_WCLK_POL_INV (0x1 << 3) +#define DA7213_DAI_WCLK_POL_MASK (0x1 << 3) #define DA7213_DAI_CLK_EN_MASK (0x1 << 7) /* DA7213_DAI_CTRL = 0x29 */ diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c index 121a8190f93e..7d9d1f84eed8 100644 --- a/sound/soc/codecs/da7219.c +++ b/sound/soc/codecs/da7219.c @@ -797,6 +797,7 @@ static int da7219_dai_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct clk *bclk = da7219->dai_clks[DA7219_DAI_BCLK_IDX]; u8 pll_ctrl, pll_status; int i = 0, ret; bool srm_lock = false; @@ -805,11 +806,11 @@ static int da7219_dai_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_PRE_PMU: if (da7219->master) { /* Enable DAI clks for master mode */ - if (da7219->dai_clks) { - ret = clk_prepare_enable(da7219->dai_clks); + if (bclk) { + ret = clk_prepare_enable(bclk); if (ret) { dev_err(component->dev, - "Failed to enable dai_clks\n"); + "Failed to enable DAI clks\n"); return ret; } } else { @@ -852,8 +853,8 @@ static int da7219_dai_event(struct snd_soc_dapm_widget *w, /* Disable DAI clks if in master mode */ if (da7219->master) { - if (da7219->dai_clks) - clk_disable_unprepare(da7219->dai_clks); + if (bclk) + clk_disable_unprepare(bclk); else snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, @@ -1385,17 +1386,50 @@ static int da7219_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) return 0; } +static int da7219_set_bclks_per_wclk(struct snd_soc_component *component, + unsigned long factor) +{ + u8 bclks_per_wclk; + + switch (factor) { + case 32: + bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_32; + break; + case 64: + bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_64; + break; + case 128: + bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_128; + break; + case 256: + bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_256; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, + DA7219_DAI_BCLKS_PER_WCLK_MASK, + bclks_per_wclk); + + return 0; +} + static int da7219_set_dai_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_component *component = dai->component; struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct clk *wclk = da7219->dai_clks[DA7219_DAI_WCLK_IDX]; + struct clk *bclk = da7219->dai_clks[DA7219_DAI_BCLK_IDX]; unsigned int ch_mask; - u8 dai_bclks_per_wclk, slot_offset; + unsigned long sr, bclk_rate; + u8 slot_offset; u16 offset; __le16 dai_offset; u32 frame_size; + int ret; /* No channels enabled so disable TDM */ if (!tx_mask) { @@ -1432,28 +1466,26 @@ static int da7219_set_dai_tdm_slot(struct snd_soc_dai *dai, */ if (da7219->master) { frame_size = slots * slot_width; - switch (frame_size) { - case 32: - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_32; - break; - case 64: - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_64; - break; - case 128: - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_128; - break; - case 256: - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_256; - break; - default: - dev_err(component->dev, "Invalid frame size %d\n", - frame_size); - return -EINVAL; - } - snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, - DA7219_DAI_BCLKS_PER_WCLK_MASK, - dai_bclks_per_wclk); + if (bclk) { + sr = clk_get_rate(wclk); + bclk_rate = sr * frame_size; + ret = clk_set_rate(bclk, bclk_rate); + if (ret) { + dev_err(component->dev, + "Failed to set TDM BCLK rate %lu: %d\n", + bclk_rate, ret); + return ret; + } + } else { + ret = da7219_set_bclks_per_wclk(component, frame_size); + if (ret) { + dev_err(component->dev, + "Failed to set TDM BCLKs per WCLK %d: %d\n", + frame_size, ret); + return ret; + } + } } dai_offset = cpu_to_le16(offset); @@ -1471,44 +1503,12 @@ static int da7219_set_dai_tdm_slot(struct snd_soc_dai *dai, return 0; } -static int da7219_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) +static int da7219_set_sr(struct snd_soc_component *component, + unsigned long rate) { - struct snd_soc_component *component = dai->component; - struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); - u8 dai_ctrl = 0, dai_bclks_per_wclk = 0, fs; - unsigned int channels; - int word_len = params_width(params); - int frame_size; + u8 fs; - switch (word_len) { - case 16: - dai_ctrl |= DA7219_DAI_WORD_LENGTH_S16_LE; - break; - case 20: - dai_ctrl |= DA7219_DAI_WORD_LENGTH_S20_LE; - break; - case 24: - dai_ctrl |= DA7219_DAI_WORD_LENGTH_S24_LE; - break; - case 32: - dai_ctrl |= DA7219_DAI_WORD_LENGTH_S32_LE; - break; - default: - return -EINVAL; - } - - channels = params_channels(params); - if ((channels < 1) || (channels > DA7219_DAI_CH_NUM_MAX)) { - dev_err(component->dev, - "Invalid number of channels, only 1 to %d supported\n", - DA7219_DAI_CH_NUM_MAX); - return -EINVAL; - } - dai_ctrl |= channels << DA7219_DAI_CH_NUM_SHIFT; - - switch (params_rate(params)) { + switch (rate) { case 8000: fs = DA7219_SR_8000; break; @@ -1546,28 +1546,118 @@ static int da7219_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } + snd_soc_component_write(component, DA7219_SR, fs); + + return 0; +} + +static int da7219_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct clk *wclk = da7219->dai_clks[DA7219_DAI_WCLK_IDX]; + struct clk *bclk = da7219->dai_clks[DA7219_DAI_BCLK_IDX]; + u8 dai_ctrl = 0; + unsigned int channels; + unsigned long sr, bclk_rate; + int word_len = params_width(params); + int frame_size, ret; + + switch (word_len) { + case 16: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S16_LE; + break; + case 20: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S20_LE; + break; + case 24: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S24_LE; + break; + case 32: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S32_LE; + break; + default: + return -EINVAL; + } + + channels = params_channels(params); + if ((channels < 1) || (channels > DA7219_DAI_CH_NUM_MAX)) { + dev_err(component->dev, + "Invalid number of channels, only 1 to %d supported\n", + DA7219_DAI_CH_NUM_MAX); + return -EINVAL; + } + dai_ctrl |= channels << DA7219_DAI_CH_NUM_SHIFT; + + sr = params_rate(params); + if (da7219->master && wclk) { + ret = clk_set_rate(wclk, sr); + if (ret) { + dev_err(component->dev, + "Failed to set WCLK SR %lu: %d\n", sr, ret); + return ret; + } + } else { + ret = da7219_set_sr(component, sr); + if (ret) { + dev_err(component->dev, + "Failed to set SR %lu: %d\n", sr, ret); + return ret; + } + } + /* * If we're master, then we have a limited set of BCLK rates we * support. For slave mode this isn't the case and the codec can detect * the BCLK rate automatically. */ if (da7219->master && !da7219->tdm_en) { - frame_size = word_len * 2; - if (frame_size <= 32) - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_32; + if ((word_len * DA7219_DAI_CH_NUM_MAX) <= 32) + frame_size = 32; else - dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_64; + frame_size = 64; + + if (bclk) { + bclk_rate = frame_size * sr; + /* + * Rounding the rate here avoids failure trying to set a + * new rate on an already enabled bclk. In that + * instance this will just set the same rate as is + * currently in use, and so should continue without + * problem, as long as the BCLK rate is suitable for the + * desired frame size. + */ + bclk_rate = clk_round_rate(bclk, bclk_rate); + if ((bclk_rate / sr) < frame_size) { + dev_err(component->dev, + "BCLK rate mismatch against frame size"); + return -EINVAL; + } - snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, - DA7219_DAI_BCLKS_PER_WCLK_MASK, - dai_bclks_per_wclk); + ret = clk_set_rate(bclk, bclk_rate); + if (ret) { + dev_err(component->dev, + "Failed to set BCLK rate %lu: %d\n", + bclk_rate, ret); + return ret; + } + } else { + ret = da7219_set_bclks_per_wclk(component, frame_size); + if (ret) { + dev_err(component->dev, + "Failed to set BCLKs per WCLK %d: %d\n", + frame_size, ret); + return ret; + } + } } snd_soc_component_update_bits(component, DA7219_DAI_CTRL, DA7219_DAI_WORD_LENGTH_MASK | DA7219_DAI_CH_NUM_MASK, dai_ctrl); - snd_soc_component_write(component, DA7219_SR, fs); return 0; } @@ -1583,20 +1673,26 @@ static const struct snd_soc_dai_ops da7219_dai_ops = { #define DA7219_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +#define DA7219_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + static struct snd_soc_dai_driver da7219_dai = { .name = "da7219-hifi", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = DA7219_DAI_CH_NUM_MAX, - .rates = SNDRV_PCM_RATE_8000_96000, + .rates = DA7219_RATES, .formats = DA7219_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = DA7219_DAI_CH_NUM_MAX, - .rates = SNDRV_PCM_RATE_8000_96000, + .rates = DA7219_RATES, .formats = DA7219_FORMATS, }, .ops = &da7219_dai_ops, @@ -1672,11 +1768,14 @@ static struct da7219_pdata *da7219_fw_to_pdata(struct snd_soc_component *compone pdata->wakeup_source = device_property_read_bool(dev, "wakeup-source"); - pdata->dai_clks_name = "da7219-dai-clks"; - if (device_property_read_string(dev, "clock-output-names", - &pdata->dai_clks_name)) - dev_warn(dev, "Using default clk name: %s\n", - pdata->dai_clks_name); + pdata->dai_clk_names[DA7219_DAI_WCLK_IDX] = "da7219-dai-wclk"; + pdata->dai_clk_names[DA7219_DAI_BCLK_IDX] = "da7219-dai-bclk"; + if (device_property_read_string_array(dev, "clock-output-names", + pdata->dai_clk_names, + DA7219_DAI_NUM_CLKS) < 0) + dev_warn(dev, "Using default DAI clk names: %s, %s\n", + pdata->dai_clk_names[DA7219_DAI_WCLK_IDX], + pdata->dai_clk_names[DA7219_DAI_BCLK_IDX]); if (device_property_read_u32(dev, "dlg,micbias-lvl", &of_val32) >= 0) pdata->micbias_lvl = da7219_fw_micbias_lvl(dev, of_val32); @@ -1793,12 +1892,16 @@ static int da7219_handle_supplies(struct snd_soc_component *component) } #ifdef CONFIG_COMMON_CLK -static int da7219_dai_clks_prepare(struct clk_hw *hw) +static int da7219_wclk_prepare(struct clk_hw *hw) { struct da7219_priv *da7219 = - container_of(hw, struct da7219_priv, dai_clks_hw); + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); struct snd_soc_component *component = da7219->component; + if (!da7219->master) + return -EINVAL; + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, DA7219_DAI_CLK_EN_MASK, DA7219_DAI_CLK_EN_MASK); @@ -1806,33 +1909,42 @@ static int da7219_dai_clks_prepare(struct clk_hw *hw) return 0; } -static void da7219_dai_clks_unprepare(struct clk_hw *hw) +static void da7219_wclk_unprepare(struct clk_hw *hw) { struct da7219_priv *da7219 = - container_of(hw, struct da7219_priv, dai_clks_hw); + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); struct snd_soc_component *component = da7219->component; + if (!da7219->master) + return; + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, DA7219_DAI_CLK_EN_MASK, 0); } -static int da7219_dai_clks_is_prepared(struct clk_hw *hw) +static int da7219_wclk_is_prepared(struct clk_hw *hw) { struct da7219_priv *da7219 = - container_of(hw, struct da7219_priv, dai_clks_hw); + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); struct snd_soc_component *component = da7219->component; u8 clk_reg; + if (!da7219->master) + return -EINVAL; + clk_reg = snd_soc_component_read32(component, DA7219_DAI_CLK_MODE); return !!(clk_reg & DA7219_DAI_CLK_EN_MASK); } -static unsigned long da7219_dai_clks_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) +static unsigned long da7219_wclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) { struct da7219_priv *da7219 = - container_of(hw, struct da7219_priv, dai_clks_hw); + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); struct snd_soc_component *component = da7219->component; u8 fs = snd_soc_component_read32(component, DA7219_SR); @@ -1864,11 +1976,148 @@ static unsigned long da7219_dai_clks_recalc_rate(struct clk_hw *hw, } } -static const struct clk_ops da7219_dai_clks_ops = { - .prepare = da7219_dai_clks_prepare, - .unprepare = da7219_dai_clks_unprepare, - .is_prepared = da7219_dai_clks_is_prepared, - .recalc_rate = da7219_dai_clks_recalc_rate, +static long da7219_wclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); + + if (!da7219->master) + return -EINVAL; + + if (rate < 11025) + return 8000; + else if (rate < 12000) + return 11025; + else if (rate < 16000) + return 12000; + else if (rate < 22050) + return 16000; + else if (rate < 24000) + return 22050; + else if (rate < 32000) + return 24000; + else if (rate < 44100) + return 32000; + else if (rate < 48000) + return 44100; + else if (rate < 88200) + return 48000; + else if (rate < 96000) + return 88200; + else + return 96000; +} + +static int da7219_wclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_WCLK_IDX]); + struct snd_soc_component *component = da7219->component; + + if (!da7219->master) + return -EINVAL; + + return da7219_set_sr(component, rate); +} + +static unsigned long da7219_bclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_BCLK_IDX]); + struct snd_soc_component *component = da7219->component; + u8 bclks_per_wclk = snd_soc_component_read32(component, + DA7219_DAI_CLK_MODE); + + switch (bclks_per_wclk & DA7219_DAI_BCLKS_PER_WCLK_MASK) { + case DA7219_DAI_BCLKS_PER_WCLK_32: + return parent_rate * 32; + case DA7219_DAI_BCLKS_PER_WCLK_64: + return parent_rate * 64; + case DA7219_DAI_BCLKS_PER_WCLK_128: + return parent_rate * 128; + case DA7219_DAI_BCLKS_PER_WCLK_256: + return parent_rate * 256; + default: + return 0; + } +} + +static unsigned long da7219_bclk_get_factor(unsigned long rate, + unsigned long parent_rate) +{ + unsigned long factor; + + factor = rate / parent_rate; + if (factor < 64) + return 32; + else if (factor < 128) + return 64; + else if (factor < 256) + return 128; + else + return 256; +} + +static long da7219_bclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_BCLK_IDX]); + unsigned long factor; + + if (!*parent_rate || !da7219->master) + return -EINVAL; + + /* + * We don't allow changing the parent rate as some BCLK rates can be + * derived from multiple parent WCLK rates (BCLK rates are set as a + * multiplier of WCLK in HW). We just do some rounding down based on the + * parent WCLK rate set and find the appropriate multiplier of BCLK to + * get the rounded down BCLK value. + */ + factor = da7219_bclk_get_factor(rate, *parent_rate); + + return *parent_rate * factor; +} + +static int da7219_bclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, + dai_clks_hw[DA7219_DAI_BCLK_IDX]); + struct snd_soc_component *component = da7219->component; + unsigned long factor; + + if (!da7219->master) + return -EINVAL; + + factor = da7219_bclk_get_factor(rate, parent_rate); + + return da7219_set_bclks_per_wclk(component, factor); +} + +static const struct clk_ops da7219_dai_clk_ops[DA7219_DAI_NUM_CLKS] = { + [DA7219_DAI_WCLK_IDX] = { + .prepare = da7219_wclk_prepare, + .unprepare = da7219_wclk_unprepare, + .is_prepared = da7219_wclk_is_prepared, + .recalc_rate = da7219_wclk_recalc_rate, + .round_rate = da7219_wclk_round_rate, + .set_rate = da7219_wclk_set_rate, + }, + [DA7219_DAI_BCLK_IDX] = { + .recalc_rate = da7219_bclk_recalc_rate, + .round_rate = da7219_bclk_round_rate, + .set_rate = da7219_bclk_set_rate, + }, }; static int da7219_register_dai_clks(struct snd_soc_component *component) @@ -1876,47 +2125,81 @@ static int da7219_register_dai_clks(struct snd_soc_component *component) struct device *dev = component->dev; struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); struct da7219_pdata *pdata = da7219->pdata; - struct clk_init_data init = {}; - struct clk *dai_clks; - struct clk_lookup *dai_clks_lookup; const char *parent_name; + int i, ret; - if (da7219->mclk) { - parent_name = __clk_get_name(da7219->mclk); - init.parent_names = &parent_name; - init.num_parents = 1; - } else { - init.parent_names = NULL; - init.num_parents = 0; - } + for (i = 0; i < DA7219_DAI_NUM_CLKS; ++i) { + struct clk_init_data init = {}; + struct clk *dai_clk; + struct clk_lookup *dai_clk_lookup; + struct clk_hw *dai_clk_hw = &da7219->dai_clks_hw[i]; - init.name = pdata->dai_clks_name; - init.ops = &da7219_dai_clks_ops; - init.flags = CLK_GET_RATE_NOCACHE; - da7219->dai_clks_hw.init = &init; + switch (i) { + case DA7219_DAI_WCLK_IDX: + /* + * If we can, make MCLK the parent of WCLK to ensure + * it's enabled as required. + */ + if (da7219->mclk) { + parent_name = __clk_get_name(da7219->mclk); + init.parent_names = &parent_name; + init.num_parents = 1; + } else { + init.parent_names = NULL; + init.num_parents = 0; + } + break; + case DA7219_DAI_BCLK_IDX: + /* Make WCLK the parent of BCLK */ + parent_name = __clk_get_name(da7219->dai_clks[DA7219_DAI_WCLK_IDX]); + init.parent_names = &parent_name; + init.num_parents = 1; + break; + default: + dev_err(dev, "Invalid clock index\n"); + ret = -EINVAL; + goto err; + } - dai_clks = devm_clk_register(dev, &da7219->dai_clks_hw); - if (IS_ERR(dai_clks)) { - dev_warn(dev, "Failed to register DAI clocks: %ld\n", - PTR_ERR(dai_clks)); - return PTR_ERR(dai_clks); - } - da7219->dai_clks = dai_clks; + init.name = pdata->dai_clk_names[i]; + init.ops = &da7219_dai_clk_ops[i]; + init.flags = CLK_GET_RATE_NOCACHE | CLK_SET_RATE_GATE; + dai_clk_hw->init = &init; + + dai_clk = devm_clk_register(dev, dai_clk_hw); + if (IS_ERR(dai_clk)) { + dev_warn(dev, "Failed to register %s: %ld\n", + init.name, PTR_ERR(dai_clk)); + ret = PTR_ERR(dai_clk); + goto err; + } + da7219->dai_clks[i] = dai_clk; - /* If we're using DT, then register as provider accordingly */ - if (dev->of_node) { - devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, - &da7219->dai_clks_hw); - } else { - dai_clks_lookup = clkdev_create(dai_clks, pdata->dai_clks_name, - "%s", dev_name(dev)); - if (!dai_clks_lookup) - return -ENOMEM; - else - da7219->dai_clks_lookup = dai_clks_lookup; + /* If we're using DT, then register as provider accordingly */ + if (dev->of_node) { + devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + dai_clk_hw); + } else { + dai_clk_lookup = clkdev_create(dai_clk, init.name, + "%s", dev_name(dev)); + if (!dai_clk_lookup) { + ret = -ENOMEM; + goto err; + } else { + da7219->dai_clks_lookup[i] = dai_clk_lookup; + } + } } return 0; + +err: + do { + if (da7219->dai_clks_lookup[i]) + clkdev_drop(da7219->dai_clks_lookup[i]); + } while (i-- > 0); + + return ret; } #else static inline int da7219_register_dai_clks(struct snd_soc_component *component) @@ -2080,12 +2363,17 @@ err_disable_reg: static void da7219_remove(struct snd_soc_component *component) { struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +#ifdef CONFIG_COMMON_CLK + int i; +#endif da7219_aad_exit(component); #ifdef CONFIG_COMMON_CLK - if (da7219->dai_clks_lookup) - clkdev_drop(da7219->dai_clks_lookup); + for (i = DA7219_DAI_NUM_CLKS - 1; i >= 0; --i) { + if (da7219->dai_clks_lookup[i]) + clkdev_drop(da7219->dai_clks_lookup[i]); + } #endif /* Supplies */ diff --git a/sound/soc/codecs/da7219.h b/sound/soc/codecs/da7219.h index 018819c631fb..f3b180bc986f 100644 --- a/sound/soc/codecs/da7219.h +++ b/sound/soc/codecs/da7219.h @@ -820,10 +820,10 @@ struct da7219_priv { struct mutex pll_lock; #ifdef CONFIG_COMMON_CLK - struct clk_hw dai_clks_hw; + struct clk_hw dai_clks_hw[DA7219_DAI_NUM_CLKS]; #endif - struct clk_lookup *dai_clks_lookup; - struct clk *dai_clks; + struct clk_lookup *dai_clks_lookup[DA7219_DAI_NUM_CLKS]; + struct clk *dai_clks[DA7219_DAI_NUM_CLKS]; struct clk *mclk; unsigned int mclk_rate; diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 6d4a323f786b..ec2770b3f77d 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -43,6 +43,7 @@ struct es8316_priv { unsigned int sysclk; unsigned int allowed_rates[NR_SUPPORTED_MCLK_LRCK_RATIOS]; struct snd_pcm_hw_constraint_list sysclk_constraints; + bool jd_inverted; }; /* @@ -577,6 +578,9 @@ static irqreturn_t es8316_irq(int irq, void *data) if (!es8316->jack) goto out; + if (es8316->jd_inverted) + flags ^= ES8316_GPIO_FLAG_HP_NOT_INSERTED; + dev_dbg(comp->dev, "gpio flags %#04x\n", flags); if (flags & ES8316_GPIO_FLAG_HP_NOT_INSERTED) { /* Jack removed, or spurious IRQ? */ @@ -592,6 +596,8 @@ static irqreturn_t es8316_irq(int irq, void *data) /* Jack inserted, determine type */ es8316_enable_micbias_for_mic_gnd_short_detect(comp); regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags); + if (es8316->jd_inverted) + flags ^= ES8316_GPIO_FLAG_HP_NOT_INSERTED; dev_dbg(comp->dev, "gpio flags %#04x\n", flags); if (flags & ES8316_GPIO_FLAG_HP_NOT_INSERTED) { /* Jack unplugged underneath us */ @@ -633,6 +639,14 @@ static void es8316_enable_jack_detect(struct snd_soc_component *component, { struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component); + /* + * Init es8316->jd_inverted here and not in the probe, as we cannot + * guarantee that the bytchr-es8316 driver, which might set this + * property, will probe before us. + */ + es8316->jd_inverted = device_property_read_bool(component->dev, + "everest,jack-detect-inverted"); + mutex_lock(&es8316->lock); es8316->jack = jack; diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c index f889d94c8e3c..7d4940256914 100644 --- a/sound/soc/codecs/hdac_hda.c +++ b/sound/soc/codecs/hdac_hda.c @@ -328,6 +328,12 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) dev_err(&hdev->dev, "failed to create hda codec %d\n", ret); goto error_no_pm; } + /* + * Overwrite type to HDA_DEV_ASOC since it is a ASoC driver + * hda_codec.c will check this flag to determine if unregister + * device is needed. + */ + hdev->type = HDA_DEV_ASOC; /* * snd_hda_codec_device_new decrements the usage count so call get pm diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index 5eeb0fe836a9..660e0587f399 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -455,24 +455,11 @@ static int hdac_hdmi_set_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hparams, struct snd_soc_dai *dai) { struct hdac_hdmi_priv *hdmi = snd_soc_dai_get_drvdata(dai); - struct hdac_device *hdev = hdmi->hdev; struct hdac_hdmi_dai_port_map *dai_map; - struct hdac_hdmi_port *port; struct hdac_hdmi_pcm *pcm; int format; dai_map = &hdmi->dai_map[dai->id]; - port = dai_map->port; - - if (!port) - return -ENODEV; - - if ((!port->eld.monitor_present) || (!port->eld.eld_valid)) { - dev_err(&hdev->dev, - "device is not configured for this pin:port%d:%d\n", - port->pin->nid, port->id); - return -ENODEV; - } format = snd_hdac_calc_stream_format(params_rate(hparams), params_channels(hparams), params_format(hparams), @@ -1855,6 +1842,17 @@ static int hdmi_codec_probe(struct snd_soc_component *component) hdmi->card = dapm->card->snd_card; /* + * Setup a device_link between card device and HDMI codec device. + * The card device is the consumer and the HDMI codec device is + * the supplier. With this setting, we can make sure that the audio + * domain in display power will be always turned on before operating + * on the HDMI audio codec registers. + * Let's use the flag DL_FLAG_AUTOREMOVE_CONSUMER. This can make + * sure the device link is freed when the machine driver is removed. + */ + device_link_add(component->card->dev, &hdev->dev, DL_FLAG_RPM_ACTIVE | + DL_FLAG_AUTOREMOVE_CONSUMER); + /* * hdac_device core already sets the state to active and calls * get_noresume. So enable runtime and set the device to suspend. */ diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 35df73e42cbc..39caf19abb0b 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -439,8 +439,12 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, if (!ret) { ret = snd_pcm_hw_constraint_eld(substream->runtime, hcp->eld); - if (ret) + if (ret) { + mutex_lock(&hcp->current_stream_lock); + hcp->current_stream = NULL; + mutex_unlock(&hcp->current_stream_lock); return ret; + } } /* Select chmap supported */ hdmi_codec_eld_chmap(hcp); @@ -492,10 +496,6 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, return ret; } - ret = hdmi_codec_new_stream(substream, dai); - if (ret) - return ret; - hdmi_audio_infoframe_init(&hp.cea); hp.cea.channels = params_channels(params); hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; @@ -757,7 +757,7 @@ static int hdmi_codec_probe(struct platform_device *pdev) dev_dbg(dev, "%s()\n", __func__); if (!hcd) { - dev_err(dev, "%s: No plalform data\n", __func__); + dev_err(dev, "%s: No platform data\n", __func__); return -EINVAL; } diff --git a/sound/soc/codecs/lochnagar-sc.c b/sound/soc/codecs/lochnagar-sc.c new file mode 100644 index 000000000000..3209b39e46af --- /dev/null +++ b/sound/soc/codecs/lochnagar-sc.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Lochnagar sound card driver +// +// Copyright (c) 2017-2019 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. +// +// Author: Charles Keepax <ckeepax@opensource.cirrus.com> +// Piotr Stankiewicz <piotrs@opensource.cirrus.com> + +#include <linux/clk.h> +#include <linux/module.h> +#include <sound/soc.h> + +#include <linux/mfd/lochnagar.h> +#include <linux/mfd/lochnagar1_regs.h> +#include <linux/mfd/lochnagar2_regs.h> + +struct lochnagar_sc_priv { + struct clk *mclk; +}; + +static const struct snd_soc_dapm_widget lochnagar_sc_widgets[] = { + SND_SOC_DAPM_LINE("Line Jack", NULL), + SND_SOC_DAPM_LINE("USB Audio", NULL), +}; + +static const struct snd_soc_dapm_route lochnagar_sc_routes[] = { + { "Line Jack", NULL, "AIF1 Playback" }, + { "AIF1 Capture", NULL, "Line Jack" }, + + { "USB Audio", NULL, "USB1 Playback" }, + { "USB Audio", NULL, "USB2 Playback" }, + { "USB1 Capture", NULL, "USB Audio" }, + { "USB2 Capture", NULL, "USB Audio" }, +}; + +static const unsigned int lochnagar_sc_chan_vals[] = { + 4, 8, +}; + +static const struct snd_pcm_hw_constraint_list lochnagar_sc_chan_constraint = { + .count = ARRAY_SIZE(lochnagar_sc_chan_vals), + .list = lochnagar_sc_chan_vals, +}; + +static const unsigned int lochnagar_sc_rate_vals[] = { + 8000, 16000, 24000, 32000, 48000, 96000, 192000, + 22050, 44100, 88200, 176400, +}; + +static const struct snd_pcm_hw_constraint_list lochnagar_sc_rate_constraint = { + .count = ARRAY_SIZE(lochnagar_sc_rate_vals), + .list = lochnagar_sc_rate_vals, +}; + +static int lochnagar_sc_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval range = { + .min = 8000, + .max = 24576000 / hw_param_interval(params, rule->deps[0])->max, + }; + + return snd_interval_refine(hw_param_interval(params, rule->var), + &range); +} + +static int lochnagar_sc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *comp = dai->component; + struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &lochnagar_sc_rate_constraint); + if (ret) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + lochnagar_sc_hw_rule_rate, priv, + SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); +} + +static int lochnagar_sc_line_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *comp = dai->component; + struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); + int ret; + + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(dai->dev, "Failed to enable MCLK: %d\n", ret); + return ret; + } + + ret = lochnagar_sc_startup(substream, dai); + if (ret) + return ret; + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &lochnagar_sc_chan_constraint); +} + +static void lochnagar_sc_line_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *comp = dai->component; + struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp); + + clk_disable_unprepare(priv->mclk); +} + +static int lochnagar_sc_check_fmt(struct snd_soc_dai *dai, unsigned int fmt, + unsigned int tar) +{ + tar |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + + if ((fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) != tar) + return -EINVAL; + + return 0; +} + +static int lochnagar_sc_set_line_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBS_CFS); +} + +static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBM_CFM); +} + +static const struct snd_soc_dai_ops lochnagar_sc_line_ops = { + .startup = lochnagar_sc_line_startup, + .shutdown = lochnagar_sc_line_shutdown, + .set_fmt = lochnagar_sc_set_line_fmt, +}; + +static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = { + .startup = lochnagar_sc_startup, + .set_fmt = lochnagar_sc_set_usb_fmt, +}; + +static struct snd_soc_dai_driver lochnagar_sc_dai[] = { + { + .name = "lochnagar-line", + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 4, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 4, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &lochnagar_sc_line_ops, + .symmetric_rates = true, + .symmetric_samplebits = true, + }, + { + .name = "lochnagar-usb1", + .playback = { + .stream_name = "USB1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "USB1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &lochnagar_sc_usb_ops, + .symmetric_rates = true, + .symmetric_samplebits = true, + }, + { + .name = "lochnagar-usb2", + .playback = { + .stream_name = "USB2 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "USB2 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &lochnagar_sc_usb_ops, + .symmetric_rates = true, + .symmetric_samplebits = true, + }, +}; + +static const struct snd_soc_component_driver lochnagar_sc_driver = { + .non_legacy_dai_naming = 1, + + .dapm_widgets = lochnagar_sc_widgets, + .num_dapm_widgets = ARRAY_SIZE(lochnagar_sc_widgets), + .dapm_routes = lochnagar_sc_routes, + .num_dapm_routes = ARRAY_SIZE(lochnagar_sc_routes), +}; + +static int lochnagar_sc_probe(struct platform_device *pdev) +{ + struct lochnagar_sc_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(priv->mclk)) { + ret = PTR_ERR(priv->mclk); + dev_err(&pdev->dev, "Failed to get MCLK: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, priv); + + return devm_snd_soc_register_component(&pdev->dev, + &lochnagar_sc_driver, + lochnagar_sc_dai, + ARRAY_SIZE(lochnagar_sc_dai)); +} + +static const struct of_device_id lochnagar_of_match[] = { + { .compatible = "cirrus,lochnagar2-soundcard" }, + {} +}; +MODULE_DEVICE_TABLE(of, lochnagar_of_match); + +static struct platform_driver lochnagar_sc_codec_driver = { + .driver = { + .name = "lochnagar-soundcard", + .of_match_table = of_match_ptr(lochnagar_of_match), + }, + + .probe = lochnagar_sc_probe, +}; +module_platform_driver(lochnagar_sc_codec_driver); + +MODULE_DESCRIPTION("ASoC Lochnagar Sound Card Driver"); +MODULE_AUTHOR("Piotr Stankiewicz <piotrs@opensource.cirrus.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:lochnagar-soundcard"); diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index 30c242c38d99..7619ea31ab50 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -1194,14 +1194,14 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { &max98090_right_rcv_mixer_controls[0], ARRAY_SIZE(max98090_right_rcv_mixer_controls)), - SND_SOC_DAPM_MUX("LINMOD Mux", M98090_REG_LOUTR_MIXER, - M98090_LINMOD_SHIFT, 0, &max98090_linmod_mux), + SND_SOC_DAPM_MUX("LINMOD Mux", SND_SOC_NOPM, 0, 0, + &max98090_linmod_mux), - SND_SOC_DAPM_MUX("MIXHPLSEL Mux", M98090_REG_HP_CONTROL, - M98090_MIXHPLSEL_SHIFT, 0, &max98090_mixhplsel_mux), + SND_SOC_DAPM_MUX("MIXHPLSEL Mux", SND_SOC_NOPM, 0, 0, + &max98090_mixhplsel_mux), - SND_SOC_DAPM_MUX("MIXHPRSEL Mux", M98090_REG_HP_CONTROL, - M98090_MIXHPRSEL_SHIFT, 0, &max98090_mixhprsel_mux), + SND_SOC_DAPM_MUX("MIXHPRSEL Mux", SND_SOC_NOPM, 0, 0, + &max98090_mixhprsel_mux), SND_SOC_DAPM_PGA("HP Left Out", M98090_REG_OUTPUT_ENABLE, M98090_HPLEN_SHIFT, 0, NULL, 0), diff --git a/sound/soc/codecs/max98357a.c b/sound/soc/codecs/max98357a.c index d469576b5a7b..d037a3e4d323 100644 --- a/sound/soc/codecs/max98357a.c +++ b/sound/soc/codecs/max98357a.c @@ -97,7 +97,10 @@ static struct snd_soc_dai_driver max98357a_dai_driver = { SNDRV_PCM_FMTBIT_S32, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000, .rate_min = 8000, .rate_max = 96000, diff --git a/sound/soc/codecs/mt6358.c b/sound/soc/codecs/mt6358.c index d4c4fee6d3d9..50b3fc5457ea 100644 --- a/sound/soc/codecs/mt6358.c +++ b/sound/soc/codecs/mt6358.c @@ -320,32 +320,6 @@ enum { #define DL_GAIN_N_40DB_REG (DL_GAIN_N_40DB << 7 | DL_GAIN_N_40DB) #define DL_GAIN_REG_MASK 0x0f9f -static void lo_store_gain(struct mt6358_priv *priv) -{ - unsigned int reg; - unsigned int gain_l, gain_r; - - regmap_read(priv->regmap, MT6358_ZCD_CON1, ®); - gain_l = (reg >> RG_AUDLOLGAIN_SFT) & RG_AUDLOLGAIN_MASK; - gain_r = (reg >> RG_AUDLORGAIN_SFT) & RG_AUDLORGAIN_MASK; - - priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTL] = gain_l; - priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTR] = gain_r; -} - -static void hp_store_gain(struct mt6358_priv *priv) -{ - unsigned int reg; - unsigned int gain_l, gain_r; - - regmap_read(priv->regmap, MT6358_ZCD_CON2, ®); - gain_l = (reg >> RG_AUDHPLGAIN_SFT) & RG_AUDHPLGAIN_MASK; - gain_r = (reg >> RG_AUDHPRGAIN_SFT) & RG_AUDHPRGAIN_MASK; - - priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTL] = gain_l; - priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTR] = gain_r; -} - static void hp_zcd_disable(struct mt6358_priv *priv) { regmap_write(priv->regmap, MT6358_ZCD_CON0, 0x0000); @@ -405,10 +379,9 @@ static bool is_valid_hp_pga_idx(int reg_idx) reg_idx == DL_GAIN_N_40DB; } -static void headset_volume_ramp(struct mt6358_priv *priv, - int from, int to) +static void headset_volume_ramp(struct mt6358_priv *priv, int from, int to) { - int offset = 0, count = 1, reg_idx; + int offset = 0, count = 0, reg_idx; if (!is_valid_hp_pga_idx(from) || !is_valid_hp_pga_idx(to)) dev_warn(priv->dev, "%s(), volume index is not valid, from %d, to %d\n", @@ -422,7 +395,7 @@ static void headset_volume_ramp(struct mt6358_priv *priv, else offset = from - to; - while (offset > 0) { + while (offset >= 0) { if (to > from) reg_idx = from + count; else @@ -440,25 +413,76 @@ static void headset_volume_ramp(struct mt6358_priv *priv, } } +static int mt6358_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct mt6358_priv *priv = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg; + int ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + switch (mc->reg) { + case MT6358_ZCD_CON2: + regmap_read(priv->regmap, MT6358_ZCD_CON2, ®); + priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTL] = + (reg >> RG_AUDHPLGAIN_SFT) & RG_AUDHPLGAIN_MASK; + priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTR] = + (reg >> RG_AUDHPRGAIN_SFT) & RG_AUDHPRGAIN_MASK; + break; + case MT6358_ZCD_CON1: + regmap_read(priv->regmap, MT6358_ZCD_CON1, ®); + priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTL] = + (reg >> RG_AUDLOLGAIN_SFT) & RG_AUDLOLGAIN_MASK; + priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTR] = + (reg >> RG_AUDLORGAIN_SFT) & RG_AUDLORGAIN_MASK; + break; + case MT6358_ZCD_CON3: + regmap_read(priv->regmap, MT6358_ZCD_CON3, ®); + priv->ana_gain[AUDIO_ANALOG_VOLUME_HSOUTL] = + (reg >> RG_AUDHSGAIN_SFT) & RG_AUDHSGAIN_MASK; + priv->ana_gain[AUDIO_ANALOG_VOLUME_HSOUTR] = + (reg >> RG_AUDHSGAIN_SFT) & RG_AUDHSGAIN_MASK; + break; + case MT6358_AUDENC_ANA_CON0: + case MT6358_AUDENC_ANA_CON1: + regmap_read(priv->regmap, MT6358_AUDENC_ANA_CON0, ®); + priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP1] = + (reg >> RG_AUDPREAMPLGAIN_SFT) & RG_AUDPREAMPLGAIN_MASK; + regmap_read(priv->regmap, MT6358_AUDENC_ANA_CON1, ®); + priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP2] = + (reg >> RG_AUDPREAMPRGAIN_SFT) & RG_AUDPREAMPRGAIN_MASK; + break; + } + + return ret; +} + static const DECLARE_TLV_DB_SCALE(playback_tlv, -1000, 100, 0); static const DECLARE_TLV_DB_SCALE(pga_tlv, 0, 600, 0); static const struct snd_kcontrol_new mt6358_snd_controls[] = { /* dl pga gain */ - SOC_DOUBLE_TLV("Headphone Volume", - MT6358_ZCD_CON2, 0, 7, 0x12, 1, - playback_tlv), - SOC_DOUBLE_TLV("Lineout Volume", - MT6358_ZCD_CON1, 0, 7, 0x12, 1, - playback_tlv), - SOC_SINGLE_TLV("Handset Volume", - MT6358_ZCD_CON3, 0, 0x12, 1, - playback_tlv), + SOC_DOUBLE_EXT_TLV("Headphone Volume", + MT6358_ZCD_CON2, 0, 7, 0x12, 1, + snd_soc_get_volsw, mt6358_put_volsw, playback_tlv), + SOC_DOUBLE_EXT_TLV("Lineout Volume", + MT6358_ZCD_CON1, 0, 7, 0x12, 1, + snd_soc_get_volsw, mt6358_put_volsw, playback_tlv), + SOC_SINGLE_EXT_TLV("Handset Volume", + MT6358_ZCD_CON3, 0, 0x12, 1, + snd_soc_get_volsw, mt6358_put_volsw, playback_tlv), /* ul pga gain */ - SOC_DOUBLE_R_TLV("PGA Volume", - MT6358_AUDENC_ANA_CON0, MT6358_AUDENC_ANA_CON1, - 8, 4, 0, - pga_tlv), + SOC_DOUBLE_R_EXT_TLV("PGA Volume", + MT6358_AUDENC_ANA_CON0, MT6358_AUDENC_ANA_CON1, + 8, 4, 0, + snd_soc_get_volsw, mt6358_put_volsw, pga_tlv), }; /* MUX */ @@ -832,8 +856,6 @@ static int mtk_hp_enable(struct mt6358_priv *priv) /* Reduce ESD resistance of AU_REFN */ regmap_write(priv->regmap, MT6358_AUDDEC_ANA_CON2, 0x4000); - /* save target gain to restore after hardware open complete */ - hp_store_gain(priv); /* Set HPR/HPL gain as minimum (~ -40dB) */ regmap_write(priv->regmap, MT6358_ZCD_CON2, DL_GAIN_N_40DB_REG); @@ -1043,8 +1065,6 @@ static int mtk_hp_spk_enable(struct mt6358_priv *priv) /* Reduce ESD resistance of AU_REFN */ regmap_write(priv->regmap, MT6358_AUDDEC_ANA_CON2, 0x4000); - /* save target gain to restore after hardware open complete */ - hp_store_gain(priv); /* Set HPR/HPL gain to -10dB */ regmap_write(priv->regmap, MT6358_ZCD_CON2, DL_GAIN_N_10DB_REG); @@ -1104,7 +1124,6 @@ static int mtk_hp_spk_enable(struct mt6358_priv *priv) hp_main_output_ramp(priv, true); /* Set LO gain as minimum (~ -40dB) */ - lo_store_gain(priv); regmap_write(priv->regmap, MT6358_ZCD_CON1, DL_GAIN_N_40DB_REG); /* apply volume setting */ headset_volume_ramp(priv, @@ -1740,6 +1759,21 @@ static void mt6358_dmic_disable(struct mt6358_priv *priv) regmap_write(priv->regmap, MT6358_AUDENC_ANA_CON9, 0x0000); } +static void mt6358_restore_pga(struct mt6358_priv *priv) +{ + unsigned int gain_l, gain_r; + + gain_l = priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP1]; + gain_r = priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP2]; + + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON0, + RG_AUDPREAMPLGAIN_MASK_SFT, + gain_l << RG_AUDPREAMPLGAIN_SFT); + regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON1, + RG_AUDPREAMPRGAIN_MASK_SFT, + gain_r << RG_AUDPREAMPRGAIN_SFT); +} + static int mt_mic_type_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) @@ -1764,6 +1798,7 @@ static int mt_mic_type_event(struct snd_soc_dapm_widget *w, mt6358_amic_enable(priv); break; } + mt6358_restore_pga(priv); break; case SND_SOC_DAPM_POST_PMD: diff --git a/sound/soc/codecs/nau8810.c b/sound/soc/codecs/nau8810.c index 645aa0794123..dd82c65cfa7f 100644 --- a/sound/soc/codecs/nau8810.c +++ b/sound/soc/codecs/nau8810.c @@ -493,7 +493,7 @@ static int nau8810_set_sysclk(struct snd_soc_dai *dai, return 0; } -static int nau88l0_calc_pll(unsigned int pll_in, +static int nau8810_calc_pll(unsigned int pll_in, unsigned int fs, struct nau8810_pll *pll_param) { u64 f2, f2_max, pll_ratio; @@ -505,7 +505,8 @@ static int nau88l0_calc_pll(unsigned int pll_in, f2_max = 0; scal_sel = ARRAY_SIZE(nau8810_mclk_scaler); for (i = 0; i < ARRAY_SIZE(nau8810_mclk_scaler); i++) { - f2 = 256 * fs * 4 * nau8810_mclk_scaler[i] / 10; + f2 = 256ULL * fs * 4 * nau8810_mclk_scaler[i]; + f2 = div_u64(f2, 10); if (f2 > NAU_PLL_FREQ_MIN && f2 < NAU_PLL_FREQ_MAX && f2_max < f2) { f2_max = f2; @@ -542,7 +543,7 @@ static int nau8810_set_pll(struct snd_soc_dai *codec_dai, int pll_id, int ret, fs; fs = freq_out / 256; - ret = nau88l0_calc_pll(freq_in, fs, pll_param); + ret = nau8810_calc_pll(freq_in, fs, pll_param); if (ret < 0) { dev_err(nau8810->dev, "Unsupported input clock %d\n", freq_in); return ret; @@ -667,6 +668,24 @@ static int nau8810_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); int val_len = 0, val_rate = 0, ret = 0; + unsigned int ctrl_val, bclk_fs, bclk_div; + + /* Select BCLK configuration if the codec as master. */ + regmap_read(nau8810->regmap, NAU8810_REG_CLOCK, &ctrl_val); + if (ctrl_val & NAU8810_CLKIO_MASTER) { + /* get the bclk and fs ratio */ + bclk_fs = snd_soc_params_to_bclk(params) / params_rate(params); + if (bclk_fs <= 32) + bclk_div = NAU8810_BCLKDIV_8; + else if (bclk_fs <= 64) + bclk_div = NAU8810_BCLKDIV_4; + else if (bclk_fs <= 128) + bclk_div = NAU8810_BCLKDIV_2; + else + return -EINVAL; + regmap_update_bits(nau8810->regmap, NAU8810_REG_CLOCK, + NAU8810_BCLKSEL_MASK, bclk_div); + } switch (params_width(params)) { case 16: diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c index 08d3fe192e65..e0d5839fe1a7 100644 --- a/sound/soc/codecs/pcm3168a.c +++ b/sound/soc/codecs/pcm3168a.c @@ -457,13 +457,16 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream, if (chan > 2) { switch (fmt) { case PCM3168A_FMT_I2S: + case PCM3168A_FMT_DSP_A: fmt = PCM3168A_FMT_I2S_TDM; break; case PCM3168A_FMT_LEFT_J: + case PCM3168A_FMT_DSP_B: fmt = PCM3168A_FMT_LEFT_J_TDM; break; default: - dev_err(component->dev, "TDM is supported under I2S/Left_J only\n"); + dev_err(component->dev, + "TDM is supported under DSP/I2S/Left_J only\n"); return -EINVAL; } } @@ -526,6 +529,8 @@ static int pcm3168a_startup(struct snd_pcm_substream *substream, break; case PCM3168A_FMT_LEFT_J: case PCM3168A_FMT_I2S: + case PCM3168A_FMT_DSP_A: + case PCM3168A_FMT_DSP_B: sample_min = 24; channel_max = channel_maxs[tx]; break; diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 9a0751978090..cd45d41df4ec 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3419,6 +3419,9 @@ static int rt5645_probe(struct snd_soc_component *component) RT5645_HWEQ_NUM, sizeof(struct rt5645_eq_param_s), GFP_KERNEL); + if (!rt5645->eq_param) + return -ENOMEM; + return 0; } @@ -3631,6 +3634,11 @@ static const struct rt5645_platform_data jd_mode3_platform_data = { .jd_mode = 3, }; +static const struct rt5645_platform_data lattepanda_board_platform_data = { + .jd_mode = 2, + .inv_jd1_1 = true +}; + static const struct dmi_system_id dmi_platform_data[] = { { .ident = "Chrome Buddy", @@ -3728,6 +3736,15 @@ static const struct dmi_system_id dmi_platform_data[] = { }, .driver_data = (void *)&intel_braswell_platform_data, }, + { + .ident = "LattePanda board", + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + DMI_EXACT_MATCH(DMI_BOARD_VERSION, "Default string"), + }, + .driver_data = (void *)&lattepanda_board_platform_data, + }, { } }; diff --git a/sound/soc/codecs/rt5651.c b/sound/soc/codecs/rt5651.c index 29b2d60076b0..cb8252ff31cb 100644 --- a/sound/soc/codecs/rt5651.c +++ b/sound/soc/codecs/rt5651.c @@ -1645,7 +1645,10 @@ static bool rt5651_jack_inserted(struct snd_soc_component *component) break; } - return val == 0; + if (rt5651->jd_active_high) + return val != 0; + else + return val == 0; } /* Jack detect and button-press timings */ @@ -1868,20 +1871,47 @@ static void rt5651_enable_jack_detect(struct snd_soc_component *component, case RT5651_JD1_1: snd_soc_component_update_bits(component, RT5651_JD_CTRL2, RT5651_JD_TRG_SEL_MASK, RT5651_JD_TRG_SEL_JD1_1); - snd_soc_component_update_bits(component, RT5651_IRQ_CTRL1, - RT5651_JD1_1_IRQ_EN, RT5651_JD1_1_IRQ_EN); + /* active-low is normal, set inv flag for active-high */ + if (rt5651->jd_active_high) + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD1_1_IRQ_EN | RT5651_JD1_1_INV, + RT5651_JD1_1_IRQ_EN | RT5651_JD1_1_INV); + else + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD1_1_IRQ_EN | RT5651_JD1_1_INV, + RT5651_JD1_1_IRQ_EN); break; case RT5651_JD1_2: snd_soc_component_update_bits(component, RT5651_JD_CTRL2, RT5651_JD_TRG_SEL_MASK, RT5651_JD_TRG_SEL_JD1_2); - snd_soc_component_update_bits(component, RT5651_IRQ_CTRL1, - RT5651_JD1_2_IRQ_EN, RT5651_JD1_2_IRQ_EN); + /* active-low is normal, set inv flag for active-high */ + if (rt5651->jd_active_high) + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD1_2_IRQ_EN | RT5651_JD1_2_INV, + RT5651_JD1_2_IRQ_EN | RT5651_JD1_2_INV); + else + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD1_2_IRQ_EN | RT5651_JD1_2_INV, + RT5651_JD1_2_IRQ_EN); break; case RT5651_JD2: snd_soc_component_update_bits(component, RT5651_JD_CTRL2, RT5651_JD_TRG_SEL_MASK, RT5651_JD_TRG_SEL_JD2); - snd_soc_component_update_bits(component, RT5651_IRQ_CTRL1, - RT5651_JD2_IRQ_EN, RT5651_JD2_IRQ_EN); + /* active-low is normal, set inv flag for active-high */ + if (rt5651->jd_active_high) + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD2_IRQ_EN | RT5651_JD2_INV, + RT5651_JD2_IRQ_EN | RT5651_JD2_INV); + else + snd_soc_component_update_bits(component, + RT5651_IRQ_CTRL1, + RT5651_JD2_IRQ_EN | RT5651_JD2_INV, + RT5651_JD2_IRQ_EN); break; default: dev_err(component->dev, "Currently only JD1_1 / JD1_2 / JD2 are supported\n"); @@ -1986,6 +2016,9 @@ static void rt5651_apply_properties(struct snd_soc_component *component) "realtek,jack-detect-source", &val) == 0) rt5651->jd_src = val; + if (device_property_read_bool(component->dev, "realtek,jack-detect-not-inverted")) + rt5651->jd_active_high = true; + /* * Testing on various boards has shown that good defaults for the OVCD * threshold and scale-factor are 2000µA and 0.75. For an effective diff --git a/sound/soc/codecs/rt5651.h b/sound/soc/codecs/rt5651.h index 41fcb8b5eb40..05b0f6f8b95d 100644 --- a/sound/soc/codecs/rt5651.h +++ b/sound/soc/codecs/rt5651.h @@ -2083,6 +2083,7 @@ struct rt5651_priv { int release_count; int poll_count; unsigned int jd_src; + bool jd_active_high; unsigned int ovcd_th; unsigned int ovcd_sf; diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c index 84501c2020c7..84b6bd8b50e1 100644 --- a/sound/soc/codecs/rt5677-spi.c +++ b/sound/soc/codecs/rt5677-spi.c @@ -25,6 +25,7 @@ #include <linux/sysfs.h> #include <linux/clk.h> #include <linux/firmware.h> +#include <linux/acpi.h> #include "rt5677-spi.h" @@ -57,13 +58,15 @@ static DEFINE_MUTEX(spi_mutex); * RT5677_SPI_READ/WRITE_32: Transfer 4 bytes * RT5677_SPI_READ/WRITE_BURST: Transfer any multiples of 8 bytes * - * For example, reading 260 bytes at 0x60030002 uses the following commands: - * 0x60030002 RT5677_SPI_READ_16 2 bytes + * Note: + * 16 Bit writes and reads are restricted to the address range + * 0x18020000 ~ 0x18021000 + * + * For example, reading 256 bytes at 0x60030004 uses the following commands: * 0x60030004 RT5677_SPI_READ_32 4 bytes * 0x60030008 RT5677_SPI_READ_BURST 240 bytes * 0x600300F8 RT5677_SPI_READ_BURST 8 bytes * 0x60030100 RT5677_SPI_READ_32 4 bytes - * 0x60030104 RT5677_SPI_READ_16 2 bytes * * Input: * @read: true for read commands; false for write commands @@ -78,15 +81,13 @@ static u8 rt5677_spi_select_cmd(bool read, u32 align, u32 remain, u32 *len) { u8 cmd; - if (align == 2 || align == 6 || remain == 2) { - cmd = RT5677_SPI_READ_16; - *len = 2; - } else if (align == 4 || remain <= 6) { + if (align == 4 || remain <= 4) { cmd = RT5677_SPI_READ_32; *len = 4; } else { cmd = RT5677_SPI_READ_BURST; - *len = min_t(u32, remain & ~7, RT5677_SPI_BURST_LEN); + *len = (((remain - 1) >> 3) + 1) << 3; + *len = min_t(u32, *len, RT5677_SPI_BURST_LEN); } return read ? cmd : cmd + 1; } @@ -107,7 +108,7 @@ static void rt5677_spi_reverse(u8 *dst, u32 dstlen, const u8 *src, u32 srclen) } } -/* Read DSP address space using SPI. addr and len have to be 2-byte aligned. */ +/* Read DSP address space using SPI. addr and len have to be 4-byte aligned. */ int rt5677_spi_read(u32 addr, void *rxbuf, size_t len) { u32 offset; @@ -123,7 +124,7 @@ int rt5677_spi_read(u32 addr, void *rxbuf, size_t len) if (!g_spi) return -ENODEV; - if ((addr & 1) || (len & 1)) { + if ((addr & 3) || (len & 3)) { dev_err(&g_spi->dev, "Bad read align 0x%x(%zu)\n", addr, len); return -EACCES; } @@ -158,13 +159,13 @@ int rt5677_spi_read(u32 addr, void *rxbuf, size_t len) } EXPORT_SYMBOL_GPL(rt5677_spi_read); -/* Write DSP address space using SPI. addr has to be 2-byte aligned. - * If len is not 2-byte aligned, an extra byte of zero is written at the end +/* Write DSP address space using SPI. addr has to be 4-byte aligned. + * If len is not 4-byte aligned, then extra zeros are written at the end * as padding. */ int rt5677_spi_write(u32 addr, const void *txbuf, size_t len) { - u32 offset, len_with_pad = len; + u32 offset; int status = 0; struct spi_transfer t; struct spi_message m; @@ -177,22 +178,19 @@ int rt5677_spi_write(u32 addr, const void *txbuf, size_t len) if (!g_spi) return -ENODEV; - if (addr & 1) { + if (addr & 3) { dev_err(&g_spi->dev, "Bad write align 0x%x(%zu)\n", addr, len); return -EACCES; } - if (len & 1) - len_with_pad = len + 1; - memset(&t, 0, sizeof(t)); t.tx_buf = buf; t.speed_hz = RT5677_SPI_FREQ; spi_message_init_with_transfers(&m, &t, 1); - for (offset = 0; offset < len_with_pad;) { + for (offset = 0; offset < len;) { spi_cmd = rt5677_spi_select_cmd(false, (addr + offset) & 7, - len_with_pad - offset, &t.len); + len - offset, &t.len); /* Construct SPI message header */ buf[0] = spi_cmd; @@ -226,9 +224,16 @@ static int rt5677_spi_probe(struct spi_device *spi) return 0; } +static const struct acpi_device_id rt5677_spi_acpi_id[] = { + { "RT5677AA", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, rt5677_spi_acpi_id); + static struct spi_driver rt5677_spi_driver = { .driver = { .name = "rt5677", + .acpi_match_table = ACPI_PTR(rt5677_spi_acpi_id), }, .probe = rt5677_spi_probe, }; diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c index 86a7fa31c294..505fb3d7b1c5 100644 --- a/sound/soc/codecs/rt5682.c +++ b/sound/soc/codecs/rt5682.c @@ -2588,6 +2588,7 @@ static int rt5682_i2c_probe(struct i2c_client *i2c, rt5682_reset(rt5682->regmap); + mutex_init(&rt5682->calibrate_mutex); rt5682_calibrate(rt5682); ret = regmap_multi_reg_write(rt5682->regmap, patch_list, @@ -2654,7 +2655,6 @@ static int rt5682_i2c_probe(struct i2c_client *i2c, INIT_DELAYED_WORK(&rt5682->jd_check_work, rt5682_jd_check_handler); - mutex_init(&rt5682->calibrate_mutex); if (i2c->irq) { ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, diff --git a/sound/soc/codecs/simple-amplifier.c b/sound/soc/codecs/simple-amplifier.c index c07e8a80b4b7..351aa55c384e 100644 --- a/sound/soc/codecs/simple-amplifier.c +++ b/sound/soc/codecs/simple-amplifier.c @@ -89,7 +89,8 @@ static int simple_amp_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, priv); - priv->gpiod_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + priv->gpiod_enable = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); if (IS_ERR(priv->gpiod_enable)) { err = PTR_ERR(priv->gpiod_enable); if (err != -EPROBE_DEFER) diff --git a/sound/soc/codecs/sirf-audio-codec.c b/sound/soc/codecs/sirf-audio-codec.c index e424499a8450..e0af21050078 100644 --- a/sound/soc/codecs/sirf-audio-codec.c +++ b/sound/soc/codecs/sirf-audio-codec.c @@ -461,9 +461,6 @@ static int sirf_audio_codec_driver_probe(struct platform_device *pdev) struct sirf_audio_codec *sirf_audio_codec; void __iomem *base; struct resource *mem_res; - const struct of_device_id *match; - - match = of_match_node(sirf_audio_codec_of_match, pdev->dev.of_node); sirf_audio_codec = devm_kzalloc(&pdev->dev, sizeof(struct sirf_audio_codec), GFP_KERNEL); diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index c544a1e35f5e..9b37e98da0db 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -25,6 +25,7 @@ #include <linux/of_gpio.h> #include <linux/slab.h> #include <sound/core.h> +#include <sound/jack.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -89,6 +90,7 @@ static bool aic31xx_volatile(struct device *dev, unsigned int reg) case AIC31XX_INTRADCFLAG: /* Sticky interrupt flags */ case AIC31XX_INTRDACFLAG2: case AIC31XX_INTRADCFLAG2: + case AIC31XX_HSDETECT: return true; } return false; @@ -163,6 +165,7 @@ struct aic31xx_priv { struct aic31xx_pdata pdata; struct regulator_bulk_data supplies[AIC31XX_NUM_SUPPLIES]; struct aic31xx_disable_nb disable_nb[AIC31XX_NUM_SUPPLIES]; + struct snd_soc_jack *jack; unsigned int sysclk; u8 p_div; int rate_div_line; @@ -1261,6 +1264,20 @@ static int aic31xx_set_bias_level(struct snd_soc_component *component, return 0; } +static int aic31xx_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *data) +{ + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + + aic31xx->jack = jack; + + /* Enable/Disable jack detection */ + regmap_write(aic31xx->regmap, AIC31XX_HSDETECT, + jack ? AIC31XX_HSD_ENABLE : 0); + + return 0; +} + static int aic31xx_codec_probe(struct snd_soc_component *component) { struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); @@ -1301,6 +1318,7 @@ static int aic31xx_codec_probe(struct snd_soc_component *component) static const struct snd_soc_component_driver soc_codec_driver_aic31xx = { .probe = aic31xx_codec_probe, + .set_jack = aic31xx_set_jack, .set_bias_level = aic31xx_set_bias_level, .controls = common31xx_snd_controls, .num_controls = ARRAY_SIZE(common31xx_snd_controls), @@ -1405,8 +1423,47 @@ static irqreturn_t aic31xx_irq(int irq, void *data) dev_err(dev, "Short circuit on Left output is detected\n"); if (value & AIC31XX_HPRSCDETECT) dev_err(dev, "Short circuit on Right output is detected\n"); + if (value & (AIC31XX_HSPLUG | AIC31XX_BUTTONPRESS)) { + unsigned int val; + int status = 0; + + ret = regmap_read(aic31xx->regmap, AIC31XX_INTRDACFLAG2, + &val); + if (ret) { + dev_err(dev, "Failed to read interrupt mask: %d\n", + ret); + goto exit; + } + + if (val & AIC31XX_BUTTONPRESS) + status |= SND_JACK_BTN_0; + + ret = regmap_read(aic31xx->regmap, AIC31XX_HSDETECT, &val); + if (ret) { + dev_err(dev, "Failed to read headset type: %d\n", ret); + goto exit; + } + + switch ((val & AIC31XX_HSD_TYPE_MASK) >> + AIC31XX_HSD_TYPE_SHIFT) { + case AIC31XX_HSD_HP: + status |= SND_JACK_HEADPHONE; + break; + case AIC31XX_HSD_HS: + status |= SND_JACK_HEADSET; + break; + default: + break; + } + + if (aic31xx->jack) + snd_soc_jack_report(aic31xx->jack, status, + AIC31XX_JACK_MASK); + } if (value & ~(AIC31XX_HPLSCDETECT | - AIC31XX_HPRSCDETECT)) + AIC31XX_HPRSCDETECT | + AIC31XX_HSPLUG | + AIC31XX_BUTTONPRESS)) dev_err(dev, "Unknown DAC interrupt flags: 0x%08x\n", value); read_overflow: @@ -1518,6 +1575,8 @@ static int aic31xx_i2c_probe(struct i2c_client *i2c, AIC31XX_GPIO1_FUNC_SHIFT); regmap_write(aic31xx->regmap, AIC31XX_INT1CTRL, + AIC31XX_HSPLUGDET | + AIC31XX_BUTTONPRESSDET | AIC31XX_SC | AIC31XX_ENGINE); diff --git a/sound/soc/codecs/tlv320aic31xx.h b/sound/soc/codecs/tlv320aic31xx.h index 2636f2c6bc79..cb024955c978 100644 --- a/sound/soc/codecs/tlv320aic31xx.h +++ b/sound/soc/codecs/tlv320aic31xx.h @@ -20,6 +20,10 @@ #define AIC31XX_MINIDSP_BIT BIT(2) #define DAC31XX_BIT BIT(3) +#define AIC31XX_JACK_MASK (SND_JACK_HEADPHONE | \ + SND_JACK_HEADSET | \ + SND_JACK_BTN_0) + enum aic31xx_type { AIC3100 = 0, AIC3110 = AIC31XX_STEREO_CLASS_D_BIT, @@ -220,6 +224,14 @@ struct aic31xx_pdata { /* AIC31XX_DACMUTE */ #define AIC31XX_DACMUTE_MASK GENMASK(3, 2) +/* AIC31XX_HSDETECT */ +#define AIC31XX_HSD_ENABLE BIT(7) +#define AIC31XX_HSD_TYPE_MASK GENMASK(6, 5) +#define AIC31XX_HSD_TYPE_SHIFT 5 +#define AIC31XX_HSD_NONE 0x00 +#define AIC31XX_HSD_HP 0x01 +#define AIC31XX_HSD_HS 0x03 + /* AIC31XX_MICBIAS */ #define AIC31XX_MICBIAS_MASK GENMASK(1, 0) #define AIC31XX_MICBIAS_SHIFT 0 diff --git a/sound/soc/codecs/tlv320aic32x4-clk.c b/sound/soc/codecs/tlv320aic32x4-clk.c new file mode 100644 index 000000000000..156c153c12ab --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4-clk.c @@ -0,0 +1,483 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Clock Tree for the Texas Instruments TLV320AIC32x4 + * + * Copyright 2019 Annaliese McDermond + * + * Author: Annaliese McDermond <nh6z@nh6z.net> + */ + +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/regmap.h> +#include <linux/device.h> + +#include "tlv320aic32x4.h" + +#define to_clk_aic32x4(_hw) container_of(_hw, struct clk_aic32x4, hw) +struct clk_aic32x4 { + struct clk_hw hw; + struct device *dev; + struct regmap *regmap; + unsigned int reg; +}; + +/* + * struct clk_aic32x4_pll_muldiv - Multiplier/divider settings + * @p: Divider + * @r: first multiplier + * @j: integer part of second multiplier + * @d: decimal part of second multiplier + */ +struct clk_aic32x4_pll_muldiv { + u8 p; + u16 r; + u8 j; + u16 d; +}; + +struct aic32x4_clkdesc { + const char *name; + const char * const *parent_names; + unsigned int num_parents; + const struct clk_ops *ops; + unsigned int reg; +}; + +static int clk_aic32x4_pll_prepare(struct clk_hw *hw) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + return regmap_update_bits(pll->regmap, AIC32X4_PLLPR, + AIC32X4_PLLEN, AIC32X4_PLLEN); +} + +static void clk_aic32x4_pll_unprepare(struct clk_hw *hw) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + regmap_update_bits(pll->regmap, AIC32X4_PLLPR, + AIC32X4_PLLEN, 0); +} + +static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + unsigned int val; + int ret; + + ret = regmap_read(pll->regmap, AIC32X4_PLLPR, &val); + if (ret < 0) + return ret; + + return !!(val & AIC32X4_PLLEN); +} + +static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, + struct clk_aic32x4_pll_muldiv *settings) +{ + /* Change to use regmap_bulk_read? */ + unsigned int val; + int ret; + + ret = regmap_read(pll->regmap, AIC32X4_PLLPR, &val); + if (ret < 0) + return ret; + settings->r = val & AIC32X4_PLL_R_MASK; + settings->p = (val & AIC32X4_PLL_P_MASK) >> AIC32X4_PLL_P_SHIFT; + + ret = regmap_read(pll->regmap, AIC32X4_PLLJ, &val); + if (ret < 0) + return ret; + settings->j = val; + + ret = regmap_read(pll->regmap, AIC32X4_PLLDMSB, &val); + if (ret < 0) + return ret; + settings->d = val << 8; + + ret = regmap_read(pll->regmap, AIC32X4_PLLDLSB, &val); + if (ret < 0) + return ret; + settings->d |= val; + + return 0; +} + +static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, + struct clk_aic32x4_pll_muldiv *settings) +{ + int ret; + /* Change to use regmap_bulk_write for some if not all? */ + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, + AIC32X4_PLL_R_MASK, settings->r); + if (ret < 0) + return ret; + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, + AIC32X4_PLL_P_MASK, + settings->p << AIC32X4_PLL_P_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_write(pll->regmap, AIC32X4_PLLJ, settings->j); + if (ret < 0) + return ret; + + ret = regmap_write(pll->regmap, AIC32X4_PLLDMSB, (settings->d >> 8)); + if (ret < 0) + return ret; + ret = regmap_write(pll->regmap, AIC32X4_PLLDLSB, (settings->d & 0xff)); + if (ret < 0) + return ret; + + return 0; +} + +static unsigned long clk_aic32x4_pll_calc_rate( + struct clk_aic32x4_pll_muldiv *settings, + unsigned long parent_rate) +{ + u64 rate; + /* + * We scale j by 10000 to account for the decimal part of P and divide + * it back out later. + */ + rate = (u64) parent_rate * settings->r * + ((settings->j * 10000) + settings->d); + + return (unsigned long) DIV_ROUND_UP_ULL(rate, settings->p * 10000); +} + +static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + unsigned long rate, unsigned long parent_rate) +{ + u64 multiplier; + + settings->p = parent_rate / AIC32X4_MAX_PLL_CLKIN + 1; + if (settings->p > 8) + return -1; + + /* + * We scale this figure by 10000 so that we can get the decimal part + * of the multiplier. This is because we can't do floating point + * math in the kernel. + */ + multiplier = (u64) rate * settings->p * 10000; + do_div(multiplier, parent_rate); + + /* + * J can't be over 64, so R can scale this. + * R can't be greater than 4. + */ + settings->r = ((u32) multiplier / 640000) + 1; + if (settings->r > 4) + return -1; + do_div(multiplier, settings->r); + + /* + * J can't be < 1. + */ + if (multiplier < 10000) + return -1; + + /* Figure out the integer part, J, and the fractional part, D. */ + settings->j = (u32) multiplier / 10000; + settings->d = (u32) multiplier % 10000; + + return 0; +} + +static unsigned long clk_aic32x4_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; + int ret; + + ret = clk_aic32x4_pll_get_muldiv(pll, &settings); + if (ret < 0) + return 0; + + return clk_aic32x4_pll_calc_rate(&settings, parent_rate); +} + +static long clk_aic32x4_pll_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *parent_rate) +{ + struct clk_aic32x4_pll_muldiv settings; + int ret; + + ret = clk_aic32x4_pll_calc_muldiv(&settings, rate, *parent_rate); + if (ret < 0) + return 0; + + return clk_aic32x4_pll_calc_rate(&settings, *parent_rate); +} + +static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; + int ret; + + ret = clk_aic32x4_pll_calc_muldiv(&settings, rate, parent_rate); + if (ret < 0) + return -EINVAL; + + return clk_aic32x4_pll_set_muldiv(pll, &settings); +} + +static int clk_aic32x4_pll_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + return regmap_update_bits(pll->regmap, + AIC32X4_CLKMUX, + AIC32X4_PLL_CLKIN_MASK, + index << AIC32X4_PLL_CLKIN_SHIFT); +} + +static u8 clk_aic32x4_pll_get_parent(struct clk_hw *hw) +{ + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + unsigned int val; + + regmap_read(pll->regmap, AIC32X4_PLLPR, &val); + + return (val & AIC32X4_PLL_CLKIN_MASK) >> AIC32X4_PLL_CLKIN_SHIFT; +} + + +static const struct clk_ops aic32x4_pll_ops = { + .prepare = clk_aic32x4_pll_prepare, + .unprepare = clk_aic32x4_pll_unprepare, + .is_prepared = clk_aic32x4_pll_is_prepared, + .recalc_rate = clk_aic32x4_pll_recalc_rate, + .round_rate = clk_aic32x4_pll_round_rate, + .set_rate = clk_aic32x4_pll_set_rate, + .set_parent = clk_aic32x4_pll_set_parent, + .get_parent = clk_aic32x4_pll_get_parent, +}; + +static int clk_aic32x4_codec_clkin_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_aic32x4 *mux = to_clk_aic32x4(hw); + + return regmap_update_bits(mux->regmap, + AIC32X4_CLKMUX, + AIC32X4_CODEC_CLKIN_MASK, index << AIC32X4_CODEC_CLKIN_SHIFT); +} + +static u8 clk_aic32x4_codec_clkin_get_parent(struct clk_hw *hw) +{ + struct clk_aic32x4 *mux = to_clk_aic32x4(hw); + unsigned int val; + + regmap_read(mux->regmap, AIC32X4_CLKMUX, &val); + + return (val & AIC32X4_CODEC_CLKIN_MASK) >> AIC32X4_CODEC_CLKIN_SHIFT; +} + +static const struct clk_ops aic32x4_codec_clkin_ops = { + .set_parent = clk_aic32x4_codec_clkin_set_parent, + .get_parent = clk_aic32x4_codec_clkin_get_parent, +}; + +static int clk_aic32x4_div_prepare(struct clk_hw *hw) +{ + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + return regmap_update_bits(div->regmap, div->reg, + AIC32X4_DIVEN, AIC32X4_DIVEN); +} + +static void clk_aic32x4_div_unprepare(struct clk_hw *hw) +{ + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + regmap_update_bits(div->regmap, div->reg, + AIC32X4_DIVEN, 0); +} + +static int clk_aic32x4_div_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + u8 divisor; + + divisor = DIV_ROUND_UP(parent_rate, rate); + if (divisor > 128) + return -EINVAL; + + return regmap_update_bits(div->regmap, div->reg, + AIC32X4_DIV_MASK, divisor); +} + +static long clk_aic32x4_div_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long divisor; + + divisor = DIV_ROUND_UP(*parent_rate, rate); + if (divisor > 128) + return -EINVAL; + + return DIV_ROUND_UP(*parent_rate, divisor); +} + +static unsigned long clk_aic32x4_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + unsigned int val; + + regmap_read(div->regmap, div->reg, &val); + + return DIV_ROUND_UP(parent_rate, val & AIC32X4_DIV_MASK); +} + +static const struct clk_ops aic32x4_div_ops = { + .prepare = clk_aic32x4_div_prepare, + .unprepare = clk_aic32x4_div_unprepare, + .set_rate = clk_aic32x4_div_set_rate, + .round_rate = clk_aic32x4_div_round_rate, + .recalc_rate = clk_aic32x4_div_recalc_rate, +}; + +static int clk_aic32x4_bdiv_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_aic32x4 *mux = to_clk_aic32x4(hw); + + return regmap_update_bits(mux->regmap, AIC32X4_IFACE3, + AIC32X4_BDIVCLK_MASK, index); +} + +static u8 clk_aic32x4_bdiv_get_parent(struct clk_hw *hw) +{ + struct clk_aic32x4 *mux = to_clk_aic32x4(hw); + unsigned int val; + + regmap_read(mux->regmap, AIC32X4_IFACE3, &val); + + return val & AIC32X4_BDIVCLK_MASK; +} + +static const struct clk_ops aic32x4_bdiv_ops = { + .prepare = clk_aic32x4_div_prepare, + .unprepare = clk_aic32x4_div_unprepare, + .set_parent = clk_aic32x4_bdiv_set_parent, + .get_parent = clk_aic32x4_bdiv_get_parent, + .set_rate = clk_aic32x4_div_set_rate, + .round_rate = clk_aic32x4_div_round_rate, + .recalc_rate = clk_aic32x4_div_recalc_rate, +}; + +static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + { + .name = "pll", + .parent_names = + (const char* []) { "mclk", "bclk", "gpio", "din" }, + .num_parents = 4, + .ops = &aic32x4_pll_ops, + .reg = 0, + }, + { + .name = "codec_clkin", + .parent_names = + (const char *[]) { "mclk", "bclk", "gpio", "pll" }, + .num_parents = 4, + .ops = &aic32x4_codec_clkin_ops, + .reg = 0, + }, + { + .name = "ndac", + .parent_names = (const char * []) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NDAC, + }, + { + .name = "mdac", + .parent_names = (const char * []) { "ndac" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MDAC, + }, + { + .name = "nadc", + .parent_names = (const char * []) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NADC, + }, + { + .name = "madc", + .parent_names = (const char * []) { "nadc" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MADC, + }, + { + .name = "bdiv", + .parent_names = + (const char *[]) { "ndac", "mdac", "nadc", "madc" }, + .num_parents = 4, + .ops = &aic32x4_bdiv_ops, + .reg = AIC32X4_BCLKN, + }, +}; + +static struct clk *aic32x4_register_clk(struct device *dev, + struct aic32x4_clkdesc *desc) +{ + struct clk_init_data init; + struct clk_aic32x4 *priv; + const char *devname = dev_name(dev); + + init.ops = desc->ops; + init.name = desc->name; + init.parent_names = desc->parent_names; + init.num_parents = desc->num_parents; + init.flags = 0; + + priv = devm_kzalloc(dev, sizeof(struct clk_aic32x4), GFP_KERNEL); + if (priv == NULL) + return (struct clk *) -ENOMEM; + + priv->dev = dev; + priv->hw.init = &init; + priv->regmap = dev_get_regmap(dev, NULL); + priv->reg = desc->reg; + + clk_hw_register_clkdev(&priv->hw, desc->name, devname); + return devm_clk_register(dev, &priv->hw); +} + +int aic32x4_register_clocks(struct device *dev, const char *mclk_name) +{ + int i; + + /* + * These lines are here to preserve the current functionality of + * the driver with regard to the DT. These should eventually be set + * by DT nodes so that the connections can be set up in configuration + * rather than code. + */ + aic32x4_clkdesc_array[0].parent_names = + (const char* []) { mclk_name, "bclk", "gpio", "din" }; + aic32x4_clkdesc_array[1].parent_names = + (const char *[]) { mclk_name, "bclk", "gpio", "pll" }; + + for (i = 0; i < ARRAY_SIZE(aic32x4_clkdesc_array); ++i) + aic32x4_register_clk(dev, &aic32x4_clkdesc_array[i]); + + return 0; +} +EXPORT_SYMBOL_GPL(aic32x4_register_clocks); diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c index 22c3a6bc0b6c..6d54cbf70a0b 100644 --- a/sound/soc/codecs/tlv320aic32x4-i2c.c +++ b/sound/soc/codecs/tlv320aic32x4-i2c.c @@ -1,21 +1,11 @@ -/* - * linux/sound/soc/codecs/tlv320aic32x4-i2c.c +/* SPDX-License-Identifier: GPL-2.0 * - * Copyright 2011 NW Digital Radio + * Copyright 2011-2019 NW Digital Radio * * Author: Annaliese McDermond <nh6z@nh6z.net> * * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/i2c.h> diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c index aa5b7ba0254b..a22e7700bfc8 100644 --- a/sound/soc/codecs/tlv320aic32x4-spi.c +++ b/sound/soc/codecs/tlv320aic32x4-spi.c @@ -1,21 +1,11 @@ -/* - * linux/sound/soc/codecs/tlv320aic32x4-spi.c +/* SPDX-License-Identifier: GPL-2.0 * - * Copyright 2011 NW Digital Radio + * Copyright 2011-2019 NW Digital Radio * * Author: Annaliese McDermond <nh6z@nh6z.net> * * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/spi/spi.h> diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 5520044929f4..83608f386aef 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -14,7 +14,7 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License @@ -33,6 +33,7 @@ #include <linux/cdev.h> #include <linux/slab.h> #include <linux/clk.h> +#include <linux/of_clk.h> #include <linux/regulator/consumer.h> #include <sound/tlv320aic32x4.h> @@ -46,29 +47,13 @@ #include "tlv320aic32x4.h" -struct aic32x4_rate_divs { - u32 mclk; - u32 rate; - u8 p_val; - u8 pll_j; - u16 pll_d; - u16 dosr; - u8 ndac; - u8 mdac; - u8 aosr; - u8 nadc; - u8 madc; - u8 blck_N; -}; - struct aic32x4_priv { struct regmap *regmap; - u32 sysclk; u32 power_cfg; u32 micpga_routing; bool swapdacs; int rstn_gpio; - struct clk *mclk; + const char *mclk_name; struct regulator *supply_ldo; struct regulator *supply_iov; @@ -257,9 +242,24 @@ static DECLARE_TLV_DB_SCALE(tlv_driver_gain, -600, 100, 0); /* -12dB min, 0.5dB steps */ static DECLARE_TLV_DB_SCALE(tlv_adc_vol, -1200, 50, 0); +static const char * const lo_cm_text[] = { + "Full Chip", "1.65V", +}; + +static SOC_ENUM_SINGLE_DECL(lo_cm_enum, AIC32X4_CMMODE, 3, lo_cm_text); + +static const char * const ptm_text[] = { + "P3", "P2", "P1", +}; + +static SOC_ENUM_SINGLE_DECL(l_ptm_enum, AIC32X4_LPLAYBACK, 2, ptm_text); +static SOC_ENUM_SINGLE_DECL(r_ptm_enum, AIC32X4_RPLAYBACK, 2, ptm_text); + static const struct snd_kcontrol_new aic32x4_snd_controls[] = { SOC_DOUBLE_R_S_TLV("PCM Playback Volume", AIC32X4_LDACVOL, AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), + SOC_ENUM("DAC Left Playback PowerTune Switch", l_ptm_enum), + SOC_ENUM("DAC Right Playback PowerTune Switch", r_ptm_enum), SOC_DOUBLE_R_S_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN, AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), @@ -270,6 +270,7 @@ static const struct snd_kcontrol_new aic32x4_snd_controls[] = { AIC32X4_HPRGAIN, 6, 0x01, 1), SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN, AIC32X4_LORGAIN, 6, 0x01, 1), + SOC_ENUM("LO Playback Common Mode Switch", lo_cm_enum), SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL, AIC32X4_RMICPGAVOL, 7, 0x01, 1), @@ -305,38 +306,6 @@ static const struct snd_kcontrol_new aic32x4_snd_controls[] = { 0, 0x0F, 0), }; -static const struct aic32x4_rate_divs aic32x4_divs[] = { - /* 8k rate */ - {12000000, 8000, 1, 7, 6800, 768, 5, 3, 128, 5, 18, 24}, - {24000000, 8000, 2, 7, 6800, 768, 15, 1, 64, 45, 4, 24}, - {25000000, 8000, 2, 7, 3728, 768, 15, 1, 64, 45, 4, 24}, - /* 11.025k rate */ - {12000000, 11025, 1, 7, 5264, 512, 8, 2, 128, 8, 8, 16}, - {24000000, 11025, 2, 7, 5264, 512, 16, 1, 64, 32, 4, 16}, - /* 16k rate */ - {12000000, 16000, 1, 7, 6800, 384, 5, 3, 128, 5, 9, 12}, - {24000000, 16000, 2, 7, 6800, 384, 15, 1, 64, 18, 5, 12}, - {25000000, 16000, 2, 7, 3728, 384, 15, 1, 64, 18, 5, 12}, - /* 22.05k rate */ - {12000000, 22050, 1, 7, 5264, 256, 4, 4, 128, 4, 8, 8}, - {24000000, 22050, 2, 7, 5264, 256, 16, 1, 64, 16, 4, 8}, - {25000000, 22050, 2, 7, 2253, 256, 16, 1, 64, 16, 4, 8}, - /* 32k rate */ - {12000000, 32000, 1, 7, 1680, 192, 2, 7, 64, 2, 21, 6}, - {24000000, 32000, 2, 7, 1680, 192, 7, 2, 64, 7, 6, 6}, - /* 44.1k rate */ - {12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4}, - {24000000, 44100, 2, 7, 5264, 128, 8, 2, 64, 8, 4, 4}, - {25000000, 44100, 2, 7, 2253, 128, 8, 2, 64, 8, 4, 4}, - /* 48k rate */ - {12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4}, - {24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4}, - {25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4}, - - /* 96k rate */ - {25000000, 96000, 2, 7, 8643, 64, 4, 4, 64, 4, 4, 1}, -}; - static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_HPLROUTE, 3, 1, 0), SOC_DAPM_SINGLE("IN1_L Switch", AIC32X4_HPLROUTE, 2, 1, 0), @@ -391,7 +360,7 @@ static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), }; -/* Right mixer pins */ +/* Right mixer pins */ static SOC_ENUM_SINGLE_DECL(in1r_rpga_p_enum, AIC32X4_RMICPGAPIN, 6, resistor_text); static SOC_ENUM_SINGLE_DECL(in2r_rpga_p_enum, AIC32X4_RMICPGAPIN, 4, resistor_text); static SOC_ENUM_SINGLE_DECL(in3r_rpga_p_enum, AIC32X4_RMICPGAPIN, 2, resistor_text); @@ -595,7 +564,7 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { static const struct regmap_range_cfg aic32x4_regmap_pages[] = { { .selector_reg = 0, - .selector_mask = 0xff, + .selector_mask = 0xff, .window_start = 0, .window_len = 128, .range_min = 0, @@ -610,35 +579,17 @@ const struct regmap_config aic32x4_regmap_config = { }; EXPORT_SYMBOL(aic32x4_regmap_config); -static inline int aic32x4_get_divs(int mclk, int rate) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(aic32x4_divs); i++) { - if ((aic32x4_divs[i].rate == rate) - && (aic32x4_divs[i].mclk == mclk)) { - return i; - } - } - printk(KERN_ERR "aic32x4: master clock and sample rate is not supported\n"); - return -EINVAL; -} - static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_component *component = codec_dai->component; - struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + struct clk *mclk; + struct clk *pll; - switch (freq) { - case 12000000: - case 24000000: - case 25000000: - aic32x4->sysclk = freq; - return 0; - } - printk(KERN_ERR "aic32x4: invalid frequency to set DAI system clock\n"); - return -EINVAL; + pll = devm_clk_get(component->dev, "pll"); + mclk = clk_get_parent(pll); + + return clk_set_rate(mclk, freq); } static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) @@ -688,103 +639,175 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) } snd_soc_component_update_bits(component, AIC32X4_IFACE1, - AIC32X4_IFACE1_DATATYPE_MASK | - AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); + AIC32X4_IFACE1_DATATYPE_MASK | + AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); snd_soc_component_update_bits(component, AIC32X4_IFACE2, - AIC32X4_DATA_OFFSET_MASK, iface_reg_2); + AIC32X4_DATA_OFFSET_MASK, iface_reg_2); snd_soc_component_update_bits(component, AIC32X4_IFACE3, - AIC32X4_BCLKINV_MASK, iface_reg_3); + AIC32X4_BCLKINV_MASK, iface_reg_3); return 0; } -static int aic32x4_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) +static int aic32x4_set_aosr(struct snd_soc_component *component, u8 aosr) { - struct snd_soc_component *component = dai->component; - struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); - u8 iface1_reg = 0; - u8 dacsetup_reg = 0; - int i; - - i = aic32x4_get_divs(aic32x4->sysclk, params_rate(params)); - if (i < 0) { - printk(KERN_ERR "aic32x4: sampling rate not supported\n"); - return i; - } + return snd_soc_component_write(component, AIC32X4_AOSR, aosr); +} - /* MCLK as PLL_CLKIN */ - snd_soc_component_update_bits(component, AIC32X4_CLKMUX, AIC32X4_PLL_CLKIN_MASK, - AIC32X4_PLL_CLKIN_MCLK << AIC32X4_PLL_CLKIN_SHIFT); - /* PLL as CODEC_CLKIN */ - snd_soc_component_update_bits(component, AIC32X4_CLKMUX, AIC32X4_CODEC_CLKIN_MASK, - AIC32X4_CODEC_CLKIN_PLL << AIC32X4_CODEC_CLKIN_SHIFT); - /* DAC_MOD_CLK as BDIV_CLKIN */ - snd_soc_component_update_bits(component, AIC32X4_IFACE3, AIC32X4_BDIVCLK_MASK, - AIC32X4_DACMOD2BCLK << AIC32X4_BDIVCLK_SHIFT); +static int aic32x4_set_dosr(struct snd_soc_component *component, u16 dosr) +{ + snd_soc_component_write(component, AIC32X4_DOSRMSB, dosr >> 8); + snd_soc_component_write(component, AIC32X4_DOSRLSB, + (dosr & 0xff)); - /* We will fix R value to 1 and will make P & J=K.D as variable */ - snd_soc_component_update_bits(component, AIC32X4_PLLPR, AIC32X4_PLL_R_MASK, 0x01); + return 0; +} - /* PLL P value */ - snd_soc_component_update_bits(component, AIC32X4_PLLPR, AIC32X4_PLL_P_MASK, - aic32x4_divs[i].p_val << AIC32X4_PLL_P_SHIFT); +static int aic32x4_set_processing_blocks(struct snd_soc_component *component, + u8 r_block, u8 p_block) +{ + if (r_block > 18 || p_block > 25) + return -EINVAL; - /* PLL J value */ - snd_soc_component_write(component, AIC32X4_PLLJ, aic32x4_divs[i].pll_j); + snd_soc_component_write(component, AIC32X4_ADCSPB, r_block); + snd_soc_component_write(component, AIC32X4_DACSPB, p_block); - /* PLL D value */ - snd_soc_component_write(component, AIC32X4_PLLDMSB, (aic32x4_divs[i].pll_d >> 8)); - snd_soc_component_write(component, AIC32X4_PLLDLSB, (aic32x4_divs[i].pll_d & 0xff)); + return 0; +} - /* NDAC divider value */ - snd_soc_component_update_bits(component, AIC32X4_NDAC, - AIC32X4_NDAC_MASK, aic32x4_divs[i].ndac); +static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned int sample_rate) +{ + u8 aosr; + u16 dosr; + u8 adc_resource_class, dac_resource_class; + u8 madc, nadc, mdac, ndac, max_nadc, min_mdac, max_ndac; + u8 dosr_increment; + u16 max_dosr, min_dosr; + unsigned long adc_clock_rate, dac_clock_rate; + int ret; - /* MDAC divider value */ - snd_soc_component_update_bits(component, AIC32X4_MDAC, - AIC32X4_MDAC_MASK, aic32x4_divs[i].mdac); + struct clk_bulk_data clocks[] = { + { .id = "pll" }, + { .id = "nadc" }, + { .id = "madc" }, + { .id = "ndac" }, + { .id = "mdac" }, + { .id = "bdiv" }, + }; + ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); + if (ret) + return ret; - /* DOSR MSB & LSB values */ - snd_soc_component_write(component, AIC32X4_DOSRMSB, aic32x4_divs[i].dosr >> 8); - snd_soc_component_write(component, AIC32X4_DOSRLSB, (aic32x4_divs[i].dosr & 0xff)); + if (sample_rate <= 48000) { + aosr = 128; + adc_resource_class = 6; + dac_resource_class = 8; + dosr_increment = 8; + aic32x4_set_processing_blocks(component, 1, 1); + } else if (sample_rate <= 96000) { + aosr = 64; + adc_resource_class = 6; + dac_resource_class = 8; + dosr_increment = 4; + aic32x4_set_processing_blocks(component, 1, 9); + } else if (sample_rate == 192000) { + aosr = 32; + adc_resource_class = 3; + dac_resource_class = 4; + dosr_increment = 2; + aic32x4_set_processing_blocks(component, 13, 19); + } else { + dev_err(component->dev, "Sampling rate not supported\n"); + return -EINVAL; + } - /* NADC divider value */ - snd_soc_component_update_bits(component, AIC32X4_NADC, - AIC32X4_NADC_MASK, aic32x4_divs[i].nadc); + madc = DIV_ROUND_UP((32 * adc_resource_class), aosr); + max_dosr = (AIC32X4_MAX_DOSR_FREQ / sample_rate / dosr_increment) * + dosr_increment; + min_dosr = (AIC32X4_MIN_DOSR_FREQ / sample_rate / dosr_increment) * + dosr_increment; + max_nadc = AIC32X4_MAX_CODEC_CLKIN_FREQ / (madc * aosr * sample_rate); + + for (nadc = max_nadc; nadc > 0; --nadc) { + adc_clock_rate = nadc * madc * aosr * sample_rate; + for (dosr = max_dosr; dosr >= min_dosr; + dosr -= dosr_increment) { + min_mdac = DIV_ROUND_UP((32 * dac_resource_class), dosr); + max_ndac = AIC32X4_MAX_CODEC_CLKIN_FREQ / + (min_mdac * dosr * sample_rate); + for (mdac = min_mdac; mdac <= 128; ++mdac) { + for (ndac = max_ndac; ndac > 0; --ndac) { + dac_clock_rate = ndac * mdac * dosr * + sample_rate; + if (dac_clock_rate == adc_clock_rate) { + if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) + continue; + + clk_set_rate(clocks[0].clk, + dac_clock_rate); + + clk_set_rate(clocks[1].clk, + sample_rate * aosr * + madc); + clk_set_rate(clocks[2].clk, + sample_rate * aosr); + aic32x4_set_aosr(component, + aosr); + + clk_set_rate(clocks[3].clk, + sample_rate * dosr * + mdac); + clk_set_rate(clocks[4].clk, + sample_rate * dosr); + aic32x4_set_dosr(component, + dosr); + + clk_set_rate(clocks[5].clk, + sample_rate * 32); + return 0; + } + } + } + } + } - /* MADC divider value */ - snd_soc_component_update_bits(component, AIC32X4_MADC, - AIC32X4_MADC_MASK, aic32x4_divs[i].madc); + dev_err(component->dev, + "Could not set clocks to support sample rate.\n"); + return -EINVAL; +} - /* AOSR value */ - snd_soc_component_write(component, AIC32X4_AOSR, aic32x4_divs[i].aosr); +static int aic32x4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + u8 iface1_reg = 0; + u8 dacsetup_reg = 0; - /* BCLK N divider */ - snd_soc_component_update_bits(component, AIC32X4_BCLKN, - AIC32X4_BCLK_MASK, aic32x4_divs[i].blck_N); + aic32x4_setup_clocks(component, params_rate(params)); switch (params_width(params)) { case 16: iface1_reg |= (AIC32X4_WORD_LEN_16BITS << - AIC32X4_IFACE1_DATALEN_SHIFT); + AIC32X4_IFACE1_DATALEN_SHIFT); break; case 20: iface1_reg |= (AIC32X4_WORD_LEN_20BITS << - AIC32X4_IFACE1_DATALEN_SHIFT); + AIC32X4_IFACE1_DATALEN_SHIFT); break; case 24: iface1_reg |= (AIC32X4_WORD_LEN_24BITS << - AIC32X4_IFACE1_DATALEN_SHIFT); + AIC32X4_IFACE1_DATALEN_SHIFT); break; case 32: iface1_reg |= (AIC32X4_WORD_LEN_32BITS << - AIC32X4_IFACE1_DATALEN_SHIFT); + AIC32X4_IFACE1_DATALEN_SHIFT); break; } snd_soc_component_update_bits(component, AIC32X4_IFACE1, - AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); + AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); if (params_channels(params) == 1) { dacsetup_reg = AIC32X4_RDAC2LCHN | AIC32X4_LDAC2LCHN; @@ -795,7 +818,7 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, dacsetup_reg = AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN; } snd_soc_component_update_bits(component, AIC32X4_DACSETUP, - AIC32X4_DAC_CHAN_MASK, dacsetup_reg); + AIC32X4_DAC_CHAN_MASK, dacsetup_reg); return 0; } @@ -805,7 +828,7 @@ static int aic32x4_mute(struct snd_soc_dai *dai, int mute) struct snd_soc_component *component = dai->component; snd_soc_component_update_bits(component, AIC32X4_DACMUTE, - AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); + AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); return 0; } @@ -813,41 +836,25 @@ static int aic32x4_mute(struct snd_soc_dai *dai, int mute) static int aic32x4_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { - struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); int ret; + struct clk_bulk_data clocks[] = { + { .id = "madc" }, + { .id = "mdac" }, + { .id = "bdiv" }, + }; + + ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); + if (ret) + return ret; + switch (level) { case SND_SOC_BIAS_ON: - /* Switch on master clock */ - ret = clk_prepare_enable(aic32x4->mclk); + ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks); if (ret) { - dev_err(component->dev, "Failed to enable master clock\n"); + dev_err(component->dev, "Failed to enable clocks\n"); return ret; } - - /* Switch on PLL */ - snd_soc_component_update_bits(component, AIC32X4_PLLPR, - AIC32X4_PLLEN, AIC32X4_PLLEN); - - /* Switch on NDAC Divider */ - snd_soc_component_update_bits(component, AIC32X4_NDAC, - AIC32X4_NDACEN, AIC32X4_NDACEN); - - /* Switch on MDAC Divider */ - snd_soc_component_update_bits(component, AIC32X4_MDAC, - AIC32X4_MDACEN, AIC32X4_MDACEN); - - /* Switch on NADC Divider */ - snd_soc_component_update_bits(component, AIC32X4_NADC, - AIC32X4_NADCEN, AIC32X4_NADCEN); - - /* Switch on MADC Divider */ - snd_soc_component_update_bits(component, AIC32X4_MADC, - AIC32X4_MADCEN, AIC32X4_MADCEN); - - /* Switch on BCLK_N Divider */ - snd_soc_component_update_bits(component, AIC32X4_BCLKN, - AIC32X4_BCLKEN, AIC32X4_BCLKEN); break; case SND_SOC_BIAS_PREPARE: break; @@ -856,32 +863,7 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) break; - /* Switch off BCLK_N Divider */ - snd_soc_component_update_bits(component, AIC32X4_BCLKN, - AIC32X4_BCLKEN, 0); - - /* Switch off MADC Divider */ - snd_soc_component_update_bits(component, AIC32X4_MADC, - AIC32X4_MADCEN, 0); - - /* Switch off NADC Divider */ - snd_soc_component_update_bits(component, AIC32X4_NADC, - AIC32X4_NADCEN, 0); - - /* Switch off MDAC Divider */ - snd_soc_component_update_bits(component, AIC32X4_MDAC, - AIC32X4_MDACEN, 0); - - /* Switch off NDAC Divider */ - snd_soc_component_update_bits(component, AIC32X4_NDAC, - AIC32X4_NDACEN, 0); - - /* Switch off PLL */ - snd_soc_component_update_bits(component, AIC32X4_PLLPR, - AIC32X4_PLLEN, 0); - - /* Switch off master clock */ - clk_disable_unprepare(aic32x4->mclk); + clk_bulk_disable_unprepare(ARRAY_SIZE(clocks), clocks); break; case SND_SOC_BIAS_OFF: break; @@ -889,8 +871,8 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, return 0; } -#define AIC32X4_RATES SNDRV_PCM_RATE_8000_96000 -#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ +#define AIC32X4_RATES SNDRV_PCM_RATE_8000_192000 +#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) static const struct snd_soc_dai_ops aic32x4_ops = { @@ -903,17 +885,17 @@ static const struct snd_soc_dai_ops aic32x4_ops = { static struct snd_soc_dai_driver aic32x4_dai = { .name = "tlv320aic32x4-hifi", .playback = { - .stream_name = "Playback", - .channels_min = 1, - .channels_max = 2, - .rates = AIC32X4_RATES, - .formats = AIC32X4_FORMATS,}, + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = AIC32X4_RATES, - .formats = AIC32X4_FORMATS,}, + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, .ops = &aic32x4_ops, .symmetric_rates = 1, }; @@ -926,7 +908,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP1 */ if (aic32x4->setup->gpio_func[0] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_DINCTL, - aic32x4->setup->gpio_func[0]); + aic32x4->setup->gpio_func[0]); snd_soc_add_component_controls(component, aic32x4_mfp1, ARRAY_SIZE(aic32x4_mfp1)); } @@ -934,7 +916,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP2 */ if (aic32x4->setup->gpio_func[1] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_DOUTCTL, - aic32x4->setup->gpio_func[1]); + aic32x4->setup->gpio_func[1]); snd_soc_add_component_controls(component, aic32x4_mfp2, ARRAY_SIZE(aic32x4_mfp2)); } @@ -942,7 +924,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP3 */ if (aic32x4->setup->gpio_func[2] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_SCLKCTL, - aic32x4->setup->gpio_func[2]); + aic32x4->setup->gpio_func[2]); snd_soc_add_component_controls(component, aic32x4_mfp3, ARRAY_SIZE(aic32x4_mfp3)); } @@ -950,7 +932,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP4 */ if (aic32x4->setup->gpio_func[3] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_MISOCTL, - aic32x4->setup->gpio_func[3]); + aic32x4->setup->gpio_func[3]); snd_soc_add_component_controls(component, aic32x4_mfp4, ARRAY_SIZE(aic32x4_mfp4)); } @@ -958,7 +940,7 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) /* MFP5 */ if (aic32x4->setup->gpio_func[4] != AIC32X4_MFPX_DEFAULT_VALUE) { snd_soc_component_write(component, AIC32X4_GPIOCTL, - aic32x4->setup->gpio_func[4]); + aic32x4->setup->gpio_func[4]); snd_soc_add_component_controls(component, aic32x4_mfp5, ARRAY_SIZE(aic32x4_mfp5)); } @@ -968,6 +950,18 @@ static int aic32x4_component_probe(struct snd_soc_component *component) { struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); u32 tmp_reg; + int ret; + + struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, + { .id = "mdac" }, + }; + + ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); + if (ret) + return ret; if (gpio_is_valid(aic32x4->rstn_gpio)) { ndelay(10); @@ -980,10 +974,13 @@ static int aic32x4_component_probe(struct snd_soc_component *component) if (aic32x4->setup) aic32x4_setup_gpios(component); + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); + /* Power platform configuration */ if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { - snd_soc_component_write(component, AIC32X4_MICBIAS, AIC32X4_MICBIAS_LDOIN | - AIC32X4_MICBIAS_2075V); + snd_soc_component_write(component, AIC32X4_MICBIAS, + AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); } if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) snd_soc_component_write(component, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); @@ -1046,12 +1043,18 @@ static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, struct device_node *np) { struct aic32x4_setup_data *aic32x4_setup; + int ret; aic32x4_setup = devm_kzalloc(aic32x4->dev, sizeof(*aic32x4_setup), GFP_KERNEL); if (!aic32x4_setup) return -ENOMEM; + ret = of_property_match_string(np, "clock-names", "mclk"); + if (ret < 0) + return -EINVAL; + aic32x4->mclk_name = of_clk_get_parent_name(np, ret); + aic32x4->swapdacs = false; aic32x4->micpga_routing = 0; aic32x4->rstn_gpio = of_get_named_gpio(np, "reset-gpios", 0); @@ -1173,7 +1176,7 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap) return PTR_ERR(regmap); aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), - GFP_KERNEL); + GFP_KERNEL); if (aic32x4 == NULL) return -ENOMEM; @@ -1185,6 +1188,7 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap) aic32x4->swapdacs = pdata->swapdacs; aic32x4->micpga_routing = pdata->micpga_routing; aic32x4->rstn_gpio = pdata->rstn_gpio; + aic32x4->mclk_name = "mclk"; } else if (np) { ret = aic32x4_parse_dt(aic32x4, np); if (ret) { @@ -1196,13 +1200,12 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap) aic32x4->swapdacs = false; aic32x4->micpga_routing = 0; aic32x4->rstn_gpio = -1; + aic32x4->mclk_name = "mclk"; } - aic32x4->mclk = devm_clk_get(dev, "mclk"); - if (IS_ERR(aic32x4->mclk)) { - dev_err(dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n"); - return PTR_ERR(aic32x4->mclk); - } + ret = aic32x4_register_clocks(dev, aic32x4->mclk_name); + if (ret) + return ret; if (gpio_is_valid(aic32x4->rstn_gpio)) { ret = devm_gpio_request_one(dev, aic32x4->rstn_gpio, diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h index c2d74025bf4b..40734211bc0e 100644 --- a/sound/soc/codecs/tlv320aic32x4.h +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -16,6 +16,7 @@ struct regmap_config; extern const struct regmap_config aic32x4_regmap_config; int aic32x4_probe(struct device *dev, struct regmap *regmap); int aic32x4_remove(struct device *dev); +int aic32x4_register_clocks(struct device *dev, const char *mclk_name); /* tlv320aic32x4 register space (in decimal to match datasheet) */ @@ -77,6 +78,8 @@ int aic32x4_remove(struct device *dev); #define AIC32X4_PWRCFG AIC32X4_REG(1, 1) #define AIC32X4_LDOCTL AIC32X4_REG(1, 2) +#define AIC32X4_LPLAYBACK AIC32X4_REG(1, 3) +#define AIC32X4_RPLAYBACK AIC32X4_REG(1, 4) #define AIC32X4_OUTPWRCTL AIC32X4_REG(1, 9) #define AIC32X4_CMMODE AIC32X4_REG(1, 10) #define AIC32X4_HPLROUTE AIC32X4_REG(1, 12) @@ -205,4 +208,14 @@ int aic32x4_remove(struct device *dev); #define AIC32X4_RMICPGANIN_IN1L_10K 0x10 #define AIC32X4_RMICPGANIN_CM1R_10K 0x40 +/* Common mask and enable for all of the dividers */ +#define AIC32X4_DIVEN BIT(7) +#define AIC32X4_DIV_MASK GENMASK(6, 0) + +/* Clock Limits */ +#define AIC32X4_MAX_DOSR_FREQ 6200000 +#define AIC32X4_MIN_DOSR_FREQ 2800000 +#define AIC32X4_MAX_CODEC_CLKIN_FREQ 110000000 +#define AIC32X4_MAX_PLL_CLKIN 20000000 + #endif /* _TLV320AIC32X4_H */ diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 981f88a5f615..a04a7cedd99d 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -5188,6 +5188,7 @@ static int wcd9335_slim_status(struct slim_device *sdev, wcd->slim = sdev; wcd->slim_ifc_dev = of_slim_get_device(sdev->ctrl, ifc_dev_np); + of_node_put(ifc_dev_np); if (!wcd->slim_ifc_dev) { dev_err(dev, "Unable to get SLIM Interface device\n"); return -EINVAL; diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index 4466e195b66d..b32e8313954d 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -646,6 +646,8 @@ static int wm5102_adsp_power_ev(struct snd_soc_dapm_widget *w, return ret; } } + + wm_adsp2_set_dspclk(w, v); break; case SND_SOC_DAPM_POST_PMD: @@ -659,7 +661,7 @@ static int wm5102_adsp_power_ev(struct snd_soc_dapm_widget *w, break; } - return wm_adsp2_early_event(w, kcontrol, event, v); + return wm_adsp_early_event(w, kcontrol, event); } static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index b25877fa529d..1f500cc8d96a 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -211,7 +211,9 @@ static int wm5110_adsp_power_ev(struct snd_soc_dapm_widget *w, v = (v & ARIZONA_SYSCLK_FREQ_MASK) >> ARIZONA_SYSCLK_FREQ_SHIFT; - return wm_adsp2_early_event(w, kcontrol, event, v); + wm_adsp2_set_dspclk(w, v); + + return wm_adsp_early_event(w, kcontrol, event); } static const struct reg_sequence wm5110_no_dre_left_enable[] = { diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index b0b48eb9c7c9..b26e6b825a90 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -227,6 +227,89 @@ */ #define WM_ADSP_FW_EVENT_SHUTDOWN 0x000001 +/* + * HALO system info + */ +#define HALO_AHBM_WINDOW_DEBUG_0 0x02040 +#define HALO_AHBM_WINDOW_DEBUG_1 0x02044 + +/* + * HALO core + */ +#define HALO_SCRATCH1 0x005c0 +#define HALO_SCRATCH2 0x005c8 +#define HALO_SCRATCH3 0x005d0 +#define HALO_SCRATCH4 0x005d8 +#define HALO_CCM_CORE_CONTROL 0x41000 +#define HALO_CORE_SOFT_RESET 0x00010 +#define HALO_WDT_CONTROL 0x47000 + +/* + * HALO MPU banks + */ +#define HALO_MPU_XMEM_ACCESS_0 0x43000 +#define HALO_MPU_YMEM_ACCESS_0 0x43004 +#define HALO_MPU_WINDOW_ACCESS_0 0x43008 +#define HALO_MPU_XREG_ACCESS_0 0x4300C +#define HALO_MPU_YREG_ACCESS_0 0x43014 +#define HALO_MPU_XMEM_ACCESS_1 0x43018 +#define HALO_MPU_YMEM_ACCESS_1 0x4301C +#define HALO_MPU_WINDOW_ACCESS_1 0x43020 +#define HALO_MPU_XREG_ACCESS_1 0x43024 +#define HALO_MPU_YREG_ACCESS_1 0x4302C +#define HALO_MPU_XMEM_ACCESS_2 0x43030 +#define HALO_MPU_YMEM_ACCESS_2 0x43034 +#define HALO_MPU_WINDOW_ACCESS_2 0x43038 +#define HALO_MPU_XREG_ACCESS_2 0x4303C +#define HALO_MPU_YREG_ACCESS_2 0x43044 +#define HALO_MPU_XMEM_ACCESS_3 0x43048 +#define HALO_MPU_YMEM_ACCESS_3 0x4304C +#define HALO_MPU_WINDOW_ACCESS_3 0x43050 +#define HALO_MPU_XREG_ACCESS_3 0x43054 +#define HALO_MPU_YREG_ACCESS_3 0x4305C +#define HALO_MPU_XM_VIO_ADDR 0x43100 +#define HALO_MPU_XM_VIO_STATUS 0x43104 +#define HALO_MPU_YM_VIO_ADDR 0x43108 +#define HALO_MPU_YM_VIO_STATUS 0x4310C +#define HALO_MPU_PM_VIO_ADDR 0x43110 +#define HALO_MPU_PM_VIO_STATUS 0x43114 +#define HALO_MPU_LOCK_CONFIG 0x43140 + +/* + * HALO_AHBM_WINDOW_DEBUG_1 + */ +#define HALO_AHBM_CORE_ERR_ADDR_MASK 0x0fffff00 +#define HALO_AHBM_CORE_ERR_ADDR_SHIFT 8 +#define HALO_AHBM_FLAGS_ERR_MASK 0x000000ff + +/* + * HALO_CCM_CORE_CONTROL + */ +#define HALO_CORE_EN 0x00000001 + +/* + * HALO_CORE_SOFT_RESET + */ +#define HALO_CORE_SOFT_RESET_MASK 0x00000001 + +/* + * HALO_WDT_CONTROL + */ +#define HALO_WDT_EN_MASK 0x00000001 + +/* + * HALO_MPU_?M_VIO_STATUS + */ +#define HALO_MPU_VIO_STS_MASK 0x007e0000 +#define HALO_MPU_VIO_STS_SHIFT 17 +#define HALO_MPU_VIO_ERR_WR_MASK 0x00008000 +#define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff +#define HALO_MPU_VIO_ERR_SRC_SHIFT 0 + +static struct wm_adsp_ops wm_adsp1_ops; +static struct wm_adsp_ops wm_adsp2_ops[]; +static struct wm_adsp_ops wm_halo_ops; + struct wm_adsp_buf { struct list_head list; void *buf; @@ -306,6 +389,12 @@ struct wm_adsp_system_config_xm_hdr { __be32 build_job_number; }; +struct wm_halo_system_config_xm_hdr { + __be32 halo_heartbeat; + __be32 build_job_name[3]; + __be32 build_job_number; +}; + struct wm_adsp_alg_xm_struct { __be32 magic; __be32 smoothing; @@ -532,12 +621,18 @@ static const char *wm_adsp_mem_region_name(unsigned int type) switch (type) { case WMFW_ADSP1_PM: return "PM"; + case WMFW_HALO_PM_PACKED: + return "PM_PACKED"; case WMFW_ADSP1_DM: return "DM"; case WMFW_ADSP2_XM: return "XM"; + case WMFW_HALO_XM_PACKED: + return "XM_PACKED"; case WMFW_ADSP2_YM: return "YM"; + case WMFW_HALO_YM_PACKED: + return "YM_PACKED"; case WMFW_ADSP1_ZM: return "ZM"; default: @@ -769,17 +864,12 @@ static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp, static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *mem, unsigned int offset) { - if (WARN_ON(!mem)) - return offset; switch (mem->type) { case WMFW_ADSP1_PM: return mem->base + (offset * 3); case WMFW_ADSP1_DM: - return mem->base + (offset * 2); case WMFW_ADSP2_XM: - return mem->base + (offset * 2); case WMFW_ADSP2_YM: - return mem->base + (offset * 2); case WMFW_ADSP1_ZM: return mem->base + (offset * 2); default: @@ -788,49 +878,72 @@ static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *mem, } } -static void wm_adsp2_show_fw_status(struct wm_adsp *dsp) +static unsigned int wm_halo_region_to_reg(struct wm_adsp_region const *mem, + unsigned int offset) +{ + switch (mem->type) { + case WMFW_ADSP2_XM: + case WMFW_ADSP2_YM: + return mem->base + (offset * 4); + case WMFW_HALO_XM_PACKED: + case WMFW_HALO_YM_PACKED: + return (mem->base + (offset * 3)) & ~0x3; + case WMFW_HALO_PM_PACKED: + return mem->base + (offset * 5); + default: + WARN(1, "Unknown memory region type"); + return offset; + } +} + +static void wm_adsp_read_fw_status(struct wm_adsp *dsp, + int noffs, unsigned int *offs) { - unsigned int scratch[4]; - unsigned int addr = dsp->base + ADSP2_SCRATCH0; unsigned int i; int ret; - for (i = 0; i < ARRAY_SIZE(scratch); ++i) { - ret = regmap_read(dsp->regmap, addr + i, &scratch[i]); + for (i = 0; i < noffs; ++i) { + ret = regmap_read(dsp->regmap, dsp->base + offs[i], &offs[i]); if (ret) { adsp_err(dsp, "Failed to read SCRATCH%u: %d\n", i, ret); return; } } +} + +static void wm_adsp2_show_fw_status(struct wm_adsp *dsp) +{ + unsigned int offs[] = { + ADSP2_SCRATCH0, ADSP2_SCRATCH1, ADSP2_SCRATCH2, ADSP2_SCRATCH3, + }; + + wm_adsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", - scratch[0], scratch[1], scratch[2], scratch[3]); + offs[0], offs[1], offs[2], offs[3]); } static void wm_adsp2v2_show_fw_status(struct wm_adsp *dsp) { - unsigned int scratch[2]; - int ret; + unsigned int offs[] = { ADSP2V2_SCRATCH0_1, ADSP2V2_SCRATCH2_3 }; - ret = regmap_read(dsp->regmap, dsp->base + ADSP2V2_SCRATCH0_1, - &scratch[0]); - if (ret) { - adsp_err(dsp, "Failed to read SCRATCH0_1: %d\n", ret); - return; - } + wm_adsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); - ret = regmap_read(dsp->regmap, dsp->base + ADSP2V2_SCRATCH2_3, - &scratch[1]); - if (ret) { - adsp_err(dsp, "Failed to read SCRATCH2_3: %d\n", ret); - return; - } + adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", + offs[0] & 0xFFFF, offs[0] >> 16, + offs[1] & 0xFFFF, offs[1] >> 16); +} + +static void wm_halo_show_fw_status(struct wm_adsp *dsp) +{ + unsigned int offs[] = { + HALO_SCRATCH1, HALO_SCRATCH2, HALO_SCRATCH3, HALO_SCRATCH4, + }; + + wm_adsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", - scratch[0] & 0xFFFF, - scratch[0] >> 16, - scratch[1] & 0xFFFF, - scratch[1] >> 16); + offs[0], offs[1], offs[2], offs[3]); } static inline struct wm_coeff_ctl *bytes_ext_to_ctl(struct soc_bytes_ext *ext) @@ -851,7 +964,7 @@ static int wm_coeff_base_reg(struct wm_coeff_ctl *ctl, unsigned int *reg) return -EINVAL; } - *reg = wm_adsp_region_to_reg(mem, ctl->alg_region.base + ctl->offset); + *reg = dsp->ops->region_to_reg(mem, ctl->alg_region.base + ctl->offset); return 0; } @@ -1339,28 +1452,33 @@ static int wm_adsp_create_control(struct wm_adsp *dsp, case 1: snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %x", dsp->name, region_name, alg_region->alg); + subname = NULL; /* don't append subname */ break; - default: + case 2: ret = snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s%c %.12s %x", dsp->name, *region_name, wm_adsp_fw_text[dsp->fw], alg_region->alg); + break; + default: + ret = snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "%s %.12s %x", dsp->name, + wm_adsp_fw_text[dsp->fw], alg_region->alg); + break; + } - /* Truncate the subname from the start if it is too long */ - if (subname) { - int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; - int skip = 0; + if (subname) { + int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; + int skip = 0; - if (dsp->component->name_prefix) - avail -= strlen(dsp->component->name_prefix) + 1; + if (dsp->component->name_prefix) + avail -= strlen(dsp->component->name_prefix) + 1; - if (subname_len > avail) - skip = subname_len - avail; + /* Truncate the subname from the start if it is too long */ + if (subname_len > avail) + skip = subname_len - avail; - snprintf(name + ret, - SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, " %.*s", - subname_len - skip, subname + skip); - } - break; + snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, + " %.*s", subname_len - skip, subname + skip); } list_for_each_entry(ctl, &dsp->ctl_list, list) { @@ -1647,6 +1765,62 @@ static int wm_adsp_parse_coeff(struct wm_adsp *dsp, return 0; } +static unsigned int wm_adsp1_parse_sizes(struct wm_adsp *dsp, + const char * const file, + unsigned int pos, + const struct firmware *firmware) +{ + const struct wmfw_adsp1_sizes *adsp1_sizes; + + adsp1_sizes = (void *)&firmware->data[pos]; + + adsp_dbg(dsp, "%s: %d DM, %d PM, %d ZM\n", file, + le32_to_cpu(adsp1_sizes->dm), le32_to_cpu(adsp1_sizes->pm), + le32_to_cpu(adsp1_sizes->zm)); + + return pos + sizeof(*adsp1_sizes); +} + +static unsigned int wm_adsp2_parse_sizes(struct wm_adsp *dsp, + const char * const file, + unsigned int pos, + const struct firmware *firmware) +{ + const struct wmfw_adsp2_sizes *adsp2_sizes; + + adsp2_sizes = (void *)&firmware->data[pos]; + + adsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n", file, + le32_to_cpu(adsp2_sizes->xm), le32_to_cpu(adsp2_sizes->ym), + le32_to_cpu(adsp2_sizes->pm), le32_to_cpu(adsp2_sizes->zm)); + + return pos + sizeof(*adsp2_sizes); +} + +static bool wm_adsp_validate_version(struct wm_adsp *dsp, unsigned int version) +{ + switch (version) { + case 0: + adsp_warn(dsp, "Deprecated file format %d\n", version); + return true; + case 1: + case 2: + return true; + default: + return false; + } +} + +static bool wm_halo_validate_version(struct wm_adsp *dsp, unsigned int version) +{ + switch (version) { + case 3: + return true; + default: + return false; + } +} + static int wm_adsp_load(struct wm_adsp *dsp) { LIST_HEAD(buf_list); @@ -1655,7 +1829,6 @@ static int wm_adsp_load(struct wm_adsp *dsp) unsigned int pos = 0; const struct wmfw_header *header; const struct wmfw_adsp1_sizes *adsp1_sizes; - const struct wmfw_adsp2_sizes *adsp2_sizes; const struct wmfw_footer *footer; const struct wmfw_region *region; const struct wm_adsp_region *mem; @@ -1664,7 +1837,7 @@ static int wm_adsp_load(struct wm_adsp *dsp) struct wm_adsp_buf *buf; unsigned int reg; int regions = 0; - int ret, offset, type, sizes; + int ret, offset, type; file = kzalloc(PAGE_SIZE, GFP_KERNEL); if (file == NULL) @@ -1695,15 +1868,7 @@ static int wm_adsp_load(struct wm_adsp *dsp) goto out_fw; } - switch (header->ver) { - case 0: - adsp_warn(dsp, "%s: Depreciated file format %d\n", - file, header->ver); - break; - case 1: - case 2: - break; - default: + if (!dsp->ops->validate_version(dsp, header->ver)) { adsp_err(dsp, "%s: unknown file format %d\n", file, header->ver); goto out_fw; @@ -1718,39 +1883,13 @@ static int wm_adsp_load(struct wm_adsp *dsp) goto out_fw; } - switch (dsp->type) { - case WMFW_ADSP1: - pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); - adsp1_sizes = (void *)&(header[1]); - footer = (void *)&(adsp1_sizes[1]); - sizes = sizeof(*adsp1_sizes); + pos = sizeof(*header); + pos = dsp->ops->parse_sizes(dsp, file, pos, firmware); - adsp_dbg(dsp, "%s: %d DM, %d PM, %d ZM\n", - file, le32_to_cpu(adsp1_sizes->dm), - le32_to_cpu(adsp1_sizes->pm), - le32_to_cpu(adsp1_sizes->zm)); - break; - - case WMFW_ADSP2: - pos = sizeof(*header) + sizeof(*adsp2_sizes) + sizeof(*footer); - adsp2_sizes = (void *)&(header[1]); - footer = (void *)&(adsp2_sizes[1]); - sizes = sizeof(*adsp2_sizes); - - adsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n", - file, le32_to_cpu(adsp2_sizes->xm), - le32_to_cpu(adsp2_sizes->ym), - le32_to_cpu(adsp2_sizes->pm), - le32_to_cpu(adsp2_sizes->zm)); - break; - - default: - WARN(1, "Unknown DSP type"); - goto out_fw; - } + footer = (void *)&firmware->data[pos]; + pos += sizeof(*footer); - if (le32_to_cpu(header->len) != sizeof(*header) + - sizes + sizeof(*footer)) { + if (le32_to_cpu(header->len) != pos) { adsp_err(dsp, "%s: unexpected header length %d\n", file, le32_to_cpu(header->len)); goto out_fw; @@ -1767,7 +1906,6 @@ static int wm_adsp_load(struct wm_adsp *dsp) text = NULL; offset = le32_to_cpu(region->offset) & 0xffffff; type = be32_to_cpu(region->type) & 0xff; - mem = wm_adsp_find_region(dsp, type); switch (type) { case WMFW_NAME_TEXT: @@ -1795,8 +1933,17 @@ static int wm_adsp_load(struct wm_adsp *dsp) case WMFW_ADSP2_XM: case WMFW_ADSP2_YM: case WMFW_ADSP1_ZM: + case WMFW_HALO_PM_PACKED: + case WMFW_HALO_XM_PACKED: + case WMFW_HALO_YM_PACKED: + mem = wm_adsp_find_region(dsp, type); + if (!mem) { + adsp_err(dsp, "No region of type: %x\n", type); + goto out_fw; + } + region_name = wm_adsp_mem_region_name(type); - reg = wm_adsp_region_to_reg(mem, offset); + reg = dsp->ops->region_to_reg(mem, offset); break; default: adsp_warn(dsp, @@ -1909,7 +2056,7 @@ static void *wm_adsp_read_algs(struct wm_adsp *dsp, size_t n_algs, } /* Read the terminator first to validate the length */ - reg = wm_adsp_region_to_reg(mem, pos + len); + reg = dsp->ops->region_to_reg(mem, pos + len); ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); if (ret != 0) { @@ -1929,7 +2076,7 @@ static void *wm_adsp_read_algs(struct wm_adsp *dsp, size_t n_algs, if (!alg) return ERR_PTR(-ENOMEM); - reg = wm_adsp_region_to_reg(mem, pos); + reg = dsp->ops->region_to_reg(mem, pos); ret = regmap_raw_read(dsp->regmap, reg, alg, len); if (ret != 0) { @@ -1989,6 +2136,47 @@ static void wm_adsp_free_alg_regions(struct wm_adsp *dsp) } } +static void wmfw_parse_id_header(struct wm_adsp *dsp, + struct wmfw_id_hdr *fw, int nalgs) +{ + dsp->fw_id = be32_to_cpu(fw->id); + dsp->fw_id_version = be32_to_cpu(fw->ver); + + adsp_info(dsp, "Firmware: %x v%d.%d.%d, %d algorithms\n", + dsp->fw_id, (dsp->fw_id_version & 0xff0000) >> 16, + (dsp->fw_id_version & 0xff00) >> 8, dsp->fw_id_version & 0xff, + nalgs); +} + +static void wmfw_v3_parse_id_header(struct wm_adsp *dsp, + struct wmfw_v3_id_hdr *fw, int nalgs) +{ + dsp->fw_id = be32_to_cpu(fw->id); + dsp->fw_id_version = be32_to_cpu(fw->ver); + dsp->fw_vendor_id = be32_to_cpu(fw->vendor_id); + + adsp_info(dsp, "Firmware: %x vendor: 0x%x v%d.%d.%d, %d algorithms\n", + dsp->fw_id, dsp->fw_vendor_id, + (dsp->fw_id_version & 0xff0000) >> 16, + (dsp->fw_id_version & 0xff00) >> 8, dsp->fw_id_version & 0xff, + nalgs); +} + +static int wm_adsp_create_regions(struct wm_adsp *dsp, __be32 id, int nregions, + int *type, __be32 *base) +{ + struct wm_adsp_alg_region *alg_region; + int i; + + for (i = 0; i < nregions; i++) { + alg_region = wm_adsp_create_region(dsp, type[i], id, base[i]); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + } + + return 0; +} + static int wm_adsp1_setup_algs(struct wm_adsp *dsp) { struct wmfw_adsp1_id_hdr adsp1_id; @@ -2012,13 +2200,8 @@ static int wm_adsp1_setup_algs(struct wm_adsp *dsp) } n_algs = be32_to_cpu(adsp1_id.n_algs); - dsp->fw_id = be32_to_cpu(adsp1_id.fw.id); - adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n", - dsp->fw_id, - (be32_to_cpu(adsp1_id.fw.ver) & 0xff0000) >> 16, - (be32_to_cpu(adsp1_id.fw.ver) & 0xff00) >> 8, - be32_to_cpu(adsp1_id.fw.ver) & 0xff, - n_algs); + + wmfw_parse_id_header(dsp, &adsp1_id.fw, n_algs); alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_ZM, adsp1_id.fw.id, adsp1_id.zm); @@ -2118,14 +2301,8 @@ static int wm_adsp2_setup_algs(struct wm_adsp *dsp) } n_algs = be32_to_cpu(adsp2_id.n_algs); - dsp->fw_id = be32_to_cpu(adsp2_id.fw.id); - dsp->fw_id_version = be32_to_cpu(adsp2_id.fw.ver); - adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n", - dsp->fw_id, - (dsp->fw_id_version & 0xff0000) >> 16, - (dsp->fw_id_version & 0xff00) >> 8, - dsp->fw_id_version & 0xff, - n_algs); + + wmfw_parse_id_header(dsp, &adsp2_id.fw, n_algs); alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_XM, adsp2_id.fw.id, adsp2_id.xm); @@ -2230,6 +2407,78 @@ out: return ret; } +static int wm_halo_create_regions(struct wm_adsp *dsp, __be32 id, + __be32 xm_base, __be32 ym_base) +{ + int types[] = { + WMFW_ADSP2_XM, WMFW_HALO_XM_PACKED, + WMFW_ADSP2_YM, WMFW_HALO_YM_PACKED + }; + __be32 bases[] = { xm_base, xm_base, ym_base, ym_base }; + + return wm_adsp_create_regions(dsp, id, ARRAY_SIZE(types), types, bases); +} + +static int wm_halo_setup_algs(struct wm_adsp *dsp) +{ + struct wmfw_halo_id_hdr halo_id; + struct wmfw_halo_alg_hdr *halo_alg; + const struct wm_adsp_region *mem; + unsigned int pos, len; + size_t n_algs; + int i, ret; + + mem = wm_adsp_find_region(dsp, WMFW_ADSP2_XM); + if (WARN_ON(!mem)) + return -EINVAL; + + ret = regmap_raw_read(dsp->regmap, mem->base, &halo_id, + sizeof(halo_id)); + if (ret != 0) { + adsp_err(dsp, "Failed to read algorithm info: %d\n", + ret); + return ret; + } + + n_algs = be32_to_cpu(halo_id.n_algs); + + wmfw_v3_parse_id_header(dsp, &halo_id.fw, n_algs); + + ret = wm_halo_create_regions(dsp, halo_id.fw.id, + halo_id.xm_base, halo_id.ym_base); + if (ret) + return ret; + + /* Calculate offset and length in DSP words */ + pos = sizeof(halo_id) / sizeof(u32); + len = (sizeof(*halo_alg) * n_algs) / sizeof(u32); + + halo_alg = wm_adsp_read_algs(dsp, n_algs, mem, pos, len); + if (IS_ERR(halo_alg)) + return PTR_ERR(halo_alg); + + for (i = 0; i < n_algs; i++) { + adsp_info(dsp, + "%d: ID %x v%d.%d.%d XM@%x YM@%x\n", + i, be32_to_cpu(halo_alg[i].alg.id), + (be32_to_cpu(halo_alg[i].alg.ver) & 0xff0000) >> 16, + (be32_to_cpu(halo_alg[i].alg.ver) & 0xff00) >> 8, + be32_to_cpu(halo_alg[i].alg.ver) & 0xff, + be32_to_cpu(halo_alg[i].xm_base), + be32_to_cpu(halo_alg[i].ym_base)); + + ret = wm_halo_create_regions(dsp, halo_alg[i].alg.id, + halo_alg[i].xm_base, + halo_alg[i].ym_base); + if (ret) + goto out; + } + +out: + kfree(halo_alg); + return ret; +} + static int wm_adsp_load_coeff(struct wm_adsp *dsp) { LIST_HEAD(buf_list); @@ -2324,7 +2573,7 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp) adsp_err(dsp, "No ZM\n"); break; } - reg = wm_adsp_region_to_reg(mem, 0); + reg = dsp->ops->region_to_reg(mem, 0); } else { region_name = "register"; @@ -2336,6 +2585,9 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp) case WMFW_ADSP1_ZM: case WMFW_ADSP2_XM: case WMFW_ADSP2_YM: + case WMFW_HALO_XM_PACKED: + case WMFW_HALO_YM_PACKED: + case WMFW_HALO_PM_PACKED: adsp_dbg(dsp, "%s.%d: %d bytes in %x for %x\n", file, blocks, le32_to_cpu(blk->len), type, le32_to_cpu(blk->id)); @@ -2350,7 +2602,7 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp) le32_to_cpu(blk->id)); if (alg_region) { reg = alg_region->base; - reg = wm_adsp_region_to_reg(mem, reg); + reg = dsp->ops->region_to_reg(mem, reg); reg += offset; } else { adsp_err(dsp, "No %x for algorithm %x\n", @@ -2464,6 +2716,8 @@ static int wm_adsp_common_init(struct wm_adsp *dsp) int wm_adsp1_init(struct wm_adsp *dsp) { + dsp->ops = &wm_adsp1_ops; + return wm_adsp_common_init(dsp); } EXPORT_SYMBOL_GPL(wm_adsp1_init); @@ -2583,23 +2837,11 @@ err_mutex: } EXPORT_SYMBOL_GPL(wm_adsp1_event); -static int wm_adsp2_ena(struct wm_adsp *dsp) +static int wm_adsp2v2_enable_core(struct wm_adsp *dsp) { unsigned int val; int ret, count; - switch (dsp->rev) { - case 0: - ret = regmap_update_bits_async(dsp->regmap, - dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, ADSP2_SYS_ENA); - if (ret != 0) - return ret; - break; - default: - break; - } - /* Wait for the RAM to start, should be near instantaneous */ for (count = 0; count < 10; ++count) { ret = regmap_read(dsp->regmap, dsp->base + ADSP2_STATUS1, &val); @@ -2622,7 +2864,78 @@ static int wm_adsp2_ena(struct wm_adsp *dsp) return 0; } -static void wm_adsp2_boot_work(struct work_struct *work) +static int wm_adsp2_enable_core(struct wm_adsp *dsp) +{ + int ret; + + ret = regmap_update_bits_async(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, ADSP2_SYS_ENA); + if (ret != 0) + return ret; + + return wm_adsp2v2_enable_core(dsp); +} + +static int wm_adsp2_lock(struct wm_adsp *dsp, unsigned int lock_regions) +{ + struct regmap *regmap = dsp->regmap; + unsigned int code0, code1, lock_reg; + + if (!(lock_regions & WM_ADSP2_REGION_ALL)) + return 0; + + lock_regions &= WM_ADSP2_REGION_ALL; + lock_reg = dsp->base + ADSP2_LOCK_REGION_1_LOCK_REGION_0; + + while (lock_regions) { + code0 = code1 = 0; + if (lock_regions & BIT(0)) { + code0 = ADSP2_LOCK_CODE_0; + code1 = ADSP2_LOCK_CODE_1; + } + if (lock_regions & BIT(1)) { + code0 |= ADSP2_LOCK_CODE_0 << ADSP2_LOCK_REGION_SHIFT; + code1 |= ADSP2_LOCK_CODE_1 << ADSP2_LOCK_REGION_SHIFT; + } + regmap_write(regmap, lock_reg, code0); + regmap_write(regmap, lock_reg, code1); + lock_regions >>= 2; + lock_reg += 2; + } + + return 0; +} + +static int wm_adsp2_enable_memory(struct wm_adsp *dsp) +{ + return regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, ADSP2_MEM_ENA); +} + +static void wm_adsp2_disable_memory(struct wm_adsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, 0); +} + +static void wm_adsp2_disable_core(struct wm_adsp *dsp) +{ + regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); +} + +static void wm_adsp2v2_disable_core(struct wm_adsp *dsp) +{ + regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2V2_WDMA_CONFIG_2, 0); +} + +static void wm_adsp_boot_work(struct work_struct *work) { struct wm_adsp *dsp = container_of(work, struct wm_adsp, @@ -2631,20 +2944,23 @@ static void wm_adsp2_boot_work(struct work_struct *work) mutex_lock(&dsp->pwr_lock); - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, ADSP2_MEM_ENA); - if (ret != 0) - goto err_mutex; + if (dsp->ops->enable_memory) { + ret = dsp->ops->enable_memory(dsp); + if (ret != 0) + goto err_mutex; + } - ret = wm_adsp2_ena(dsp); - if (ret != 0) - goto err_mem; + if (dsp->ops->enable_core) { + ret = dsp->ops->enable_core(dsp); + if (ret != 0) + goto err_mem; + } ret = wm_adsp_load(dsp); if (ret != 0) goto err_ena; - ret = wm_adsp2_setup_algs(dsp); + ret = dsp->ops->setup_algs(dsp); if (ret != 0) goto err_ena; @@ -2657,17 +2973,8 @@ static void wm_adsp2_boot_work(struct work_struct *work) if (ret != 0) goto err_ena; - switch (dsp->rev) { - case 0: - /* Turn DSP back off until we are ready to run */ - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, 0); - if (ret != 0) - goto err_ena; - break; - default: - break; - } + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); dsp->booted = true; @@ -2676,35 +2983,62 @@ static void wm_adsp2_boot_work(struct work_struct *work) return; err_ena: - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0); + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); err_mem: - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, 0); + if (dsp->ops->disable_memory) + dsp->ops->disable_memory(dsp); err_mutex: mutex_unlock(&dsp->pwr_lock); } -static void wm_adsp2_set_dspclk(struct wm_adsp *dsp, unsigned int freq) +static int wm_halo_configure_mpu(struct wm_adsp *dsp, unsigned int lock_regions) +{ + struct reg_sequence config[] = { + { dsp->base + HALO_MPU_LOCK_CONFIG, 0x5555 }, + { dsp->base + HALO_MPU_LOCK_CONFIG, 0xAAAA }, + { dsp->base + HALO_MPU_XMEM_ACCESS_0, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_0, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_0, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_0, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_0, lock_regions }, + { dsp->base + HALO_MPU_XMEM_ACCESS_1, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_1, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_1, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_1, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_1, lock_regions }, + { dsp->base + HALO_MPU_XMEM_ACCESS_2, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_2, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_2, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_2, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_2, lock_regions }, + { dsp->base + HALO_MPU_XMEM_ACCESS_3, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_3, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_3, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_3, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_3, lock_regions }, + { dsp->base + HALO_MPU_LOCK_CONFIG, 0 }, + }; + + return regmap_multi_reg_write(dsp->regmap, config, ARRAY_SIZE(config)); +} + +int wm_adsp2_set_dspclk(struct snd_soc_dapm_widget *w, unsigned int freq) { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); + struct wm_adsp *dsp = &dsps[w->shift]; int ret; - switch (dsp->rev) { - case 0: - ret = regmap_update_bits_async(dsp->regmap, - dsp->base + ADSP2_CLOCKING, - ADSP2_CLK_SEL_MASK, - freq << ADSP2_CLK_SEL_SHIFT); - if (ret) { - adsp_err(dsp, "Failed to set clock rate: %d\n", ret); - return; - } - break; - default: - /* clock is handled by parent codec driver */ - break; - } + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CLOCKING, + ADSP2_CLK_SEL_MASK, + freq << ADSP2_CLK_SEL_SHIFT); + if (ret) + adsp_err(dsp, "Failed to set clock rate: %d\n", ret); + + return ret; } +EXPORT_SYMBOL_GPL(wm_adsp2_set_dspclk); int wm_adsp2_preloader_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -2751,19 +3085,18 @@ EXPORT_SYMBOL_GPL(wm_adsp2_preloader_put); static void wm_adsp_stop_watchdog(struct wm_adsp *dsp) { - switch (dsp->rev) { - case 0: - case 1: - return; - default: - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_WATCHDOG, - ADSP2_WDT_ENA_MASK, 0); - } + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_WATCHDOG, + ADSP2_WDT_ENA_MASK, 0); } -int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event, - unsigned int freq) +static void wm_halo_stop_watchdog(struct wm_adsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + HALO_WDT_CONTROL, + HALO_WDT_EN_MASK, 0); +} + +int wm_adsp_early_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); @@ -2772,7 +3105,6 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - wm_adsp2_set_dspclk(dsp, freq); queue_work(system_unbound_wq, &dsp->boot_work); break; case SND_SOC_DAPM_PRE_PMD: @@ -2785,8 +3117,8 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, dsp->booted = false; - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, 0); + if (dsp->ops->disable_memory) + dsp->ops->disable_memory(dsp); list_for_each_entry(ctl, &dsp->ctl_list, list) ctl->enabled = 0; @@ -2803,10 +3135,23 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, return 0; } -EXPORT_SYMBOL_GPL(wm_adsp2_early_event); +EXPORT_SYMBOL_GPL(wm_adsp_early_event); + +static int wm_adsp2_start_core(struct wm_adsp *dsp) +{ + return regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_CORE_ENA | ADSP2_START, + ADSP2_CORE_ENA | ADSP2_START); +} -int wm_adsp2_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +static void wm_adsp2_stop_core(struct wm_adsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_CORE_ENA | ADSP2_START, 0); +} + +int wm_adsp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); @@ -2824,23 +3169,31 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, goto err; } - ret = wm_adsp2_ena(dsp); - if (ret != 0) - goto err; + if (dsp->ops->enable_core) { + ret = dsp->ops->enable_core(dsp); + if (ret != 0) + goto err; + } /* Sync set controls */ ret = wm_coeff_sync_controls(dsp); if (ret != 0) goto err; - wm_adsp2_lock(dsp, dsp->lock_regions); + if (dsp->ops->lock_memory) { + ret = dsp->ops->lock_memory(dsp, dsp->lock_regions); + if (ret != 0) { + adsp_err(dsp, "Error configuring MPU: %d\n", + ret); + goto err; + } + } - ret = regmap_update_bits(dsp->regmap, - dsp->base + ADSP2_CONTROL, - ADSP2_CORE_ENA | ADSP2_START, - ADSP2_CORE_ENA | ADSP2_START); - if (ret != 0) - goto err; + if (dsp->ops->start_core) { + ret = dsp->ops->start_core(dsp); + if (ret != 0) + goto err; + } if (wm_adsp_fw[dsp->fw].num_caps != 0) { ret = wm_adsp_buffer_init(dsp); @@ -2851,56 +3204,27 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, dsp->running = true; mutex_unlock(&dsp->pwr_lock); - break; case SND_SOC_DAPM_PRE_PMD: /* Tell the firmware to cleanup */ wm_adsp_signal_event_controls(dsp, WM_ADSP_FW_EVENT_SHUTDOWN); - wm_adsp_stop_watchdog(dsp); + if (dsp->ops->stop_watchdog) + dsp->ops->stop_watchdog(dsp); /* Log firmware state, it can be useful for analysis */ - switch (dsp->rev) { - case 0: - wm_adsp2_show_fw_status(dsp); - break; - default: - wm_adsp2v2_show_fw_status(dsp); - break; - } + if (dsp->ops->show_fw_status) + dsp->ops->show_fw_status(dsp); mutex_lock(&dsp->pwr_lock); dsp->running = false; - regmap_update_bits(dsp->regmap, - dsp->base + ADSP2_CONTROL, - ADSP2_CORE_ENA | ADSP2_START, 0); - - /* Make sure DMAs are quiesced */ - switch (dsp->rev) { - case 0: - regmap_write(dsp->regmap, - dsp->base + ADSP2_RDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, - dsp->base + ADSP2_WDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, - dsp->base + ADSP2_WDMA_CONFIG_2, 0); - - regmap_update_bits(dsp->regmap, - dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, 0); - break; - default: - regmap_write(dsp->regmap, - dsp->base + ADSP2_RDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, - dsp->base + ADSP2_WDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, - dsp->base + ADSP2V2_WDMA_CONFIG_2, 0); - break; - } + if (dsp->ops->stop_core) + dsp->ops->stop_core(dsp); + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); if (wm_adsp_fw[dsp->fw].num_caps != 0) wm_adsp_buffer_free(dsp); @@ -2918,12 +3242,31 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, return 0; err: - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0); + if (dsp->ops->stop_core) + dsp->ops->stop_core(dsp); + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); mutex_unlock(&dsp->pwr_lock); return ret; } -EXPORT_SYMBOL_GPL(wm_adsp2_event); +EXPORT_SYMBOL_GPL(wm_adsp_event); + +static int wm_halo_start_core(struct wm_adsp *dsp) +{ + return regmap_update_bits(dsp->regmap, + dsp->base + HALO_CCM_CORE_CONTROL, + HALO_CORE_EN, HALO_CORE_EN); +} + +static void wm_halo_stop_core(struct wm_adsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + HALO_CCM_CORE_CONTROL, + HALO_CORE_EN, 0); + + /* reset halo core with CORE_SOFT_RESET */ + regmap_update_bits(dsp->regmap, dsp->base + HALO_CORE_SOFT_RESET, + HALO_CORE_SOFT_RESET_MASK, 1); +} int wm_adsp2_component_probe(struct wm_adsp *dsp, struct snd_soc_component *component) { @@ -2969,17 +3312,39 @@ int wm_adsp2_init(struct wm_adsp *dsp) "Failed to clear memory retention: %d\n", ret); return ret; } + + dsp->ops = &wm_adsp2_ops[0]; + break; + case 1: + dsp->ops = &wm_adsp2_ops[1]; break; default: + dsp->ops = &wm_adsp2_ops[2]; break; } - INIT_WORK(&dsp->boot_work, wm_adsp2_boot_work); + INIT_WORK(&dsp->boot_work, wm_adsp_boot_work); return 0; } EXPORT_SYMBOL_GPL(wm_adsp2_init); +int wm_halo_init(struct wm_adsp *dsp) +{ + int ret; + + ret = wm_adsp_common_init(dsp); + if (ret) + return ret; + + dsp->ops = &wm_halo_ops; + + INIT_WORK(&dsp->boot_work, wm_adsp_boot_work); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_halo_init); + void wm_adsp2_remove(struct wm_adsp *dsp) { struct wm_coeff_ctl *ctl; @@ -3016,7 +3381,7 @@ static int wm_adsp_compr_attach(struct wm_adsp_compr *compr) return -EINVAL; compr->buf = buf; - compr->buf->compr = compr; + buf->compr = compr; return 0; } @@ -3224,7 +3589,7 @@ static int wm_adsp_read_data_block(struct wm_adsp *dsp, int mem_type, if (!mem) return -EINVAL; - reg = wm_adsp_region_to_reg(mem, mem_addr); + reg = dsp->ops->region_to_reg(mem, mem_addr); ret = regmap_raw_read(dsp->regmap, reg, data, sizeof(*data) * num_words); @@ -3252,7 +3617,7 @@ static int wm_adsp_write_data_word(struct wm_adsp *dsp, int mem_type, if (!mem) return -EINVAL; - reg = wm_adsp_region_to_reg(mem, mem_addr); + reg = dsp->ops->region_to_reg(mem, mem_addr); data = cpu_to_be32(data & 0x00ffffffu); @@ -3363,7 +3728,7 @@ static int wm_adsp_buffer_parse_legacy(struct wm_adsp *dsp) return -ENOMEM; alg_region = wm_adsp_find_alg_region(dsp, WMFW_ADSP2_XM, dsp->fw_id); - xmalg = sizeof(struct wm_adsp_system_config_xm_hdr) / sizeof(__be32); + xmalg = dsp->ops->sys_config_size / sizeof(__be32); addr = alg_region->base + xmalg + ALG_XM_FIELD(magic); ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr, &magic); @@ -3522,8 +3887,7 @@ static int wm_adsp_buffer_free(struct wm_adsp *dsp) struct wm_adsp_compr_buf *buf, *tmp; list_for_each_entry_safe(buf, tmp, &dsp->buffer_list, list) { - if (buf->compr) - wm_adsp_compr_detach(buf->compr); + wm_adsp_compr_detach(buf->compr); kfree(buf->name); kfree(buf->regions); @@ -3728,7 +4092,7 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream, buf = compr->buf; - if (!compr->buf || compr->buf->error) { + if (dsp->fatal_error || !buf || buf->error) { snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); ret = -EIO; goto out; @@ -3748,7 +4112,7 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream, if (buf->avail < wm_adsp_compr_frag_words(compr)) { ret = wm_adsp_buffer_get_error(buf); if (ret < 0) { - if (compr->buf->error) + if (buf->error) snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); goto out; @@ -3832,12 +4196,13 @@ static int wm_adsp_buffer_capture_block(struct wm_adsp_compr *compr, int target) static int wm_adsp_compr_read(struct wm_adsp_compr *compr, char __user *buf, size_t count) { + struct wm_adsp *dsp = compr->dsp; int ntotal = 0; int nwords, nbytes; compr_dbg(compr, "Requested read of %zu bytes\n", count); - if (!compr->buf || compr->buf->error) { + if (dsp->fatal_error || !compr->buf || compr->buf->error) { snd_compr_stop_error(compr->stream, SNDRV_PCM_STATE_XRUN); return -EIO; } @@ -3891,37 +4256,6 @@ int wm_adsp_compr_copy(struct snd_compr_stream *stream, char __user *buf, } EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); -int wm_adsp2_lock(struct wm_adsp *dsp, unsigned int lock_regions) -{ - struct regmap *regmap = dsp->regmap; - unsigned int code0, code1, lock_reg; - - if (!(lock_regions & WM_ADSP2_REGION_ALL)) - return 0; - - lock_regions &= WM_ADSP2_REGION_ALL; - lock_reg = dsp->base + ADSP2_LOCK_REGION_1_LOCK_REGION_0; - - while (lock_regions) { - code0 = code1 = 0; - if (lock_regions & BIT(0)) { - code0 = ADSP2_LOCK_CODE_0; - code1 = ADSP2_LOCK_CODE_1; - } - if (lock_regions & BIT(1)) { - code0 |= ADSP2_LOCK_CODE_0 << ADSP2_LOCK_REGION_SHIFT; - code1 |= ADSP2_LOCK_CODE_1 << ADSP2_LOCK_REGION_SHIFT; - } - regmap_write(regmap, lock_reg, code0); - regmap_write(regmap, lock_reg, code1); - lock_regions >>= 2; - lock_reg += 2; - } - - return 0; -} -EXPORT_SYMBOL_GPL(wm_adsp2_lock); - static void wm_adsp_fatal_error(struct wm_adsp *dsp) { struct wm_adsp_compr *compr; @@ -3929,11 +4263,8 @@ static void wm_adsp_fatal_error(struct wm_adsp *dsp) dsp->fatal_error = true; list_for_each_entry(compr, &dsp->compr_list, list) { - if (compr->stream) { - snd_compr_stop_error(compr->stream, - SNDRV_PCM_STATE_XRUN); + if (compr->stream) snd_compr_fragment_elapsed(compr->stream); - } } } @@ -3954,7 +4285,7 @@ irqreturn_t wm_adsp2_bus_error(struct wm_adsp *dsp) if (val & ADSP2_WDT_TIMEOUT_STS_MASK) { adsp_err(dsp, "watchdog timeout error\n"); - wm_adsp_stop_watchdog(dsp); + dsp->ops->stop_watchdog(dsp); wm_adsp_fatal_error(dsp); } @@ -4002,4 +4333,159 @@ error: } EXPORT_SYMBOL_GPL(wm_adsp2_bus_error); +irqreturn_t wm_halo_bus_error(struct wm_adsp *dsp) +{ + struct regmap *regmap = dsp->regmap; + unsigned int fault[6]; + struct reg_sequence clear[] = { + { dsp->base + HALO_MPU_XM_VIO_STATUS, 0x0 }, + { dsp->base + HALO_MPU_YM_VIO_STATUS, 0x0 }, + { dsp->base + HALO_MPU_PM_VIO_STATUS, 0x0 }, + }; + int ret; + + mutex_lock(&dsp->pwr_lock); + + ret = regmap_read(regmap, dsp->base_sysinfo + HALO_AHBM_WINDOW_DEBUG_1, + fault); + if (ret) { + adsp_warn(dsp, "Failed to read AHB DEBUG_1: %d\n", ret); + goto exit_unlock; + } + + adsp_warn(dsp, "AHB: STATUS: 0x%x ADDR: 0x%x\n", + *fault & HALO_AHBM_FLAGS_ERR_MASK, + (*fault & HALO_AHBM_CORE_ERR_ADDR_MASK) >> + HALO_AHBM_CORE_ERR_ADDR_SHIFT); + + ret = regmap_read(regmap, dsp->base_sysinfo + HALO_AHBM_WINDOW_DEBUG_0, + fault); + if (ret) { + adsp_warn(dsp, "Failed to read AHB DEBUG_0: %d\n", ret); + goto exit_unlock; + } + + adsp_warn(dsp, "AHB: SYS_ADDR: 0x%x\n", *fault); + + ret = regmap_bulk_read(regmap, dsp->base + HALO_MPU_XM_VIO_ADDR, + fault, ARRAY_SIZE(fault)); + if (ret) { + adsp_warn(dsp, "Failed to read MPU fault info: %d\n", ret); + goto exit_unlock; + } + + adsp_warn(dsp, "XM: STATUS:0x%x ADDR:0x%x\n", fault[1], fault[0]); + adsp_warn(dsp, "YM: STATUS:0x%x ADDR:0x%x\n", fault[3], fault[2]); + adsp_warn(dsp, "PM: STATUS:0x%x ADDR:0x%x\n", fault[5], fault[4]); + + ret = regmap_multi_reg_write(dsp->regmap, clear, ARRAY_SIZE(clear)); + if (ret) + adsp_warn(dsp, "Failed to clear MPU status: %d\n", ret); + +exit_unlock: + mutex_unlock(&dsp->pwr_lock); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wm_halo_bus_error); + +irqreturn_t wm_halo_wdt_expire(int irq, void *data) +{ + struct wm_adsp *dsp = data; + + mutex_lock(&dsp->pwr_lock); + + adsp_warn(dsp, "WDT Expiry Fault\n"); + dsp->ops->stop_watchdog(dsp); + wm_adsp_fatal_error(dsp); + + mutex_unlock(&dsp->pwr_lock); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wm_halo_wdt_expire); + +static struct wm_adsp_ops wm_adsp1_ops = { + .validate_version = wm_adsp_validate_version, + .parse_sizes = wm_adsp1_parse_sizes, + .region_to_reg = wm_adsp_region_to_reg, +}; + +static struct wm_adsp_ops wm_adsp2_ops[] = { + { + .sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr), + .parse_sizes = wm_adsp2_parse_sizes, + .validate_version = wm_adsp_validate_version, + .setup_algs = wm_adsp2_setup_algs, + .region_to_reg = wm_adsp_region_to_reg, + + .show_fw_status = wm_adsp2_show_fw_status, + + .enable_memory = wm_adsp2_enable_memory, + .disable_memory = wm_adsp2_disable_memory, + + .enable_core = wm_adsp2_enable_core, + .disable_core = wm_adsp2_disable_core, + + .start_core = wm_adsp2_start_core, + .stop_core = wm_adsp2_stop_core, + + }, + { + .sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr), + .parse_sizes = wm_adsp2_parse_sizes, + .validate_version = wm_adsp_validate_version, + .setup_algs = wm_adsp2_setup_algs, + .region_to_reg = wm_adsp_region_to_reg, + + .show_fw_status = wm_adsp2v2_show_fw_status, + + .enable_memory = wm_adsp2_enable_memory, + .disable_memory = wm_adsp2_disable_memory, + .lock_memory = wm_adsp2_lock, + + .enable_core = wm_adsp2v2_enable_core, + .disable_core = wm_adsp2v2_disable_core, + + .start_core = wm_adsp2_start_core, + .stop_core = wm_adsp2_stop_core, + }, + { + .sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr), + .parse_sizes = wm_adsp2_parse_sizes, + .validate_version = wm_adsp_validate_version, + .setup_algs = wm_adsp2_setup_algs, + .region_to_reg = wm_adsp_region_to_reg, + + .show_fw_status = wm_adsp2v2_show_fw_status, + .stop_watchdog = wm_adsp_stop_watchdog, + + .enable_memory = wm_adsp2_enable_memory, + .disable_memory = wm_adsp2_disable_memory, + .lock_memory = wm_adsp2_lock, + + .enable_core = wm_adsp2v2_enable_core, + .disable_core = wm_adsp2v2_disable_core, + + .start_core = wm_adsp2_start_core, + .stop_core = wm_adsp2_stop_core, + }, +}; + +static struct wm_adsp_ops wm_halo_ops = { + .sys_config_size = sizeof(struct wm_halo_system_config_xm_hdr), + .parse_sizes = wm_adsp2_parse_sizes, + .validate_version = wm_halo_validate_version, + .setup_algs = wm_halo_setup_algs, + .region_to_reg = wm_halo_region_to_reg, + + .show_fw_status = wm_halo_show_fw_status, + .stop_watchdog = wm_halo_stop_watchdog, + + .lock_memory = wm_halo_configure_mpu, + + .start_core = wm_halo_start_core, + .stop_core = wm_halo_stop_core, +}; + MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 8f09b4419a91..3631c9200c5d 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -54,6 +54,7 @@ struct wm_adsp_alg_region { struct wm_adsp_compr; struct wm_adsp_compr_buf; +struct wm_adsp_ops; struct wm_adsp { const char *part; @@ -66,7 +67,10 @@ struct wm_adsp { struct regmap *regmap; struct snd_soc_component *component; + struct wm_adsp_ops *ops; + unsigned int base; + unsigned int base_sysinfo; unsigned int sysclk_reg; unsigned int sysclk_mask; unsigned int sysclk_shift; @@ -75,6 +79,7 @@ struct wm_adsp { unsigned int fw_id; unsigned int fw_id_version; + unsigned int fw_vendor_id; const struct wm_adsp_region *mem; int num_mems; @@ -106,6 +111,32 @@ struct wm_adsp { }; +struct wm_adsp_ops { + unsigned int sys_config_size; + + bool (*validate_version)(struct wm_adsp *dsp, unsigned int version); + unsigned int (*parse_sizes)(struct wm_adsp *dsp, + const char * const file, + unsigned int pos, + const struct firmware *firmware); + int (*setup_algs)(struct wm_adsp *dsp); + unsigned int (*region_to_reg)(struct wm_adsp_region const *mem, + unsigned int offset); + + void (*show_fw_status)(struct wm_adsp *dsp); + void (*stop_watchdog)(struct wm_adsp *dsp); + + int (*enable_memory)(struct wm_adsp *dsp); + void (*disable_memory)(struct wm_adsp *dsp); + int (*lock_memory)(struct wm_adsp *dsp, unsigned int lock_regions); + + int (*enable_core)(struct wm_adsp *dsp); + void (*disable_core)(struct wm_adsp *dsp); + + int (*start_core)(struct wm_adsp *dsp); + void (*stop_core)(struct wm_adsp *dsp); +}; + #define WM_ADSP1(wname, num) \ SND_SOC_DAPM_PGA_E(wname, SND_SOC_NOPM, num, 0, NULL, 0, \ wm_adsp1_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) @@ -121,7 +152,7 @@ struct wm_adsp { .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD, \ .subseq = 100, /* Ensure we run after SYSCLK supply widget */ }, \ { .id = snd_soc_dapm_out_drv, .name = wname, \ - .reg = SND_SOC_NOPM, .shift = num, .event = wm_adsp2_event, \ + .reg = SND_SOC_NOPM, .shift = num, .event = wm_adsp_event, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD } #define WM_ADSP_FW_CONTROL(dspname, num) \ @@ -135,17 +166,22 @@ int wm_adsp2_init(struct wm_adsp *dsp); void wm_adsp2_remove(struct wm_adsp *dsp); int wm_adsp2_component_probe(struct wm_adsp *dsp, struct snd_soc_component *component); int wm_adsp2_component_remove(struct wm_adsp *dsp, struct snd_soc_component *component); +int wm_halo_init(struct wm_adsp *dsp); + int wm_adsp1_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); -int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event, - unsigned int freq); -int wm_adsp2_lock(struct wm_adsp *adsp, unsigned int regions); +int wm_adsp_early_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + irqreturn_t wm_adsp2_bus_error(struct wm_adsp *adsp); +irqreturn_t wm_halo_bus_error(struct wm_adsp *dsp); +irqreturn_t wm_halo_wdt_expire(int irq, void *data); -int wm_adsp2_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event); +int wm_adsp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +int wm_adsp2_set_dspclk(struct snd_soc_dapm_widget *w, unsigned int freq); int wm_adsp2_preloader_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); diff --git a/sound/soc/codecs/wmfw.h b/sound/soc/codecs/wmfw.h index 0c3f50acb8b1..14b2d1a2fc59 100644 --- a/sound/soc/codecs/wmfw.h +++ b/sound/soc/codecs/wmfw.h @@ -73,6 +73,14 @@ struct wmfw_id_hdr { __be32 ver; } __packed; +struct wmfw_v3_id_hdr { + __be32 core_id; + __be32 block_rev; + __be32 vendor_id; + __be32 id; + __be32 ver; +} __packed; + struct wmfw_adsp1_id_hdr { struct wmfw_id_hdr fw; __be32 zm; @@ -88,6 +96,15 @@ struct wmfw_adsp2_id_hdr { __be32 n_algs; } __packed; +struct wmfw_halo_id_hdr { + struct wmfw_v3_id_hdr fw; + __be32 xm_base; + __be32 xm_size; + __be32 ym_base; + __be32 ym_size; + __be32 n_algs; +} __packed; + struct wmfw_alg_hdr { __be32 id; __be32 ver; @@ -106,6 +123,14 @@ struct wmfw_adsp2_alg_hdr { __be32 ym; } __packed; +struct wmfw_halo_alg_hdr { + struct wmfw_alg_hdr alg; + __be32 xm_base; + __be32 xm_size; + __be32 ym_base; + __be32 ym_size; +} __packed; + struct wmfw_adsp_alg_data { __le32 id; u8 name[WMFW_MAX_ALG_NAME]; @@ -154,6 +179,7 @@ struct wmfw_coeff_item { #define WMFW_ADSP1 1 #define WMFW_ADSP2 2 +#define WMFW_HALO 4 #define WMFW_ABSOLUTE 0xf0 #define WMFW_ALGORITHM_DATA 0xf2 @@ -169,4 +195,8 @@ struct wmfw_coeff_item { #define WMFW_ADSP2_XM 5 #define WMFW_ADSP2_YM 6 +#define WMFW_HALO_PM_PACKED 0x10 +#define WMFW_HALO_XM_PACKED 0x11 +#define WMFW_HALO_YM_PACKED 0x12 + #endif diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 7b1d9970be8b..55ed47c599e2 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -24,6 +24,13 @@ config SND_SOC_FSL_SAI This option is only useful for out-of-tree drivers since in-tree drivers select it automatically. +config SND_SOC_FSL_AUDMIX + tristate "Audio Mixer (AUDMIX) module support" + select REGMAP_MMIO + help + Say Y if you want to add Audio Mixer (AUDMIX) + support for the NXP iMX CPUs. + config SND_SOC_FSL_SSI tristate "Synchronous Serial Interface module (SSI) support" select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n @@ -182,16 +189,17 @@ config SND_MPC52xx_SOC_EFIKA endif # SND_POWERPC_SOC +config SND_SOC_IMX_PCM_FIQ + tristate + default y if SND_SOC_IMX_SSI=y && (SND_SOC_FSL_SSI=m || SND_SOC_FSL_SPDIF=m) && (MXC_TZIC || MXC_AVIC) + select FIQ + if SND_IMX_SOC config SND_SOC_IMX_SSI tristate select SND_SOC_FSL_UTILS -config SND_SOC_IMX_PCM_FIQ - tristate - select FIQ - comment "SoC Audio support for Freescale i.MX boards:" config SND_MXC_SOC_WM1133_EV1 @@ -296,6 +304,15 @@ config SND_SOC_FSL_ASOC_CARD CS4271, CS4272 and SGTL5000. Say Y if you want to add support for Freescale Generic ASoC Sound Card. +config SND_SOC_IMX_AUDMIX + tristate "SoC Audio support for i.MX boards with AUDMIX" + select SND_SOC_FSL_AUDMIX + select SND_SOC_FSL_SAI + help + SoC Audio support for i.MX boards with Audio Mixer + Say Y if you want to add support for SoC audio on an i.MX board with + an Audio Mixer. + endif # SND_IMX_SOC endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 3c0ff315b971..c0dd04422fe9 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -12,6 +12,7 @@ snd-soc-p1022-rdk-objs := p1022_rdk.o obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o # Freescale SSI/DMA/SAI/SPDIF Support +snd-soc-fsl-audmix-objs := fsl_audmix.o snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o snd-soc-fsl-sai-objs := fsl_sai.o @@ -22,6 +23,8 @@ snd-soc-fsl-esai-objs := fsl_esai.o snd-soc-fsl-micfil-objs := fsl_micfil.o snd-soc-fsl-utils-objs := fsl_utils.o snd-soc-fsl-dma-objs := fsl_dma.o + +obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o @@ -59,6 +62,7 @@ snd-soc-imx-es8328-objs := imx-es8328.o snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o snd-soc-imx-spdif-objs := imx-spdif.o snd-soc-imx-mc13783-objs := imx-mc13783.o +snd-soc-imx-audmix-objs := imx-audmix.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o @@ -68,3 +72,4 @@ obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o +obj-$(CONFIG_SND_SOC_IMX_AUDMIX) += snd-soc-imx-audmix.o diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c index 191426a6d9ad..d648268cb454 100644 --- a/sound/soc/fsl/eukrea-tlv320.c +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -1,19 +1,13 @@ -/* - * eukrea-tlv320.c -- SoC audio for eukrea_cpuimxXX in I2S mode - * - * Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com> - * - * based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c - * which is Copyright 2009 Simtec Electronics - * and on sound/soc/imx/phycore-ac97.c which is - * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// eukrea-tlv320.c -- SoC audio for eukrea_cpuimxXX in I2S mode +// +// Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com> +// +// based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c +// which is Copyright 2009 Simtec Electronics +// and on sound/soc/imx/phycore-ac97.c which is +// Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> #include <linux/errno.h> #include <linux/module.h> @@ -118,13 +112,13 @@ static int eukrea_tlv320_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "fsl,mux-int-port node missing or invalid.\n"); - return ret; + goto err; } ret = of_property_read_u32(np, "fsl,mux-ext-port", &ext_port); if (ret) { dev_err(&pdev->dev, "fsl,mux-ext-port node missing or invalid.\n"); - return ret; + goto err; } /* diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c new file mode 100644 index 000000000000..3897a54a11fe --- /dev/null +++ b/sound/soc/fsl/fsl_audmix.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NXP AUDMIX ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright 2017 NXP + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include "fsl_audmix.h" + +#define SOC_ENUM_SINGLE_S(xreg, xshift, xtexts) \ + SOC_ENUM_SINGLE(xreg, xshift, ARRAY_SIZE(xtexts), xtexts) + +static const char + *tdm_sel[] = { "TDM1", "TDM2", }, + *mode_sel[] = { "Disabled", "TDM1", "TDM2", "Mixed", }, + *width_sel[] = { "16b", "18b", "20b", "24b", "32b", }, + *endis_sel[] = { "Disabled", "Enabled", }, + *updn_sel[] = { "Downward", "Upward", }, + *mask_sel[] = { "Unmask", "Mask", }; + +static const struct soc_enum fsl_audmix_enum[] = { +/* FSL_AUDMIX_CTR enums */ +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MIXCLK_SHIFT, tdm_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_OUTSRC_SHIFT, mode_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_OUTWIDTH_SHIFT, width_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MASKRTDF_SHIFT, mask_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MASKCKDF_SHIFT, mask_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_SYNCMODE_SHIFT, endis_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_SYNCSRC_SHIFT, tdm_sel), +/* FSL_AUDMIX_ATCR0 enums */ +SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR0, 0, endis_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR0, 1, updn_sel), +/* FSL_AUDMIX_ATCR1 enums */ +SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR1, 0, endis_sel), +SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR1, 1, updn_sel), +}; + +struct fsl_audmix_state { + u8 tdms; + u8 clk; + char msg[64]; +}; + +static const struct fsl_audmix_state prms[4][4] = {{ + /* DIS->DIS, do nothing */ + { .tdms = 0, .clk = 0, .msg = "" }, + /* DIS->TDM1*/ + { .tdms = 1, .clk = 1, .msg = "DIS->TDM1: TDM1 not started!\n" }, + /* DIS->TDM2*/ + { .tdms = 2, .clk = 2, .msg = "DIS->TDM2: TDM2 not started!\n" }, + /* DIS->MIX */ + { .tdms = 3, .clk = 0, .msg = "DIS->MIX: Please start both TDMs!\n" } +}, { /* TDM1->DIS */ + { .tdms = 1, .clk = 0, .msg = "TDM1->DIS: TDM1 not started!\n" }, + /* TDM1->TDM1, do nothing */ + { .tdms = 0, .clk = 0, .msg = "" }, + /* TDM1->TDM2 */ + { .tdms = 3, .clk = 2, .msg = "TDM1->TDM2: Please start both TDMs!\n" }, + /* TDM1->MIX */ + { .tdms = 3, .clk = 0, .msg = "TDM1->MIX: Please start both TDMs!\n" } +}, { /* TDM2->DIS */ + { .tdms = 2, .clk = 0, .msg = "TDM2->DIS: TDM2 not started!\n" }, + /* TDM2->TDM1 */ + { .tdms = 3, .clk = 1, .msg = "TDM2->TDM1: Please start both TDMs!\n" }, + /* TDM2->TDM2, do nothing */ + { .tdms = 0, .clk = 0, .msg = "" }, + /* TDM2->MIX */ + { .tdms = 3, .clk = 0, .msg = "TDM2->MIX: Please start both TDMs!\n" } +}, { /* MIX->DIS */ + { .tdms = 3, .clk = 0, .msg = "MIX->DIS: Please start both TDMs!\n" }, + /* MIX->TDM1 */ + { .tdms = 3, .clk = 1, .msg = "MIX->TDM1: Please start both TDMs!\n" }, + /* MIX->TDM2 */ + { .tdms = 3, .clk = 2, .msg = "MIX->TDM2: Please start both TDMs!\n" }, + /* MIX->MIX, do nothing */ + { .tdms = 0, .clk = 0, .msg = "" } +}, }; + +static int fsl_audmix_state_trans(struct snd_soc_component *comp, + unsigned int *mask, unsigned int *ctr, + const struct fsl_audmix_state prm) +{ + struct fsl_audmix *priv = snd_soc_component_get_drvdata(comp); + /* Enforce all required TDMs are started */ + if ((priv->tdms & prm.tdms) != prm.tdms) { + dev_dbg(comp->dev, "%s", prm.msg); + return -EINVAL; + } + + switch (prm.clk) { + case 1: + case 2: + /* Set mix clock */ + (*mask) |= FSL_AUDMIX_CTR_MIXCLK_MASK; + (*ctr) |= FSL_AUDMIX_CTR_MIXCLK(prm.clk - 1); + break; + default: + break; + } + + return 0; +} + +static int fsl_audmix_put_mix_clk_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_audmix *priv = snd_soc_component_get_drvdata(comp); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int reg_val, val, mix_clk; + int ret = 0; + + /* Get current state */ + ret = snd_soc_component_read(comp, FSL_AUDMIX_CTR, ®_val); + if (ret) + return ret; + + mix_clk = ((reg_val & FSL_AUDMIX_CTR_MIXCLK_MASK) + >> FSL_AUDMIX_CTR_MIXCLK_SHIFT); + val = snd_soc_enum_item_to_val(e, item[0]); + + dev_dbg(comp->dev, "TDMs=x%08x, val=x%08x\n", priv->tdms, val); + + /** + * Ensure the current selected mixer clock is available + * for configuration propagation + */ + if (!(priv->tdms & BIT(mix_clk))) { + dev_err(comp->dev, + "Started TDM%d needed for config propagation!\n", + mix_clk + 1); + return -EINVAL; + } + + if (!(priv->tdms & BIT(val))) { + dev_err(comp->dev, + "The selected clock source has no TDM%d enabled!\n", + val + 1); + return -EINVAL; + } + + return snd_soc_put_enum_double(kcontrol, ucontrol); +} + +static int fsl_audmix_put_out_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_audmix *priv = snd_soc_component_get_drvdata(comp); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + u32 out_src, mix_clk; + unsigned int reg_val, val, mask = 0, ctr = 0; + int ret = 0; + + /* Get current state */ + ret = snd_soc_component_read(comp, FSL_AUDMIX_CTR, ®_val); + if (ret) + return ret; + + /* "From" state */ + out_src = ((reg_val & FSL_AUDMIX_CTR_OUTSRC_MASK) + >> FSL_AUDMIX_CTR_OUTSRC_SHIFT); + mix_clk = ((reg_val & FSL_AUDMIX_CTR_MIXCLK_MASK) + >> FSL_AUDMIX_CTR_MIXCLK_SHIFT); + + /* "To" state */ + val = snd_soc_enum_item_to_val(e, item[0]); + + dev_dbg(comp->dev, "TDMs=x%08x, val=x%08x\n", priv->tdms, val); + + /* Check if state is changing ... */ + if (out_src == val) + return 0; + /** + * Ensure the current selected mixer clock is available + * for configuration propagation + */ + if (!(priv->tdms & BIT(mix_clk))) { + dev_err(comp->dev, + "Started TDM%d needed for config propagation!\n", + mix_clk + 1); + return -EINVAL; + } + + /* Check state transition constraints */ + ret = fsl_audmix_state_trans(comp, &mask, &ctr, prms[out_src][val]); + if (ret) + return ret; + + /* Complete transition to new state */ + mask |= FSL_AUDMIX_CTR_OUTSRC_MASK; + ctr |= FSL_AUDMIX_CTR_OUTSRC(val); + + return snd_soc_component_update_bits(comp, FSL_AUDMIX_CTR, mask, ctr); +} + +static const struct snd_kcontrol_new fsl_audmix_snd_controls[] = { + /* FSL_AUDMIX_CTR controls */ + SOC_ENUM_EXT("Mixing Clock Source", fsl_audmix_enum[0], + snd_soc_get_enum_double, fsl_audmix_put_mix_clk_src), + SOC_ENUM_EXT("Output Source", fsl_audmix_enum[1], + snd_soc_get_enum_double, fsl_audmix_put_out_src), + SOC_ENUM("Output Width", fsl_audmix_enum[2]), + SOC_ENUM("Frame Rate Diff Error", fsl_audmix_enum[3]), + SOC_ENUM("Clock Freq Diff Error", fsl_audmix_enum[4]), + SOC_ENUM("Sync Mode Config", fsl_audmix_enum[5]), + SOC_ENUM("Sync Mode Clk Source", fsl_audmix_enum[6]), + /* TDM1 Attenuation controls */ + SOC_ENUM("TDM1 Attenuation", fsl_audmix_enum[7]), + SOC_ENUM("TDM1 Attenuation Direction", fsl_audmix_enum[8]), + SOC_SINGLE("TDM1 Attenuation Step Divider", FSL_AUDMIX_ATCR0, + 2, 0x00fff, 0), + SOC_SINGLE("TDM1 Attenuation Initial Value", FSL_AUDMIX_ATIVAL0, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM1 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP0, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM1 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN0, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM1 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT0, + 0, 0x3ffff, 0), + /* TDM2 Attenuation controls */ + SOC_ENUM("TDM2 Attenuation", fsl_audmix_enum[9]), + SOC_ENUM("TDM2 Attenuation Direction", fsl_audmix_enum[10]), + SOC_SINGLE("TDM2 Attenuation Step Divider", FSL_AUDMIX_ATCR1, + 2, 0x00fff, 0), + SOC_SINGLE("TDM2 Attenuation Initial Value", FSL_AUDMIX_ATIVAL1, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM2 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP1, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM2 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN1, + 0, 0x3ffff, 0), + SOC_SINGLE("TDM2 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT1, + 0, 0x3ffff, 0), +}; + +static int fsl_audmix_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *comp = dai->component; + u32 mask = 0, ctr = 0; + + /* AUDMIX is working in DSP_A format only */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + break; + default: + return -EINVAL; + } + + /* For playback the AUDMIX is slave, and for record is master */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + /* Output data will be written on positive edge of the clock */ + ctr |= FSL_AUDMIX_CTR_OUTCKPOL(0); + break; + case SND_SOC_DAIFMT_NB_NF: + /* Output data will be written on negative edge of the clock */ + ctr |= FSL_AUDMIX_CTR_OUTCKPOL(1); + break; + default: + return -EINVAL; + } + + mask |= FSL_AUDMIX_CTR_OUTCKPOL_MASK; + + return snd_soc_component_update_bits(comp, FSL_AUDMIX_CTR, mask, ctr); +} + +static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_audmix *priv = snd_soc_dai_get_drvdata(dai); + + /* Capture stream shall not be handled */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + priv->tdms |= BIT(dai->driver->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + priv->tdms &= ~BIT(dai->driver->id); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops fsl_audmix_dai_ops = { + .set_fmt = fsl_audmix_dai_set_fmt, + .trigger = fsl_audmix_dai_trigger, +}; + +static struct snd_soc_dai_driver fsl_audmix_dai[] = { + { + .id = 0, + .name = "audmix-0", + .playback = { + .stream_name = "AUDMIX-Playback-0", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AUDMIX_FORMATS, + }, + .capture = { + .stream_name = "AUDMIX-Capture-0", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AUDMIX_FORMATS, + }, + .ops = &fsl_audmix_dai_ops, + }, + { + .id = 1, + .name = "audmix-1", + .playback = { + .stream_name = "AUDMIX-Playback-1", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AUDMIX_FORMATS, + }, + .capture = { + .stream_name = "AUDMIX-Capture-1", + .channels_min = 8, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_AUDMIX_FORMATS, + }, + .ops = &fsl_audmix_dai_ops, + }, +}; + +static const struct snd_soc_component_driver fsl_audmix_component = { + .name = "fsl-audmix-dai", + .controls = fsl_audmix_snd_controls, + .num_controls = ARRAY_SIZE(fsl_audmix_snd_controls), +}; + +static bool fsl_audmix_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_AUDMIX_CTR: + case FSL_AUDMIX_STR: + case FSL_AUDMIX_ATCR0: + case FSL_AUDMIX_ATIVAL0: + case FSL_AUDMIX_ATSTPUP0: + case FSL_AUDMIX_ATSTPDN0: + case FSL_AUDMIX_ATSTPTGT0: + case FSL_AUDMIX_ATTNVAL0: + case FSL_AUDMIX_ATSTP0: + case FSL_AUDMIX_ATCR1: + case FSL_AUDMIX_ATIVAL1: + case FSL_AUDMIX_ATSTPUP1: + case FSL_AUDMIX_ATSTPDN1: + case FSL_AUDMIX_ATSTPTGT1: + case FSL_AUDMIX_ATTNVAL1: + case FSL_AUDMIX_ATSTP1: + return true; + default: + return false; + } +} + +static bool fsl_audmix_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_AUDMIX_CTR: + case FSL_AUDMIX_ATCR0: + case FSL_AUDMIX_ATIVAL0: + case FSL_AUDMIX_ATSTPUP0: + case FSL_AUDMIX_ATSTPDN0: + case FSL_AUDMIX_ATSTPTGT0: + case FSL_AUDMIX_ATCR1: + case FSL_AUDMIX_ATIVAL1: + case FSL_AUDMIX_ATSTPUP1: + case FSL_AUDMIX_ATSTPDN1: + case FSL_AUDMIX_ATSTPTGT1: + return true; + default: + return false; + } +} + +static const struct reg_default fsl_audmix_reg[] = { + { FSL_AUDMIX_CTR, 0x00060 }, + { FSL_AUDMIX_STR, 0x00003 }, + { FSL_AUDMIX_ATCR0, 0x00000 }, + { FSL_AUDMIX_ATIVAL0, 0x3FFFF }, + { FSL_AUDMIX_ATSTPUP0, 0x2AAAA }, + { FSL_AUDMIX_ATSTPDN0, 0x30000 }, + { FSL_AUDMIX_ATSTPTGT0, 0x00010 }, + { FSL_AUDMIX_ATTNVAL0, 0x00000 }, + { FSL_AUDMIX_ATSTP0, 0x00000 }, + { FSL_AUDMIX_ATCR1, 0x00000 }, + { FSL_AUDMIX_ATIVAL1, 0x3FFFF }, + { FSL_AUDMIX_ATSTPUP1, 0x2AAAA }, + { FSL_AUDMIX_ATSTPDN1, 0x30000 }, + { FSL_AUDMIX_ATSTPTGT1, 0x00010 }, + { FSL_AUDMIX_ATTNVAL1, 0x00000 }, + { FSL_AUDMIX_ATSTP1, 0x00000 }, +}; + +static const struct regmap_config fsl_audmix_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = FSL_AUDMIX_ATSTP1, + .reg_defaults = fsl_audmix_reg, + .num_reg_defaults = ARRAY_SIZE(fsl_audmix_reg), + .readable_reg = fsl_audmix_readable_reg, + .writeable_reg = fsl_audmix_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static const struct of_device_id fsl_audmix_ids[] = { + { + .compatible = "fsl,imx8qm-audmix", + .data = "imx-audmix", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_audmix_ids); + +static int fsl_audmix_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fsl_audmix *priv; + struct resource *res; + const char *mdrv; + const struct of_device_id *of_id; + void __iomem *regs; + int ret; + + of_id = of_match_device(fsl_audmix_ids, dev); + if (!of_id || !of_id->data) + return -EINVAL; + + mdrv = of_id->data; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Get the addresses */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + priv->regmap = devm_regmap_init_mmio_clk(dev, "ipg", regs, + &fsl_audmix_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(dev, "failed to init regmap\n"); + return PTR_ERR(priv->regmap); + } + + priv->ipg_clk = devm_clk_get(dev, "ipg"); + if (IS_ERR(priv->ipg_clk)) { + dev_err(dev, "failed to get ipg clock\n"); + return PTR_ERR(priv->ipg_clk); + } + + platform_set_drvdata(pdev, priv); + pm_runtime_enable(dev); + + ret = devm_snd_soc_register_component(dev, &fsl_audmix_component, + fsl_audmix_dai, + ARRAY_SIZE(fsl_audmix_dai)); + if (ret) { + dev_err(dev, "failed to register ASoC DAI\n"); + return ret; + } + + priv->pdev = platform_device_register_data(dev, mdrv, 0, NULL, 0); + if (IS_ERR(priv->pdev)) { + ret = PTR_ERR(priv->pdev); + dev_err(dev, "failed to register platform %s: %d\n", mdrv, ret); + } + + return ret; +} + +static int fsl_audmix_remove(struct platform_device *pdev) +{ + struct fsl_audmix *priv = dev_get_drvdata(&pdev->dev); + + if (priv->pdev) + platform_device_unregister(priv->pdev); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_audmix_runtime_resume(struct device *dev) +{ + struct fsl_audmix *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(priv->ipg_clk); + if (ret) { + dev_err(dev, "Failed to enable IPG clock: %d\n", ret); + return ret; + } + + regcache_cache_only(priv->regmap, false); + regcache_mark_dirty(priv->regmap); + + return regcache_sync(priv->regmap); +} + +static int fsl_audmix_runtime_suspend(struct device *dev) +{ + struct fsl_audmix *priv = dev_get_drvdata(dev); + + regcache_cache_only(priv->regmap, true); + + clk_disable_unprepare(priv->ipg_clk); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops fsl_audmix_pm = { + SET_RUNTIME_PM_OPS(fsl_audmix_runtime_suspend, + fsl_audmix_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver fsl_audmix_driver = { + .probe = fsl_audmix_probe, + .remove = fsl_audmix_remove, + .driver = { + .name = "fsl-audmix", + .of_match_table = fsl_audmix_ids, + .pm = &fsl_audmix_pm, + }, +}; +module_platform_driver(fsl_audmix_driver); + +MODULE_DESCRIPTION("NXP AUDMIX ASoC DAI driver"); +MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); +MODULE_ALIAS("platform:fsl-audmix"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_audmix.h b/sound/soc/fsl/fsl_audmix.h new file mode 100644 index 000000000000..7812ffec45c5 --- /dev/null +++ b/sound/soc/fsl/fsl_audmix.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * NXP AUDMIX ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright 2017 NXP + */ + +#ifndef __FSL_AUDMIX_H +#define __FSL_AUDMIX_H + +#define FSL_AUDMIX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) +/* AUDMIX Registers */ +#define FSL_AUDMIX_CTR 0x200 /* Control */ +#define FSL_AUDMIX_STR 0x204 /* Status */ + +#define FSL_AUDMIX_ATCR0 0x208 /* Attenuation Control */ +#define FSL_AUDMIX_ATIVAL0 0x20c /* Attenuation Initial Value */ +#define FSL_AUDMIX_ATSTPUP0 0x210 /* Attenuation step up factor */ +#define FSL_AUDMIX_ATSTPDN0 0x214 /* Attenuation step down factor */ +#define FSL_AUDMIX_ATSTPTGT0 0x218 /* Attenuation step target */ +#define FSL_AUDMIX_ATTNVAL0 0x21c /* Attenuation Value */ +#define FSL_AUDMIX_ATSTP0 0x220 /* Attenuation step number */ + +#define FSL_AUDMIX_ATCR1 0x228 /* Attenuation Control */ +#define FSL_AUDMIX_ATIVAL1 0x22c /* Attenuation Initial Value */ +#define FSL_AUDMIX_ATSTPUP1 0x230 /* Attenuation step up factor */ +#define FSL_AUDMIX_ATSTPDN1 0x234 /* Attenuation step down factor */ +#define FSL_AUDMIX_ATSTPTGT1 0x238 /* Attenuation step target */ +#define FSL_AUDMIX_ATTNVAL1 0x23c /* Attenuation Value */ +#define FSL_AUDMIX_ATSTP1 0x240 /* Attenuation step number */ + +/* AUDMIX Control Register */ +#define FSL_AUDMIX_CTR_MIXCLK_SHIFT 0 +#define FSL_AUDMIX_CTR_MIXCLK_MASK BIT(FSL_AUDMIX_CTR_MIXCLK_SHIFT) +#define FSL_AUDMIX_CTR_MIXCLK(i) ((i) << FSL_AUDMIX_CTR_MIXCLK_SHIFT) +#define FSL_AUDMIX_CTR_OUTSRC_SHIFT 1 +#define FSL_AUDMIX_CTR_OUTSRC_MASK (0x3 << FSL_AUDMIX_CTR_OUTSRC_SHIFT) +#define FSL_AUDMIX_CTR_OUTSRC(i) (((i) << FSL_AUDMIX_CTR_OUTSRC_SHIFT)\ + & FSL_AUDMIX_CTR_OUTSRC_MASK) +#define FSL_AUDMIX_CTR_OUTWIDTH_SHIFT 3 +#define FSL_AUDMIX_CTR_OUTWIDTH_MASK (0x7 << FSL_AUDMIX_CTR_OUTWIDTH_SHIFT) +#define FSL_AUDMIX_CTR_OUTWIDTH(i) (((i) << FSL_AUDMIX_CTR_OUTWIDTH_SHIFT)\ + & FSL_AUDMIX_CTR_OUTWIDTH_MASK) +#define FSL_AUDMIX_CTR_OUTCKPOL_SHIFT 6 +#define FSL_AUDMIX_CTR_OUTCKPOL_MASK BIT(FSL_AUDMIX_CTR_OUTCKPOL_SHIFT) +#define FSL_AUDMIX_CTR_OUTCKPOL(i) ((i) << FSL_AUDMIX_CTR_OUTCKPOL_SHIFT) +#define FSL_AUDMIX_CTR_MASKRTDF_SHIFT 7 +#define FSL_AUDMIX_CTR_MASKRTDF_MASK BIT(FSL_AUDMIX_CTR_MASKRTDF_SHIFT) +#define FSL_AUDMIX_CTR_MASKRTDF(i) ((i) << FSL_AUDMIX_CTR_MASKRTDF_SHIFT) +#define FSL_AUDMIX_CTR_MASKCKDF_SHIFT 8 +#define FSL_AUDMIX_CTR_MASKCKDF_MASK BIT(FSL_AUDMIX_CTR_MASKCKDF_SHIFT) +#define FSL_AUDMIX_CTR_MASKCKDF(i) ((i) << FSL_AUDMIX_CTR_MASKCKDF_SHIFT) +#define FSL_AUDMIX_CTR_SYNCMODE_SHIFT 9 +#define FSL_AUDMIX_CTR_SYNCMODE_MASK BIT(FSL_AUDMIX_CTR_SYNCMODE_SHIFT) +#define FSL_AUDMIX_CTR_SYNCMODE(i) ((i) << FSL_AUDMIX_CTR_SYNCMODE_SHIFT) +#define FSL_AUDMIX_CTR_SYNCSRC_SHIFT 10 +#define FSL_AUDMIX_CTR_SYNCSRC_MASK BIT(FSL_AUDMIX_CTR_SYNCSRC_SHIFT) +#define FSL_AUDMIX_CTR_SYNCSRC(i) ((i) << FSL_AUDMIX_CTR_SYNCSRC_SHIFT) + +/* AUDMIX Status Register */ +#define FSL_AUDMIX_STR_RATEDIFF BIT(0) +#define FSL_AUDMIX_STR_CLKDIFF BIT(1) +#define FSL_AUDMIX_STR_MIXSTAT_SHIFT 2 +#define FSL_AUDMIX_STR_MIXSTAT_MASK (0x3 << FSL_AUDMIX_STR_MIXSTAT_SHIFT) +#define FSL_AUDMIX_STR_MIXSTAT(i) (((i) & FSL_AUDMIX_STR_MIXSTAT_MASK) \ + >> FSL_AUDMIX_STR_MIXSTAT_SHIFT) +/* AUDMIX Attenuation Control Register */ +#define FSL_AUDMIX_ATCR_AT_EN BIT(0) +#define FSL_AUDMIX_ATCR_AT_UPDN BIT(1) +#define FSL_AUDMIX_ATCR_ATSTPDIF_SHIFT 2 +#define FSL_AUDMIX_ATCR_ATSTPDFI_MASK \ + (0xfff << FSL_AUDMIX_ATCR_ATSTPDIF_SHIFT) + +/* AUDMIX Attenuation Initial Value Register */ +#define FSL_AUDMIX_ATIVAL_ATINVAL_MASK 0x3FFFF + +/* AUDMIX Attenuation Step Up Factor Register */ +#define FSL_AUDMIX_ATSTPUP_ATSTEPUP_MASK 0x3FFFF + +/* AUDMIX Attenuation Step Down Factor Register */ +#define FSL_AUDMIX_ATSTPDN_ATSTEPDN_MASK 0x3FFFF + +/* AUDMIX Attenuation Step Target Register */ +#define FSL_AUDMIX_ATSTPTGT_ATSTPTG_MASK 0x3FFFF + +/* AUDMIX Attenuation Value Register */ +#define FSL_AUDMIX_ATTNVAL_ATCURVAL_MASK 0x3FFFF + +/* AUDMIX Attenuation Step Number Register */ +#define FSL_AUDMIX_ATSTP_STPCTR_MASK 0x3FFFF + +#define FSL_AUDMIX_MAX_DAIS 2 +struct fsl_audmix { + struct platform_device *pdev; + struct regmap *regmap; + struct clk *ipg_clk; + u8 tdms; +}; + +#endif /* __FSL_AUDMIX_H */ diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index 78871de35086..e22508301412 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -1,18 +1,14 @@ -/* - * Freescale DMA ALSA SoC PCM driver - * - * Author: Timur Tabi <timur@freescale.com> - * - * Copyright 2007-2010 Freescale Semiconductor, Inc. - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. - * - * This driver implements ASoC support for the Elo DMA controller, which is - * the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms, - * the PCM driver is what handles the DMA buffer. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale DMA ALSA SoC PCM driver +// +// Author: Timur Tabi <timur@freescale.com> +// +// Copyright 2007-2010 Freescale Semiconductor, Inc. +// +// This driver implements ASoC support for the Elo DMA controller, which is +// the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms, +// the PCM driver is what handles the DMA buffer. #include <linux/module.h> #include <linux/init.h> diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h index 78fee97e8036..f19ae765b656 100644 --- a/sound/soc/fsl/fsl_dma.h +++ b/sound/soc/fsl/fsl_dma.h @@ -1,9 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #ifndef _MPC8610_PCM_H diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index 3623aa9a6f2e..bad0dfed6b68 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -218,7 +218,7 @@ static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); struct clk *clksrc = esai_priv->extalclk; - bool tx = clk_id <= ESAI_HCKT_EXTAL; + bool tx = (clk_id <= ESAI_HCKT_EXTAL || esai_priv->synchronous); bool in = dir == SND_SOC_CLOCK_IN; u32 ratio, ecr = 0; unsigned long clk_rate; @@ -251,9 +251,9 @@ static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, break; case ESAI_HCKT_EXTAL: ecr |= ESAI_ECR_ETI; - /* fall through */ + break; case ESAI_HCKR_EXTAL: - ecr |= ESAI_ECR_ERI; + ecr |= esai_priv->synchronous ? ESAI_ECR_ETI : ESAI_ECR_ERI; break; default: return -EINVAL; @@ -537,10 +537,18 @@ static int fsl_esai_hw_params(struct snd_pcm_substream *substream, bclk = params_rate(params) * slot_width * esai_priv->slots; - ret = fsl_esai_set_bclk(dai, tx, bclk); + ret = fsl_esai_set_bclk(dai, esai_priv->synchronous || tx, bclk); if (ret) return ret; + mask = ESAI_xCR_xSWS_MASK; + val = ESAI_xCR_xSWS(slot_width, width); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val); + /* Recording in synchronous mode needs to set TCR also */ + if (!tx && esai_priv->synchronous) + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, val); + /* Use Normal mode to support monaural audio */ regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ? @@ -556,10 +564,9 @@ static int fsl_esai_hw_params(struct snd_pcm_substream *substream, regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), mask, val); - mask = ESAI_xCR_xSWS_MASK | (tx ? ESAI_xCR_PADC : 0); - val = ESAI_xCR_xSWS(slot_width, width) | (tx ? ESAI_xCR_PADC : 0); - - regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val); + if (tx) + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, + ESAI_xCR_PADC, ESAI_xCR_PADC); /* Remove ESAI personal reset by configuring ESAI_PCRC and ESAI_PRRC */ regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index 40c07e756481..f7f2d29f1bfe 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -151,12 +151,9 @@ static inline int get_clk_div(struct fsl_micfil *micfil, { u32 ctrl2_reg; long mclk_rate; - int osr; int clk_div; regmap_read(micfil->regmap, REG_MICFIL_CTRL2, &ctrl2_reg); - osr = 16 - ((ctrl2_reg & MICFIL_CTRL2_CICOSR_MASK) - >> MICFIL_CTRL2_CICOSR_SHIFT); mclk_rate = clk_get_rate(micfil->mclk); diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index db9e0872f73d..8593269156bd 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -9,6 +9,7 @@ #include <linux/dmaengine.h> #include <linux/module.h> #include <linux/of_address.h> +#include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/time.h> @@ -268,12 +269,14 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, case SND_SOC_DAIFMT_CBS_CFS: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; val_cr4 |= FSL_SAI_CR4_FSD_MSTR; + sai->is_slave_mode = false; break; case SND_SOC_DAIFMT_CBM_CFM: sai->is_slave_mode = true; break; case SND_SOC_DAIFMT_CBS_CFM: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; + sai->is_slave_mode = false; break; case SND_SOC_DAIFMT_CBM_CFS: val_cr4 |= FSL_SAI_CR4_FSD_MSTR; @@ -899,6 +902,8 @@ static int fsl_sai_probe(struct platform_device *pdev) platform_set_drvdata(pdev, sai); + pm_runtime_enable(&pdev->dev); + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, &fsl_sai_dai, 1); if (ret) @@ -910,6 +915,13 @@ static int fsl_sai_probe(struct platform_device *pdev) return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); } +static int fsl_sai_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + static const struct of_device_id fsl_sai_ids[] = { { .compatible = "fsl,vf610-sai", }, { .compatible = "fsl,imx6sx-sai", }, @@ -918,8 +930,8 @@ static const struct of_device_id fsl_sai_ids[] = { }; MODULE_DEVICE_TABLE(of, fsl_sai_ids); -#ifdef CONFIG_PM_SLEEP -static int fsl_sai_suspend(struct device *dev) +#ifdef CONFIG_PM +static int fsl_sai_runtime_suspend(struct device *dev) { struct fsl_sai *sai = dev_get_drvdata(dev); @@ -929,7 +941,7 @@ static int fsl_sai_suspend(struct device *dev) return 0; } -static int fsl_sai_resume(struct device *dev) +static int fsl_sai_runtime_resume(struct device *dev) { struct fsl_sai *sai = dev_get_drvdata(dev); @@ -941,14 +953,18 @@ static int fsl_sai_resume(struct device *dev) regmap_write(sai->regmap, FSL_SAI_RCSR, 0); return regcache_sync(sai->regmap); } -#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ static const struct dev_pm_ops fsl_sai_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(fsl_sai_suspend, fsl_sai_resume) + SET_RUNTIME_PM_OPS(fsl_sai_runtime_suspend, + fsl_sai_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; static struct platform_driver fsl_sai_driver = { .probe = fsl_sai_probe, + .remove = fsl_sai_remove, .driver = { .name = "fsl-sai", .pm = &fsl_sai_pm_ops, diff --git a/sound/soc/fsl/fsl_utils.c b/sound/soc/fsl/fsl_utils.c index 9981668ab590..040d06b89f00 100644 --- a/sound/soc/fsl/fsl_utils.c +++ b/sound/soc/fsl/fsl_utils.c @@ -71,6 +71,7 @@ int fsl_asoc_get_dma_channel(struct device_node *ssi_np, iprop = of_get_property(dma_np, "cell-index", NULL); if (!iprop) { of_node_put(dma_np); + of_node_put(dma_channel_np); return -EINVAL; } *dma_id = be32_to_cpup(iprop); diff --git a/sound/soc/fsl/imx-audmix.c b/sound/soc/fsl/imx-audmix.c new file mode 100644 index 000000000000..9aaf3e5b45b9 --- /dev/null +++ b/sound/soc/fsl/imx-audmix.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/pm_runtime.h> +#include "fsl_sai.h" +#include "fsl_audmix.h" + +struct imx_audmix { + struct platform_device *pdev; + struct snd_soc_card card; + struct platform_device *audmix_pdev; + struct platform_device *out_pdev; + struct clk *cpu_mclk; + int num_dai; + struct snd_soc_dai_link *dai; + int num_dai_conf; + struct snd_soc_codec_conf *dai_conf; + int num_dapm_routes; + struct snd_soc_dapm_route *dapm_routes; +}; + +static const u32 imx_audmix_rates[] = { + 8000, 12000, 16000, 24000, 32000, 48000, 64000, 96000, +}; + +static const struct snd_pcm_hw_constraint_list imx_audmix_rate_constraints = { + .count = ARRAY_SIZE(imx_audmix_rates), + .list = imx_audmix_rates, +}; + +static int imx_audmix_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_audmix *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = rtd->card->dev; + unsigned long clk_rate = clk_get_rate(priv->cpu_mclk); + int ret; + + if (clk_rate % 24576000 == 0) { + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &imx_audmix_rate_constraints); + if (ret < 0) + return ret; + } else { + dev_warn(dev, "mclk may be not supported %lu\n", clk_rate); + } + + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, + 1, 8); + if (ret < 0) + return ret; + + return snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, + FSL_AUDMIX_FORMATS); +} + +static int imx_audmix_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF; + u32 channels = params_channels(params); + int ret, dir; + + /* For playback the AUDMIX is slave, and for record is master */ + fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM; + dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN; + + /* set DAI configuration */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, FSL_SAI_CLK_MAST1, 0, dir); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + /* + * Per datasheet, AUDMIX expects 8 slots and 32 bits + * for every slot in TDM mode. + */ + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, BIT(channels) - 1, + BIT(channels) - 1, 8, 32); + if (ret) + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + + return ret; +} + +static int imx_audmix_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF; + int ret; + + if (!tx) + return 0; + + /* For playback the AUDMIX is slave */ + fmt |= SND_SOC_DAIFMT_CBM_CFM; + + /* set AUDMIX DAI configuration */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) + dev_err(dev, "failed to set AUDMIX DAI fmt: %d\n", ret); + + return ret; +} + +static struct snd_soc_ops imx_audmix_fe_ops = { + .startup = imx_audmix_fe_startup, + .hw_params = imx_audmix_fe_hw_params, +}; + +static struct snd_soc_ops imx_audmix_be_ops = { + .hw_params = imx_audmix_be_hw_params, +}; + +static int imx_audmix_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *audmix_np = NULL, *out_cpu_np = NULL; + struct platform_device *audmix_pdev = NULL; + struct platform_device *cpu_pdev; + struct of_phandle_args args; + struct imx_audmix *priv; + int i, num_dai, ret; + const char *fe_name_pref = "HiFi-AUDMIX-FE-"; + char *be_name, *be_pb, *be_cp, *dai_name, *capture_dai_name; + + if (pdev->dev.parent) { + audmix_np = pdev->dev.parent->of_node; + } else { + dev_err(&pdev->dev, "Missing parent device.\n"); + return -EINVAL; + } + + if (!audmix_np) { + dev_err(&pdev->dev, "Missing DT node for parent device.\n"); + return -EINVAL; + } + + audmix_pdev = of_find_device_by_node(audmix_np); + if (!audmix_pdev) { + dev_err(&pdev->dev, "Missing AUDMIX platform device for %s\n", + np->full_name); + return -EINVAL; + } + put_device(&audmix_pdev->dev); + + num_dai = of_count_phandle_with_args(audmix_np, "dais", NULL); + if (num_dai != FSL_AUDMIX_MAX_DAIS) { + dev_err(&pdev->dev, "Need 2 dais to be provided for %s\n", + audmix_np->full_name); + return -EINVAL; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->num_dai = 2 * num_dai; + priv->dai = devm_kzalloc(&pdev->dev, priv->num_dai * + sizeof(struct snd_soc_dai_link), GFP_KERNEL); + if (!priv->dai) + return -ENOMEM; + + priv->num_dai_conf = num_dai; + priv->dai_conf = devm_kzalloc(&pdev->dev, priv->num_dai_conf * + sizeof(struct snd_soc_codec_conf), + GFP_KERNEL); + if (!priv->dai_conf) + return -ENOMEM; + + priv->num_dapm_routes = 3 * num_dai; + priv->dapm_routes = devm_kzalloc(&pdev->dev, priv->num_dapm_routes * + sizeof(struct snd_soc_dapm_route), + GFP_KERNEL); + if (!priv->dapm_routes) + return -ENOMEM; + + for (i = 0; i < num_dai; i++) { + ret = of_parse_phandle_with_args(audmix_np, "dais", NULL, i, + &args); + if (ret < 0) { + dev_err(&pdev->dev, "of_parse_phandle_with_args failed\n"); + return ret; + } + + cpu_pdev = of_find_device_by_node(args.np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + return -EINVAL; + } + put_device(&cpu_pdev->dev); + + dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%s", + fe_name_pref, args.np->full_name + 1); + + dev_info(pdev->dev.parent, "DAI FE name:%s\n", dai_name); + + if (i == 0) { + out_cpu_np = args.np; + capture_dai_name = + devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s", + dai_name, "CPU-Capture"); + } + + priv->dai[i].name = dai_name; + priv->dai[i].stream_name = "HiFi-AUDMIX-FE"; + priv->dai[i].codec_dai_name = "snd-soc-dummy-dai"; + priv->dai[i].codec_name = "snd-soc-dummy"; + priv->dai[i].cpu_of_node = args.np; + priv->dai[i].cpu_dai_name = dev_name(&cpu_pdev->dev); + priv->dai[i].platform_of_node = args.np; + priv->dai[i].dynamic = 1; + priv->dai[i].dpcm_playback = 1; + priv->dai[i].dpcm_capture = (i == 0 ? 1 : 0); + priv->dai[i].ignore_pmdown_time = 1; + priv->dai[i].ops = &imx_audmix_fe_ops; + + /* Add AUDMIX Backend */ + be_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "audmix-%d", i); + be_pb = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "AUDMIX-Playback-%d", i); + be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "AUDMIX-Capture-%d", i); + + priv->dai[num_dai + i].name = be_name; + priv->dai[num_dai + i].codec_dai_name = "snd-soc-dummy-dai"; + priv->dai[num_dai + i].codec_name = "snd-soc-dummy"; + priv->dai[num_dai + i].cpu_of_node = audmix_np; + priv->dai[num_dai + i].cpu_dai_name = be_name; + priv->dai[num_dai + i].platform_name = "snd-soc-dummy"; + priv->dai[num_dai + i].no_pcm = 1; + priv->dai[num_dai + i].dpcm_playback = 1; + priv->dai[num_dai + i].dpcm_capture = 1; + priv->dai[num_dai + i].ignore_pmdown_time = 1; + priv->dai[num_dai + i].ops = &imx_audmix_be_ops; + + priv->dai_conf[i].of_node = args.np; + priv->dai_conf[i].name_prefix = dai_name; + + priv->dapm_routes[i].source = + devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s", + dai_name, "CPU-Playback"); + priv->dapm_routes[i].sink = be_pb; + priv->dapm_routes[num_dai + i].source = be_pb; + priv->dapm_routes[num_dai + i].sink = be_cp; + priv->dapm_routes[2 * num_dai + i].source = be_cp; + priv->dapm_routes[2 * num_dai + i].sink = capture_dai_name; + } + + cpu_pdev = of_find_device_by_node(out_cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + return -EINVAL; + } + put_device(&cpu_pdev->dev); + + priv->cpu_mclk = devm_clk_get(&cpu_pdev->dev, "mclk1"); + if (IS_ERR(priv->cpu_mclk)) { + ret = PTR_ERR(priv->cpu_mclk); + dev_err(&cpu_pdev->dev, "failed to get DAI mclk1: %d\n", ret); + return -EINVAL; + } + + priv->audmix_pdev = audmix_pdev; + priv->out_pdev = cpu_pdev; + + priv->card.dai_link = priv->dai; + priv->card.num_links = priv->num_dai; + priv->card.codec_conf = priv->dai_conf; + priv->card.num_configs = priv->num_dai_conf; + priv->card.dapm_routes = priv->dapm_routes; + priv->card.num_dapm_routes = priv->num_dapm_routes; + priv->card.dev = pdev->dev.parent; + priv->card.owner = THIS_MODULE; + priv->card.name = "imx-audmix"; + + platform_set_drvdata(pdev, &priv->card); + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(pdev->dev.parent, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed\n"); + return ret; + } + + return ret; +} + +static struct platform_driver imx_audmix_driver = { + .probe = imx_audmix_probe, + .driver = { + .name = "imx-audmix", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(imx_audmix_driver); + +MODULE_DESCRIPTION("NXP AUDMIX ASoC machine driver"); +MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); +MODULE_ALIAS("platform:imx-audmix"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c index 99e07b01a2ce..04e59e66711d 100644 --- a/sound/soc/fsl/imx-audmux.c +++ b/sound/soc/fsl/imx-audmux.c @@ -1,21 +1,11 @@ -/* - * Copyright 2012 Freescale Semiconductor, Inc. - * Copyright 2012 Linaro Ltd. - * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> - * - * Initial development of this code was funded by - * Phytec Messtechnik GmbH, http://www.phytec.de - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright 2012 Freescale Semiconductor, Inc. +// Copyright 2012 Linaro Ltd. +// Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> +// +// Initial development of this code was funded by +// Phytec Messtechnik GmbH, http://www.phytec.de #include <linux/clk.h> #include <linux/debugfs.h> diff --git a/sound/soc/fsl/imx-es8328.c b/sound/soc/fsl/imx-es8328.c index 9953438086e4..c9d8739b04a9 100644 --- a/sound/soc/fsl/imx-es8328.c +++ b/sound/soc/fsl/imx-es8328.c @@ -1,14 +1,7 @@ -/* - * Copyright 2012 Freescale Semiconductor, Inc. - * Copyright 2012 Linaro Ltd. - * - * The code contained herein is licensed under the GNU General Public - * License. You may obtain a copy of the GNU General Public License - * Version 2 or later at the following locations: - * - * http://www.opensource.org/licenses/gpl-license.html - * http://www.gnu.org/copyleft/gpl.html - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright 2012 Freescale Semiconductor, Inc. +// Copyright 2012 Linaro Ltd. #include <linux/gpio.h> #include <linux/module.h> diff --git a/sound/soc/fsl/imx-mc13783.c b/sound/soc/fsl/imx-mc13783.c index 9d19b808f634..545815a27074 100644 --- a/sound/soc/fsl/imx-mc13783.c +++ b/sound/soc/fsl/imx-mc13783.c @@ -1,17 +1,11 @@ -/* - * imx-mc13783.c -- SoC audio for imx based boards with mc13783 codec - * - * Copyright 2012 Philippe Retornaz, <philippe.retornaz@epfl.ch> - * - * Heavly based on phycore-mc13783: - * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// imx-mc13783.c -- SoC audio for imx based boards with mc13783 codec +// +// Copyright 2012 Philippe Retornaz, <philippe.retornaz@epfl.ch> +// +// Heavly based on phycore-mc13783: +// Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> #include <linux/module.h> #include <linux/moduleparam.h> diff --git a/sound/soc/fsl/imx-pcm-fiq.c b/sound/soc/fsl/imx-pcm-fiq.c index 0578f3486847..c49aea4fba56 100644 --- a/sound/soc/fsl/imx-pcm-fiq.c +++ b/sound/soc/fsl/imx-pcm-fiq.c @@ -1,16 +1,11 @@ -/* - * imx-pcm-fiq.c -- ALSA Soc Audio Layer - * - * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> - * - * This code is based on code copyrighted by Freescale, - * Liam Girdwood, Javier Martin and probably others. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// imx-pcm-fiq.c -- ALSA Soc Audio Layer +// +// Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> +// +// This code is based on code copyrighted by Freescale, +// Liam Girdwood, Javier Martin and probably others. + #include <linux/clk.h> #include <linux/delay.h> #include <linux/device.h> diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h index 133c4470acad..5dd406774d3e 100644 --- a/sound/soc/fsl/imx-pcm.h +++ b/sound/soc/fsl/imx-pcm.h @@ -1,13 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> * * This code is based on code copyrighted by Freescale, * Liam Girdwood, Javier Martin and probably others. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. */ #ifndef _IMX_PCM_H diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c index 797d66e43d49..4f7f210beb18 100644 --- a/sound/soc/fsl/imx-spdif.c +++ b/sound/soc/fsl/imx-spdif.c @@ -1,13 +1,6 @@ -/* - * Copyright (C) 2013 Freescale Semiconductor, Inc. - * - * The code contained herein is licensed under the GNU General Public - * License. You may obtain a copy of the GNU General Public License - * Version 2 or later at the following locations: - * - * http://www.opensource.org/licenses/gpl-license.html - * http://www.gnu.org/copyleft/gpl.html - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (C) 2013 Freescale Semiconductor, Inc. #include <linux/module.h> #include <linux/of_platform.h> diff --git a/sound/soc/fsl/imx-ssi.c b/sound/soc/fsl/imx-ssi.c index 06790615e04e..9038b61317be 100644 --- a/sound/soc/fsl/imx-ssi.c +++ b/sound/soc/fsl/imx-ssi.c @@ -1,35 +1,28 @@ -/* - * imx-ssi.c -- ALSA Soc Audio Layer - * - * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> - * - * This code is based on code copyrighted by Freescale, - * Liam Girdwood, Javier Martin and probably others. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * - * The i.MX SSI core has some nasty limitations in AC97 mode. While most - * sane processor vendors have a FIFO per AC97 slot, the i.MX has only - * one FIFO which combines all valid receive slots. We cannot even select - * which slots we want to receive. The WM9712 with which this driver - * was developed with always sends GPIO status data in slot 12 which - * we receive in our (PCM-) data stream. The only chance we have is to - * manually skip this data in the FIQ handler. With sampling rates different - * from 48000Hz not every frame has valid receive data, so the ratio - * between pcm data and GPIO status data changes. Our FIQ handler is not - * able to handle this, hence this driver only works with 48000Hz sampling - * rate. - * Reading and writing AC97 registers is another challenge. The core - * provides us status bits when the read register is updated with *another* - * value. When we read the same register two times (and the register still - * contains the same value) these status bits are not set. We work - * around this by not polling these bits but only wait a fixed delay. - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// imx-ssi.c -- ALSA Soc Audio Layer +// +// Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> +// +// This code is based on code copyrighted by Freescale, +// Liam Girdwood, Javier Martin and probably others. +// +// The i.MX SSI core has some nasty limitations in AC97 mode. While most +// sane processor vendors have a FIFO per AC97 slot, the i.MX has only +// one FIFO which combines all valid receive slots. We cannot even select +// which slots we want to receive. The WM9712 with which this driver +// was developed with always sends GPIO status data in slot 12 which +// we receive in our (PCM-) data stream. The only chance we have is to +// manually skip this data in the FIQ handler. With sampling rates different +// from 48000Hz not every frame has valid receive data, so the ratio +// between pcm data and GPIO status data changes. Our FIQ handler is not +// able to handle this, hence this driver only works with 48000Hz sampling +// rate. +// Reading and writing AC97 registers is another challenge. The core +// provides us status bits when the read register is updated with *another* +// value. When we read the same register two times (and the register still +// contains the same value) these status bits are not set. We work +// around this by not polling these bits but only wait a fixed delay. #include <linux/clk.h> #include <linux/delay.h> diff --git a/sound/soc/fsl/imx-ssi.h b/sound/soc/fsl/imx-ssi.h index be6562365b6a..19cd0937e740 100644 --- a/sound/soc/fsl/imx-ssi.h +++ b/sound/soc/fsl/imx-ssi.h @@ -1,8 +1,4 @@ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _IMX_SSI_H #define _IMX_SSI_H diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c index c1a4544eb16b..ccf9301889fe 100644 --- a/sound/soc/fsl/mpc5200_dma.c +++ b/sound/soc/fsl/mpc5200_dma.c @@ -1,10 +1,10 @@ -/* - * Freescale MPC5200 PSC DMA - * ALSA SoC Platform driver - * - * Copyright (C) 2008 Secret Lab Technologies Ltd. - * Copyright (C) 2009 Jon Smirl, Digispeaker - */ +// SPDX-License-Identifier: GPL-2.0-only +// +// Freescale MPC5200 PSC DMA +// ALSA SoC Platform driver +// +// Copyright (C) 2008 Secret Lab Technologies Ltd. +// Copyright (C) 2009 Jon Smirl, Digispeaker #include <linux/module.h> #include <linux/of_device.h> diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c index 07ee355ee385..e5b9c04d1565 100644 --- a/sound/soc/fsl/mpc5200_psc_ac97.c +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -1,13 +1,9 @@ -/* - * linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip. - * - * Copyright (C) 2009 Jon Smirl, Digispeaker - * Author: Jon Smirl <jonsmirl@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip. +// +// Copyright (C) 2009 Jon Smirl, Digispeaker +// Author: Jon Smirl <jonsmirl@gmail.com> #include <linux/module.h> #include <linux/of_device.h> diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c index d8232943ccb6..9bc01f374b39 100644 --- a/sound/soc/fsl/mpc5200_psc_i2s.c +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -1,10 +1,10 @@ -/* - * Freescale MPC5200 PSC in I2S mode - * ALSA SoC Digital Audio Interface (DAI) driver - * - * Copyright (C) 2008 Secret Lab Technologies Ltd. - * Copyright (C) 2009 Jon Smirl, Digispeaker - */ +// SPDX-License-Identifier: GPL-2.0-only +// +// Freescale MPC5200 PSC in I2S mode +// ALSA SoC Digital Audio Interface (DAI) driver +// +// Copyright (C) 2008 Secret Lab Technologies Ltd. +// Copyright (C) 2009 Jon Smirl, Digispeaker #include <linux/module.h> #include <linux/of_device.h> diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index a639b52c16f6..f6261a3eeb0f 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -1,14 +1,10 @@ -/** - * Freescale MPC8610HPCD ALSA SoC Machine driver - * - * Author: Timur Tabi <timur@freescale.com> - * - * Copyright 2007-2010 Freescale Semiconductor, Inc. - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale MPC8610HPCD ALSA SoC Machine driver +// +// Author: Timur Tabi <timur@freescale.com> +// +// Copyright 2007-2010 Freescale Semiconductor, Inc. #include <linux/module.h> #include <linux/interrupt.h> diff --git a/sound/soc/fsl/mx27vis-aic32x4.c b/sound/soc/fsl/mx27vis-aic32x4.c index d7ec3d20065c..37a4520aef62 100644 --- a/sound/soc/fsl/mx27vis-aic32x4.c +++ b/sound/soc/fsl/mx27vis-aic32x4.c @@ -1,25 +1,10 @@ -/* - * mx27vis-aic32x4.c - * - * Copyright 2011 Vista Silicon S.L. - * - * Author: Javier Martin <javier.martin@vista-silicon.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// mx27vis-aic32x4.c +// +// Copyright 2011 Vista Silicon S.L. +// +// Author: Javier Martin <javier.martin@vista-silicon.com> #include <linux/module.h> #include <linux/moduleparam.h> diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c index 41c623c55c16..80384f70878d 100644 --- a/sound/soc/fsl/p1022_ds.c +++ b/sound/soc/fsl/p1022_ds.c @@ -1,14 +1,10 @@ -/** - * Freescale P1022DS ALSA SoC Machine driver - * - * Author: Timur Tabi <timur@freescale.com> - * - * Copyright 2010 Freescale Semiconductor, Inc. - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale P1022DS ALSA SoC Machine driver +// +// Author: Timur Tabi <timur@freescale.com> +// +// Copyright 2010 Freescale Semiconductor, Inc. #include <linux/module.h> #include <linux/fsl/guts.h> diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c index 4afbdd610bfa..1c32c2d8c6b0 100644 --- a/sound/soc/fsl/p1022_rdk.c +++ b/sound/soc/fsl/p1022_rdk.c @@ -1,21 +1,17 @@ -/** - * Freescale P1022RDK ALSA SoC Machine driver - * - * Author: Timur Tabi <timur@freescale.com> - * - * Copyright 2012 Freescale Semiconductor, Inc. - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. - * - * Note: in order for audio to work correctly, the output controls need - * to be enabled, because they control the clock. So for playback, for - * example: - * - * amixer sset 'Left Output Mixer PCM' on - * amixer sset 'Right Output Mixer PCM' on - */ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale P1022RDK ALSA SoC Machine driver +// +// Author: Timur Tabi <timur@freescale.com> +// +// Copyright 2012 Freescale Semiconductor, Inc. +// +// Note: in order for audio to work correctly, the output controls need +// to be enabled, because they control the clock. So for playback, for +// example: +// +// amixer sset 'Left Output Mixer PCM' on +// amixer sset 'Right Output Mixer PCM' on #include <linux/module.h> #include <linux/fsl/guts.h> diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c index e339f36cea95..a7fe4ad25c52 100644 --- a/sound/soc/fsl/pcm030-audio-fabric.c +++ b/sound/soc/fsl/pcm030-audio-fabric.c @@ -1,14 +1,10 @@ -/* - * Phytec pcm030 driver for the PSC of the Freescale MPC52xx - * configured as AC97 interface - * - * Copyright 2008 Jon Smirl, Digispeaker - * Author: Jon Smirl <jonsmirl@gmail.com> - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// Phytec pcm030 driver for the PSC of the Freescale MPC52xx +// configured as AC97 interface +// +// Copyright 2008 Jon Smirl, Digispeaker +// Author: Jon Smirl <jonsmirl@gmail.com> #include <linux/init.h> #include <linux/module.h> diff --git a/sound/soc/fsl/phycore-ac97.c b/sound/soc/fsl/phycore-ac97.c index 66fb6c4614d2..fe7ba6db7c96 100644 --- a/sound/soc/fsl/phycore-ac97.c +++ b/sound/soc/fsl/phycore-ac97.c @@ -1,14 +1,8 @@ -/* - * phycore-ac97.c -- SoC audio for imx_phycore in AC97 mode - * - * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// phycore-ac97.c -- SoC audio for imx_phycore in AC97 mode +// +// Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> #include <linux/module.h> #include <linux/moduleparam.h> diff --git a/sound/soc/fsl/wm1133-ev1.c b/sound/soc/fsl/wm1133-ev1.c index 2f80b21b2921..aad24ccbef90 100644 --- a/sound/soc/fsl/wm1133-ev1.c +++ b/sound/soc/fsl/wm1133-ev1.c @@ -1,16 +1,11 @@ -/* - * wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS - * - * Copyright (c) 2010 Wolfson Microelectronics plc - * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> - * - * Based on an earlier driver for the same hardware by Liam Girdwood. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS +// +// Copyright (c) 2010 Wolfson Microelectronics plc +// Author: Mark Brown <broonie@opensource.wolfsonmicro.com> +// +// Based on an earlier driver for the same hardware by Liam Girdwood. #include <linux/platform_device.h> #include <linux/clk.h> diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index 69bc4848d787..ec7e673ba475 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -22,37 +22,6 @@ #define DPCM_SELECTABLE 1 -struct graph_priv { - struct snd_soc_card snd_card; - struct graph_dai_props { - struct asoc_simple_dai *cpu_dai; - struct asoc_simple_dai *codec_dai; - struct snd_soc_dai_link_component codecs; /* single codec */ - struct snd_soc_dai_link_component platforms; - struct asoc_simple_card_data adata; - struct snd_soc_codec_conf *codec_conf; - unsigned int mclk_fs; - } *dai_props; - struct asoc_simple_jack hp_jack; - struct asoc_simple_jack mic_jack; - struct snd_soc_dai_link *dai_link; - struct asoc_simple_dai *dais; - struct snd_soc_codec_conf *codec_conf; - struct gpio_desc *pa_gpio; -}; - -struct link_info { - int dais; /* number of dai */ - int link; /* number of link */ - int conf; /* number of codec_conf */ - int cpu; /* turn for CPU / Codec */ -}; - -#define graph_priv_to_card(priv) (&(priv)->snd_card) -#define graph_priv_to_props(priv, i) ((priv)->dai_props + (i)) -#define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev) -#define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i)) - #define PREFIX "audio-graph-card," static int graph_outdrv_event(struct snd_soc_dapm_widget *w, @@ -60,7 +29,7 @@ static int graph_outdrv_event(struct snd_soc_dapm_widget *w, int event) { struct snd_soc_dapm_context *dapm = w->dapm; - struct graph_priv *priv = snd_soc_card_get_drvdata(dapm->card); + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(dapm->card); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -82,127 +51,156 @@ static const struct snd_soc_dapm_widget graph_dapm_widgets[] = { SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), }; -static int graph_startup(struct snd_pcm_substream *substream) +static const struct snd_soc_ops graph_ops = { + .startup = asoc_simple_startup, + .shutdown = asoc_simple_shutdown, + .hw_params = asoc_simple_hw_params, +}; + +static int graph_get_dai_id(struct device_node *ep) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); + struct device_node *node; + struct device_node *endpoint; + struct of_endpoint info; + int i, id; int ret; - ret = asoc_simple_card_clk_enable(dai_props->cpu_dai); - if (ret) + /* use driver specified DAI ID if exist */ + ret = snd_soc_get_dai_id(ep); + if (ret != -ENOTSUPP) return ret; - ret = asoc_simple_card_clk_enable(dai_props->codec_dai); - if (ret) - asoc_simple_card_clk_disable(dai_props->cpu_dai); + /* use endpoint/port reg if exist */ + ret = of_graph_parse_endpoint(ep, &info); + if (ret == 0) { + /* + * Because it will count port/endpoint if it doesn't have "reg". + * But, we can't judge whether it has "no reg", or "reg = <0>" + * only of_graph_parse_endpoint(). + * We need to check "reg" property + */ + if (of_get_property(ep, "reg", NULL)) + return info.id; - return ret; -} + node = of_get_parent(ep); + of_node_put(node); + if (of_get_property(node, "reg", NULL)) + return info.port; + } + node = of_graph_get_port_parent(ep); -static void graph_shutdown(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); + /* + * Non HDMI sound case, counting port/endpoint on its DT + * is enough. Let's count it. + */ + i = 0; + id = -1; + for_each_endpoint_of_node(node, endpoint) { + if (endpoint == ep) + id = i; + i++; + } - asoc_simple_card_clk_disable(dai_props->cpu_dai); + of_node_put(node); + + if (id < 0) + return -ENODEV; - asoc_simple_card_clk_disable(dai_props->codec_dai); + return id; } -static int graph_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) +static int asoc_simple_parse_dai(struct device_node *ep, + struct snd_soc_dai_link_component *dlc, + struct device_node **dai_of_node, + const char **dai_name, + int *is_single_link) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); - unsigned int mclk, mclk_fs = 0; - int ret = 0; - - if (dai_props->mclk_fs) - mclk_fs = dai_props->mclk_fs; - - if (mclk_fs) { - mclk = params_rate(params) * mclk_fs; - ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, - SND_SOC_CLOCK_IN); - if (ret && ret != -ENOTSUPP) - goto err; - - ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, - SND_SOC_CLOCK_OUT); - if (ret && ret != -ENOTSUPP) - goto err; + struct device_node *node; + struct of_phandle_args args; + int ret; + + /* + * Use snd_soc_dai_link_component instead of legacy style. + * It is only for codec, but cpu will be supported in the future. + * see + * soc-core.c :: snd_soc_init_multicodec() + */ + if (dlc) { + dai_name = &dlc->dai_name; + dai_of_node = &dlc->of_node; } - return 0; -err: - return ret; -} -static const struct snd_soc_ops graph_ops = { - .startup = graph_startup, - .shutdown = graph_shutdown, - .hw_params = graph_hw_params, -}; + if (!ep) + return 0; + if (!dai_name) + return 0; -static int graph_dai_init(struct snd_soc_pcm_runtime *rtd) -{ - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); - int ret = 0; + node = of_graph_get_port_parent(ep); - ret = asoc_simple_card_init_dai(rtd->codec_dai, - dai_props->codec_dai); - if (ret < 0) - return ret; + /* Get dai->name */ + args.np = node; + args.args[0] = graph_get_dai_id(ep); + args.args_count = (of_graph_get_endpoint_count(node) > 1); - ret = asoc_simple_card_init_dai(rtd->cpu_dai, - dai_props->cpu_dai); + ret = snd_soc_get_dai_name(&args, dai_name); if (ret < 0) return ret; - return 0; -} - -static int graph_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) -{ - struct graph_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); + *dai_of_node = node; - asoc_simple_card_convert_fixup(&dai_props->adata, params); + if (is_single_link) + *is_single_link = of_graph_get_endpoint_count(node) == 1; return 0; } -static void graph_get_conversion(struct device *dev, - struct device_node *ep, - struct asoc_simple_card_data *adata) +static void graph_parse_convert(struct device *dev, + struct device_node *ep, + struct asoc_simple_data *adata) { struct device_node *top = dev->of_node; struct device_node *port = of_get_parent(ep); struct device_node *ports = of_get_parent(port); struct device_node *node = of_graph_get_port_parent(ep); - asoc_simple_card_parse_convert(dev, top, NULL, adata); - asoc_simple_card_parse_convert(dev, node, PREFIX, adata); - asoc_simple_card_parse_convert(dev, ports, NULL, adata); - asoc_simple_card_parse_convert(dev, port, NULL, adata); - asoc_simple_card_parse_convert(dev, ep, NULL, adata); + asoc_simple_parse_convert(dev, top, NULL, adata); + asoc_simple_parse_convert(dev, node, PREFIX, adata); + asoc_simple_parse_convert(dev, ports, NULL, adata); + asoc_simple_parse_convert(dev, port, NULL, adata); + asoc_simple_parse_convert(dev, ep, NULL, adata); + + of_node_put(port); + of_node_put(ports); + of_node_put(node); } -static int graph_dai_link_of_dpcm(struct graph_priv *priv, +static void graph_parse_mclk_fs(struct device_node *top, + struct device_node *ep, + struct simple_dai_props *props) +{ + struct device_node *port = of_get_parent(ep); + struct device_node *ports = of_get_parent(port); + struct device_node *node = of_graph_get_port_parent(ep); + + of_property_read_u32(top, "mclk-fs", &props->mclk_fs); + of_property_read_u32(ports, "mclk-fs", &props->mclk_fs); + of_property_read_u32(port, "mclk-fs", &props->mclk_fs); + of_property_read_u32(ep, "mclk-fs", &props->mclk_fs); + + of_node_put(port); + of_node_put(ports); + of_node_put(node); +} + +static int graph_dai_link_of_dpcm(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li, int dup_codec) { - struct device *dev = graph_priv_to_dev(priv); - struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, li->link); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, li->link); + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); struct device_node *top = dev->of_node; struct device_node *ep = li->cpu ? cpu_ep : codec_ep; struct device_node *port; @@ -224,18 +222,12 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, dev_dbg(dev, "link_of DPCM (%pOF)\n", ep); - of_property_read_u32(top, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(ports, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(port, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(ep, "mclk-fs", &dai_props->mclk_fs); - - graph_get_conversion(dev, ep, &dai_props->adata); - of_node_put(ports); of_node_put(port); of_node_put(node); if (li->cpu) { + int is_single_links = 0; /* BE is dummy */ codecs->of_node = NULL; @@ -249,23 +241,22 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, dai = dai_props->cpu_dai = &priv->dais[li->dais++]; - ret = asoc_simple_card_parse_graph_cpu(ep, dai_link); + ret = asoc_simple_parse_cpu(ep, dai_link, &is_single_links); if (ret) return ret; - ret = asoc_simple_card_parse_clk_cpu(dev, ep, dai_link, dai); + ret = asoc_simple_parse_clk_cpu(dev, ep, dai_link, dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "fe.%s", - dai_link->cpu_dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "fe.%s", + dai_link->cpu_dai_name); if (ret < 0) return ret; /* card->num_links includes Codec */ - asoc_simple_card_canonicalize_cpu(dai_link, - of_graph_get_endpoint_count(dai_link->cpu_of_node) == 1); + asoc_simple_canonicalize_cpu(dai_link, is_single_links); } else { struct snd_soc_codec_conf *cconf; @@ -276,7 +267,7 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, /* BE settings */ dai_link->no_pcm = 1; - dai_link->be_hw_params_fixup = graph_be_hw_params_fixup; + dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup; dai = dai_props->codec_dai = &priv->dais[li->dais++]; @@ -284,17 +275,17 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, cconf = dai_props->codec_conf = &priv->codec_conf[li->conf++]; - ret = asoc_simple_card_parse_graph_codec(ep, dai_link); + ret = asoc_simple_parse_codec(ep, dai_link); if (ret < 0) return ret; - ret = asoc_simple_card_parse_clk_codec(dev, ep, dai_link, dai); + ret = asoc_simple_parse_clk_codec(dev, ep, dai_link, dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "be.%s", - codecs->dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "be.%s", + codecs->dai_name); if (ret < 0) return ret; @@ -309,51 +300,45 @@ static int graph_dai_link_of_dpcm(struct graph_priv *priv, "prefix"); } - asoc_simple_card_canonicalize_platform(dai_link); + graph_parse_convert(dev, ep, &dai_props->adata); + graph_parse_mclk_fs(top, ep, dai_props); - ret = asoc_simple_card_of_parse_tdm(ep, dai); + asoc_simple_canonicalize_platform(dai_link); + + ret = asoc_simple_parse_tdm(ep, dai); if (ret) return ret; - ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep, - NULL, &dai_link->dai_fmt); + ret = asoc_simple_parse_daifmt(dev, cpu_ep, codec_ep, + NULL, &dai_link->dai_fmt); if (ret < 0) return ret; dai_link->dpcm_playback = 1; dai_link->dpcm_capture = 1; dai_link->ops = &graph_ops; - dai_link->init = graph_dai_init; + dai_link->init = asoc_simple_dai_init; return 0; } -static int graph_dai_link_of(struct graph_priv *priv, +static int graph_dai_link_of(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li) { - struct device *dev = graph_priv_to_dev(priv); - struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, li->link); - struct graph_dai_props *dai_props = graph_priv_to_props(priv, li->link); + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); struct device_node *top = dev->of_node; - struct device_node *cpu_port; - struct device_node *cpu_ports; - struct device_node *codec_port; - struct device_node *codec_ports; struct asoc_simple_dai *cpu_dai; struct asoc_simple_dai *codec_dai; - int ret; + int ret, single_cpu; /* Do it only CPU turn */ if (!li->cpu) return 0; - cpu_port = of_get_parent(cpu_ep); - cpu_ports = of_get_parent(cpu_port); - codec_port = of_get_parent(codec_ep); - codec_ports = of_get_parent(codec_port); - dev_dbg(dev, "link_of (%pOF)\n", cpu_ep); li->link++; @@ -364,84 +349,74 @@ static int graph_dai_link_of(struct graph_priv *priv, dai_props->codec_dai = &priv->dais[li->dais++]; /* Factor to mclk, used in hw_params() */ - of_property_read_u32(top, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(cpu_ports, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(codec_ports, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(cpu_port, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(codec_port, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(cpu_ep, "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(codec_ep, "mclk-fs", &dai_props->mclk_fs); - of_node_put(cpu_port); - of_node_put(cpu_ports); - of_node_put(codec_port); - of_node_put(codec_ports); - - ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep, - NULL, &dai_link->dai_fmt); + graph_parse_mclk_fs(top, cpu_ep, dai_props); + graph_parse_mclk_fs(top, codec_ep, dai_props); + + ret = asoc_simple_parse_daifmt(dev, cpu_ep, codec_ep, + NULL, &dai_link->dai_fmt); if (ret < 0) return ret; - ret = asoc_simple_card_parse_graph_cpu(cpu_ep, dai_link); + ret = asoc_simple_parse_cpu(cpu_ep, dai_link, &single_cpu); if (ret < 0) return ret; - ret = asoc_simple_card_parse_graph_codec(codec_ep, dai_link); + ret = asoc_simple_parse_codec(codec_ep, dai_link); if (ret < 0) return ret; - ret = asoc_simple_card_of_parse_tdm(cpu_ep, cpu_dai); + ret = asoc_simple_parse_tdm(cpu_ep, cpu_dai); if (ret < 0) return ret; - ret = asoc_simple_card_of_parse_tdm(codec_ep, codec_dai); + ret = asoc_simple_parse_tdm(codec_ep, codec_dai); if (ret < 0) return ret; - ret = asoc_simple_card_parse_clk_cpu(dev, cpu_ep, dai_link, cpu_dai); + ret = asoc_simple_parse_clk_cpu(dev, cpu_ep, dai_link, cpu_dai); if (ret < 0) return ret; - ret = asoc_simple_card_parse_clk_codec(dev, codec_ep, dai_link, codec_dai); + ret = asoc_simple_parse_clk_codec(dev, codec_ep, dai_link, codec_dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "%s-%s", - dai_link->cpu_dai_name, - dai_link->codecs->dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codecs->dai_name); if (ret < 0) return ret; dai_link->ops = &graph_ops; - dai_link->init = graph_dai_init; + dai_link->init = asoc_simple_dai_init; - asoc_simple_card_canonicalize_platform(dai_link); - asoc_simple_card_canonicalize_cpu(dai_link, - of_graph_get_endpoint_count(dai_link->cpu_of_node) == 1); + asoc_simple_canonicalize_cpu(dai_link, single_cpu); + asoc_simple_canonicalize_platform(dai_link); return 0; } -static int graph_for_each_link(struct graph_priv *priv, +static int graph_for_each_link(struct asoc_simple_priv *priv, struct link_info *li, - int (*func_noml)(struct graph_priv *priv, + int (*func_noml)(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li), - int (*func_dpcm)(struct graph_priv *priv, + int (*func_dpcm)(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li, int dup_codec)) { struct of_phandle_iterator it; - struct device *dev = graph_priv_to_dev(priv); + struct device *dev = simple_priv_to_dev(priv); struct device_node *node = dev->of_node; struct device_node *cpu_port; struct device_node *cpu_ep; struct device_node *codec_ep; struct device_node *codec_port; struct device_node *codec_port_old = NULL; - struct asoc_simple_card_data adata; + struct asoc_simple_data adata; uintptr_t dpcm_selectable = (uintptr_t)of_device_get_match_data(dev); int rc, ret; @@ -465,8 +440,8 @@ static int graph_for_each_link(struct graph_priv *priv, /* get convert-xxx property */ memset(&adata, 0, sizeof(adata)); - graph_get_conversion(dev, codec_ep, &adata); - graph_get_conversion(dev, cpu_ep, &adata); + graph_parse_convert(dev, codec_ep, &adata); + graph_parse_convert(dev, cpu_ep, &adata); /* * It is DPCM @@ -492,17 +467,17 @@ static int graph_for_each_link(struct graph_priv *priv, return 0; } -static int graph_parse_of(struct graph_priv *priv) +static int graph_parse_of(struct asoc_simple_priv *priv) { - struct snd_soc_card *card = graph_priv_to_card(priv); + struct snd_soc_card *card = simple_priv_to_card(priv); struct link_info li; int ret; - ret = asoc_simple_card_of_parse_widgets(card, NULL); + ret = asoc_simple_parse_widgets(card, NULL); if (ret < 0) return ret; - ret = asoc_simple_card_of_parse_routing(card, NULL); + ret = asoc_simple_parse_routing(card, NULL); if (ret < 0) return ret; @@ -527,15 +502,15 @@ static int graph_parse_of(struct graph_priv *priv) return ret; } - return asoc_simple_card_parse_card_name(card, NULL); + return asoc_simple_parse_card_name(card, NULL); } -static int graph_count_noml(struct graph_priv *priv, +static int graph_count_noml(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li) { - struct device *dev = graph_priv_to_dev(priv); + struct device *dev = simple_priv_to_dev(priv); li->link += 1; /* 1xCPU-Codec */ li->dais += 2; /* 1xCPU + 1xCodec */ @@ -545,13 +520,13 @@ static int graph_count_noml(struct graph_priv *priv, return 0; } -static int graph_count_dpcm(struct graph_priv *priv, +static int graph_count_dpcm(struct asoc_simple_priv *priv, struct device_node *cpu_ep, struct device_node *codec_ep, struct link_info *li, int dup_codec) { - struct device *dev = graph_priv_to_dev(priv); + struct device *dev = simple_priv_to_dev(priv); li->link++; /* 1xCPU-dummy */ li->dais++; /* 1xCPU */ @@ -567,10 +542,10 @@ static int graph_count_dpcm(struct graph_priv *priv, return 0; } -static void graph_get_dais_count(struct graph_priv *priv, +static void graph_get_dais_count(struct asoc_simple_priv *priv, struct link_info *li) { - struct device *dev = graph_priv_to_dev(priv); + struct device *dev = simple_priv_to_dev(priv); /* * link_num : number of links. @@ -627,14 +602,14 @@ static void graph_get_dais_count(struct graph_priv *priv, static int graph_card_probe(struct snd_soc_card *card) { - struct graph_priv *priv = snd_soc_card_get_drvdata(card); + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card); int ret; - ret = asoc_simple_card_init_hp(card, &priv->hp_jack, NULL); + ret = asoc_simple_init_hp(card, &priv->hp_jack, NULL); if (ret < 0) return ret; - ret = asoc_simple_card_init_mic(card, &priv->mic_jack, NULL); + ret = asoc_simple_init_mic(card, &priv->mic_jack, NULL); if (ret < 0) return ret; @@ -643,22 +618,18 @@ static int graph_card_probe(struct snd_soc_card *card) static int graph_probe(struct platform_device *pdev) { - struct graph_priv *priv; - struct snd_soc_dai_link *dai_link; - struct graph_dai_props *dai_props; - struct asoc_simple_dai *dais; + struct asoc_simple_priv *priv; struct device *dev = &pdev->dev; struct snd_soc_card *card; - struct snd_soc_codec_conf *cconf; struct link_info li; - int ret, i; + int ret; /* Allocate the private data and the DAI link array */ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - card = graph_priv_to_card(priv); + card = simple_priv_to_card(priv); card->owner = THIS_MODULE; card->dev = dev; card->dapm_widgets = graph_dapm_widgets; @@ -670,25 +641,9 @@ static int graph_probe(struct platform_device *pdev) if (!li.link || !li.dais) return -EINVAL; - dai_props = devm_kcalloc(dev, li.link, sizeof(*dai_props), GFP_KERNEL); - dai_link = devm_kcalloc(dev, li.link, sizeof(*dai_link), GFP_KERNEL); - dais = devm_kcalloc(dev, li.dais, sizeof(*dais), GFP_KERNEL); - cconf = devm_kcalloc(dev, li.conf, sizeof(*cconf), GFP_KERNEL); - if (!dai_props || !dai_link || !dais) - return -ENOMEM; - - /* - * Use snd_soc_dai_link_component instead of legacy style - * It is codec only. but cpu/platform will be supported in the future. - * see - * soc-core.c :: snd_soc_init_multicodec() - */ - for (i = 0; i < li.link; i++) { - dai_link[i].codecs = &dai_props[i].codecs; - dai_link[i].num_codecs = 1; - dai_link[i].platforms = &dai_props[i].platforms; - dai_link[i].num_platforms = 1; - } + ret = asoc_simple_init_priv(priv, &li); + if (ret < 0) + return ret; priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); if (IS_ERR(priv->pa_gpio)) { @@ -697,16 +652,6 @@ static int graph_probe(struct platform_device *pdev) return ret; } - priv->dai_props = dai_props; - priv->dai_link = dai_link; - priv->dais = dais; - priv->codec_conf = cconf; - - card->dai_link = dai_link; - card->num_links = li.link; - card->codec_conf = cconf; - card->num_configs = li.conf; - ret = graph_parse_of(priv); if (ret < 0) { if (ret != -EPROBE_DEFER) @@ -716,13 +661,15 @@ static int graph_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(card, priv); + asoc_simple_debug_info(priv); + ret = devm_snd_soc_register_card(dev, card); if (ret < 0) goto err; return 0; err: - asoc_simple_card_clean_reference(card); + asoc_simple_clean_reference(card); return ret; } @@ -731,7 +678,7 @@ static int graph_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); - return asoc_simple_card_clean_reference(card); + return asoc_simple_clean_reference(card); } static const struct of_device_id graph_of_match[] = { diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 5c1424f03620..f4c6375d11c7 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -14,8 +14,8 @@ #include <sound/jack.h> #include <sound/simple_card_utils.h> -void asoc_simple_card_convert_fixup(struct asoc_simple_card_data *data, - struct snd_pcm_hw_params *params) +void asoc_simple_convert_fixup(struct asoc_simple_data *data, + struct snd_pcm_hw_params *params) { struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); @@ -30,12 +30,12 @@ void asoc_simple_card_convert_fixup(struct asoc_simple_card_data *data, channels->min = channels->max = data->convert_channels; } -EXPORT_SYMBOL_GPL(asoc_simple_card_convert_fixup); +EXPORT_SYMBOL_GPL(asoc_simple_convert_fixup); -void asoc_simple_card_parse_convert(struct device *dev, - struct device_node *np, - char *prefix, - struct asoc_simple_card_data *data) +void asoc_simple_parse_convert(struct device *dev, + struct device_node *np, + char *prefix, + struct asoc_simple_data *data) { char prop[128]; @@ -49,17 +49,14 @@ void asoc_simple_card_parse_convert(struct device *dev, /* channels transfer */ snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-channels"); of_property_read_u32(np, prop, &data->convert_channels); - - dev_dbg(dev, "convert_rate %d\n", data->convert_rate); - dev_dbg(dev, "convert_channels %d\n", data->convert_channels); } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_convert); +EXPORT_SYMBOL_GPL(asoc_simple_parse_convert); -int asoc_simple_card_parse_daifmt(struct device *dev, - struct device_node *node, - struct device_node *codec, - char *prefix, - unsigned int *retfmt) +int asoc_simple_parse_daifmt(struct device *dev, + struct device_node *node, + struct device_node *codec, + char *prefix, + unsigned int *retfmt) { struct device_node *bitclkmaster = NULL; struct device_node *framemaster = NULL; @@ -93,15 +90,13 @@ int asoc_simple_card_parse_daifmt(struct device *dev, *retfmt = daifmt; - dev_dbg(dev, "format : %04x\n", daifmt); - return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_daifmt); +EXPORT_SYMBOL_GPL(asoc_simple_parse_daifmt); -int asoc_simple_card_set_dailink_name(struct device *dev, - struct snd_soc_dai_link *dai_link, - const char *fmt, ...) +int asoc_simple_set_dailink_name(struct device *dev, + struct snd_soc_dai_link *dai_link, + const char *fmt, ...) { va_list ap; char *name = NULL; @@ -116,16 +111,14 @@ int asoc_simple_card_set_dailink_name(struct device *dev, dai_link->name = name; dai_link->stream_name = name; - - dev_dbg(dev, "name : %s\n", name); } return ret; } -EXPORT_SYMBOL_GPL(asoc_simple_card_set_dailink_name); +EXPORT_SYMBOL_GPL(asoc_simple_set_dailink_name); -int asoc_simple_card_parse_card_name(struct snd_soc_card *card, - char *prefix) +int asoc_simple_parse_card_name(struct snd_soc_card *card, + char *prefix) { int ret; @@ -146,34 +139,30 @@ int asoc_simple_card_parse_card_name(struct snd_soc_card *card, if (!card->name && card->dai_link) card->name = card->dai_link->name; - dev_dbg(card->dev, "Card Name: %s\n", card->name ? card->name : ""); - return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_card_name); +EXPORT_SYMBOL_GPL(asoc_simple_parse_card_name); -int asoc_simple_card_clk_enable(struct asoc_simple_dai *dai) +static int asoc_simple_clk_enable(struct asoc_simple_dai *dai) { if (dai) return clk_prepare_enable(dai->clk); return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_clk_enable); -void asoc_simple_card_clk_disable(struct asoc_simple_dai *dai) +static void asoc_simple_clk_disable(struct asoc_simple_dai *dai) { if (dai) clk_disable_unprepare(dai->clk); } -EXPORT_SYMBOL_GPL(asoc_simple_card_clk_disable); - -int asoc_simple_card_parse_clk(struct device *dev, - struct device_node *node, - struct device_node *dai_of_node, - struct asoc_simple_dai *simple_dai, - const char *dai_name, - struct snd_soc_dai_link_component *dlc) + +int asoc_simple_parse_clk(struct device *dev, + struct device_node *node, + struct device_node *dai_of_node, + struct asoc_simple_dai *simple_dai, + const char *dai_name, + struct snd_soc_dai_link_component *dlc) { struct clk *clk; u32 val; @@ -184,10 +173,8 @@ int asoc_simple_card_parse_clk(struct device *dev, * see * soc-core.c :: snd_soc_init_multicodec() */ - if (dlc) { + if (dlc) dai_of_node = dlc->of_node; - dai_name = dlc->dai_name; - } /* * Parse dai->sysclk come from "clocks = <&xxx>" @@ -211,158 +198,113 @@ int asoc_simple_card_parse_clk(struct device *dev, if (of_property_read_bool(node, "system-clock-direction-out")) simple_dai->clk_direction = SND_SOC_CLOCK_OUT; - dev_dbg(dev, "%s : sysclk = %d, direction %d\n", dai_name, - simple_dai->sysclk, simple_dai->clk_direction); - return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_clk); - -int asoc_simple_card_parse_dai(struct device_node *node, - struct snd_soc_dai_link_component *dlc, - struct device_node **dai_of_node, - const char **dai_name, - const char *list_name, - const char *cells_name, - int *is_single_link) +EXPORT_SYMBOL_GPL(asoc_simple_parse_clk); + +int asoc_simple_startup(struct snd_pcm_substream *substream) { - struct of_phandle_args args; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); int ret; - if (!node) - return 0; - - /* - * Use snd_soc_dai_link_component instead of legacy style. - * It is only for codec, but cpu will be supported in the future. - * see - * soc-core.c :: snd_soc_init_multicodec() - */ - if (dlc) { - dai_name = &dlc->dai_name; - dai_of_node = &dlc->of_node; - } - - /* - * Get node via "sound-dai = <&phandle port>" - * it will be used as xxx_of_node on soc_bind_dai_link() - */ - ret = of_parse_phandle_with_args(node, list_name, cells_name, 0, &args); + ret = asoc_simple_clk_enable(dai_props->cpu_dai); if (ret) return ret; - /* Get dai->name */ - if (dai_name) { - ret = snd_soc_of_get_dai_name(node, dai_name); - if (ret < 0) - return ret; - } - - *dai_of_node = args.np; - - if (is_single_link) - *is_single_link = !args.args_count; + ret = asoc_simple_clk_enable(dai_props->codec_dai); + if (ret) + asoc_simple_clk_disable(dai_props->cpu_dai); - return 0; + return ret; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_dai); +EXPORT_SYMBOL_GPL(asoc_simple_startup); -static int asoc_simple_card_get_dai_id(struct device_node *ep) +void asoc_simple_shutdown(struct snd_pcm_substream *substream) { - struct device_node *node; - struct device_node *endpoint; - struct of_endpoint info; - int i, id; - int ret; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); - /* use driver specified DAI ID if exist */ - ret = snd_soc_get_dai_id(ep); - if (ret != -ENOTSUPP) - return ret; + asoc_simple_clk_disable(dai_props->cpu_dai); - /* use endpoint/port reg if exist */ - ret = of_graph_parse_endpoint(ep, &info); - if (ret == 0) { - /* - * Because it will count port/endpoint if it doesn't have "reg". - * But, we can't judge whether it has "no reg", or "reg = <0>" - * only of_graph_parse_endpoint(). - * We need to check "reg" property - */ - if (of_get_property(ep, "reg", NULL)) - return info.id; - - node = of_get_parent(ep); - of_node_put(node); - if (of_get_property(node, "reg", NULL)) - return info.port; - } - node = of_graph_get_port_parent(ep); + asoc_simple_clk_disable(dai_props->codec_dai); +} +EXPORT_SYMBOL_GPL(asoc_simple_shutdown); - /* - * Non HDMI sound case, counting port/endpoint on its DT - * is enough. Let's count it. - */ - i = 0; - id = -1; - for_each_endpoint_of_node(node, endpoint) { - if (endpoint == ep) - id = i; - i++; - } +static int asoc_simple_set_clk_rate(struct asoc_simple_dai *simple_dai, + unsigned long rate) +{ + if (!simple_dai) + return 0; - of_node_put(node); + if (!simple_dai->clk) + return 0; - if (id < 0) - return -ENODEV; + if (clk_get_rate(simple_dai->clk) == rate) + return 0; - return id; + return clk_set_rate(simple_dai->clk, rate); } -int asoc_simple_card_parse_graph_dai(struct device_node *ep, - struct snd_soc_dai_link_component *dlc, - struct device_node **dai_of_node, - const char **dai_name) +int asoc_simple_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) { - struct device_node *node; - struct of_phandle_args args; - int ret; - - /* - * Use snd_soc_dai_link_component instead of legacy style. - * It is only for codec, but cpu will be supported in the future. - * see - * soc-core.c :: snd_soc_init_multicodec() - */ - if (dlc) { - dai_name = &dlc->dai_name; - dai_of_node = &dlc->of_node; - } + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + unsigned int mclk, mclk_fs = 0; + int ret = 0; + + if (dai_props->mclk_fs) + mclk_fs = dai_props->mclk_fs; + + if (mclk_fs) { + mclk = params_rate(params) * mclk_fs; + + ret = asoc_simple_set_clk_rate(dai_props->codec_dai, mclk); + if (ret < 0) + return ret; - if (!ep) - return 0; - if (!dai_name) - return 0; + ret = asoc_simple_set_clk_rate(dai_props->cpu_dai, mclk); + if (ret < 0) + return ret; - node = of_graph_get_port_parent(ep); + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + goto err; - /* Get dai->name */ - args.np = node; - args.args[0] = asoc_simple_card_get_dai_id(ep); - args.args_count = (of_graph_get_endpoint_count(node) > 1); + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret && ret != -ENOTSUPP) + goto err; + } + return 0; +err: + return ret; +} +EXPORT_SYMBOL_GPL(asoc_simple_hw_params); - ret = snd_soc_get_dai_name(&args, dai_name); - if (ret < 0) - return ret; +int asoc_simple_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); - *dai_of_node = node; + asoc_simple_convert_fixup(&dai_props->adata, params); return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_parse_graph_dai); +EXPORT_SYMBOL_GPL(asoc_simple_be_hw_params_fixup); -int asoc_simple_card_init_dai(struct snd_soc_dai *dai, - struct asoc_simple_dai *simple_dai) +static int asoc_simple_init_dai(struct snd_soc_dai *dai, + struct asoc_simple_dai *simple_dai) { int ret; @@ -392,18 +334,37 @@ int asoc_simple_card_init_dai(struct snd_soc_dai *dai, return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_init_dai); -void asoc_simple_card_canonicalize_platform(struct snd_soc_dai_link *dai_link) +int asoc_simple_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); + int ret; + + ret = asoc_simple_init_dai(rtd->codec_dai, + dai_props->codec_dai); + if (ret < 0) + return ret; + + ret = asoc_simple_init_dai(rtd->cpu_dai, + dai_props->cpu_dai); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_dai_init); + +void asoc_simple_canonicalize_platform(struct snd_soc_dai_link *dai_link) { /* Assumes platform == cpu */ if (!dai_link->platforms->of_node) dai_link->platforms->of_node = dai_link->cpu_of_node; } -EXPORT_SYMBOL_GPL(asoc_simple_card_canonicalize_platform); +EXPORT_SYMBOL_GPL(asoc_simple_canonicalize_platform); -void asoc_simple_card_canonicalize_cpu(struct snd_soc_dai_link *dai_link, - int is_single_links) +void asoc_simple_canonicalize_cpu(struct snd_soc_dai_link *dai_link, + int is_single_links) { /* * In soc_bind_dai_link() will check cpu name after @@ -417,9 +378,9 @@ void asoc_simple_card_canonicalize_cpu(struct snd_soc_dai_link *dai_link, if (is_single_links) dai_link->cpu_dai_name = NULL; } -EXPORT_SYMBOL_GPL(asoc_simple_card_canonicalize_cpu); +EXPORT_SYMBOL_GPL(asoc_simple_canonicalize_cpu); -int asoc_simple_card_clean_reference(struct snd_soc_card *card) +int asoc_simple_clean_reference(struct snd_soc_card *card) { struct snd_soc_dai_link *dai_link; int i; @@ -430,10 +391,10 @@ int asoc_simple_card_clean_reference(struct snd_soc_card *card) } return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_clean_reference); +EXPORT_SYMBOL_GPL(asoc_simple_clean_reference); -int asoc_simple_card_of_parse_routing(struct snd_soc_card *card, - char *prefix) +int asoc_simple_parse_routing(struct snd_soc_card *card, + char *prefix) { struct device_node *node = card->dev->of_node; char prop[128]; @@ -448,10 +409,10 @@ int asoc_simple_card_of_parse_routing(struct snd_soc_card *card, return snd_soc_of_parse_audio_routing(card, prop); } -EXPORT_SYMBOL_GPL(asoc_simple_card_of_parse_routing); +EXPORT_SYMBOL_GPL(asoc_simple_parse_routing); -int asoc_simple_card_of_parse_widgets(struct snd_soc_card *card, - char *prefix) +int asoc_simple_parse_widgets(struct snd_soc_card *card, + char *prefix) { struct device_node *node = card->dev->of_node; char prop[128]; @@ -467,11 +428,68 @@ int asoc_simple_card_of_parse_widgets(struct snd_soc_card *card, /* no widgets is not error */ return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_of_parse_widgets); +EXPORT_SYMBOL_GPL(asoc_simple_parse_widgets); + +int asoc_simple_parse_pin_switches(struct snd_soc_card *card, + char *prefix) +{ + const unsigned int nb_controls_max = 16; + const char **strings, *control_name; + struct snd_kcontrol_new *controls; + struct device *dev = card->dev; + unsigned int i, nb_controls; + char prop[128]; + int ret; + + if (!prefix) + prefix = ""; + + snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches"); + + if (!of_property_read_bool(dev->of_node, prop)) + return 0; + + strings = devm_kcalloc(dev, nb_controls_max, + sizeof(*strings), GFP_KERNEL); + if (!strings) + return -ENOMEM; + + ret = of_property_read_string_array(dev->of_node, prop, + strings, nb_controls_max); + if (ret < 0) + return ret; + + nb_controls = (unsigned int)ret; + + controls = devm_kcalloc(dev, nb_controls, + sizeof(*controls), GFP_KERNEL); + if (!controls) + return -ENOMEM; + + for (i = 0; i < nb_controls; i++) { + control_name = devm_kasprintf(dev, GFP_KERNEL, + "%s Switch", strings[i]); + if (!control_name) + return -ENOMEM; + + controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + controls[i].name = control_name; + controls[i].info = snd_soc_dapm_info_pin_switch; + controls[i].get = snd_soc_dapm_get_pin_switch; + controls[i].put = snd_soc_dapm_put_pin_switch; + controls[i].private_value = (unsigned long)strings[i]; + } + + card->controls = controls; + card->num_controls = nb_controls; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_parse_pin_switches); -int asoc_simple_card_init_jack(struct snd_soc_card *card, - struct asoc_simple_jack *sjack, - int is_hp, char *prefix) +int asoc_simple_init_jack(struct snd_soc_card *card, + struct asoc_simple_jack *sjack, + int is_hp, char *prefix) { struct device *dev = card->dev; enum of_gpio_flags flags; @@ -522,7 +540,61 @@ int asoc_simple_card_init_jack(struct snd_soc_card *card, return 0; } -EXPORT_SYMBOL_GPL(asoc_simple_card_init_jack); +EXPORT_SYMBOL_GPL(asoc_simple_init_jack); + +int asoc_simple_init_priv(struct asoc_simple_priv *priv, + struct link_info *li) +{ + struct snd_soc_card *card = simple_priv_to_card(priv); + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link; + struct simple_dai_props *dai_props; + struct asoc_simple_dai *dais; + struct snd_soc_codec_conf *cconf = NULL; + int i; + + dai_props = devm_kcalloc(dev, li->link, sizeof(*dai_props), GFP_KERNEL); + dai_link = devm_kcalloc(dev, li->link, sizeof(*dai_link), GFP_KERNEL); + dais = devm_kcalloc(dev, li->dais, sizeof(*dais), GFP_KERNEL); + if (!dai_props || !dai_link || !dais) + return -ENOMEM; + + if (li->conf) { + cconf = devm_kcalloc(dev, li->conf, sizeof(*cconf), GFP_KERNEL); + if (!cconf) + return -ENOMEM; + } + + /* + * Use snd_soc_dai_link_component instead of legacy style + * It is codec only. but cpu/platform will be supported in the future. + * see + * soc-core.c :: snd_soc_init_multicodec() + * + * "platform" might be removed + * see + * simple-card-utils.c :: asoc_simple_canonicalize_platform() + */ + for (i = 0; i < li->link; i++) { + dai_link[i].codecs = &dai_props[i].codecs; + dai_link[i].num_codecs = 1; + dai_link[i].platforms = &dai_props[i].platforms; + dai_link[i].num_platforms = 1; + } + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + priv->dais = dais; + priv->codec_conf = cconf; + + card->dai_link = priv->dai_link; + card->num_links = li->link; + card->codec_conf = cconf; + card->num_configs = li->conf; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_init_priv); /* Module information */ MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 34de32efc4c4..9b568f578bcd 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -18,179 +18,98 @@ #define DPCM_SELECTABLE 1 -struct simple_priv { - struct snd_soc_card snd_card; - struct simple_dai_props { - struct asoc_simple_dai *cpu_dai; - struct asoc_simple_dai *codec_dai; - struct snd_soc_dai_link_component codecs; /* single codec */ - struct snd_soc_dai_link_component platforms; - struct asoc_simple_card_data adata; - struct snd_soc_codec_conf *codec_conf; - unsigned int mclk_fs; - } *dai_props; - struct asoc_simple_jack hp_jack; - struct asoc_simple_jack mic_jack; - struct snd_soc_dai_link *dai_link; - struct asoc_simple_dai *dais; - struct snd_soc_codec_conf *codec_conf; -}; - -struct link_info { - int dais; /* number of dai */ - int link; /* number of link */ - int conf; /* number of codec_conf */ - int cpu; /* turn for CPU / Codec */ -}; - -#define simple_priv_to_card(priv) (&(priv)->snd_card) -#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) -#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) -#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) - #define DAI "sound-dai" #define CELL "#sound-dai-cells" #define PREFIX "simple-audio-card," -static int simple_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = - simple_priv_to_props(priv, rtd->num); - int ret; - - ret = asoc_simple_card_clk_enable(dai_props->cpu_dai); - if (ret) - return ret; - - ret = asoc_simple_card_clk_enable(dai_props->codec_dai); - if (ret) - asoc_simple_card_clk_disable(dai_props->cpu_dai); - - return ret; -} - -static void simple_shutdown(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = - simple_priv_to_props(priv, rtd->num); - - asoc_simple_card_clk_disable(dai_props->cpu_dai); - - asoc_simple_card_clk_disable(dai_props->codec_dai); -} +static const struct snd_soc_ops simple_ops = { + .startup = asoc_simple_startup, + .shutdown = asoc_simple_shutdown, + .hw_params = asoc_simple_hw_params, +}; -static int simple_set_clk_rate(struct asoc_simple_dai *simple_dai, - unsigned long rate) +static int asoc_simple_parse_dai(struct device_node *node, + struct snd_soc_dai_link_component *dlc, + struct device_node **dai_of_node, + const char **dai_name, + int *is_single_link) { - if (!simple_dai) - return 0; - - if (!simple_dai->clk) - return 0; + struct of_phandle_args args; + int ret; - if (clk_get_rate(simple_dai->clk) == rate) + if (!node) return 0; - return clk_set_rate(simple_dai->clk, rate); -} - -static int simple_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 = rtd->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = - simple_priv_to_props(priv, rtd->num); - unsigned int mclk, mclk_fs = 0; - int ret = 0; - - if (dai_props->mclk_fs) - mclk_fs = dai_props->mclk_fs; - - if (mclk_fs) { - mclk = params_rate(params) * mclk_fs; + /* + * Use snd_soc_dai_link_component instead of legacy style. + * It is only for codec, but cpu will be supported in the future. + * see + * soc-core.c :: snd_soc_init_multicodec() + */ + if (dlc) { + dai_name = &dlc->dai_name; + dai_of_node = &dlc->of_node; + } - ret = simple_set_clk_rate(dai_props->codec_dai, mclk); - if (ret < 0) - return ret; + /* + * Get node via "sound-dai = <&phandle port>" + * it will be used as xxx_of_node on soc_bind_dai_link() + */ + ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args); + if (ret) + return ret; - ret = simple_set_clk_rate(dai_props->cpu_dai, mclk); + /* Get dai->name */ + if (dai_name) { + ret = snd_soc_of_get_dai_name(node, dai_name); if (ret < 0) return ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, - SND_SOC_CLOCK_IN); - if (ret && ret != -ENOTSUPP) - goto err; - - ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, - SND_SOC_CLOCK_OUT); - if (ret && ret != -ENOTSUPP) - goto err; } - return 0; -err: - return ret; -} - -static const struct snd_soc_ops simple_ops = { - .startup = simple_startup, - .shutdown = simple_shutdown, - .hw_params = simple_hw_params, -}; -static int simple_dai_init(struct snd_soc_pcm_runtime *rtd) -{ - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); - int ret; + *dai_of_node = args.np; - ret = asoc_simple_card_init_dai(rtd->codec_dai, - dai_props->codec_dai); - if (ret < 0) - return ret; - - ret = asoc_simple_card_init_dai(rtd->cpu_dai, - dai_props->cpu_dai); - if (ret < 0) - return ret; + if (is_single_link) + *is_single_link = !args.args_count; return 0; } -static int simple_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params) +static void simple_parse_convert(struct device *dev, + struct device_node *np, + struct asoc_simple_data *adata) { - struct simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); + struct device_node *top = dev->of_node; + struct device_node *node = of_get_parent(np); - asoc_simple_card_convert_fixup(&dai_props->adata, params); + asoc_simple_parse_convert(dev, top, PREFIX, adata); + asoc_simple_parse_convert(dev, node, PREFIX, adata); + asoc_simple_parse_convert(dev, node, NULL, adata); + asoc_simple_parse_convert(dev, np, NULL, adata); - return 0; + of_node_put(node); } -static void simple_get_conversion(struct device *dev, - struct device_node *np, - struct asoc_simple_card_data *adata) +static void simple_parse_mclk_fs(struct device_node *top, + struct device_node *cpu, + struct device_node *codec, + struct simple_dai_props *props, + char *prefix) { - struct device_node *top = dev->of_node; - struct device_node *node = of_get_parent(np); + struct device_node *node = of_get_parent(cpu); + char prop[128]; - asoc_simple_card_parse_convert(dev, top, PREFIX, adata); - asoc_simple_card_parse_convert(dev, node, PREFIX, adata); - asoc_simple_card_parse_convert(dev, node, NULL, adata); - asoc_simple_card_parse_convert(dev, np, NULL, adata); + snprintf(prop, sizeof(prop), "%smclk-fs", PREFIX); + of_property_read_u32(top, prop, &props->mclk_fs); + + snprintf(prop, sizeof(prop), "%smclk-fs", prefix); + of_property_read_u32(node, prop, &props->mclk_fs); + of_property_read_u32(cpu, prop, &props->mclk_fs); + of_property_read_u32(codec, prop, &props->mclk_fs); of_node_put(node); } -static int simple_dai_link_of_dpcm(struct simple_priv *priv, +static int simple_dai_link_of_dpcm(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, @@ -203,7 +122,6 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, struct snd_soc_dai_link_component *codecs = dai_link->codecs; struct device_node *top = dev->of_node; struct device_node *node = of_get_parent(np); - char prop[128]; char *prefix = ""; int ret; @@ -241,22 +159,21 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, dai = dai_props->cpu_dai = &priv->dais[li->dais++]; - ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL, - &is_single_links); + ret = asoc_simple_parse_cpu(np, dai_link, &is_single_links); if (ret) return ret; - ret = asoc_simple_card_parse_clk_cpu(dev, np, dai_link, dai); + ret = asoc_simple_parse_clk_cpu(dev, np, dai_link, dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "fe.%s", - dai_link->cpu_dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "fe.%s", + dai_link->cpu_dai_name); if (ret < 0) return ret; - asoc_simple_card_canonicalize_cpu(dai_link, is_single_links); + asoc_simple_canonicalize_cpu(dai_link, is_single_links); } else { struct snd_soc_codec_conf *cconf; @@ -267,7 +184,7 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, /* BE settings */ dai_link->no_pcm = 1; - dai_link->be_hw_params_fixup = simple_be_hw_params_fixup; + dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup; dai = dai_props->codec_dai = &priv->dais[li->dais++]; @@ -275,17 +192,17 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, cconf = dai_props->codec_conf = &priv->codec_conf[li->conf++]; - ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL); + ret = asoc_simple_parse_codec(np, dai_link); if (ret < 0) return ret; - ret = asoc_simple_card_parse_clk_codec(dev, np, dai_link, dai); + ret = asoc_simple_parse_clk_codec(dev, np, dai_link, dai); if (ret < 0) return ret; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "be.%s", - codecs->dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "be.%s", + codecs->dai_name); if (ret < 0) return ret; @@ -298,33 +215,29 @@ static int simple_dai_link_of_dpcm(struct simple_priv *priv, "prefix"); } - simple_get_conversion(dev, np, &dai_props->adata); + simple_parse_convert(dev, np, &dai_props->adata); + simple_parse_mclk_fs(top, np, codec, dai_props, prefix); - asoc_simple_card_canonicalize_platform(dai_link); + asoc_simple_canonicalize_platform(dai_link); - ret = asoc_simple_card_of_parse_tdm(np, dai); + ret = asoc_simple_parse_tdm(np, dai); if (ret) return ret; - snprintf(prop, sizeof(prop), "%smclk-fs", prefix); - of_property_read_u32(top, PREFIX "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(node, prop, &dai_props->mclk_fs); - of_property_read_u32(np, prop, &dai_props->mclk_fs); - - ret = asoc_simple_card_parse_daifmt(dev, node, codec, - prefix, &dai_link->dai_fmt); + ret = asoc_simple_parse_daifmt(dev, node, codec, + prefix, &dai_link->dai_fmt); if (ret < 0) return ret; dai_link->dpcm_playback = 1; dai_link->dpcm_capture = 1; dai_link->ops = &simple_ops; - dai_link->init = simple_dai_init; + dai_link->init = asoc_simple_dai_init; return 0; } -static int simple_dai_link_of(struct simple_priv *priv, +static int simple_dai_link_of(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, @@ -370,58 +283,53 @@ static int simple_dai_link_of(struct simple_priv *priv, codec_dai = dai_props->codec_dai = &priv->dais[li->dais++]; - ret = asoc_simple_card_parse_daifmt(dev, node, codec, - prefix, &dai_link->dai_fmt); + ret = asoc_simple_parse_daifmt(dev, node, codec, + prefix, &dai_link->dai_fmt); if (ret < 0) goto dai_link_of_err; - snprintf(prop, sizeof(prop), "%smclk-fs", prefix); - of_property_read_u32(top, PREFIX "mclk-fs", &dai_props->mclk_fs); - of_property_read_u32(node, prop, &dai_props->mclk_fs); - of_property_read_u32(cpu, prop, &dai_props->mclk_fs); - of_property_read_u32(codec, prop, &dai_props->mclk_fs); + simple_parse_mclk_fs(top, cpu, codec, dai_props, prefix); - ret = asoc_simple_card_parse_cpu(cpu, dai_link, - DAI, CELL, &single_cpu); + ret = asoc_simple_parse_cpu(cpu, dai_link, &single_cpu); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_codec(codec, dai_link, DAI, CELL); + ret = asoc_simple_parse_codec(codec, dai_link); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_platform(plat, dai_link, DAI, CELL); + ret = asoc_simple_parse_platform(plat, dai_link); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_of_parse_tdm(cpu, cpu_dai); + ret = asoc_simple_parse_tdm(cpu, cpu_dai); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_of_parse_tdm(codec, codec_dai); + ret = asoc_simple_parse_tdm(codec, codec_dai); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_clk_cpu(dev, cpu, dai_link, cpu_dai); + ret = asoc_simple_parse_clk_cpu(dev, cpu, dai_link, cpu_dai); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_parse_clk_codec(dev, codec, dai_link, codec_dai); + ret = asoc_simple_parse_clk_codec(dev, codec, dai_link, codec_dai); if (ret < 0) goto dai_link_of_err; - ret = asoc_simple_card_set_dailink_name(dev, dai_link, - "%s-%s", - dai_link->cpu_dai_name, - dai_link->codecs->dai_name); + ret = asoc_simple_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codecs->dai_name); if (ret < 0) goto dai_link_of_err; dai_link->ops = &simple_ops; - dai_link->init = simple_dai_init; + dai_link->init = asoc_simple_dai_init; - asoc_simple_card_canonicalize_cpu(dai_link, single_cpu); - asoc_simple_card_canonicalize_platform(dai_link); + asoc_simple_canonicalize_cpu(dai_link, single_cpu); + asoc_simple_canonicalize_platform(dai_link); dai_link_of_err: of_node_put(plat); @@ -430,13 +338,13 @@ dai_link_of_err: return ret; } -static int simple_for_each_link(struct simple_priv *priv, +static int simple_for_each_link(struct asoc_simple_priv *priv, struct link_info *li, - int (*func_noml)(struct simple_priv *priv, + int (*func_noml)(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, bool is_top), - int (*func_dpcm)(struct simple_priv *priv, + int (*func_dpcm)(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, bool is_top)) @@ -457,7 +365,7 @@ static int simple_for_each_link(struct simple_priv *priv, /* loop for all dai-link */ do { - struct asoc_simple_card_data adata; + struct asoc_simple_data adata; struct device_node *codec; struct device_node *np; int num = of_get_child_count(node); @@ -475,7 +383,7 @@ static int simple_for_each_link(struct simple_priv *priv, /* get convert-xxx property */ memset(&adata, 0, sizeof(adata)); for_each_child_of_node(node, np) - simple_get_conversion(dev, np, &adata); + simple_parse_convert(dev, np, &adata); /* loop for all CPU/Codec node */ for_each_child_of_node(node, np) { @@ -507,7 +415,7 @@ static int simple_for_each_link(struct simple_priv *priv, } static int simple_parse_aux_devs(struct device_node *node, - struct simple_priv *priv) + struct asoc_simple_priv *priv) { struct device *dev = simple_priv_to_dev(priv); struct device_node *aux_node; @@ -537,7 +445,7 @@ static int simple_parse_aux_devs(struct device_node *node, return 0; } -static int simple_parse_of(struct simple_priv *priv) +static int simple_parse_of(struct asoc_simple_priv *priv) { struct device *dev = simple_priv_to_dev(priv); struct device_node *top = dev->of_node; @@ -548,11 +456,15 @@ static int simple_parse_of(struct simple_priv *priv) if (!top) return -EINVAL; - ret = asoc_simple_card_of_parse_widgets(card, PREFIX); + ret = asoc_simple_parse_widgets(card, PREFIX); if (ret < 0) return ret; - ret = asoc_simple_card_of_parse_routing(card, PREFIX); + ret = asoc_simple_parse_routing(card, PREFIX); + if (ret < 0) + return ret; + + ret = asoc_simple_parse_pin_switches(card, PREFIX); if (ret < 0) return ret; @@ -578,7 +490,7 @@ static int simple_parse_of(struct simple_priv *priv) return ret; } - ret = asoc_simple_card_parse_card_name(card, PREFIX); + ret = asoc_simple_parse_card_name(card, PREFIX); if (ret < 0) return ret; @@ -587,7 +499,7 @@ static int simple_parse_of(struct simple_priv *priv) return ret; } -static int simple_count_noml(struct simple_priv *priv, +static int simple_count_noml(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, bool is_top) @@ -599,7 +511,7 @@ static int simple_count_noml(struct simple_priv *priv, return 0; } -static int simple_count_dpcm(struct simple_priv *priv, +static int simple_count_dpcm(struct asoc_simple_priv *priv, struct device_node *np, struct device_node *codec, struct link_info *li, bool is_top) @@ -612,7 +524,7 @@ static int simple_count_dpcm(struct simple_priv *priv, return 0; } -static void simple_get_dais_count(struct simple_priv *priv, +static void simple_get_dais_count(struct asoc_simple_priv *priv, struct link_info *li) { struct device *dev = simple_priv_to_dev(priv); @@ -681,14 +593,14 @@ static void simple_get_dais_count(struct simple_priv *priv, static int simple_soc_probe(struct snd_soc_card *card) { - struct simple_priv *priv = snd_soc_card_get_drvdata(card); + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card); int ret; - ret = asoc_simple_card_init_hp(card, &priv->hp_jack, PREFIX); + ret = asoc_simple_init_hp(card, &priv->hp_jack, PREFIX); if (ret < 0) return ret; - ret = asoc_simple_card_init_mic(card, &priv->mic_jack, PREFIX); + ret = asoc_simple_init_mic(card, &priv->mic_jack, PREFIX); if (ret < 0) return ret; @@ -697,16 +609,12 @@ static int simple_soc_probe(struct snd_soc_card *card) static int simple_probe(struct platform_device *pdev) { - struct simple_priv *priv; - struct snd_soc_dai_link *dai_link; - struct simple_dai_props *dai_props; - struct asoc_simple_dai *dais; + struct asoc_simple_priv *priv; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct snd_soc_card *card; - struct snd_soc_codec_conf *cconf; struct link_info li; - int ret, i; + int ret; /* Allocate the private data and the DAI link array */ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -723,35 +631,9 @@ static int simple_probe(struct platform_device *pdev) if (!li.link || !li.dais) return -EINVAL; - dai_props = devm_kcalloc(dev, li.link, sizeof(*dai_props), GFP_KERNEL); - dai_link = devm_kcalloc(dev, li.link, sizeof(*dai_link), GFP_KERNEL); - dais = devm_kcalloc(dev, li.dais, sizeof(*dais), GFP_KERNEL); - cconf = devm_kcalloc(dev, li.conf, sizeof(*cconf), GFP_KERNEL); - if (!dai_props || !dai_link || !dais) - return -ENOMEM; - - /* - * Use snd_soc_dai_link_component instead of legacy style - * It is codec only. but cpu/platform will be supported in the future. - * see - * soc-core.c :: snd_soc_init_multicodec() - */ - for (i = 0; i < li.link; i++) { - dai_link[i].codecs = &dai_props[i].codecs; - dai_link[i].num_codecs = 1; - dai_link[i].platforms = &dai_props[i].platforms; - dai_link[i].num_platforms = 1; - } - - priv->dai_props = dai_props; - priv->dai_link = dai_link; - priv->dais = dais; - priv->codec_conf = cconf; - - card->dai_link = priv->dai_link; - card->num_links = li.link; - card->codec_conf = cconf; - card->num_configs = li.conf; + ret = asoc_simple_init_priv(priv, &li); + if (ret < 0) + return ret; if (np && of_device_is_available(np)) { @@ -766,6 +648,9 @@ static int simple_probe(struct platform_device *pdev) struct asoc_simple_card_info *cinfo; struct snd_soc_dai_link_component *codecs; struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dai_link = priv->dai_link; + struct simple_dai_props *dai_props = priv->dai_props; + int dai_idx = 0; cinfo = dev->platform_data; @@ -798,22 +683,24 @@ static int simple_probe(struct platform_device *pdev) dai_link->stream_name = cinfo->name; dai_link->cpu_dai_name = cinfo->cpu_dai.name; dai_link->dai_fmt = cinfo->daifmt; - dai_link->init = simple_dai_init; - memcpy(priv->dai_props->cpu_dai, &cinfo->cpu_dai, - sizeof(*priv->dai_props->cpu_dai)); - memcpy(priv->dai_props->codec_dai, &cinfo->codec_dai, - sizeof(*priv->dai_props->codec_dai)); + dai_link->init = asoc_simple_dai_init; + memcpy(dai_props->cpu_dai, &cinfo->cpu_dai, + sizeof(*dai_props->cpu_dai)); + memcpy(dai_props->codec_dai, &cinfo->codec_dai, + sizeof(*dai_props->codec_dai)); } snd_soc_card_set_drvdata(card, priv); + asoc_simple_debug_info(priv); + ret = devm_snd_soc_register_card(dev, card); if (ret < 0) goto err; return 0; err: - asoc_simple_card_clean_reference(card); + asoc_simple_clean_reference(card); return ret; } @@ -822,7 +709,7 @@ static int simple_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); - return asoc_simple_card_clean_reference(card); + return asoc_simple_clean_reference(card); } static const struct of_device_id simple_of_match[] = { diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index bd9fd2035c55..fc1396adde71 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -196,13 +196,18 @@ config SND_SOC_INTEL_SKYLAKE_COMMON endif ## SND_SOC_INTEL_SKYLAKE_FAMILY +endif ## SND_SOC_INTEL_SST_TOPLEVEL + +if SND_SOC_INTEL_SST_TOPLEVEL || SND_SOC_SOF_INTEL_TOPLEVEL + config SND_SOC_ACPI_INTEL_MATCH tristate select SND_SOC_ACPI if ACPI # this option controls the compilation of ACPI matching tables and # helpers and is not meant to be selected by the user. -endif ## SND_SOC_INTEL_SST_TOPLEVEL +endif ## SND_SOC_INTEL_SST_TOPLEVEL || SND_SOC_SOF_INTEL_TOPLEVEL + # ASoC codec drivers source "sound/soc/intel/boards/Kconfig" diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index 12d6b73e9531..e39473a6a5d9 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -1,6 +1,6 @@ menuconfig SND_SOC_INTEL_MACH bool "Intel Machine drivers" - depends on SND_SOC_INTEL_SST_TOPLEVEL + depends on SND_SOC_INTEL_SST_TOPLEVEL || SND_SOC_SOF_INTEL_TOPLEVEL help Intel ASoC Machine Drivers. If you have a Intel machine that has an audio controller with a DSP and I2S or DMIC port, then @@ -16,7 +16,9 @@ if SND_SOC_INTEL_HASWELL config SND_SOC_INTEL_HASWELL_MACH tristate "Haswell Lynxpoint" - depends on X86_INTEL_LPSS && I2C && I2C_DESIGNWARE_PLATFORM + depends on I2C + depends on I2C_DESIGNWARE_PLATFORM || COMPILE_TEST + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_RT5640 help This adds support for the Lynxpoint Audio DSP on Intel(R) Haswell @@ -24,9 +26,16 @@ config SND_SOC_INTEL_HASWELL_MACH Say Y or m if you have such a device. If unsure select "N". +endif ## SND_SOC_INTEL_HASWELL + +if SND_SOC_INTEL_HASWELL || SND_SOC_SOF_BROADWELL + config SND_SOC_INTEL_BDW_RT5677_MACH tristate "Broadwell with RT5677 codec" - depends on X86_INTEL_LPSS && I2C && I2C_DESIGNWARE_PLATFORM && GPIOLIB + depends on I2C + depends on I2C_DESIGNWARE_PLATFORM || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_RT5677 help This adds support for Intel Broadwell platform based boards with @@ -36,20 +45,23 @@ config SND_SOC_INTEL_BDW_RT5677_MACH config SND_SOC_INTEL_BROADWELL_MACH tristate "Broadwell Wildcatpoint" - depends on X86_INTEL_LPSS && I2C && I2C_DESIGNWARE_PLATFORM + depends on I2C + depends on I2C_DESIGNWARE_PLATFORM || COMPILE_TEST + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_RT286 help This adds support for the Wilcatpoint Audio DSP on Intel(R) Broadwell Ultrabook platforms. Say Y or m if you have such a device. This is a recommended option. If unsure select "N". -endif ## SND_SOC_INTEL_HASWELL +endif ## SND_SOC_INTEL_HASWELL || SND_SOC_SOF_BROADWELL if SND_SOC_INTEL_BAYTRAIL config SND_SOC_INTEL_BYT_MAX98090_MACH tristate "Baytrail with MAX98090 codec" - depends on X86_INTEL_LPSS && I2C + depends on I2C + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_MAX98090 help This adds audio driver for Intel Baytrail platform based boards @@ -59,7 +71,8 @@ config SND_SOC_INTEL_BYT_MAX98090_MACH config SND_SOC_INTEL_BYT_RT5640_MACH tristate "Baytrail with RT5640 codec" - depends on X86_INTEL_LPSS && I2C + depends on I2C + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_RT5640 help This adds audio driver for Intel Baytrail platform based boards @@ -68,11 +81,12 @@ config SND_SOC_INTEL_BYT_RT5640_MACH endif ## SND_SOC_INTEL_BAYTRAIL -if SND_SST_ATOM_HIFI2_PLATFORM +if SND_SST_ATOM_HIFI2_PLATFORM || SND_SOC_SOF_BAYTRAIL config SND_SOC_INTEL_BYTCR_RT5640_MACH tristate "Baytrail and Baytrail-CR with RT5640 codec" - depends on X86_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_RT5640 help @@ -83,7 +97,8 @@ config SND_SOC_INTEL_BYTCR_RT5640_MACH config SND_SOC_INTEL_BYTCR_RT5651_MACH tristate "Baytrail and Baytrail-CR with RT5651 codec" - depends on X86_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_RT5651 help @@ -94,7 +109,8 @@ config SND_SOC_INTEL_BYTCR_RT5651_MACH config SND_SOC_INTEL_CHT_BSW_RT5672_MACH tristate "Cherrytrail & Braswell with RT5672 codec" - depends on X86_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_RT5670 help @@ -105,7 +121,8 @@ config SND_SOC_INTEL_CHT_BSW_RT5672_MACH config SND_SOC_INTEL_CHT_BSW_RT5645_MACH tristate "Cherrytrail & Braswell with RT5645/5650 codec" - depends on X86_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_RT5645 help @@ -116,7 +133,8 @@ config SND_SOC_INTEL_CHT_BSW_RT5645_MACH config SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH tristate "Cherrytrail & Braswell with MAX98090 & TI codec" - depends on X86_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_MAX98090 select SND_SOC_TS3A227E help @@ -127,7 +145,8 @@ config SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH config SND_SOC_INTEL_CHT_BSW_NAU8824_MACH tristate "Cherrytrail & Braswell with NAU88L24 codec" - depends on X86_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_NAU8824 help @@ -138,7 +157,8 @@ config SND_SOC_INTEL_CHT_BSW_NAU8824_MACH config SND_SOC_INTEL_BYT_CHT_DA7213_MACH tristate "Baytrail & Cherrytrail with DA7212/7213 codec" - depends on X86_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_DA7213 help @@ -149,7 +169,8 @@ config SND_SOC_INTEL_BYT_CHT_DA7213_MACH config SND_SOC_INTEL_BYT_CHT_ES8316_MACH tristate "Baytrail & Cherrytrail with ES8316 codec" - depends on X86_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST select SND_SOC_ACPI select SND_SOC_ES8316 help @@ -158,9 +179,14 @@ config SND_SOC_INTEL_BYT_CHT_ES8316_MACH Say Y or m if you have such a device. This is a recommended option. If unsure select "N". +endif ## SND_SST_ATOM_HIFI2_PLATFORM || SND_SOC_SOF_BAYTRAIL + +if SND_SST_ATOM_HIFI2_PLATFORM + config SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH tristate "Baytrail & Cherrytrail platform with no codec (MinnowBoard MAX, Up)" - depends on X86_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST help This adds support for ASoC machine driver for the MinnowBoard Max or Up boards and provides access to I2S signals on the Low-Speed @@ -176,7 +202,8 @@ if SND_SOC_INTEL_SKL config SND_SOC_INTEL_SKL_RT286_MACH tristate "SKL with RT286 I2S mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_RT286 select SND_SOC_DMIC select SND_SOC_HDAC_HDMI @@ -188,7 +215,8 @@ config SND_SOC_INTEL_SKL_RT286_MACH config SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH tristate "SKL with NAU88L25 and SSM4567 in I2S Mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_NAU8825 select SND_SOC_SSM4567 select SND_SOC_DMIC @@ -201,7 +229,8 @@ config SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH config SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH tristate "SKL with NAU88L25 and MAX98357A in I2S Mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_NAU8825 select SND_SOC_MAX98357A select SND_SOC_DMIC @@ -218,7 +247,8 @@ if SND_SOC_INTEL_APL config SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH tristate "Broxton with DA7219 and MAX98357A in I2S Mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_DA7219 select SND_SOC_MAX98357A select SND_SOC_DMIC @@ -232,7 +262,8 @@ config SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH config SND_SOC_INTEL_BXT_RT298_MACH tristate "Broxton with RT298 I2S mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_RT298 select SND_SOC_DMIC select SND_SOC_HDAC_HDMI @@ -249,7 +280,8 @@ if SND_SOC_INTEL_KBL config SND_SOC_INTEL_KBL_RT5663_MAX98927_MACH tristate "KBL with RT5663 and MAX98927 in I2S Mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_RT5663 select SND_SOC_MAX98927 select SND_SOC_DMIC @@ -263,7 +295,8 @@ config SND_SOC_INTEL_KBL_RT5663_MAX98927_MACH config SND_SOC_INTEL_KBL_RT5663_RT5514_MAX98927_MACH tristate "KBL with RT5663, RT5514 and MAX98927 in I2S Mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST depends on SPI select SND_SOC_RT5663 select SND_SOC_RT5514 @@ -278,7 +311,8 @@ config SND_SOC_INTEL_KBL_RT5663_RT5514_MAX98927_MACH config SND_SOC_INTEL_KBL_DA7219_MAX98357A_MACH tristate "KBL with DA7219 and MAX98357A in I2S Mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_DA7219 select SND_SOC_MAX98357A select SND_SOC_DMIC @@ -290,7 +324,8 @@ config SND_SOC_INTEL_KBL_DA7219_MAX98357A_MACH config SND_SOC_INTEL_KBL_DA7219_MAX98927_MACH tristate "KBL with DA7219 and MAX98927 in I2S Mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_DA7219 select SND_SOC_MAX98927 select SND_SOC_MAX98373 @@ -304,7 +339,8 @@ config SND_SOC_INTEL_KBL_DA7219_MAX98927_MACH config SND_SOC_INTEL_KBL_RT5660_MACH tristate "KBL with RT5660 in I2S Mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_RT5660 select SND_SOC_HDAC_HDMI help @@ -314,11 +350,12 @@ config SND_SOC_INTEL_KBL_RT5660_MACH endif ## SND_SOC_INTEL_KBL -if SND_SOC_INTEL_GLK +if SND_SOC_INTEL_GLK || (SND_SOC_SOF_GEMINILAKE && SND_SOC_SOF_HDA_LINK) config SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH tristate "GLK with RT5682 and MAX98357A in I2S Mode" - depends on MFD_INTEL_LPSS && I2C && ACPI + depends on I2C && ACPI + depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_RT5682 select SND_SOC_MAX98357A select SND_SOC_DMIC @@ -330,9 +367,9 @@ config SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH Say Y if you have such a device. If unsure select "N". -endif ## SND_SOC_INTEL_GLK +endif ## SND_SOC_INTEL_GLK || (SND_SOC_SOF_GEMINILAKE && SND_SOC_SOF_HDA_LINK) -if SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC +if SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC || SND_SOC_SOF_HDA_AUDIO_CODEC config SND_SOC_INTEL_SKL_HDA_DSP_GENERIC_MACH tristate "SKL/KBL/BXT/APL with HDA Codecs" @@ -344,6 +381,22 @@ config SND_SOC_INTEL_SKL_HDA_DSP_GENERIC_MACH Say Y or m if you have such a device. This is a recommended option. If unsure select "N". -endif ## SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC +endif ## SND_SOC_INTEL_SKYLAKE_HDAUDIO_CODEC || SND_SOC_SOF_HDA_AUDIO_CODEC + +if SND_SOC_SOF_HDA_COMMON || SND_SOC_SOF_BAYTRAIL +config SND_SOC_INTEL_SOF_RT5682_MACH + tristate "SOF with rt5682 codec in I2S Mode" + depends on I2C && ACPI + depends on (SND_SOC_SOF_HDA_COMMON && MFD_INTEL_LPSS) ||\ + (SND_SOC_SOF_BAYTRAIL && X86_INTEL_LPSS) + select SND_SOC_RT5682 + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI if SND_SOC_SOF_HDA_COMMON + help + This adds support for ASoC machine driver for SOF platforms + with rt5682 codec. + Say Y if you have such a device. + If unsure select "N". +endif ## SND_SOC_SOF_HDA_COMMON || SND_SOC_SOF_BAYTRAIL endif ## SND_SOC_INTEL_MACH diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index bf072ea299b7..451b3bd7d9c5 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -16,6 +16,7 @@ snd-soc-sst-cht-bsw-nau8824-objs := cht_bsw_nau8824.o snd-soc-sst-byt-cht-da7213-objs := bytcht_da7213.o snd-soc-sst-byt-cht-es8316-objs := bytcht_es8316.o snd-soc-sst-byt-cht-nocodec-objs := bytcht_nocodec.o +snd-soc-sof_rt5682-objs := sof_rt5682.o snd-soc-kbl_da7219_max98357a-objs := kbl_da7219_max98357a.o snd-soc-kbl_da7219_max98927-objs := kbl_da7219_max98927.o snd-soc-kbl_rt5663_max98927-objs := kbl_rt5663_max98927.o @@ -26,6 +27,7 @@ snd-soc-skl_hda_dsp-objs := skl_hda_dsp_generic.o skl_hda_dsp_common.o snd-skl_nau88l25_max98357a-objs := skl_nau88l25_max98357a.o snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_RT5682_MACH) += snd-soc-sof_rt5682.o obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o diff --git a/sound/soc/intel/boards/bdw-rt5677.c b/sound/soc/intel/boards/bdw-rt5677.c index 1844c88ea4e2..6520a8ea5537 100644 --- a/sound/soc/intel/boards/bdw-rt5677.c +++ b/sound/soc/intel/boards/bdw-rt5677.c @@ -180,6 +180,7 @@ static const struct snd_soc_ops bdw_rt5677_ops = { .hw_params = bdw_rt5677_hw_params, }; +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) static int bdw_rt5677_rtd_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); @@ -198,6 +199,7 @@ static int bdw_rt5677_rtd_init(struct snd_soc_pcm_runtime *rtd) return 0; } +#endif static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd) { @@ -265,7 +267,9 @@ static struct snd_soc_dai_link bdw_rt5677_dais[] = { .dynamic = 1, .codec_name = "snd-soc-dummy", .codec_dai_name = "snd-soc-dummy-dai", +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) .init = bdw_rt5677_rtd_init, +#endif .trigger = { SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST diff --git a/sound/soc/intel/boards/broadwell.c b/sound/soc/intel/boards/broadwell.c index b86c746d9b7a..0f18f8964f51 100644 --- a/sound/soc/intel/boards/broadwell.c +++ b/sound/soc/intel/boards/broadwell.c @@ -131,6 +131,7 @@ static const struct snd_soc_ops broadwell_rt286_ops = { .hw_params = broadwell_rt286_hw_params, }; +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); @@ -149,6 +150,7 @@ static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd) return 0; } +#endif /* broadwell digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link broadwell_rt286_dais[] = { @@ -161,7 +163,9 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { .dynamic = 1, .codec_name = "snd-soc-dummy", .codec_dai_name = "snd-soc-dummy-dai", +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) .init = broadwell_rtd_init, +#endif .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .dpcm_playback = 1, .dpcm_capture = 1, diff --git a/sound/soc/intel/boards/bytcht_es8316.c b/sound/soc/intel/boards/bytcht_es8316.c index 6937c00cf63d..e8c585ffd04d 100644 --- a/sound/soc/intel/boards/bytcht_es8316.c +++ b/sound/soc/intel/boards/bytcht_es8316.c @@ -22,6 +22,7 @@ #include <linux/acpi.h> #include <linux/clk.h> #include <linux/device.h> +#include <linux/dmi.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/init.h> @@ -40,6 +41,9 @@ #include "../atom/sst-atom-controls.h" #include "../common/sst-dsp.h" +/* jd-inv + terminating entry */ +#define MAX_NO_PROPS 2 + struct byt_cht_es8316_private { struct clk *mclk; struct snd_soc_jack jack; @@ -55,8 +59,9 @@ enum { #define BYT_CHT_ES8316_MAP(quirk) ((quirk) & GENMASK(3, 0)) #define BYT_CHT_ES8316_SSP0 BIT(16) #define BYT_CHT_ES8316_MONO_SPEAKER BIT(17) +#define BYT_CHT_ES8316_JD_INVERTED BIT(18) -static int quirk; +static unsigned long quirk; static int quirk_override = -1; module_param_named(quirk, quirk_override, int, 0444); @@ -72,6 +77,8 @@ static void log_quirks(struct device *dev) dev_info(dev, "quirk SSP0 enabled"); if (quirk & BYT_CHT_ES8316_MONO_SPEAKER) dev_info(dev, "quirk MONO_SPEAKER enabled\n"); + if (quirk & BYT_CHT_ES8316_JD_INVERTED) + dev_info(dev, "quirk JD_INVERTED enabled\n"); } static int byt_cht_es8316_speaker_power_event(struct snd_soc_dapm_widget *w, @@ -435,15 +442,31 @@ static const struct acpi_gpio_mapping byt_cht_es8316_gpios[] = { { }, }; +/* Please keep this list alphabetically sorted */ +static const struct dmi_system_id byt_cht_es8316_quirk_table[] = { + { /* Teclast X98 Plus II */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TECLAST"), + DMI_MATCH(DMI_PRODUCT_NAME, "X98 Plus II"), + }, + .driver_data = (void *)(BYT_CHT_ES8316_INTMIC_IN1_MAP + | BYT_CHT_ES8316_JD_INVERTED), + }, + {} +}; + static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) { static const char * const mic_name[] = { "in1", "in2" }; + struct property_entry props[MAX_NO_PROPS] = {}; struct byt_cht_es8316_private *priv; + const struct dmi_system_id *dmi_id; struct device *dev = &pdev->dev; struct snd_soc_acpi_mach *mach; const char *platform_name; struct acpi_device *adev; struct device *codec_dev; + unsigned int cnt = 0; int dai_index = 0; int i; int ret = 0; @@ -480,7 +503,10 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) return ret; /* Check for BYTCR or other platform and setup quirks */ - if (x86_match_cpu(baytrail_cpu_ids) && + dmi_id = dmi_first_match(byt_cht_es8316_quirk_table); + if (dmi_id) { + quirk = (unsigned long)dmi_id->driver_data; + } else if (x86_match_cpu(baytrail_cpu_ids) && mach->mach_params.acpi_ipc_irq_index == 0) { /* On BYTCR default to SSP0, internal-mic-in2-map, mono-spk */ quirk = BYT_CHT_ES8316_SSP0 | BYT_CHT_ES8316_INTMIC_IN2_MAP | @@ -491,7 +517,8 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) BYT_CHT_ES8316_MONO_SPEAKER; } if (quirk_override != -1) { - dev_info(dev, "Overriding quirk 0x%x => 0x%x\n", quirk, + dev_info(dev, "Overriding quirk 0x%x => 0x%x\n", + (unsigned int)quirk, quirk_override); quirk = quirk_override; } @@ -513,6 +540,15 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) if (!codec_dev) return -EPROBE_DEFER; + if (quirk & BYT_CHT_ES8316_JD_INVERTED) + props[cnt++] = PROPERTY_ENTRY_BOOL("everest,jack-detect-inverted"); + + if (cnt) { + ret = device_add_properties(codec_dev, props); + if (ret) + return ret; + } + devm_acpi_dev_add_driver_gpios(codec_dev, byt_cht_es8316_gpios); priv->speaker_en_gpio = gpiod_get_index(codec_dev, "speaker-enable", 0, diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index f9175cf6747e..dc22df9a99fb 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -98,8 +98,8 @@ struct byt_rt5640_private { static bool is_bytcr; static unsigned long byt_rt5640_quirk = BYT_RT5640_MCLK_EN; -static unsigned int quirk_override; -module_param_named(quirk, quirk_override, uint, 0444); +static int quirk_override = -1; +module_param_named(quirk, quirk_override, int, 0444); MODULE_PARM_DESC(quirk, "Board-specific quirk override"); static void log_quirks(struct device *dev) @@ -1254,7 +1254,7 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) dmi_id = dmi_first_match(byt_rt5640_quirk_table); if (dmi_id) byt_rt5640_quirk = (unsigned long)dmi_id->driver_data; - if (quirk_override) { + if (quirk_override != -1) { dev_info(&pdev->dev, "Overriding quirk 0x%x => 0x%x\n", (unsigned int)byt_rt5640_quirk, quirk_override); byt_rt5640_quirk = quirk_override; diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index b744add01d12..ca657c3e5726 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -79,14 +79,15 @@ enum { #define BYT_RT5651_SSP0_AIF2 BIT(21) #define BYT_RT5651_HP_LR_SWAPPED BIT(22) #define BYT_RT5651_MONO_SPEAKER BIT(23) +#define BYT_RT5651_JD_NOT_INV BIT(24) #define BYT_RT5651_DEFAULT_QUIRKS (BYT_RT5651_MCLK_EN | \ BYT_RT5651_JD1_1 | \ BYT_RT5651_OVCD_TH_2000UA | \ BYT_RT5651_OVCD_SF_0P75) -/* jack-detect-source + dmic-en + ovcd-th + -sf + terminating empty entry */ -#define MAX_NO_PROPS 5 +/* jack-detect-source + inv + dmic-en + ovcd-th + -sf + terminating entry */ +#define MAX_NO_PROPS 6 struct byt_rt5651_private { struct clk *mclk; @@ -101,8 +102,8 @@ static const struct acpi_gpio_mapping *byt_rt5651_gpios; static unsigned long byt_rt5651_quirk = BYT_RT5651_DEFAULT_QUIRKS | BYT_RT5651_IN2_MAP; -static unsigned int quirk_override; -module_param_named(quirk, quirk_override, uint, 0444); +static int quirk_override = -1; +module_param_named(quirk, quirk_override, int, 0444); MODULE_PARM_DESC(quirk, "Board-specific quirk override"); static void log_quirks(struct device *dev) @@ -137,6 +138,8 @@ static void log_quirks(struct device *dev) dev_info(dev, "quirk SSP0_AIF2 enabled\n"); if (byt_rt5651_quirk & BYT_RT5651_MONO_SPEAKER) dev_info(dev, "quirk MONO_SPEAKER enabled\n"); + if (byt_rt5651_quirk & BYT_RT5651_JD_NOT_INV) + dev_info(dev, "quirk JD_NOT_INV enabled\n"); } #define BYT_CODEC_DAI1 "rt5651-aif1" @@ -415,6 +418,18 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { BYT_RT5651_MONO_SPEAKER), }, { + /* Complet Electro Serv MY8307 */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Complet Electro Serv"), + DMI_MATCH(DMI_PRODUCT_NAME, "MY8307"), + }, + .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS | + BYT_RT5651_IN2_MAP | + BYT_RT5651_MONO_SPEAKER | + BYT_RT5651_JD_NOT_INV), + }, + { /* I.T.Works TW701, Ployer Momo7w and Trekstor ST70416-6 * (these all use the same mainboard) */ .callback = byt_rt5651_quirk_cb, @@ -525,6 +540,9 @@ static int byt_rt5651_add_codec_device_props(struct device *i2c_dev) if (byt_rt5651_quirk & BYT_RT5651_DMIC_EN) props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,dmic-en"); + if (byt_rt5651_quirk & BYT_RT5651_JD_NOT_INV) + props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,jack-detect-not-inverted"); + return device_add_properties(i2c_dev, props); } @@ -969,7 +987,7 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) /* check quirks before creating card */ dmi_check_system(byt_rt5651_quirk_table); - if (quirk_override) { + if (quirk_override != -1) { dev_info(&pdev->dev, "Overriding quirk 0x%x => 0x%x\n", (unsigned int)byt_rt5651_quirk, quirk_override); byt_rt5651_quirk = quirk_override; diff --git a/sound/soc/intel/boards/kbl_da7219_max98357a.c b/sound/soc/intel/boards/kbl_da7219_max98357a.c index 38f6ab74709d..07491a0f8fb8 100644 --- a/sound/soc/intel/boards/kbl_da7219_max98357a.c +++ b/sound/soc/intel/boards/kbl_da7219_max98357a.c @@ -188,7 +188,7 @@ static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) jack = &ctx->kabylake_headset; - snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA); + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP); snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); diff --git a/sound/soc/intel/boards/kbl_da7219_max98927.c b/sound/soc/intel/boards/kbl_da7219_max98927.c index 2768a572d065..f72a7bf028d7 100644 --- a/sound/soc/intel/boards/kbl_da7219_max98927.c +++ b/sound/soc/intel/boards/kbl_da7219_max98927.c @@ -52,7 +52,6 @@ struct kbl_codec_private { enum { KBL_DPCM_AUDIO_PB = 0, - KBL_DPCM_AUDIO_CP, KBL_DPCM_AUDIO_ECHO_REF_CP, KBL_DPCM_AUDIO_REF_CP, KBL_DPCM_AUDIO_DMIC_CP, @@ -60,6 +59,7 @@ enum { KBL_DPCM_AUDIO_HDMI2_PB, KBL_DPCM_AUDIO_HDMI3_PB, KBL_DPCM_AUDIO_HS_PB, + KBL_DPCM_AUDIO_CP, }; static int platform_clock_control(struct snd_soc_dapm_widget *w, @@ -311,6 +311,12 @@ static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) da7219_aad_jack_det(component, &ctx->kabylake_headset); + return 0; +} + +static int kabylake_dmic_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; ret = snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); if (ret) dev_err(rtd->dev, "SoC DMIC - Ignore suspend failed %d\n", ret); @@ -581,20 +587,6 @@ static struct snd_soc_dai_link kabylake_dais[] = { .dpcm_playback = 1, .ops = &kabylake_da7219_fe_ops, }, - [KBL_DPCM_AUDIO_CP] = { - .name = "Kbl Audio Capture Port", - .stream_name = "Audio Record", - .cpu_dai_name = "System Pin", - .platform_name = "0000:00:1f.3", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .nonatomic = 1, - .trigger = { - SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_capture = 1, - .ops = &kabylake_da7219_fe_ops, - }, [KBL_DPCM_AUDIO_ECHO_REF_CP] = { .name = "Kbl Audio Echo Reference cap", .stream_name = "Echoreference Capture", @@ -690,6 +682,20 @@ static struct snd_soc_dai_link kabylake_dais[] = { .ops = &kabylake_da7219_fe_ops, }, + [KBL_DPCM_AUDIO_CP] = { + .name = "Kbl Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &kabylake_da7219_fe_ops, + }, /* Back End DAI links */ { @@ -733,6 +739,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .cpu_dai_name = "DMIC01 Pin", .codec_name = "dmic-codec", .codec_dai_name = "dmic-hifi", + .init = kabylake_dmic_init, .platform_name = "0000:00:1f.3", .be_hw_params_fixup = kabylake_dmic_fixup, .ignore_suspend = 1, @@ -792,20 +799,6 @@ static struct snd_soc_dai_link kabylake_max98_927_373_dais[] = { .dpcm_playback = 1, .ops = &kabylake_da7219_fe_ops, }, - [KBL_DPCM_AUDIO_CP] = { - .name = "Kbl Audio Capture Port", - .stream_name = "Audio Record", - .cpu_dai_name = "System Pin", - .platform_name = "0000:00:1f.3", - .dynamic = 1, - .codec_name = "snd-soc-dummy", - .codec_dai_name = "snd-soc-dummy-dai", - .nonatomic = 1, - .trigger = { - SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, - .dpcm_capture = 1, - .ops = &kabylake_da7219_fe_ops, - }, [KBL_DPCM_AUDIO_ECHO_REF_CP] = { .name = "Kbl Audio Echo Reference cap", .stream_name = "Echoreference Capture", @@ -911,6 +904,7 @@ static struct snd_soc_dai_link kabylake_max98_927_373_dais[] = { .cpu_dai_name = "DMIC01 Pin", .codec_name = "dmic-codec", .codec_dai_name = "dmic-hifi", + .init = kabylake_dmic_init, .platform_name = "0000:00:1f.3", .be_hw_params_fixup = kabylake_dmic_fixup, .ignore_suspend = 1, diff --git a/sound/soc/intel/boards/skl_hda_dsp_common.c b/sound/soc/intel/boards/skl_hda_dsp_common.c index 3fdbf239da74..8b68f41a5b88 100644 --- a/sound/soc/intel/boards/skl_hda_dsp_common.c +++ b/sound/soc/intel/boards/skl_hda_dsp_common.c @@ -78,7 +78,6 @@ struct snd_soc_dai_link skl_hda_be_dai_links[HDA_DSP_MAX_BE_DAI_LINKS] = { .platform_name = "0000:00:1f.3", .dpcm_playback = 1, .dpcm_capture = 1, - .init = NULL, .no_pcm = 1, }, { @@ -90,7 +89,26 @@ struct snd_soc_dai_link skl_hda_be_dai_links[HDA_DSP_MAX_BE_DAI_LINKS] = { .platform_name = "0000:00:1f.3", .dpcm_playback = 1, .dpcm_capture = 1, - .init = NULL, + .no_pcm = 1, + }, + { + .name = "dmic01", + .id = 6, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:1f.3", + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "dmic16k", + .id = 7, + .cpu_dai_name = "DMIC16k Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:1f.3", + .dpcm_capture = 1, .no_pcm = 1, }, }; diff --git a/sound/soc/intel/boards/skl_hda_dsp_common.h b/sound/soc/intel/boards/skl_hda_dsp_common.h index 87c50aff56cd..daa582e513b2 100644 --- a/sound/soc/intel/boards/skl_hda_dsp_common.h +++ b/sound/soc/intel/boards/skl_hda_dsp_common.h @@ -15,7 +15,7 @@ #include <sound/core.h> #include <sound/jack.h> -#define HDA_DSP_MAX_BE_DAI_LINKS 5 +#define HDA_DSP_MAX_BE_DAI_LINKS 7 struct skl_hda_hdmi_pcm { struct list_head head; diff --git a/sound/soc/intel/boards/skl_hda_dsp_generic.c b/sound/soc/intel/boards/skl_hda_dsp_generic.c index b9a21e64ead2..fc52d3a32354 100644 --- a/sound/soc/intel/boards/skl_hda_dsp_generic.c +++ b/sound/soc/intel/boards/skl_hda_dsp_generic.c @@ -97,6 +97,9 @@ static struct snd_soc_card hda_soc_card = { }; #define IDISP_DAI_COUNT 3 +#define HDAC_DAI_COUNT 2 +#define DMIC_DAI_COUNT 2 + /* there are two routes per iDisp output */ #define IDISP_ROUTE_COUNT (IDISP_DAI_COUNT * 2) #define IDISP_CODEC_MASK 0x4 @@ -112,11 +115,23 @@ static int skl_hda_fill_card_info(struct snd_soc_acpi_mach_params *mach_params) codec_count = hweight_long(codec_mask); if (codec_count == 1 && codec_mask & IDISP_CODEC_MASK) { - num_links = IDISP_DAI_COUNT; + num_links = IDISP_DAI_COUNT + DMIC_DAI_COUNT; num_route = IDISP_ROUTE_COUNT; + + /* + * rearrange the dai link array and make the + * dmic dai links follow idsp dai links for only + * num_links of dai links need to be registered + * to ASoC. + */ + for (i = 0; i < DMIC_DAI_COUNT; i++) { + skl_hda_be_dai_links[IDISP_DAI_COUNT + i] = + skl_hda_be_dai_links[IDISP_DAI_COUNT + + HDAC_DAI_COUNT + i]; + } } else if (codec_count == 2 && codec_mask & IDISP_CODEC_MASK) { num_links = ARRAY_SIZE(skl_hda_be_dai_links); - num_route = ARRAY_SIZE(skl_hda_map), + num_route = ARRAY_SIZE(skl_hda_map); card->dapm_widgets = skl_hda_widgets; card->num_dapm_widgets = ARRAY_SIZE(skl_hda_widgets); } else { diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c new file mode 100644 index 000000000000..f28fb98cc306 --- /dev/null +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2019 Intel Corporation. + +/* + * Intel SOF Machine Driver with Realtek rt5682 Codec + * and speaker codec MAX98357A + */ +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dmi.h> +#include <asm/cpu_device_id.h> +#include <asm/intel-family.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/rt5682.h> +#include <sound/soc-acpi.h> +#include "../../codecs/rt5682.h" +#include "../../codecs/hdac_hdmi.h" + +#define NAME_SIZE 32 + +#define SOF_RT5682_SSP_CODEC(quirk) ((quirk) & GENMASK(2, 0)) +#define SOF_RT5682_SSP_CODEC_MASK (GENMASK(2, 0)) +#define SOF_RT5682_MCLK_EN BIT(3) +#define SOF_RT5682_MCLK_24MHZ BIT(4) +#define SOF_SPEAKER_AMP_PRESENT BIT(5) +#define SOF_RT5682_SSP_AMP(quirk) ((quirk) & GENMASK(8, 6)) +#define SOF_RT5682_SSP_AMP_MASK (GENMASK(8, 6)) +#define SOF_RT5682_SSP_AMP_SHIFT 6 + +/* Default: MCLK on, MCLK 19.2M, SSP0 */ +static unsigned long sof_rt5682_quirk = SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0); + +static int is_legacy_cpu; + +static struct snd_soc_jack sof_hdmi[3]; + +struct sof_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct sof_card_private { + struct snd_soc_jack sof_headset; + struct list_head hdmi_pcm_list; +}; + +static int sof_rt5682_quirk_cb(const struct dmi_system_id *id) +{ + sof_rt5682_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id sof_rt5682_quirk_table[] = { + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "WhiskeyLake Client"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_MCLK_24MHZ | + SOF_RT5682_SSP_CODEC(1)), + }, + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Google"), + DMI_MATCH(DMI_PRODUCT_NAME, "Hatch"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_MCLK_24MHZ | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1)), + }, + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Ice Lake Client"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0)), + }, + {} +}; + +static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct sof_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int sof_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + /* need to enable ASRC function for 24MHz mclk rate */ + if ((sof_rt5682_quirk & SOF_RT5682_MCLK_EN) && + (sof_rt5682_quirk & SOF_RT5682_MCLK_24MHZ)) { + rt5682_sel_asrc_clk_src(component, RT5682_DA_STEREO1_FILTER | + RT5682_AD_STEREO1_FILTER, + RT5682_CLK_SEL_I2S1_ASRC); + } + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sof_headset, NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + jack = &ctx->sof_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return ret; +}; + +static int sof_rt5682_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 = rtd->codec_dai; + int clk_id, clk_freq, pll_out, ret; + + if (sof_rt5682_quirk & SOF_RT5682_MCLK_EN) { + clk_id = RT5682_PLL1_S_MCLK; + if (sof_rt5682_quirk & SOF_RT5682_MCLK_24MHZ) + clk_freq = 24000000; + else + clk_freq = 19200000; + } else { + clk_id = RT5682_PLL1_S_BCLK1; + clk_freq = params_rate(params) * 50; + } + + pll_out = params_rate(params) * 512; + + ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out); + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_pll err = %d\n", ret); + + /* Configure sysclk for codec */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, + pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + /* + * slot_width should equal or large than data length, set them + * be the same + */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x0, 0x0, 2, + params_width(params)); + if (ret < 0) { + dev_err(rtd->dev, "set TDM slot err:%d\n", ret); + return ret; + } + + return ret; +} + +static struct snd_soc_ops sof_rt5682_ops = { + .hw_params = sof_rt5682_hw_params, +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + /* name might be overridden during probe */ + .name = "0000:00:1f.3" + } +}; + +static int sof_card_late_probe(struct snd_soc_card *card) +{ + struct sof_card_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = NULL; + char jack_name[NAME_SIZE]; + struct sof_hdmi_pcm *pcm; + int err = 0; + int i = 0; + + /* HDMI is not supported by SOF on Baytrail/CherryTrail */ + if (is_legacy_cpu) + return 0; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &sof_hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &sof_hdmi[i]); + if (err < 0) + return err; + + i++; + } + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +static const struct snd_kcontrol_new sof_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget sof_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), +}; + +static const struct snd_soc_dapm_route sof_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* other jacks */ + { "IN1P", NULL, "Headset Mic" }, + +}; + +static const struct snd_soc_dapm_route speaker_map[] = { + /* speaker */ + { "Spk", NULL, "Speaker" }, +}; + +static int speaker_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_add_routes(&card->dapm, speaker_map, + ARRAY_SIZE(speaker_map)); + + if (ret) + dev_err(rtd->dev, "Speaker map addition failed: %d\n", ret); + return ret; +} + +/* sof audio machine driver for rt5682 codec */ +static struct snd_soc_card sof_audio_card_rt5682 = { + .name = "sof_rt5682", + .owner = THIS_MODULE, + .controls = sof_controls, + .num_controls = ARRAY_SIZE(sof_controls), + .dapm_widgets = sof_widgets, + .num_dapm_widgets = ARRAY_SIZE(sof_widgets), + .dapm_routes = sof_map, + .num_dapm_routes = ARRAY_SIZE(sof_map), + .fully_routed = true, + .late_probe = sof_card_late_probe, +}; + +static const struct x86_cpu_id legacy_cpi_ids[] = { + { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT }, /* Baytrail */ + { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT }, /* Cherrytrail */ + {} +}; + +static struct snd_soc_dai_link_component rt5682_component[] = { + { + .name = "i2c-10EC5682:00", + .dai_name = "rt5682-aif1", + } +}; + +static struct snd_soc_dai_link_component dmic_component[] = { + { + .name = "dmic-codec", + .dai_name = "dmic-hifi", + } +}; + +static struct snd_soc_dai_link_component max98357a_component[] = { + { + .name = "MX98357A:00", + .dai_name = "HiFi", + } +}; + +static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, + int ssp_codec, + int ssp_amp, + int dmic_num, + int hdmi_num) +{ + struct snd_soc_dai_link_component *idisp_components; + struct snd_soc_dai_link *links; + int i, id = 0; + + links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * + sof_audio_card_rt5682.num_links, GFP_KERNEL); + if (!links) + goto devm_err; + + /* codec SSP */ + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d-Codec", ssp_codec); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].codecs = rt5682_component; + links[id].num_codecs = ARRAY_SIZE(rt5682_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_rt5682_codec_init; + links[id].ops = &sof_rt5682_ops; + links[id].nonatomic = true; + links[id].dpcm_playback = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + if (is_legacy_cpu) { + links[id].cpu_dai_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-port", + ssp_codec); + if (!links[id].cpu_dai_name) + goto devm_err; + } else { + /* + * Currently, On SKL+ platforms MCLK will be turned off in sof + * runtime suspended, and it will go into runtime suspended + * right after playback is stop. However, rt5682 will output + * static noise if sysclk turns off during playback. Set + * ignore_pmdown_time to power down rt5682 immediately and + * avoid the noise. + * It can be removed once we can control MCLK by driver. + */ + links[id].ignore_pmdown_time = 1; + links[id].cpu_dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", + ssp_codec); + if (!links[id].cpu_dai_name) + goto devm_err; + } + id++; + + /* dmic */ + for (i = 1; i <= dmic_num; i++) { + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "dmic%02d", i); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].cpu_dai_name = devm_kasprintf(dev, GFP_KERNEL, + "DMIC%02d Pin", i); + if (!links[id].cpu_dai_name) + goto devm_err; + + links[id].codecs = dmic_component; + links[id].num_codecs = ARRAY_SIZE(dmic_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].ignore_suspend = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + id++; + } + + /* HDMI */ + if (hdmi_num > 0) { + idisp_components = devm_kzalloc(dev, + sizeof(struct snd_soc_dai_link_component) * + hdmi_num, GFP_KERNEL); + if (!idisp_components) + goto devm_err; + } + for (i = 1; i <= hdmi_num; i++) { + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d", i); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].cpu_dai_name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d Pin", i); + if (!links[id].cpu_dai_name) + goto devm_err; + + idisp_components[i - 1].name = "ehdaudio0D2"; + idisp_components[i - 1].dai_name = devm_kasprintf(dev, + GFP_KERNEL, + "intel-hdmi-hifi%d", + i); + if (!idisp_components[i - 1].dai_name) + goto devm_err; + + links[id].codecs = &idisp_components[i - 1]; + links[id].num_codecs = 1; + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_hdmi_init; + links[id].dpcm_playback = 1; + links[id].no_pcm = 1; + id++; + } + + /* speaker amp */ + if (sof_rt5682_quirk & SOF_SPEAKER_AMP_PRESENT) { + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d-Codec", ssp_amp); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].codecs = max98357a_component; + links[id].num_codecs = ARRAY_SIZE(max98357a_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = speaker_codec_init, + links[id].nonatomic = true; + links[id].dpcm_playback = 1; + links[id].no_pcm = 1; + if (is_legacy_cpu) { + links[id].cpu_dai_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-port", + ssp_amp); + if (!links[id].cpu_dai_name) + goto devm_err; + + } else { + links[id].cpu_dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", + ssp_amp); + if (!links[id].cpu_dai_name) + goto devm_err; + } + } + + return links; +devm_err: + return NULL; +} + +static int sof_audio_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai_links; + struct snd_soc_acpi_mach *mach; + struct sof_card_private *ctx; + int dmic_num, hdmi_num; + int ret, ssp_amp, ssp_codec; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC); + if (!ctx) + return -ENOMEM; + + if (x86_match_cpu(legacy_cpi_ids)) { + is_legacy_cpu = 1; + dmic_num = 0; + hdmi_num = 0; + /* default quirk for legacy cpu */ + sof_rt5682_quirk = SOF_RT5682_SSP_CODEC(2); + } else { + dmic_num = 1; + hdmi_num = 3; + } + + dmi_check_system(sof_rt5682_quirk_table); + + dev_dbg(&pdev->dev, "sof_rt5682_quirk = %lx\n", sof_rt5682_quirk); + + ssp_amp = (sof_rt5682_quirk & SOF_RT5682_SSP_AMP_MASK) >> + SOF_RT5682_SSP_AMP_SHIFT; + + ssp_codec = sof_rt5682_quirk & SOF_RT5682_SSP_CODEC_MASK; + + /* compute number of dai links */ + sof_audio_card_rt5682.num_links = 1 + dmic_num + hdmi_num; + if (sof_rt5682_quirk & SOF_SPEAKER_AMP_PRESENT) + sof_audio_card_rt5682.num_links++; + + dai_links = sof_card_dai_links_create(&pdev->dev, ssp_codec, ssp_amp, + dmic_num, hdmi_num); + if (!dai_links) + return -ENOMEM; + + sof_audio_card_rt5682.dai_link = dai_links; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + sof_audio_card_rt5682.dev = &pdev->dev; + mach = (&pdev->dev)->platform_data; + + /* set platform name for each dailink */ + ret = snd_soc_fixup_dai_links_platform_name(&sof_audio_card_rt5682, + mach->mach_params.platform); + if (ret) + return ret; + + snd_soc_card_set_drvdata(&sof_audio_card_rt5682, ctx); + + return devm_snd_soc_register_card(&pdev->dev, + &sof_audio_card_rt5682); +} + +static struct platform_driver sof_audio = { + .probe = sof_audio_probe, + .driver = { + .name = "sof_rt5682", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(sof_audio) + +/* Module information */ +MODULE_DESCRIPTION("SOF Audio Machine driver"); +MODULE_AUTHOR("Bard Liao <bard.liao@intel.com>"); +MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sof_rt5682"); diff --git a/sound/soc/intel/common/soc-acpi-intel-byt-match.c b/sound/soc/intel/common/soc-acpi-intel-byt-match.c index fe812a909db4..0cfab247876a 100644 --- a/sound/soc/intel/common/soc-acpi-intel-byt-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-byt-match.c @@ -185,6 +185,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt-es8316.tplg", }, + { + .id = "10EC5682", + .drv_name = "sof_rt5682", + .sof_fw_filename = "sof-byt.ri", + .sof_tplg_filename = "sof-byt-rt5682.tplg", + }, /* some Baytrail platforms rely on RT5645, use CHT machine driver */ { .id = "10EC5645", diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c index deafd87cc764..ff9c31a39ad4 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -160,6 +160,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .sof_fw_filename = "sof-cht.ri", .sof_tplg_filename = "sof-cht-rt5640.tplg", }, + { + .id = "10EC5682", + .drv_name = "sof_rt5682", + .sof_fw_filename = "sof-cht.ri", + .sof_tplg_filename = "sof-cht-rt5682.tplg", + }, /* some CHT-T platforms rely on RT5651, use Baytrail machine driver */ { .id = "10EC5651", diff --git a/sound/soc/intel/common/soc-acpi-intel-cnl-match.c b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c index a914dd238d0a..df7c52cad5c3 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cnl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c @@ -14,6 +14,11 @@ static struct skl_machine_pdata cnl_pdata = { .use_tplg_pcm = true, }; +static struct snd_soc_acpi_codecs cml_codecs = { + .num_codecs = 1, + .codecs = {"10EC5682"} +}; + struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_machines[] = { { .id = "INT34C2", @@ -23,6 +28,20 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_machines[] = { .sof_fw_filename = "sof-cnl.ri", .sof_tplg_filename = "sof-cnl-rt274.tplg", }, + { + .id = "10EC5682", + .drv_name = "sof_rt5682", + .sof_fw_filename = "sof-cnl.ri", + .sof_tplg_filename = "sof-cml-rt5682.tplg", + }, + { + .id = "MX98357A", + .drv_name = "sof_rt5682", + .quirk_data = &cml_codecs, + .sof_fw_filename = "sof-cnl.ri", + .sof_tplg_filename = "sof-cml-rt5682-max98357a.tplg", + }, + {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cnl_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-glk-match.c b/sound/soc/intel/common/soc-acpi-intel-glk-match.c index 3f2061475ae4..616eb09e78a0 100644 --- a/sound/soc/intel/common/soc-acpi-intel-glk-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-glk-match.c @@ -31,6 +31,15 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_glk_machines[] = { .sof_fw_filename = "sof-glk.ri", .sof_tplg_filename = "sof-glk-da7219.tplg", }, + { + .id = "10EC5682", + .drv_name = "glk_rt5682_max98357a", + .fw_filename = "intel/dsp_fw_glk.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &glk_codecs, + .sof_fw_filename = "sof-glk.ri", + .sof_tplg_filename = "sof-glk-rt5682.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_glk_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-icl-match.c b/sound/soc/intel/common/soc-acpi-intel-icl-match.c index e5a6be5bc0ee..0b430b9b3673 100644 --- a/sound/soc/intel/common/soc-acpi-intel-icl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-icl-match.c @@ -23,6 +23,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_icl_machines[] = { .sof_fw_filename = "sof-icl.ri", .sof_tplg_filename = "sof-icl-rt274.tplg", }, + { + .id = "10EC5682", + .drv_name = "sof_rt5682", + .sof_fw_filename = "sof-icl.ri", + .sof_tplg_filename = "sof-icl-rt5682.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_icl_machines); diff --git a/sound/soc/intel/common/sst-firmware.c b/sound/soc/intel/common/sst-firmware.c index 1e067504b604..f830e59f93ea 100644 --- a/sound/soc/intel/common/sst-firmware.c +++ b/sound/soc/intel/common/sst-firmware.c @@ -1251,11 +1251,15 @@ struct sst_dsp *sst_dsp_new(struct device *dev, goto irq_err; err = sst_dma_new(sst); - if (err) - dev_warn(dev, "sst_dma_new failed %d\n", err); + if (err) { + dev_err(dev, "sst_dma_new failed %d\n", err); + goto dma_err; + } return sst; +dma_err: + free_irq(sst->irq, sst); irq_err: if (sst->ops->free) sst->ops->free(sst); diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.c b/sound/soc/intel/haswell/sst-haswell-ipc.c index 31fcdf12c67d..74acf9c65161 100644 --- a/sound/soc/intel/haswell/sst-haswell-ipc.c +++ b/sound/soc/intel/haswell/sst-haswell-ipc.c @@ -345,11 +345,6 @@ static inline u32 msg_get_stream_type(u32 msg) return (msg & IPC_STR_TYPE_MASK) >> IPC_STR_TYPE_SHIFT; } -static inline u32 msg_get_stage_type(u32 msg) -{ - return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; -} - static inline u32 msg_get_stream_id(u32 msg) { return (msg & IPC_STR_ID_MASK) >> IPC_STR_ID_SHIFT; @@ -666,13 +661,12 @@ static int hsw_module_message(struct sst_hsw *hsw, u32 header) static int hsw_stream_message(struct sst_hsw *hsw, u32 header) { - u32 stream_msg, stream_id, stage_type; + u32 stream_msg, stream_id; struct sst_hsw_stream *stream; int handled = 0; stream_msg = msg_get_stream_type(header); stream_id = msg_get_stream_id(header); - stage_type = msg_get_stage_type(header); stream = get_stream_by_id(hsw, stream_id); if (stream == NULL) diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig index 1a354a6b6e87..b3f9c41b4319 100644 --- a/sound/soc/jz4740/Kconfig +++ b/sound/soc/jz4740/Kconfig @@ -1,6 +1,6 @@ config SND_JZ4740_SOC tristate "SoC Audio for Ingenic JZ4740 SoC" - depends on MACH_JZ4740 || COMPILE_TEST + depends on MIPS || COMPILE_TEST select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y or M if you want to add support for codecs attached to diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index b35410e4020e..f70b7109f2b6 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -116,6 +116,33 @@ config SND_SOC_MT8183 Select Y if you have such device. If unsure select "N". +config SND_SOC_MT8183_MT6358_TS3A227E_MAX98357A + tristate "ASoC Audio driver for MT8183 with MT6358 TS3A227E MAX98357A codec" + depends on I2C + depends on SND_SOC_MT8183 + select SND_SOC_MT6358 + select SND_SOC_MAX98357A + select SND_SOC_BT_SCO + select SND_SOC_TS3A227E + help + This adds ASoC driver for Mediatek MT8183 boards + with the MT6358 TS3A227E MAX98357A audio codec. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT8183_DA7219_MAX98357A + tristate "ASoC Audio driver for MT8183 with DA7219 MAX98357A codec" + depends on SND_SOC_MT8183 + select SND_SOC_MT6358 + select SND_SOC_MAX98357A + select SND_SOC_DA7219 + select SND_SOC_BT_SCO + help + This adds ASoC driver for Mediatek MT8183 boards + with the DA7219 MAX98357A audio codec. + Select Y if you have such device. + If unsure select "N". + config SND_SOC_MTK_BTCVSD tristate "ALSA BT SCO CVSD/MSBC Driver" help diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c index cf4978be062f..fded11d14cde 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -18,11 +18,11 @@ static int mtk_regmap_update_bits(struct regmap *map, int reg, unsigned int mask, - unsigned int val) + unsigned int val, int shift) { - if (reg < 0) + if (reg < 0 || WARN_ON_ONCE(shift < 0)) return 0; - return regmap_update_bits(map, reg, mask, val); + return regmap_update_bits(map, reg, mask << shift, val << shift); } static int mtk_regmap_write(struct regmap *map, int reg, unsigned int val) @@ -49,8 +49,7 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16); /* enable agent */ mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, - 1 << memif->data->agent_disable_shift, - 0 << memif->data->agent_disable_shift); + 1, 0, memif->data->agent_disable_shift); snd_soc_set_runtime_hwparams(substream, mtk_afe_hardware); @@ -105,8 +104,7 @@ void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream, irq_id = memif->irq_usage; mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, - 1 << memif->data->agent_disable_shift, - 1 << memif->data->agent_disable_shift); + 1, 1, memif->data->agent_disable_shift); if (!memif->const_irq) { mtk_dynamic_irq_release(afe, irq_id); @@ -144,16 +142,14 @@ int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream, /* set MSB to 33-bit */ mtk_regmap_update_bits(afe->regmap, memif->data->msb_reg, - 1 << memif->data->msb_shift, - msb_at_bit33 << memif->data->msb_shift); + 1, msb_at_bit33, memif->data->msb_shift); /* set channel */ if (memif->data->mono_shift >= 0) { unsigned int mono = (params_channels(params) == 1) ? 1 : 0; mtk_regmap_update_bits(afe->regmap, memif->data->mono_reg, - 1 << memif->data->mono_shift, - mono << memif->data->mono_shift); + 1, mono, memif->data->mono_shift); } /* set rate */ @@ -166,8 +162,8 @@ int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream, return -EINVAL; mtk_regmap_update_bits(afe->regmap, memif->data->fs_reg, - memif->data->fs_maskbit << memif->data->fs_shift, - fs << memif->data->fs_shift); + memif->data->fs_maskbit, fs, + memif->data->fs_shift); return 0; } @@ -197,17 +193,14 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: - if (memif->data->enable_shift >= 0) - mtk_regmap_update_bits(afe->regmap, - memif->data->enable_reg, - 1 << memif->data->enable_shift, - 1 << memif->data->enable_shift); + mtk_regmap_update_bits(afe->regmap, + memif->data->enable_reg, + 1, 1, memif->data->enable_shift); /* set irq counter */ mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg, - irq_data->irq_cnt_maskbit - << irq_data->irq_cnt_shift, - counter << irq_data->irq_cnt_shift); + irq_data->irq_cnt_maskbit, counter, + irq_data->irq_cnt_shift); /* set irq fs */ fs = afe->irq_fs(substream, runtime->rate); @@ -216,24 +209,21 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, return -EINVAL; mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg, - irq_data->irq_fs_maskbit - << irq_data->irq_fs_shift, - fs << irq_data->irq_fs_shift); + irq_data->irq_fs_maskbit, fs, + irq_data->irq_fs_shift); /* enable interrupt */ mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, - 1 << irq_data->irq_en_shift, - 1 << irq_data->irq_en_shift); + 1, 1, irq_data->irq_en_shift); return 0; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: mtk_regmap_update_bits(afe->regmap, memif->data->enable_reg, - 1 << memif->data->enable_shift, 0); + 1, 0, memif->data->enable_shift); /* disable interrupt */ mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, - 1 << irq_data->irq_en_shift, - 0 << irq_data->irq_en_shift); + 1, 0, irq_data->irq_en_shift); /* and clear pending IRQ */ mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg, 1 << irq_data->irq_clr_shift); @@ -270,8 +260,7 @@ int mtk_afe_fe_prepare(struct snd_pcm_substream *substream, } mtk_regmap_update_bits(afe->regmap, memif->data->hd_reg, - 1 << memif->data->hd_shift, - hd_audio << memif->data->hd_shift); + 1, hd_audio, memif->data->hd_shift); return 0; } diff --git a/sound/soc/mediatek/common/mtk-btcvsd.c b/sound/soc/mediatek/common/mtk-btcvsd.c index 9a163d7064d1..bd55c546e790 100644 --- a/sound/soc/mediatek/common/mtk-btcvsd.c +++ b/sound/soc/mediatek/common/mtk-btcvsd.c @@ -193,13 +193,13 @@ static const u8 table_msbc_silence[SCO_PACKET_180] = { static void mtk_btcvsd_snd_irq_enable(struct mtk_btcvsd_snd *bt) { regmap_update_bits(bt->infra, bt->infra_misc_offset, - bt->conn_bt_cvsd_mask, bt->conn_bt_cvsd_mask); + bt->conn_bt_cvsd_mask, 0); } static void mtk_btcvsd_snd_irq_disable(struct mtk_btcvsd_snd *bt) { regmap_update_bits(bt->infra, bt->infra_misc_offset, - bt->conn_bt_cvsd_mask, 0); + bt->conn_bt_cvsd_mask, bt->conn_bt_cvsd_mask); } static void mtk_btcvsd_snd_set_state(struct mtk_btcvsd_snd *bt, diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c index 968fba4d7533..7064a9fd6f74 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -994,7 +994,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 6, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DL2", @@ -1013,7 +1012,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 7, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DL3", @@ -1032,7 +1030,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 8, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DL4", @@ -1051,7 +1048,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 9, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DL5", @@ -1070,7 +1066,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 10, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DLM", @@ -1089,7 +1084,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 12, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL1", @@ -1108,7 +1102,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 0, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL2", @@ -1127,7 +1120,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 1, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL3", @@ -1146,7 +1138,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 2, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL4", @@ -1165,7 +1156,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 3, .msb_reg = -1, - .msb_shift = -1, }, { .name = "UL5", @@ -1184,7 +1174,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 4, .msb_reg = -1, - .msb_shift = -1, }, { .name = "DLBT", @@ -1203,7 +1192,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 13, .msb_reg = -1, - .msb_shift = -1, }, { .name = "ULBT", @@ -1222,7 +1210,6 @@ static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { .agent_disable_reg = AUDIO_TOP_CON5, .agent_disable_shift = 16, .msb_reg = -1, - .msb_shift = -1, }, }; diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c index bff7d71d0742..08a6532da322 100644 --- a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c +++ b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c @@ -401,9 +401,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DL1_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_DL2] = { .name = "DL2", @@ -420,9 +418,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DL2_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_DL3] = { .name = "DL3", @@ -439,9 +435,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DL3_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_VUL] = { .name = "VUL", @@ -458,9 +452,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = VUL_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_AWB] = { .name = "AWB", @@ -477,9 +469,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = AWB_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_VUL12] = { .name = "VUL12", @@ -496,9 +486,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = VUL_DATA2_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_DAI] = { .name = "DAI", @@ -515,9 +503,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = DAI_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, [MT6797_MEMIF_MOD_DAI] = { .name = "MOD_DAI", @@ -534,9 +520,7 @@ static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { .hd_reg = AFE_MEMIF_HD_MODE, .hd_shift = MOD_DAI_HD_SFT, .agent_disable_reg = -1, - .agent_disable_shift = -1, .msb_reg = -1, - .msb_shift = -1, }, }; diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index 166aed28330d..0382896c162e 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -714,13 +714,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 21, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 1, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 0, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "DL2", .id = MT8173_AFE_MEMIF_DL2, @@ -732,13 +730,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 22, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 2, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 1, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "VUL", .id = MT8173_AFE_MEMIF_VUL, @@ -750,13 +746,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 27, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 3, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 6, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "DAI", .id = MT8173_AFE_MEMIF_DAI, @@ -768,13 +762,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = -1, .mono_shift = -1, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 4, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 5, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "AWB", .id = MT8173_AFE_MEMIF_AWB, @@ -786,13 +778,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 24, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 6, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 3, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "MOD_DAI", .id = MT8173_AFE_MEMIF_MOD_DAI, @@ -804,13 +794,11 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = AFE_DAC_CON1, .mono_shift = 30, .hd_reg = -1, - .hd_shift = -1, .enable_reg = AFE_DAC_CON0, .enable_shift = 7, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 4, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, { .name = "HDMI", .id = MT8173_AFE_MEMIF_HDMI, @@ -822,13 +810,10 @@ static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { .mono_reg = -1, .mono_shift = -1, .hd_reg = -1, - .hd_shift = -1, .enable_reg = -1, - .enable_shift = -1, .msb_reg = AFE_MEMIF_MSB, .msb_shift = 8, .agent_disable_reg = -1, - .agent_disable_shift = -1, }, }; @@ -914,7 +899,6 @@ static const struct mtk_base_irq_data irq_data[MT8173_AFE_IRQ_NUM] = { .irq_en_reg = AFE_IRQ_MCU_CON, .irq_en_shift = 12, .irq_fs_reg = -1, - .irq_fs_shift = -1, .irq_fs_maskbit = -1, .irq_clr_reg = AFE_IRQ_CLR, .irq_clr_shift = 4, diff --git a/sound/soc/mediatek/mt8183/Makefile b/sound/soc/mediatek/mt8183/Makefile index f3ee6ac98fe8..c0a3bbc2c1f6 100644 --- a/sound/soc/mediatek/mt8183/Makefile +++ b/sound/soc/mediatek/mt8183/Makefile @@ -11,3 +11,5 @@ snd-soc-mt8183-afe-objs := \ mt8183-dai-adda.o obj-$(CONFIG_SND_SOC_MT8183) += snd-soc-mt8183-afe.o +obj-$(CONFIG_SND_SOC_MT8183_MT6358_TS3A227E_MAX98357A) += mt8183-mt6358-ts3a227-max98357.o +obj-$(CONFIG_SND_SOC_MT8183_DA7219_MAX98357A) += mt8183-da7219-max98357.o diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c index 4e045dd305a7..1bc0fafe5e29 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c @@ -291,11 +291,15 @@ static struct snd_soc_dai_driver mt8183_memif_dai_driver[] = { static const struct snd_kcontrol_new memif_ul1_ch1_mix[] = { SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN21, I_ADDA_UL_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I2S0_CH1", AFE_CONN21, + I_I2S0_CH1, 1, 0), }; static const struct snd_kcontrol_new memif_ul1_ch2_mix[] = { SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN22, I_ADDA_UL_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I2S0_CH2", AFE_CONN21, + I_I2S0_CH2, 1, 0), }; static const struct snd_kcontrol_new memif_ul2_ch1_mix[] = { @@ -307,6 +311,8 @@ static const struct snd_kcontrol_new memif_ul2_ch1_mix[] = { I_DL2_CH1, 1, 0), SOC_DAPM_SINGLE_AUTODISABLE("DL3_CH1", AFE_CONN5, I_DL3_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I2S2_CH1", AFE_CONN5, + I_I2S2_CH1, 1, 0), }; static const struct snd_kcontrol_new memif_ul2_ch2_mix[] = { @@ -318,16 +324,22 @@ static const struct snd_kcontrol_new memif_ul2_ch2_mix[] = { I_DL2_CH2, 1, 0), SOC_DAPM_SINGLE_AUTODISABLE("DL3_CH2", AFE_CONN6, I_DL3_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I2S2_CH2", AFE_CONN6, + I_I2S2_CH2, 1, 0), }; static const struct snd_kcontrol_new memif_ul3_ch1_mix[] = { SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN32, I_ADDA_UL_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I2S2_CH1", AFE_CONN32, + I_I2S2_CH1, 1, 0), }; static const struct snd_kcontrol_new memif_ul3_ch2_mix[] = { SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN33, I_ADDA_UL_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I2S2_CH2", AFE_CONN33, + I_I2S2_CH2, 1, 0), }; static const struct snd_kcontrol_new memif_ul4_ch1_mix[] = { @@ -380,16 +392,22 @@ static const struct snd_soc_dapm_route mt8183_memif_routes[] = { {"UL1", NULL, "UL1_CH2"}, {"UL1_CH1", "ADDA_UL_CH1", "ADDA Capture"}, {"UL1_CH2", "ADDA_UL_CH2", "ADDA Capture"}, + {"UL1_CH1", "I2S0_CH1", "I2S0"}, + {"UL1_CH2", "I2S0_CH2", "I2S0"}, {"UL2", NULL, "UL2_CH1"}, {"UL2", NULL, "UL2_CH2"}, {"UL2_CH1", "ADDA_UL_CH1", "ADDA Capture"}, {"UL2_CH2", "ADDA_UL_CH2", "ADDA Capture"}, + {"UL2_CH1", "I2S2_CH1", "I2S2"}, + {"UL2_CH2", "I2S2_CH2", "I2S2"}, {"UL3", NULL, "UL3_CH1"}, {"UL3", NULL, "UL3_CH2"}, {"UL3_CH1", "ADDA_UL_CH1", "ADDA Capture"}, {"UL3_CH2", "ADDA_UL_CH2", "ADDA Capture"}, + {"UL3_CH1", "I2S2_CH1", "I2S2"}, + {"UL3_CH2", "I2S2_CH2", "I2S2"}, {"UL4", NULL, "UL4_CH1"}, {"UL4", NULL, "UL4_CH2"}, diff --git a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c new file mode 100644 index 000000000000..31ea8632c397 --- /dev/null +++ b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mt8183-da7219-max98357.c +// -- MT8183-DA7219-MAX98357 ALSA SoC machine driver +// +// Copyright (c) 2018 MediaTek Inc. +// Author: Shunli Wang <shunli.wang@mediatek.com> + +#include <linux/module.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <linux/pinctrl/consumer.h> + +#include "mt8183-afe-common.h" +#include "../../codecs/da7219-aad.h" +#include "../../codecs/da7219.h" + +static struct snd_soc_jack headset_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin headset_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static struct snd_soc_dai_link_component +mt8183_da7219_max98357_external_codecs[] = { + { + .name = "max98357a", + .dai_name = "HiFi", + }, + { + .name = "da7219.5-001a", + .dai_name = "da7219-hifi", + }, +}; + +static int mt8183_mt6358_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int rate = params_rate(params); + unsigned int mclk_fs_ratio = 128; + unsigned int mclk_fs = rate * mclk_fs_ratio; + + return snd_soc_dai_set_sysclk(rtd->cpu_dai, + 0, mclk_fs, SND_SOC_CLOCK_OUT); +} + +static const struct snd_soc_ops mt8183_mt6358_i2s_ops = { + .hw_params = mt8183_mt6358_i2s_hw_params, +}; + +static int mt8183_da7219_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int rate = params_rate(params); + unsigned int mclk_fs_ratio = 256; + unsigned int mclk_fs = rate * mclk_fs_ratio; + unsigned int freq; + int ret = 0, j; + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, + mclk_fs, SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(rtd->dev, "failed to set cpu dai sysclk\n"); + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + + if (!strcmp(codec_dai->component->name, "da7219.5-001a")) { + ret = snd_soc_dai_set_sysclk(codec_dai, + DA7219_CLKSRC_MCLK, + mclk_fs, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "failed to set sysclk\n"); + + if ((rate % 8000) == 0) + freq = DA7219_PLL_FREQ_OUT_98304; + else + freq = DA7219_PLL_FREQ_OUT_90316; + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_PLL_SRM, + 0, freq); + if (ret) + dev_err(rtd->dev, "failed to start PLL: %d\n", + ret); + } + } + + return ret; +} + +static int mt8183_da7219_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0, j; + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + + if (!strcmp(codec_dai->component->name, "da7219.5-001a")) { + ret = snd_soc_dai_set_pll(codec_dai, + 0, DA7219_SYSCLK_MCLK, 0, 0); + if (ret < 0) { + dev_err(rtd->dev, "failed to stop PLL: %d\n", + ret); + break; + } + } + } + + return ret; +} + +static const struct snd_soc_ops mt8183_da7219_i2s_ops = { + .hw_params = mt8183_da7219_i2s_hw_params, + .hw_free = mt8183_da7219_hw_free, +}; + +static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + + return 0; +} + +static const struct snd_soc_dapm_widget +mt8183_da7219_max98357_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("IT6505_8CH"), +}; + +static const struct snd_soc_dapm_route mt8183_da7219_max98357_dapm_routes[] = { + {"IT6505_8CH", NULL, "TDM"}, +}; + +static struct snd_soc_dai_link mt8183_da7219_max98357_dai_links[] = { + /* FE */ + { + .name = "Playback_1", + .stream_name = "Playback_1", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Playback_2", + .stream_name = "Playback_2", + .cpu_dai_name = "DL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Playback_3", + .stream_name = "Playback_3", + .cpu_dai_name = "DL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Capture_1", + .stream_name = "Capture_1", + .cpu_dai_name = "UL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_2", + .stream_name = "Capture_2", + .cpu_dai_name = "UL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_3", + .stream_name = "Capture_3", + .cpu_dai_name = "UL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_Mono_1", + .stream_name = "Capture_Mono_1", + .cpu_dai_name = "UL_MONO_1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Playback_HDMI", + .stream_name = "Playback_HDMI", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + /* BE */ + { + .name = "Primary Codec", + .cpu_dai_name = "ADDA", + .codec_dai_name = "mt6358-snd-codec-aif1", + .codec_name = "mt6358-sound", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 1", + .cpu_dai_name = "PCM 1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 2", + .cpu_dai_name = "PCM 2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "I2S0", + .cpu_dai_name = "I2S0", + .codec_dai_name = "bt-sco-pcm", + .codec_name = "bt-sco", + .no_pcm = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S1", + .cpu_dai_name = "I2S1", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S2", + .cpu_dai_name = "I2S2", + .codec_dai_name = "da7219-hifi", + .codec_name = "da7219.5-001a", + .no_pcm = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_da7219_i2s_ops, + }, + { + .name = "I2S3", + .cpu_dai_name = "I2S3", + .codecs = mt8183_da7219_max98357_external_codecs, + .num_codecs = + ARRAY_SIZE(mt8183_da7219_max98357_external_codecs), + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_da7219_i2s_ops, + }, + { + .name = "I2S5", + .cpu_dai_name = "I2S5", + .codec_dai_name = "bt-sco-pcm", + .codec_name = "bt-sco", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "TDM", + .cpu_dai_name = "TDM", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + }, +}; + +static int +mt8183_da7219_max98357_headset_init(struct snd_soc_component *component); + +static struct snd_soc_aux_dev mt8183_da7219_max98357_headset_dev = { + .name = "Headset Chip", + .init = mt8183_da7219_max98357_headset_init, +}; + +static struct snd_soc_codec_conf mt6358_codec_conf[] = { + { + .dev_name = "mt6358-sound", + .name_prefix = "Mt6358", + }, +}; + +static struct snd_soc_card mt8183_da7219_max98357_card = { + .name = "mt8183_da7219_max98357", + .owner = THIS_MODULE, + .dai_link = mt8183_da7219_max98357_dai_links, + .num_links = ARRAY_SIZE(mt8183_da7219_max98357_dai_links), + .aux_dev = &mt8183_da7219_max98357_headset_dev, + .num_aux_devs = 1, + .codec_conf = mt6358_codec_conf, + .num_configs = ARRAY_SIZE(mt6358_codec_conf), +}; + +static int +mt8183_da7219_max98357_headset_init(struct snd_soc_component *component) +{ + int ret; + + /* Enable Headset and 4 Buttons Jack detection */ + ret = snd_soc_card_jack_new(&mt8183_da7219_max98357_card, + "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &headset_jack, + headset_jack_pins, + ARRAY_SIZE(headset_jack_pins)); + if (ret) + return ret; + + da7219_aad_jack_det(component, &headset_jack); + + return ret; +} + +static int mt8183_da7219_max98357_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8183_da7219_max98357_card; + struct device_node *platform_node; + struct snd_soc_dai_link *dai_link; + struct pinctrl *default_pins; + int ret, i; + + card->dev = &pdev->dev; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + /* In the alsa soc-core, the "platform" will be + * allocated by devm_kzalloc if null. + * There is a special case that registerring + * sound card is failed at the first time, but + * the "platform" will not null when probe is trying + * again. It's not expected normally. + */ + dai_link->platforms = NULL; + + if (dai_link->platform_name) + continue; + dai_link->platform_of_node = platform_node; + } + + mt8183_da7219_max98357_headset_dev.codec_of_node = + of_parse_phandle(pdev->dev.of_node, + "mediatek,headset-codec", 0); + if (!mt8183_da7219_max98357_headset_dev.codec_of_node) { + dev_err(&pdev->dev, + "Property 'mediatek,headset-codec' missing/invalid\n"); + return -EINVAL; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + return ret; + } + + default_pins = + devm_pinctrl_get_select(&pdev->dev, PINCTRL_STATE_DEFAULT); + if (IS_ERR(default_pins)) { + dev_err(&pdev->dev, "%s set pins failed\n", + __func__); + return PTR_ERR(default_pins); + } + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt8183_da7219_max98357_dt_match[] = { + {.compatible = "mediatek,mt8183_da7219_max98357",}, + {} +}; +#endif + +static struct platform_driver mt8183_da7219_max98357_driver = { + .driver = { + .name = "mt8183_da7219_max98357", +#ifdef CONFIG_OF + .of_match_table = mt8183_da7219_max98357_dt_match, +#endif + }, + .probe = mt8183_da7219_max98357_dev_probe, +}; + +module_platform_driver(mt8183_da7219_max98357_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8183-DA7219-MAX98357 ALSA SoC machine driver"); +MODULE_AUTHOR("Shunli Wang <shunli.wang@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt8183_da7219_max98357 soc card"); + diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c new file mode 100644 index 000000000000..4e44e5689d6f --- /dev/null +++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mt8183-mt6358.c -- +// MT8183-MT6358-TS3A227-MAX98357 ALSA SoC machine driver +// +// Copyright (c) 2018 MediaTek Inc. +// Author: Shunli Wang <shunli.wang@mediatek.com> + +#include <linux/module.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <linux/pinctrl/consumer.h> + +#include "mt8183-afe-common.h" +#include "../../codecs/ts3a227e.h" + +static struct snd_soc_jack headset_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin headset_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + +}; + +static int mt8183_mt6358_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int rate = params_rate(params); + unsigned int mclk_fs_ratio = 128; + unsigned int mclk_fs = rate * mclk_fs_ratio; + + return snd_soc_dai_set_sysclk(rtd->cpu_dai, + 0, mclk_fs, SND_SOC_CLOCK_OUT); +} + +static const struct snd_soc_ops mt8183_mt6358_i2s_ops = { + .hw_params = mt8183_mt6358_i2s_hw_params, +}; + +static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + dev_dbg(rtd->dev, "%s(), fix format to 32bit\n", __func__); + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +} + +static const struct snd_soc_dapm_widget +mt8183_mt6358_ts3a227_max98357_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("IT6505_8CH"), +}; + +static const struct snd_soc_dapm_route +mt8183_mt6358_ts3a227_max98357_dapm_routes[] = { + {"IT6505_8CH", NULL, "TDM"}, +}; + +static int +mt8183_mt6358_ts3a227_max98357_bt_sco_startup( + struct snd_pcm_substream *substream) +{ + static const unsigned int rates[] = { + 8000, 16000 + }; + static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + static const unsigned int channels[] = { + 1, + }; + static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, + }; + + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + runtime->hw.channels_max = 1; + 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_msbits(runtime, 0, 16, 16); + + return 0; +} + +static const struct snd_soc_ops mt8183_mt6358_ts3a227_max98357_bt_sco_ops = { + .startup = mt8183_mt6358_ts3a227_max98357_bt_sco_startup, +}; + +static struct snd_soc_dai_link +mt8183_mt6358_ts3a227_max98357_dai_links[] = { + /* FE */ + { + .name = "Playback_1", + .stream_name = "Playback_1", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Playback_2", + .stream_name = "Playback_2", + .cpu_dai_name = "DL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &mt8183_mt6358_ts3a227_max98357_bt_sco_ops, + }, + { + .name = "Playback_3", + .stream_name = "Playback_3", + .cpu_dai_name = "DL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Capture_1", + .stream_name = "Capture_1", + .cpu_dai_name = "UL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + .ops = &mt8183_mt6358_ts3a227_max98357_bt_sco_ops, + }, + { + .name = "Capture_2", + .stream_name = "Capture_2", + .cpu_dai_name = "UL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_3", + .stream_name = "Capture_3", + .cpu_dai_name = "UL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_Mono_1", + .stream_name = "Capture_Mono_1", + .cpu_dai_name = "UL_MONO_1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Playback_HDMI", + .stream_name = "Playback_HDMI", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + /* BE */ + { + .name = "Primary Codec", + .cpu_dai_name = "ADDA", + .codec_dai_name = "mt6358-snd-codec-aif1", + .codec_name = "mt6358-sound", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 1", + .cpu_dai_name = "PCM 1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 2", + .cpu_dai_name = "PCM 2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "I2S0", + .cpu_dai_name = "I2S0", + .codec_dai_name = "bt-sco-pcm", + .codec_name = "bt-sco", + .no_pcm = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S1", + .cpu_dai_name = "I2S1", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S2", + .cpu_dai_name = "I2S2", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .no_pcm = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S3", + .cpu_dai_name = "I2S3", + .codec_dai_name = "HiFi", + .codec_name = "max98357a", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "I2S5", + .cpu_dai_name = "I2S5", + .codec_dai_name = "bt-sco-pcm", + .codec_name = "bt-sco", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + .be_hw_params_fixup = mt8183_i2s_hw_params_fixup, + .ops = &mt8183_mt6358_i2s_ops, + }, + { + .name = "TDM", + .cpu_dai_name = "TDM", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .ignore_suspend = 1, + }, +}; + +static int +mt8183_mt6358_ts3a227_max98357_headset_init(struct snd_soc_component *cpnt); + +static struct snd_soc_aux_dev mt8183_mt6358_ts3a227_max98357_headset_dev = { + .name = "Headset Chip", + .init = mt8183_mt6358_ts3a227_max98357_headset_init, +}; + +static struct snd_soc_card mt8183_mt6358_ts3a227_max98357_card = { + .name = "mt8183_mt6358_ts3a227_max98357", + .owner = THIS_MODULE, + .dai_link = mt8183_mt6358_ts3a227_max98357_dai_links, + .num_links = ARRAY_SIZE(mt8183_mt6358_ts3a227_max98357_dai_links), + .aux_dev = &mt8183_mt6358_ts3a227_max98357_headset_dev, + .num_aux_devs = 1, +}; + +static int +mt8183_mt6358_ts3a227_max98357_headset_init(struct snd_soc_component *component) +{ + int ret; + + /* Enable Headset and 4 Buttons Jack detection */ + ret = snd_soc_card_jack_new(&mt8183_mt6358_ts3a227_max98357_card, + "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &headset_jack, + headset_jack_pins, + ARRAY_SIZE(headset_jack_pins)); + if (ret) + return ret; + + ret = ts3a227e_enable_jack_detect(component, &headset_jack); + + return ret; +} + +static int +mt8183_mt6358_ts3a227_max98357_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8183_mt6358_ts3a227_max98357_card; + struct device_node *platform_node; + struct snd_soc_dai_link *dai_link; + struct pinctrl *default_pins; + int ret, i; + + card->dev = &pdev->dev; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + /* In the alsa soc-core, the "platform" will be + * allocated by devm_kzalloc if null. + * There is a special case that registerring + * sound card is failed at the first time, but + * the "platform" will not null when probe is trying + * again. It's not expected normally. + */ + dai_link->platforms = NULL; + + if (dai_link->platform_name) + continue; + dai_link->platform_of_node = platform_node; + } + + mt8183_mt6358_ts3a227_max98357_headset_dev.codec_of_node = + of_parse_phandle(pdev->dev.of_node, + "mediatek,headset-codec", 0); + if (!mt8183_mt6358_ts3a227_max98357_headset_dev.codec_of_node) { + dev_err(&pdev->dev, + "Property 'mediatek,headset-codec' missing/invalid\n"); + return -EINVAL; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + + default_pins = + devm_pinctrl_get_select(&pdev->dev, PINCTRL_STATE_DEFAULT); + if (IS_ERR(default_pins)) { + dev_err(&pdev->dev, "%s set pins failed\n", + __func__); + return PTR_ERR(default_pins); + } + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt8183_mt6358_ts3a227_max98357_dt_match[] = { + {.compatible = "mediatek,mt8183_mt6358_ts3a227_max98357",}, + {} +}; +#endif + +static struct platform_driver mt8183_mt6358_ts3a227_max98357_driver = { + .driver = { + .name = "mt8183_mt6358_ts3a227_max98357", +#ifdef CONFIG_OF + .of_match_table = mt8183_mt6358_ts3a227_max98357_dt_match, +#endif + }, + .probe = mt8183_mt6358_ts3a227_max98357_dev_probe, +}; + +module_platform_driver(mt8183_mt6358_ts3a227_max98357_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8183-MT6358-TS3A227-MAX98357 ALSA SoC machine driver"); +MODULE_AUTHOR("Shunli Wang <shunli.wang@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt8183_mt6358_ts3a227_max98357 soc card"); + diff --git a/sound/soc/meson/axg-fifo.c b/sound/soc/meson/axg-fifo.c index 75e5e480fda2..01c1c7db2510 100644 --- a/sound/soc/meson/axg-fifo.c +++ b/sound/soc/meson/axg-fifo.c @@ -19,7 +19,7 @@ * This file implements the platform operations common to the playback and * capture frontend DAI. The logic behind this two types of fifo is very * similar but some difference exist. - * These differences the respective DAI drivers + * These differences are handled in the respective DAI drivers */ static struct snd_pcm_hardware axg_fifo_hw = { @@ -133,6 +133,23 @@ static int axg_fifo_pcm_hw_params(struct snd_pcm_substream *ss, return 0; } +static int g12a_fifo_pcm_hw_params(struct snd_pcm_substream *ss, + struct snd_pcm_hw_params *params) +{ + struct axg_fifo *fifo = axg_fifo_data(ss); + struct snd_pcm_runtime *runtime = ss->runtime; + int ret; + + ret = axg_fifo_pcm_hw_params(ss, params); + if (ret) + return ret; + + /* Set the initial memory address of the DMA */ + regmap_write(fifo->map, FIFO_INIT_ADDR, runtime->dma_addr); + + return 0; +} + static int axg_fifo_pcm_hw_free(struct snd_pcm_substream *ss) { struct axg_fifo *fifo = axg_fifo_data(ss); @@ -262,6 +279,17 @@ const struct snd_pcm_ops axg_fifo_pcm_ops = { }; EXPORT_SYMBOL_GPL(axg_fifo_pcm_ops); +const struct snd_pcm_ops g12a_fifo_pcm_ops = { + .open = axg_fifo_pcm_open, + .close = axg_fifo_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = g12a_fifo_pcm_hw_params, + .hw_free = axg_fifo_pcm_hw_free, + .pointer = axg_fifo_pcm_pointer, + .trigger = axg_fifo_pcm_trigger, +}; +EXPORT_SYMBOL_GPL(g12a_fifo_pcm_ops); + int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type) { struct snd_card *card = rtd->card->snd_card; @@ -278,7 +306,7 @@ static const struct regmap_config axg_fifo_regmap_cfg = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .max_register = FIFO_STATUS2, + .max_register = FIFO_INIT_ADDR, }; int axg_fifo_probe(struct platform_device *pdev) @@ -339,6 +367,6 @@ int axg_fifo_probe(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(axg_fifo_probe); -MODULE_DESCRIPTION("Amlogic AXG fifo driver"); +MODULE_DESCRIPTION("Amlogic AXG/G12A fifo driver"); MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-fifo.h b/sound/soc/meson/axg-fifo.h index d9f516cfbeda..5caf81241dfe 100644 --- a/sound/soc/meson/axg-fifo.h +++ b/sound/soc/meson/axg-fifo.h @@ -60,6 +60,7 @@ struct snd_soc_pcm_runtime; #define FIFO_STATUS1 0x14 #define STATUS1_INT_STS(x) ((x) << 0) #define FIFO_STATUS2 0x18 +#define FIFO_INIT_ADDR 0x24 struct axg_fifo { struct regmap *map; @@ -74,6 +75,7 @@ struct axg_fifo_match_data { }; extern const struct snd_pcm_ops axg_fifo_pcm_ops; +extern const struct snd_pcm_ops g12a_fifo_pcm_ops; int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type); int axg_fifo_probe(struct platform_device *pdev); diff --git a/sound/soc/meson/axg-frddr.c b/sound/soc/meson/axg-frddr.c index a6f6f6a2eca8..2b8807737b2b 100644 --- a/sound/soc/meson/axg-frddr.c +++ b/sound/soc/meson/axg-frddr.c @@ -3,7 +3,9 @@ // Copyright (c) 2018 BayLibre, SAS. // Author: Jerome Brunet <jbrunet@baylibre.com> -/* This driver implements the frontend playback DAI of AXG based SoCs */ +/* + * This driver implements the frontend playback DAI of AXG and G12A based SoCs + */ #include <linux/clk.h> #include <linux/regmap.h> @@ -14,7 +16,29 @@ #include "axg-fifo.h" -#define CTRL0_FRDDR_PP_MODE BIT(30) +#define CTRL0_FRDDR_PP_MODE BIT(30) +#define CTRL0_SEL1_EN_SHIFT 3 +#define CTRL0_SEL2_SHIFT 4 +#define CTRL0_SEL2_EN_SHIFT 7 +#define CTRL0_SEL3_SHIFT 8 +#define CTRL0_SEL3_EN_SHIFT 11 +#define CTRL1_FRDDR_FORCE_FINISH BIT(12) + +static int g12a_frddr_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); + + /* Reset the read pointer to the FIFO_INIT_ADDR */ + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_FRDDR_FORCE_FINISH, 0); + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_FRDDR_FORCE_FINISH, CTRL1_FRDDR_FORCE_FINISH); + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_FRDDR_FORCE_FINISH, 0); + + return 0; +} static int axg_frddr_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) @@ -119,10 +143,123 @@ static const struct axg_fifo_match_data axg_frddr_match_data = { .dai_drv = &axg_frddr_dai_drv }; +static const struct snd_soc_dai_ops g12a_frddr_ops = { + .prepare = g12a_frddr_dai_prepare, + .startup = axg_frddr_dai_startup, + .shutdown = axg_frddr_dai_shutdown, +}; + +static struct snd_soc_dai_driver g12a_frddr_dai_drv = { + .name = "FRDDR", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = AXG_FIFO_CH_MAX, + .rates = AXG_FIFO_RATES, + .formats = AXG_FIFO_FORMATS, + }, + .ops = &g12a_frddr_ops, + .pcm_new = axg_frddr_pcm_new, +}; + +static const char * const g12a_frddr_sel_texts[] = { + "OUT 0", "OUT 1", "OUT 2", "OUT 3", "OUT 4", +}; + +static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel1_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT, + g12a_frddr_sel_texts); +static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel2_enum, FIFO_CTRL0, CTRL0_SEL2_SHIFT, + g12a_frddr_sel_texts); +static SOC_ENUM_SINGLE_DECL(g12a_frddr_sel3_enum, FIFO_CTRL0, CTRL0_SEL3_SHIFT, + g12a_frddr_sel_texts); + +static const struct snd_kcontrol_new g12a_frddr_out1_demux = + SOC_DAPM_ENUM("Output Src 1", g12a_frddr_sel1_enum); +static const struct snd_kcontrol_new g12a_frddr_out2_demux = + SOC_DAPM_ENUM("Output Src 2", g12a_frddr_sel2_enum); +static const struct snd_kcontrol_new g12a_frddr_out3_demux = + SOC_DAPM_ENUM("Output Src 3", g12a_frddr_sel3_enum); + +static const struct snd_kcontrol_new g12a_frddr_out1_enable = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0, + CTRL0_SEL1_EN_SHIFT, 1, 0); +static const struct snd_kcontrol_new g12a_frddr_out2_enable = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0, + CTRL0_SEL2_EN_SHIFT, 1, 0); +static const struct snd_kcontrol_new g12a_frddr_out3_enable = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", FIFO_CTRL0, + CTRL0_SEL3_EN_SHIFT, 1, 0); + +static const struct snd_soc_dapm_widget g12a_frddr_dapm_widgets[] = { + SND_SOC_DAPM_AIF_OUT("SRC 1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SRC 2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SRC 3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SWITCH("SRC 1 EN", SND_SOC_NOPM, 0, 0, + &g12a_frddr_out1_enable), + SND_SOC_DAPM_SWITCH("SRC 2 EN", SND_SOC_NOPM, 0, 0, + &g12a_frddr_out2_enable), + SND_SOC_DAPM_SWITCH("SRC 3 EN", SND_SOC_NOPM, 0, 0, + &g12a_frddr_out3_enable), + SND_SOC_DAPM_DEMUX("SINK 1 SEL", SND_SOC_NOPM, 0, 0, + &g12a_frddr_out1_demux), + SND_SOC_DAPM_DEMUX("SINK 2 SEL", SND_SOC_NOPM, 0, 0, + &g12a_frddr_out2_demux), + SND_SOC_DAPM_DEMUX("SINK 3 SEL", SND_SOC_NOPM, 0, 0, + &g12a_frddr_out3_demux), + SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT 4", NULL, 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route g12a_frddr_dapm_routes[] = { + { "SRC 1", NULL, "Playback" }, + { "SRC 2", NULL, "Playback" }, + { "SRC 3", NULL, "Playback" }, + { "SRC 1 EN", "Switch", "SRC 1" }, + { "SRC 2 EN", "Switch", "SRC 2" }, + { "SRC 3 EN", "Switch", "SRC 3" }, + { "SINK 1 SEL", NULL, "SRC 1 EN" }, + { "SINK 2 SEL", NULL, "SRC 2 EN" }, + { "SINK 3 SEL", NULL, "SRC 3 EN" }, + { "OUT 0", "OUT 0", "SINK 1 SEL" }, + { "OUT 1", "OUT 1", "SINK 1 SEL" }, + { "OUT 2", "OUT 2", "SINK 1 SEL" }, + { "OUT 3", "OUT 3", "SINK 1 SEL" }, + { "OUT 4", "OUT 4", "SINK 1 SEL" }, + { "OUT 0", "OUT 0", "SINK 2 SEL" }, + { "OUT 1", "OUT 1", "SINK 2 SEL" }, + { "OUT 2", "OUT 2", "SINK 2 SEL" }, + { "OUT 3", "OUT 3", "SINK 2 SEL" }, + { "OUT 4", "OUT 4", "SINK 2 SEL" }, + { "OUT 0", "OUT 0", "SINK 3 SEL" }, + { "OUT 1", "OUT 1", "SINK 3 SEL" }, + { "OUT 2", "OUT 2", "SINK 3 SEL" }, + { "OUT 3", "OUT 3", "SINK 3 SEL" }, + { "OUT 4", "OUT 4", "SINK 3 SEL" }, +}; + +static const struct snd_soc_component_driver g12a_frddr_component_drv = { + .dapm_widgets = g12a_frddr_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(g12a_frddr_dapm_widgets), + .dapm_routes = g12a_frddr_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(g12a_frddr_dapm_routes), + .ops = &g12a_fifo_pcm_ops +}; + +static const struct axg_fifo_match_data g12a_frddr_match_data = { + .component_drv = &g12a_frddr_component_drv, + .dai_drv = &g12a_frddr_dai_drv +}; + static const struct of_device_id axg_frddr_of_match[] = { { .compatible = "amlogic,axg-frddr", .data = &axg_frddr_match_data, + }, { + .compatible = "amlogic,g12a-frddr", + .data = &g12a_frddr_match_data, }, {} }; MODULE_DEVICE_TABLE(of, axg_frddr_of_match); @@ -136,6 +273,6 @@ static struct platform_driver axg_frddr_pdrv = { }; module_platform_driver(axg_frddr_pdrv); -MODULE_DESCRIPTION("Amlogic AXG playback fifo driver"); +MODULE_DESCRIPTION("Amlogic AXG/G12A playback fifo driver"); MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-tdm-formatter.c b/sound/soc/meson/axg-tdm-formatter.c index 43e390f9358a..0c6cce5c5773 100644 --- a/sound/soc/meson/axg-tdm-formatter.c +++ b/sound/soc/meson/axg-tdm-formatter.c @@ -68,7 +68,7 @@ EXPORT_SYMBOL_GPL(axg_tdm_formatter_set_channel_masks); static int axg_tdm_formatter_enable(struct axg_tdm_formatter *formatter) { struct axg_tdm_stream *ts = formatter->stream; - bool invert = formatter->drv->invert_sclk; + bool invert = formatter->drv->quirks->invert_sclk; int ret; /* Do nothing if the formatter is already enabled */ @@ -85,7 +85,9 @@ static int axg_tdm_formatter_enable(struct axg_tdm_formatter *formatter) return ret; /* Setup the stream parameter in the formatter */ - ret = formatter->drv->ops->prepare(formatter->map, formatter->stream); + ret = formatter->drv->ops->prepare(formatter->map, + formatter->drv->quirks, + formatter->stream); if (ret) return ret; diff --git a/sound/soc/meson/axg-tdm-formatter.h b/sound/soc/meson/axg-tdm-formatter.h index cf947caf3cb1..9ef98e955cb2 100644 --- a/sound/soc/meson/axg-tdm-formatter.h +++ b/sound/soc/meson/axg-tdm-formatter.h @@ -14,18 +14,25 @@ struct regmap; struct snd_soc_dapm_widget; struct snd_kcontrol; +struct axg_tdm_formatter_hw { + unsigned int skew_offset; + bool invert_sclk; +}; + struct axg_tdm_formatter_ops { struct axg_tdm_stream *(*get_stream)(struct snd_soc_dapm_widget *w); void (*enable)(struct regmap *map); void (*disable)(struct regmap *map); - int (*prepare)(struct regmap *map, struct axg_tdm_stream *ts); + int (*prepare)(struct regmap *map, + const struct axg_tdm_formatter_hw *quirks, + struct axg_tdm_stream *ts); }; struct axg_tdm_formatter_driver { const struct snd_soc_component_driver *component_drv; const struct regmap_config *regmap_cfg; const struct axg_tdm_formatter_ops *ops; - bool invert_sclk; + const struct axg_tdm_formatter_hw *quirks; }; int axg_tdm_formatter_set_channel_masks(struct regmap *map, diff --git a/sound/soc/meson/axg-tdmin.c b/sound/soc/meson/axg-tdmin.c index bbac44c81688..a790f925a4ef 100644 --- a/sound/soc/meson/axg-tdmin.c +++ b/sound/soc/meson/axg-tdmin.c @@ -107,21 +107,22 @@ static void axg_tdmin_disable(struct regmap *map) regmap_update_bits(map, TDMIN_CTRL, TDMIN_CTRL_ENABLE, 0); } -static int axg_tdmin_prepare(struct regmap *map, struct axg_tdm_stream *ts) +static int axg_tdmin_prepare(struct regmap *map, + const struct axg_tdm_formatter_hw *quirks, + struct axg_tdm_stream *ts) { - unsigned int val = 0; + unsigned int val, skew = quirks->skew_offset; /* Set stream skew */ switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_DSP_A: - val |= TDMIN_CTRL_IN_BIT_SKEW(3); + skew += 1; break; case SND_SOC_DAIFMT_LEFT_J: case SND_SOC_DAIFMT_RIGHT_J: case SND_SOC_DAIFMT_DSP_B: - val = TDMIN_CTRL_IN_BIT_SKEW(2); break; default: @@ -130,6 +131,8 @@ static int axg_tdmin_prepare(struct regmap *map, struct axg_tdm_stream *ts) return -EINVAL; } + val = TDMIN_CTRL_IN_BIT_SKEW(skew); + /* Set stream format mode */ switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: @@ -204,7 +207,10 @@ static const struct axg_tdm_formatter_driver axg_tdmin_drv = { .component_drv = &axg_tdmin_component_drv, .regmap_cfg = &axg_tdmin_regmap_cfg, .ops = &axg_tdmin_ops, - .invert_sclk = false, + .quirks = &(const struct axg_tdm_formatter_hw) { + .invert_sclk = false, + .skew_offset = 2, + }, }; static const struct of_device_id axg_tdmin_of_match[] = { diff --git a/sound/soc/meson/axg-tdmout.c b/sound/soc/meson/axg-tdmout.c index f73368ee1088..527bfc4487e0 100644 --- a/sound/soc/meson/axg-tdmout.c +++ b/sound/soc/meson/axg-tdmout.c @@ -124,21 +124,22 @@ static void axg_tdmout_disable(struct regmap *map) regmap_update_bits(map, TDMOUT_CTRL0, TDMOUT_CTRL0_ENABLE, 0); } -static int axg_tdmout_prepare(struct regmap *map, struct axg_tdm_stream *ts) +static int axg_tdmout_prepare(struct regmap *map, + const struct axg_tdm_formatter_hw *quirks, + struct axg_tdm_stream *ts) { - unsigned int val = 0; + unsigned int val, skew = quirks->skew_offset; /* Set the stream skew */ switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_DSP_A: - val |= TDMOUT_CTRL0_INIT_BITNUM(1); break; case SND_SOC_DAIFMT_LEFT_J: case SND_SOC_DAIFMT_RIGHT_J: case SND_SOC_DAIFMT_DSP_B: - val |= TDMOUT_CTRL0_INIT_BITNUM(2); + skew += 1; break; default: @@ -147,6 +148,8 @@ static int axg_tdmout_prepare(struct regmap *map, struct axg_tdm_stream *ts) return -EINVAL; } + val = TDMOUT_CTRL0_INIT_BITNUM(skew); + /* Set the slot width */ val |= TDMOUT_CTRL0_BITNUM(ts->iface->slot_width - 1); @@ -234,13 +237,29 @@ static const struct axg_tdm_formatter_driver axg_tdmout_drv = { .component_drv = &axg_tdmout_component_drv, .regmap_cfg = &axg_tdmout_regmap_cfg, .ops = &axg_tdmout_ops, - .invert_sclk = true, + .quirks = &(const struct axg_tdm_formatter_hw) { + .invert_sclk = true, + .skew_offset = 1, + }, +}; + +static const struct axg_tdm_formatter_driver g12a_tdmout_drv = { + .component_drv = &axg_tdmout_component_drv, + .regmap_cfg = &axg_tdmout_regmap_cfg, + .ops = &axg_tdmout_ops, + .quirks = &(const struct axg_tdm_formatter_hw) { + .invert_sclk = true, + .skew_offset = 2, + }, }; static const struct of_device_id axg_tdmout_of_match[] = { { .compatible = "amlogic,axg-tdmout", .data = &axg_tdmout_drv, + }, { + .compatible = "amlogic,g12a-tdmout", + .data = &g12a_tdmout_drv, }, {} }; MODULE_DEVICE_TABLE(of, axg_tdmout_of_match); diff --git a/sound/soc/meson/axg-toddr.c b/sound/soc/meson/axg-toddr.c index 0e9ca3882ae5..4f63e434fad4 100644 --- a/sound/soc/meson/axg-toddr.c +++ b/sound/soc/meson/axg-toddr.c @@ -24,6 +24,7 @@ #define CTRL0_TODDR_MSB_POS(x) ((x) << 8) #define CTRL0_TODDR_LSB_POS_MASK GENMASK(7, 3) #define CTRL0_TODDR_LSB_POS(x) ((x) << 3) +#define CTRL1_TODDR_FORCE_FINISH BIT(25) #define TODDR_MSB_POS 31 @@ -33,6 +34,22 @@ static int axg_toddr_pcm_new(struct snd_soc_pcm_runtime *rtd, return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_CAPTURE); } +static int g12a_toddr_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); + + /* Reset the write pointer to the FIFO_INIT_ADDR */ + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_TODDR_FORCE_FINISH, 0); + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_TODDR_FORCE_FINISH, CTRL1_TODDR_FORCE_FINISH); + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_TODDR_FORCE_FINISH, 0); + + return 0; +} + static int axg_toddr_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -172,10 +189,46 @@ static const struct axg_fifo_match_data axg_toddr_match_data = { .dai_drv = &axg_toddr_dai_drv }; +static const struct snd_soc_dai_ops g12a_toddr_ops = { + .prepare = g12a_toddr_dai_prepare, + .hw_params = axg_toddr_dai_hw_params, + .startup = axg_toddr_dai_startup, + .shutdown = axg_toddr_dai_shutdown, +}; + +static struct snd_soc_dai_driver g12a_toddr_dai_drv = { + .name = "TODDR", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = AXG_FIFO_CH_MAX, + .rates = AXG_FIFO_RATES, + .formats = AXG_FIFO_FORMATS, + }, + .ops = &g12a_toddr_ops, + .pcm_new = axg_toddr_pcm_new, +}; + +static const struct snd_soc_component_driver g12a_toddr_component_drv = { + .dapm_widgets = axg_toddr_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(axg_toddr_dapm_widgets), + .dapm_routes = axg_toddr_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(axg_toddr_dapm_routes), + .ops = &g12a_fifo_pcm_ops +}; + +static const struct axg_fifo_match_data g12a_toddr_match_data = { + .component_drv = &g12a_toddr_component_drv, + .dai_drv = &g12a_toddr_dai_drv +}; + static const struct of_device_id axg_toddr_of_match[] = { { .compatible = "amlogic,axg-toddr", .data = &axg_toddr_match_data, + }, { + .compatible = "amlogic,g12a-toddr", + .data = &g12a_toddr_match_data, }, {} }; MODULE_DEVICE_TABLE(of, axg_toddr_of_match); diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 75ceb04d8bf0..b1764af858ba 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -98,7 +98,7 @@ config SND_SOC_MSM8996 config SND_SOC_SDM845 tristate "SoC Machine driver for SDM845 boards" - depends on QCOM_APR && MFD_CROS_EC + depends on QCOM_APR && MFD_CROS_EC && I2C select SND_SOC_QDSP6 select SND_SOC_QCOM_COMMON select SND_SOC_RT5663 diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c index d0b403a0e27b..b9c1d8ad77c1 100644 --- a/sound/soc/rockchip/rockchip_pdm.c +++ b/sound/soc/rockchip/rockchip_pdm.c @@ -17,14 +17,23 @@ #include <linux/module.h> #include <linux/clk.h> #include <linux/of.h> +#include <linux/of_device.h> #include <linux/pm_runtime.h> +#include <linux/rational.h> #include <linux/regmap.h> +#include <linux/reset.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> #include "rockchip_pdm.h" #define PDM_DMA_BURST_SIZE (8) /* size * width: 8*4 = 32 bytes */ +#define PDM_SIGNOFF_CLK_RATE (100000000) + +enum rk_pdm_version { + RK_PDM_RK3229, + RK_PDM_RK3308, +}; struct rk_pdm_dev { struct device *dev; @@ -32,22 +41,51 @@ struct rk_pdm_dev { struct clk *hclk; struct regmap *regmap; struct snd_dmaengine_dai_dma_data capture_dma_data; + struct reset_control *reset; + enum rk_pdm_version version; }; struct rk_pdm_clkref { unsigned int sr; unsigned int clk; + unsigned int clk_out; +}; + +struct rk_pdm_ds_ratio { + unsigned int ratio; + unsigned int sr; }; static struct rk_pdm_clkref clkref[] = { - { 8000, 40960000 }, - { 11025, 56448000 }, - { 12000, 61440000 }, + { 8000, 40960000, 2048000 }, + { 11025, 56448000, 2822400 }, + { 12000, 61440000, 3072000 }, + { 8000, 98304000, 2048000 }, + { 12000, 98304000, 3072000 }, +}; + +static struct rk_pdm_ds_ratio ds_ratio[] = { + { 0, 192000 }, + { 0, 176400 }, + { 0, 128000 }, + { 1, 96000 }, + { 1, 88200 }, + { 1, 64000 }, + { 2, 48000 }, + { 2, 44100 }, + { 2, 32000 }, + { 3, 24000 }, + { 3, 22050 }, + { 3, 16000 }, + { 4, 12000 }, + { 4, 11025 }, + { 4, 8000 }, }; -static unsigned int get_pdm_clk(unsigned int sr) +static unsigned int get_pdm_clk(struct rk_pdm_dev *pdm, unsigned int sr, + unsigned int *clk_src, unsigned int *clk_out) { - unsigned int i, count, clk, div; + unsigned int i, count, clk, div, rate; clk = 0; if (!sr) @@ -59,14 +97,39 @@ static unsigned int get_pdm_clk(unsigned int sr) continue; div = sr / clkref[i].sr; if ((div & (div - 1)) == 0) { + *clk_out = clkref[i].clk_out; + rate = clk_round_rate(pdm->clk, clkref[i].clk); + if (rate != clkref[i].clk) + continue; clk = clkref[i].clk; + *clk_src = clkref[i].clk; break; } } + if (!clk) { + clk = clk_round_rate(pdm->clk, PDM_SIGNOFF_CLK_RATE); + *clk_src = clk; + } return clk; } +static unsigned int get_pdm_ds_ratio(unsigned int sr) +{ + unsigned int i, count, ratio; + + ratio = 0; + if (!sr) + return ratio; + + count = ARRAY_SIZE(ds_ratio); + for (i = 0; i < count; i++) { + if (sr == ds_ratio[i].sr) + ratio = ds_ratio[i].ratio; + } + return ratio; +} + static inline struct rk_pdm_dev *to_info(struct snd_soc_dai *dai) { return snd_soc_dai_get_drvdata(dai); @@ -95,46 +158,61 @@ static int rockchip_pdm_hw_params(struct snd_pcm_substream *substream, struct rk_pdm_dev *pdm = to_info(dai); unsigned int val = 0; unsigned int clk_rate, clk_div, samplerate; + unsigned int clk_src, clk_out = 0; + unsigned long m, n; + bool change; int ret; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return 0; + samplerate = params_rate(params); - clk_rate = get_pdm_clk(samplerate); + clk_rate = get_pdm_clk(pdm, samplerate, &clk_src, &clk_out); if (!clk_rate) return -EINVAL; - ret = clk_set_rate(pdm->clk, clk_rate); + ret = clk_set_rate(pdm->clk, clk_src); if (ret) return -EINVAL; - clk_div = DIV_ROUND_CLOSEST(clk_rate, samplerate); - - switch (clk_div) { - case 320: - val = PDM_CLK_320FS; - break; - case 640: - val = PDM_CLK_640FS; - break; - case 1280: - val = PDM_CLK_1280FS; - break; - case 2560: - val = PDM_CLK_2560FS; - break; - case 5120: - val = PDM_CLK_5120FS; - break; - default: - dev_err(pdm->dev, "unsupported div: %d\n", clk_div); - return -EINVAL; + if (pdm->version == RK_PDM_RK3308) { + rational_best_approximation(clk_out, clk_src, + GENMASK(16 - 1, 0), + GENMASK(16 - 1, 0), + &m, &n); + + val = (m << PDM_FD_NUMERATOR_SFT) | + (n << PDM_FD_DENOMINATOR_SFT); + regmap_update_bits_check(pdm->regmap, PDM_CTRL1, + PDM_FD_NUMERATOR_MSK | + PDM_FD_DENOMINATOR_MSK, + val, &change); + if (change) { + reset_control_assert(pdm->reset); + reset_control_deassert(pdm->reset); + rockchip_pdm_rxctrl(pdm, 0); + } + clk_div = n / m; + if (clk_div >= 40) + val = PDM_CLK_FD_RATIO_40; + else if (clk_div <= 35) + val = PDM_CLK_FD_RATIO_35; + else + return -EINVAL; + regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, + PDM_CLK_FD_RATIO_MSK, + val); } - + val = get_pdm_ds_ratio(samplerate); regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, PDM_DS_RATIO_MSK, val); regmap_update_bits(pdm->regmap, PDM_HPF_CTRL, PDM_HPF_CF_MSK, PDM_HPF_60HZ); regmap_update_bits(pdm->regmap, PDM_HPF_CTRL, PDM_HPF_LE | PDM_HPF_RE, PDM_HPF_LE | PDM_HPF_RE); regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, PDM_CLK_EN, PDM_CLK_EN); + if (pdm->version != RK_PDM_RK3229) + regmap_update_bits(pdm->regmap, PDM_CTRL0, + PDM_MODE_MSK, PDM_MODE_LJ); val = 0; switch (params_format(params)) { @@ -176,16 +254,12 @@ static int rockchip_pdm_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - regmap_update_bits(pdm->regmap, PDM_CTRL0, - PDM_PATH_MSK | PDM_VDW_MSK, - val); - regmap_update_bits(pdm->regmap, PDM_DMA_CTRL, PDM_DMA_RDL_MSK, - PDM_DMA_RDL(16)); - regmap_update_bits(pdm->regmap, PDM_SYSCONFIG, - PDM_RX_MASK | PDM_RX_CLR_MASK, - PDM_RX_STOP | PDM_RX_CLR_WR); - } + regmap_update_bits(pdm->regmap, PDM_CTRL0, + PDM_PATH_MSK | PDM_VDW_MSK, + val); + /* all channels share the single FIFO */ + regmap_update_bits(pdm->regmap, PDM_DMA_CTRL, PDM_DMA_RDL_MSK, + PDM_DMA_RDL(8 * params_channels(params))); return 0; } @@ -343,6 +417,7 @@ static bool rockchip_pdm_rd_reg(struct device *dev, unsigned int reg) case PDM_INT_CLR: case PDM_INT_ST: case PDM_DATA_VALID: + case PDM_RXFIFO_DATA: case PDM_VERSION: return true; default: @@ -354,27 +429,62 @@ static bool rockchip_pdm_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case PDM_SYSCONFIG: + case PDM_FIFO_CTRL: case PDM_INT_CLR: case PDM_INT_ST: + case PDM_RXFIFO_DATA: + return true; + default: + return false; + } +} + +static bool rockchip_pdm_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PDM_RXFIFO_DATA: return true; default: return false; } } +static const struct reg_default rockchip_pdm_reg_defaults[] = { + {0x04, 0x78000017}, + {0x08, 0x0bb8ea60}, + {0x18, 0x0000001f}, +}; + static const struct regmap_config rockchip_pdm_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, .max_register = PDM_VERSION, + .reg_defaults = rockchip_pdm_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(rockchip_pdm_reg_defaults), .writeable_reg = rockchip_pdm_wr_reg, .readable_reg = rockchip_pdm_rd_reg, .volatile_reg = rockchip_pdm_volatile_reg, + .precious_reg = rockchip_pdm_precious_reg, .cache_type = REGCACHE_FLAT, }; +static const struct of_device_id rockchip_pdm_match[] = { + { .compatible = "rockchip,pdm", + .data = (void *)RK_PDM_RK3229 }, + { .compatible = "rockchip,px30-pdm", + .data = (void *)RK_PDM_RK3308 }, + { .compatible = "rockchip,rk1808-pdm", + .data = (void *)RK_PDM_RK3308 }, + { .compatible = "rockchip,rk3308-pdm", + .data = (void *)RK_PDM_RK3308 }, + {}, +}; +MODULE_DEVICE_TABLE(of, rockchip_pdm_match); + static int rockchip_pdm_probe(struct platform_device *pdev) { + const struct of_device_id *match; struct rk_pdm_dev *pdm; struct resource *res; void __iomem *regs; @@ -384,6 +494,16 @@ static int rockchip_pdm_probe(struct platform_device *pdev) if (!pdm) return -ENOMEM; + match = of_match_device(rockchip_pdm_match, &pdev->dev); + if (match) + pdm->version = (enum rk_pdm_version)match->data; + + if (pdm->version == RK_PDM_RK3308) { + pdm->reset = devm_reset_control_get(&pdev->dev, "pdm-m"); + if (IS_ERR(pdm->reset)) + return PTR_ERR(pdm->reset); + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(regs)) @@ -429,6 +549,7 @@ static int rockchip_pdm_probe(struct platform_device *pdev) goto err_suspend; } + rockchip_pdm_rxctrl(pdm, 0); ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); if (ret) { dev_err(&pdev->dev, "could not register pcm: %d\n", ret); @@ -495,12 +616,6 @@ static const struct dev_pm_ops rockchip_pdm_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(rockchip_pdm_suspend, rockchip_pdm_resume) }; -static const struct of_device_id rockchip_pdm_match[] = { - { .compatible = "rockchip,pdm", }, - {}, -}; -MODULE_DEVICE_TABLE(of, rockchip_pdm_match); - static struct platform_driver rockchip_pdm_driver = { .probe = rockchip_pdm_probe, .remove = rockchip_pdm_remove, diff --git a/sound/soc/rockchip/rockchip_pdm.h b/sound/soc/rockchip/rockchip_pdm.h index 886b48d128fd..ae88644aa334 100644 --- a/sound/soc/rockchip/rockchip_pdm.h +++ b/sound/soc/rockchip/rockchip_pdm.h @@ -42,6 +42,9 @@ /* PDM CTRL0 */ #define PDM_PATH_MSK (0xf << 27) +#define PDM_MODE_MSK BIT(31) +#define PDM_MODE_RJ 0 +#define PDM_MODE_LJ BIT(31) #define PDM_PATH3_EN BIT(30) #define PDM_PATH2_EN BIT(29) #define PDM_PATH1_EN BIT(28) @@ -50,7 +53,16 @@ #define PDM_VDW_MSK (0x1f << 0) #define PDM_VDW(X) ((X - 1) << 0) +/* PDM CTRL1 */ +#define PDM_FD_NUMERATOR_SFT 16 +#define PDM_FD_NUMERATOR_MSK GENMASK(31, 16) +#define PDM_FD_DENOMINATOR_SFT 0 +#define PDM_FD_DENOMINATOR_MSK GENMASK(15, 0) + /* PDM CLK CTRL */ +#define PDM_CLK_FD_RATIO_MSK BIT(6) +#define PDM_CLK_FD_RATIO_40 (0X0 << 6) +#define PDM_CLK_FD_RATIO_35 BIT(6) #define PDM_CLK_MSK BIT(5) #define PDM_CLK_EN BIT(5) #define PDM_CLK_DIS (0x0 << 5) diff --git a/sound/soc/samsung/arndale_rt5631.c b/sound/soc/samsung/arndale_rt5631.c index ee1fda92f2f4..cc334e1866f6 100644 --- a/sound/soc/samsung/arndale_rt5631.c +++ b/sound/soc/samsung/arndale_rt5631.c @@ -1,15 +1,8 @@ -/* - * arndale_rt5631.c - * - * Copyright (c) 2014, Insignal Co., Ltd. - * - * Author: Claude <claude@insginal.co.kr> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (c) 2014, Insignal Co., Ltd. +// +// Author: Claude <claude@insginal.co.kr> #include <linux/module.h> #include <linux/platform_device.h> diff --git a/sound/soc/samsung/bells.c b/sound/soc/samsung/bells.c index 0e66cd8ef2f9..770845e2507a 100644 --- a/sound/soc/samsung/bells.c +++ b/sound/soc/samsung/bells.c @@ -1,13 +1,8 @@ -/* - * Bells audio support - * - * Copyright 2012 Wolfson Microelectronics - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Bells audio support +// +// Copyright 2012 Wolfson Microelectronics #include <sound/soc.h> #include <sound/soc-dapm.h> diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h index 0ae15d01a3f6..7b5d4556e0fd 100644 --- a/sound/soc/samsung/dma.h +++ b/sound/soc/samsung/dma.h @@ -1,10 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * ALSA PCM interface for the Samsung SoC + * ALSA PCM interface for the Samsung SoC */ #ifndef _SAMSUNG_DMA_H diff --git a/sound/soc/samsung/dmaengine.c b/sound/soc/samsung/dmaengine.c index 302871974cb3..2802789a323e 100644 --- a/sound/soc/samsung/dmaengine.c +++ b/sound/soc/samsung/dmaengine.c @@ -1,19 +1,9 @@ -/* - * dmaengine.c - Samsung dmaengine wrapper - * - * Author: Mark Brown <broonie@linaro.org> - * Copyright 2013 Linaro - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - */ +// SPDX-License-Identifier: GPL-2.0 +// +// dmaengine.c - Samsung dmaengine wrapper +// +// Author: Mark Brown <broonie@linaro.org> +// Copyright 2013 Linaro #include <linux/module.h> #include <sound/core.h> diff --git a/sound/soc/samsung/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c index 051935162d7b..95925c4a5964 100644 --- a/sound/soc/samsung/h1940_uda1380.c +++ b/sound/soc/samsung/h1940_uda1380.c @@ -1,17 +1,11 @@ -/* - * h1940-uda1380.c -- ALSA Soc Audio Layer - * - * Copyright (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org> - * Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com> - * - * Based on version from Arnaud Patard <arnaud.patard@rtp-net.org> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// h1940_uda1380.c - ALSA SoC Audio Layer +// +// Copyright (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org> +// Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com> +// +// Based on version from Arnaud Patard <arnaud.patard@rtp-net.org> #include <linux/types.h> #include <linux/gpio.h> diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h index 964985ea2e80..b4b5d6053503 100644 --- a/sound/soc/samsung/i2s-regs.h +++ b/sound/soc/samsung/i2s-regs.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* - * linux/sound/soc/samsung/i2s-regs.h - * * Copyright (c) 2011 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Samsung I2S driver's register header - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. */ #ifndef __SND_SOC_SAMSUNG_I2S_REGS_H diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index ab471d550d17..9722940da6a4 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -88,12 +88,6 @@ struct samsung_i2s_priv { struct platform_device *pdev; struct platform_device *pdev_sec; - /* Memory mapped SFR region */ - void __iomem *addr; - - /* Spinlock protecting access to the device's registers */ - spinlock_t lock; - /* Lock for cross interface checks */ spinlock_t pcm_lock; @@ -122,6 +116,15 @@ struct samsung_i2s_priv { /* The clock provider's data */ struct clk *clk_table[3]; struct clk_onecell_data clk_data; + + /* Spinlock protecting member fields below */ + spinlock_t lock; + + /* Memory mapped SFR region */ + void __iomem *addr; + + /* A flag indicating the I2S slave mode operation */ + bool slave_mode; }; /* Returns true if this is the 'overlay' stereo DAI */ @@ -130,15 +133,6 @@ static inline bool is_secondary(struct i2s_dai *i2s) return i2s->drv->id == SAMSUNG_I2S_ID_SECONDARY; } -/* If operating in SoC-Slave mode */ -static inline bool is_slave(struct i2s_dai *i2s) -{ - struct samsung_i2s_priv *priv = i2s->priv; - - u32 mod = readl(priv->addr + I2SMOD); - return (mod & (1 << priv->variant_regs->mss_off)) ? true : false; -} - /* If this interface of the controller is transmitting data */ static inline bool tx_active(struct i2s_dai *i2s) { @@ -715,6 +709,7 @@ static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) mod &= ~(sdf_mask | lrp_rlow | mod_slave); mod |= tmp; writel(mod, priv->addr + I2SMOD); + priv->slave_mode = (mod & mod_slave); spin_unlock_irqrestore(&priv->lock, flags); pm_runtime_put(dai->dev); @@ -917,7 +912,7 @@ static int config_setup(struct i2s_dai *i2s) set_rfs(i2s, rfs); /* Don't bother with PSR in Slave mode */ - if (is_slave(i2s)) + if (priv->slave_mode) return 0; if (!(priv->quirks & QUIRK_NO_MUXPSR)) { diff --git a/sound/soc/samsung/i2s.h b/sound/soc/samsung/i2s.h index a9832a9555cb..78b475ef98d9 100644 --- a/sound/soc/samsung/i2s.h +++ b/sound/soc/samsung/i2s.h @@ -1,13 +1,9 @@ -/* sound/soc/samsung/i2s.h - * +/* SPDX-License-Identifier: GPL-2.0 */ +/* * ALSA SoC Audio Layer - Samsung I2S Controller driver * * Copyright (c) 2010 Samsung Electronics Co. Ltd. * Jaswinder Singh <jassisinghbrar@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #ifndef __SND_SOC_SAMSUNG_I2S_H diff --git a/sound/soc/samsung/idma.c b/sound/soc/samsung/idma.c index b1f09b942410..65497cd477a5 100644 --- a/sound/soc/samsung/idma.c +++ b/sound/soc/samsung/idma.c @@ -1,16 +1,10 @@ -/* - * sound/soc/samsung/idma.c - * - * Copyright (c) 2011 Samsung Electronics Co., Ltd. - * http://www.samsung.com - * - * I2S0's Internal DMA driver - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// idma.c - I2S0 internal DMA driver +// +// Copyright (c) 2011 Samsung Electronics Co., Ltd. +// http://www.samsung.com + #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> diff --git a/sound/soc/samsung/idma.h b/sound/soc/samsung/idma.h index 8644946973e5..8a46a918ed2a 100644 --- a/sound/soc/samsung/idma.h +++ b/sound/soc/samsung/idma.h @@ -1,14 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* - * sound/soc/samsung/idma.h - * * Copyright (c) 2011 Samsung Electronics Co., Ltd * http://www.samsung.com - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * */ #ifndef __SND_SOC_SAMSUNG_IDMA_H_ diff --git a/sound/soc/samsung/jive_wm8750.c b/sound/soc/samsung/jive_wm8750.c index 529b10dc532b..f05f9e03f07d 100644 --- a/sound/soc/samsung/jive_wm8750.c +++ b/sound/soc/samsung/jive_wm8750.c @@ -1,15 +1,10 @@ -/* sound/soc/samsung/jive_wm8750.c - * - * Copyright 2007,2008 Simtec Electronics - * - * Based on sound/soc/pxa/spitz.c - * Copyright 2005 Wolfson Microelectronics PLC. - * Copyright 2005 Openedhand Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. -*/ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright 2007,2008 Simtec Electronics +// +// Based on sound/soc/pxa/spitz.c +// Copyright 2005 Wolfson Microelectronics PLC. +// Copyright 2005 Openedhand Ltd. #include <linux/module.h> #include <sound/soc.h> diff --git a/sound/soc/samsung/littlemill.c b/sound/soc/samsung/littlemill.c index 087f8d738dfb..cd70b06cc99d 100644 --- a/sound/soc/samsung/littlemill.c +++ b/sound/soc/samsung/littlemill.c @@ -1,13 +1,8 @@ -/* - * Littlemill audio support - * - * Copyright 2011 Wolfson Microelectronics - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Littlemill audio support +// +// Copyright 2011 Wolfson Microelectronics #include <sound/soc.h> #include <sound/soc-dapm.h> diff --git a/sound/soc/samsung/lowland.c b/sound/soc/samsung/lowland.c index c9081f42f373..2fdab2ac8e8c 100644 --- a/sound/soc/samsung/lowland.c +++ b/sound/soc/samsung/lowland.c @@ -1,13 +1,8 @@ -/* - * Lowland audio support - * - * Copyright 2011 Wolfson Microelectronics - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Lowland audio support +// +// Copyright 2011 Wolfson Microelectronics #include <sound/soc.h> #include <sound/soc-dapm.h> diff --git a/sound/soc/samsung/neo1973_wm8753.c b/sound/soc/samsung/neo1973_wm8753.c index 65602b935377..7e625066ddcd 100644 --- a/sound/soc/samsung/neo1973_wm8753.c +++ b/sound/soc/samsung/neo1973_wm8753.c @@ -1,18 +1,13 @@ -/* - * neo1973_wm8753.c -- SoC audio for Openmoko Neo1973 and Freerunner devices - * - * Copyright 2007 Openmoko Inc - * Author: Graeme Gregory <graeme@openmoko.org> - * Copyright 2007 Wolfson Microelectronics PLC. - * Author: Graeme Gregory - * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com - * Copyright 2009 Wolfson Microelectronics - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// neo1973_wm8753.c - SoC audio for Openmoko Neo1973 and Freerunner devices +// +// Copyright 2007 Openmoko Inc +// Author: Graeme Gregory <graeme@openmoko.org> +// Copyright 2007 Wolfson Microelectronics PLC. +// Author: Graeme Gregory +// graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com +// Copyright 2009 Wolfson Microelectronics #include <linux/module.h> #include <linux/platform_device.h> diff --git a/sound/soc/samsung/odroid.c b/sound/soc/samsung/odroid.c index 1dc54c4206f0..e688169ff12a 100644 --- a/sound/soc/samsung/odroid.c +++ b/sound/soc/samsung/odroid.c @@ -1,10 +1,6 @@ -/* - * Copyright (C) 2017 Samsung Electronics Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2017 Samsung Electronics Co., Ltd. #include <linux/clk.h> #include <linux/clk-provider.h> diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index 3c7baa561084..f6e67d0e7882 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -1,15 +1,10 @@ -/* sound/soc/samsung/pcm.c - * - * ALSA SoC Audio Layer - S3C PCM-Controller driver - * - * Copyright (c) 2009 Samsung Electronics Co. Ltd - * Author: Jaswinder Singh <jassisinghbrar@gmail.com> - * based upon I2S drivers by Ben Dooks. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// ALSA SoC Audio Layer - S3C PCM-Controller driver +// +// Copyright (c) 2009 Samsung Electronics Co. Ltd +// Author: Jaswinder Singh <jassisinghbrar@gmail.com> +// based upon I2S drivers by Ben Dooks. #include <linux/clk.h> #include <linux/io.h> diff --git a/sound/soc/samsung/pcm.h b/sound/soc/samsung/pcm.h index 726baf814613..208d8da27de1 100644 --- a/sound/soc/samsung/pcm.h +++ b/sound/soc/samsung/pcm.h @@ -1,10 +1,4 @@ -/* sound/soc/samsung/pcm.h - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __S3C_PCM_H #define __S3C_PCM_H __FILE__ diff --git a/sound/soc/samsung/regs-i2s-v2.h b/sound/soc/samsung/regs-i2s-v2.h index 5e5e5680580b..867984e75709 100644 --- a/sound/soc/samsung/regs-i2s-v2.h +++ b/sound/soc/samsung/regs-i2s-v2.h @@ -1,14 +1,10 @@ -/* linux/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h - * +/* SPDX-License-Identifier: GPL-2.0 */ +/* * Copyright 2007 Simtec Electronics <linux@simtec.co.uk> * http://armlinux.simtec.co.uk/ * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * * S3C2412 IIS register definition -*/ + */ #ifndef __ASM_ARCH_REGS_S3C2412_IIS_H #define __ASM_ARCH_REGS_S3C2412_IIS_H diff --git a/sound/soc/samsung/regs-iis.h b/sound/soc/samsung/regs-iis.h index dc6cbbe9c4f0..253e172ad3b6 100644 --- a/sound/soc/samsung/regs-iis.h +++ b/sound/soc/samsung/regs-iis.h @@ -1,13 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2003 Simtec Electronics <linux@simtec.co.uk> * http://www.simtec.co.uk/products/SWLINUX/ * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * * S3C2410 IIS register definition -*/ + */ #ifndef __SAMSUNG_REGS_IIS_H__ #define __SAMSUNG_REGS_IIS_H__ diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c index a064ca7d78c3..1dcc1b252ad1 100644 --- a/sound/soc/samsung/rx1950_uda1380.c +++ b/sound/soc/samsung/rx1950_uda1380.c @@ -1,21 +1,15 @@ -/* - * rx1950.c -- ALSA Soc Audio Layer - * - * Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com> - * - * Based on smdk2440.c and magician.c - * - * Authors: Graeme Gregory graeme.gregory@wolfsonmicro.com - * Philipp Zabel <philipp.zabel@gmail.com> - * Denis Grigoriev <dgreenday@gmail.com> - * Vasily Khoruzhick <anarsoul@gmail.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// rx1950.c - ALSA SoC Audio Layer +// +// Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com> +// +// Based on smdk2440.c and magician.c +// +// Authors: Graeme Gregory graeme.gregory@wolfsonmicro.com +// Philipp Zabel <philipp.zabel@gmail.com> +// Denis Grigoriev <dgreenday@gmail.com> +// Vasily Khoruzhick <anarsoul@gmail.com> #include <linux/types.h> #include <linux/gpio.h> diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c index 58c3e9bfc6b7..7e196b599be1 100644 --- a/sound/soc/samsung/s3c-i2s-v2.c +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -1,18 +1,14 @@ -/* ALSA Soc Audio Layer - I2S core for newer Samsung SoCs. - * - * Copyright (c) 2006 Wolfson Microelectronics PLC. - * Graeme Gregory graeme.gregory@wolfsonmicro.com - * linux@wolfsonmicro.com - * - * Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics - * http://armlinux.simtec.co.uk/ - * Ben Dooks <ben@simtec.co.uk> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// ALSA Soc Audio Layer - I2S core for newer Samsung SoCs. +// +// Copyright (c) 2006 Wolfson Microelectronics PLC. +// Graeme Gregory graeme.gregory@wolfsonmicro.com +// linux@wolfsonmicro.com +// +// Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics +// http://armlinux.simtec.co.uk/ +// Ben Dooks <ben@simtec.co.uk> #include <linux/module.h> #include <linux/delay.h> diff --git a/sound/soc/samsung/s3c-i2s-v2.h b/sound/soc/samsung/s3c-i2s-v2.h index 3fca20f7a853..fe42b77999fd 100644 --- a/sound/soc/samsung/s3c-i2s-v2.h +++ b/sound/soc/samsung/s3c-i2s-v2.h @@ -1,16 +1,11 @@ -/* sound/soc/samsung/s3c-i2s-v2.h - * +/* SPDX-License-Identifier: GPL-2.0+ */ +/* * ALSA Soc Audio Layer - S3C_I2SV2 I2S driver * * Copyright (c) 2007 Simtec Electronics * http://armlinux.simtec.co.uk/ * Ben Dooks <ben@simtec.co.uk> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. -*/ + */ /* This code is the core support for the I2S block found in a number of * Samsung SoC devices which is unofficially named I2S-V2. Currently the diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c index c08638b0e458..787a3f6e9f24 100644 --- a/sound/soc/samsung/s3c2412-i2s.c +++ b/sound/soc/samsung/s3c2412-i2s.c @@ -1,20 +1,14 @@ -/* sound/soc/samsung/s3c2412-i2s.c - * - * ALSA Soc Audio Layer - S3C2412 I2S driver - * - * Copyright (c) 2006 Wolfson Microelectronics PLC. - * Graeme Gregory graeme.gregory@wolfsonmicro.com - * linux@wolfsonmicro.com - * - * Copyright (c) 2007, 2004-2005 Simtec Electronics - * http://armlinux.simtec.co.uk/ - * Ben Dooks <ben@simtec.co.uk> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// ALSA Soc Audio Layer - S3C2412 I2S driver +// +// Copyright (c) 2006 Wolfson Microelectronics PLC. +// Graeme Gregory graeme.gregory@wolfsonmicro.com +// linux@wolfsonmicro.com +// +// Copyright (c) 2007, 2004-2005 Simtec Electronics +// http://armlinux.simtec.co.uk/ +// Ben Dooks <ben@simtec.co.uk> #include <linux/delay.h> #include <linux/gpio.h> diff --git a/sound/soc/samsung/s3c2412-i2s.h b/sound/soc/samsung/s3c2412-i2s.h index 02ad5794c0a9..bff2a797cb08 100644 --- a/sound/soc/samsung/s3c2412-i2s.h +++ b/sound/soc/samsung/s3c2412-i2s.h @@ -1,16 +1,11 @@ -/* sound/soc/samsung/s3c2412-i2s.c - * +/* SPDX-License-Identifier: GPL-2.0+ */ +/* * ALSA Soc Audio Layer - S3C2412 I2S driver * * Copyright (c) 2007 Simtec Electronics * http://armlinux.simtec.co.uk/ * Ben Dooks <ben@simtec.co.uk> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. -*/ + */ #ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H #define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__ diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c index a8026b640c95..92bdaf0878f8 100644 --- a/sound/soc/samsung/s3c24xx-i2s.c +++ b/sound/soc/samsung/s3c24xx-i2s.c @@ -1,18 +1,13 @@ -/* - * s3c24xx-i2s.c -- ALSA Soc Audio Layer - * - * (c) 2006 Wolfson Microelectronics PLC. - * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com - * - * Copyright 2004-2005 Simtec Electronics - * http://armlinux.simtec.co.uk/ - * Ben Dooks <ben@simtec.co.uk> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// s3c24xx-i2s.c -- ALSA Soc Audio Layer +// +// (c) 2006 Wolfson Microelectronics PLC. +// Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com +// +// Copyright 2004-2005 Simtec Electronics +// http://armlinux.simtec.co.uk/ +// Ben Dooks <ben@simtec.co.uk> #include <linux/delay.h> #include <linux/clk.h> diff --git a/sound/soc/samsung/s3c24xx-i2s.h b/sound/soc/samsung/s3c24xx-i2s.h index f9ca04edacb7..e073e31855d0 100644 --- a/sound/soc/samsung/s3c24xx-i2s.h +++ b/sound/soc/samsung/s3c24xx-i2s.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * s3c24xx-i2s.c -- ALSA Soc Audio Layer * @@ -5,11 +6,6 @@ * Author: Graeme Gregory * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * * Revision history * 10th Nov 2006 Initial version. */ diff --git a/sound/soc/samsung/s3c24xx_simtec.c b/sound/soc/samsung/s3c24xx_simtec.c index 6de63f3e37d5..4543705b8d87 100644 --- a/sound/soc/samsung/s3c24xx_simtec.c +++ b/sound/soc/samsung/s3c24xx_simtec.c @@ -1,11 +1,6 @@ -/* sound/soc/samsung/s3c24xx_simtec.c - * - * Copyright 2009 Simtec Electronics - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. -*/ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright 2009 Simtec Electronics #include <linux/gpio.h> #include <linux/clk.h> diff --git a/sound/soc/samsung/s3c24xx_simtec.h b/sound/soc/samsung/s3c24xx_simtec.h index 8270748a2c41..38d8384755cd 100644 --- a/sound/soc/samsung/s3c24xx_simtec.h +++ b/sound/soc/samsung/s3c24xx_simtec.h @@ -1,11 +1,7 @@ -/* sound/soc/samsung/s3c24xx_simtec.h - * +/* SPDX-License-Identifier: GPL-2.0 */ +/* * Copyright 2009 Simtec Electronics - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. -*/ + */ extern void simtec_audio_init(struct snd_soc_pcm_runtime *rtd); diff --git a/sound/soc/samsung/s3c24xx_simtec_hermes.c b/sound/soc/samsung/s3c24xx_simtec_hermes.c index 7ac924c595bf..e3528e74a338 100644 --- a/sound/soc/samsung/s3c24xx_simtec_hermes.c +++ b/sound/soc/samsung/s3c24xx_simtec_hermes.c @@ -1,11 +1,6 @@ -/* sound/soc/samsung/s3c24xx_simtec_hermes.c - * - * Copyright 2009 Simtec Electronics - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. -*/ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright 2009 Simtec Electronics #include <linux/module.h> #include <sound/soc.h> diff --git a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c index b4ed2fc1a65c..1360b881400d 100644 --- a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c +++ b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c @@ -1,11 +1,6 @@ -/* sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c - * - * Copyright 2009 Simtec Electronics - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. -*/ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright 2009 Simtec Electronics #include <linux/module.h> #include <sound/soc.h> diff --git a/sound/soc/samsung/s3c24xx_uda134x.c b/sound/soc/samsung/s3c24xx_uda134x.c index 5fb3bab6bbfe..9d68f8ca1fcc 100644 --- a/sound/soc/samsung/s3c24xx_uda134x.c +++ b/sound/soc/samsung/s3c24xx_uda134x.c @@ -1,15 +1,11 @@ -/* - * Modifications by Christian Pellegrin <chripell@evolware.org> - * - * s3c24xx_uda134x.c -- S3C24XX_UDA134X ALSA SoC Audio board driver - * - * Copyright 2007 Dension Audio Systems Ltd. - * Author: Zoltan Devai - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// Modifications by Christian Pellegrin <chripell@evolware.org> +// +// s3c24xx_uda134x.c - S3C24XX_UDA134X ALSA SoC Audio board driver +// +// Copyright 2007 Dension Audio Systems Ltd. +// Author: Zoltan Devai #include <linux/clk.h> #include <linux/gpio.h> diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c index cf0f54e652c1..b9e887ea60b2 100644 --- a/sound/soc/samsung/smartq_wm8987.c +++ b/sound/soc/samsung/smartq_wm8987.c @@ -1,17 +1,10 @@ -/* sound/soc/samsung/smartq_wm8987.c - * - * Copyright 2010 Maurus Cuelenaere <mcuelenaere@gmail.com> - * - * Based on smdk6410_wm8987.c - * Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com - * Graeme Gregory - graeme.gregory@wolfsonmicro.com - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright 2010 Maurus Cuelenaere <mcuelenaere@gmail.com> +// +// Based on smdk6410_wm8987.c +// Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com +// Graeme Gregory - graeme.gregory@wolfsonmicro.com #include <linux/gpio/consumer.h> #include <linux/module.h> diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c index 7fc7cc6d1530..87a70d872c00 100644 --- a/sound/soc/samsung/smdk_spdif.c +++ b/sound/soc/samsung/smdk_spdif.c @@ -1,14 +1,8 @@ -/* - * smdk_spdif.c -- S/PDIF audio for SMDK - * - * Copyright 2010 Samsung Electronics Co. Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// smdk_spdif.c - S/PDIF audio for SMDK +// +// Copyright (C) 2010 Samsung Electronics Co., Ltd. #include <linux/clk.h> #include <linux/module.h> diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c index 6e4dfa7e2c89..987807e6f8c3 100644 --- a/sound/soc/samsung/smdk_wm8580.c +++ b/sound/soc/samsung/smdk_wm8580.c @@ -1,14 +1,7 @@ -/* - * smdk_wm8580.c - * - * Copyright (c) 2009 Samsung Electronics Co. Ltd - * Author: Jaswinder Singh <jassisinghbrar@gmail.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (c) 2009 Samsung Electronics Co. Ltd +// Author: Jaswinder Singh <jassisinghbrar@gmail.com> #include <linux/module.h> #include <sound/soc.h> diff --git a/sound/soc/samsung/smdk_wm8994.c b/sound/soc/samsung/smdk_wm8994.c index ff57b192d37d..135d8c2745be 100644 --- a/sound/soc/samsung/smdk_wm8994.c +++ b/sound/soc/samsung/smdk_wm8994.c @@ -1,11 +1,4 @@ -/* - * smdk_wm8994.c - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ #include "../codecs/wm8994.h" #include <sound/pcm_params.h> diff --git a/sound/soc/samsung/smdk_wm8994pcm.c b/sound/soc/samsung/smdk_wm8994pcm.c index 2e621496be8b..43171d6457fa 100644 --- a/sound/soc/samsung/smdk_wm8994pcm.c +++ b/sound/soc/samsung/smdk_wm8994pcm.c @@ -1,14 +1,8 @@ -/* - * sound/soc/samsung/smdk_wm8994pcm.c - * - * Copyright (c) 2011 Samsung Electronics Co., Ltd - * http://www.samsung.com - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (c) 2011 Samsung Electronics Co., Ltd +// http://www.samsung.com + #include <linux/module.h> #include <sound/soc.h> #include <sound/pcm.h> diff --git a/sound/soc/samsung/snow.c b/sound/soc/samsung/snow.c index 5d8efc2d5c38..57ce90fe5004 100644 --- a/sound/soc/samsung/snow.c +++ b/sound/soc/samsung/snow.c @@ -1,15 +1,6 @@ -/* - * ASoC machine driver for Snow boards - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// ASoC machine driver for Snow boards #include <linux/clk.h> #include <linux/module.h> diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c index 5e4afb330416..805c57986e0b 100644 --- a/sound/soc/samsung/spdif.c +++ b/sound/soc/samsung/spdif.c @@ -1,14 +1,9 @@ -/* sound/soc/samsung/spdif.c - * - * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver - * - * Copyright (c) 2010 Samsung Electronics Co. Ltd - * http://www.samsung.com/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ +// SPDX-License-Identifier: GPL-2.0 +// +// ALSA SoC Audio Layer - Samsung S/PDIF Controller driver +// +// Copyright (c) 2010 Samsung Electronics Co. Ltd +// http://www.samsung.com/ #include <linux/clk.h> #include <linux/io.h> diff --git a/sound/soc/samsung/spdif.h b/sound/soc/samsung/spdif.h index 4f72cb446dbf..461da60ab040 100644 --- a/sound/soc/samsung/spdif.h +++ b/sound/soc/samsung/spdif.h @@ -1,13 +1,9 @@ -/* sound/soc/samsung/spdif.h - * +/* SPDX-License-Identifier: GPL-2.0 */ +/* * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver * * Copyright (c) 2010 Samsung Electronics Co. Ltd * http://www.samsung.com/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #ifndef __SND_SOC_SAMSUNG_SPDIF_H diff --git a/sound/soc/samsung/speyside.c b/sound/soc/samsung/speyside.c index 4b4147d07804..15465c84daa3 100644 --- a/sound/soc/samsung/speyside.c +++ b/sound/soc/samsung/speyside.c @@ -1,13 +1,8 @@ -/* - * Speyside audio support - * - * Copyright 2011 Wolfson Microelectronics - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Speyside audio support +// +// Copyright 2011 Wolfson Microelectronics #include <sound/soc.h> #include <sound/soc-dapm.h> diff --git a/sound/soc/samsung/tm2_wm5110.c b/sound/soc/samsung/tm2_wm5110.c index dc93941e01c3..31f4256c6c65 100644 --- a/sound/soc/samsung/tm2_wm5110.c +++ b/sound/soc/samsung/tm2_wm5110.c @@ -1,14 +1,9 @@ -/* - * Copyright (C) 2015 - 2016 Samsung Electronics Co., Ltd. - * - * Authors: Inha Song <ideal.song@samsung.com> - * Sylwester Nawrocki <s.nawrocki@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (C) 2015 - 2016 Samsung Electronics Co., Ltd. +// +// Authors: Inha Song <ideal.song@samsung.com> +// Sylwester Nawrocki <s.nawrocki@samsung.com> #include <linux/clk.h> #include <linux/gpio.h> diff --git a/sound/soc/samsung/tobermory.c b/sound/soc/samsung/tobermory.c index 998727cb4c31..14b11acb12a4 100644 --- a/sound/soc/samsung/tobermory.c +++ b/sound/soc/samsung/tobermory.c @@ -1,13 +1,8 @@ -/* - * Tobermory audio support - * - * Copyright 2011 Wolfson Microelectronics - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// Tobermory audio support +// +// Copyright 2011 Wolfson Microelectronics #include <sound/soc.h> #include <sound/soc-dapm.h> diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 4fe83e611c01..37cb61553d5f 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -300,6 +300,18 @@ int rsnd_runtime_channel_after_ctu_with_params(struct rsnd_dai_stream *io, return chan; } +int rsnd_channel_normalization(int chan) +{ + if ((chan > 8) || (chan < 0)) + return 0; + + /* TDM Extend Mode needs 8ch */ + if (chan == 6) + chan = 8; + + return chan; +} + int rsnd_runtime_channel_for_ssi_with_params(struct rsnd_dai_stream *io, struct snd_pcm_hw_params *params) { @@ -312,11 +324,7 @@ int rsnd_runtime_channel_for_ssi_with_params(struct rsnd_dai_stream *io, if (rsnd_runtime_is_multi_ssi(io)) chan /= rsnd_rdai_ssi_lane_get(rdai); - /* TDM Extend Mode needs 8ch */ - if (chan == 6) - chan = 8; - - return chan; + return rsnd_channel_normalization(chan); } int rsnd_runtime_is_multi_ssi(struct rsnd_dai_stream *io) diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 0e6ef4e18400..7727add3eb1a 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -446,6 +446,7 @@ void rsnd_parse_connect_common(struct rsnd_dai *rdai, struct device_node *playback, struct device_node *capture); +int rsnd_channel_normalization(int chan); #define rsnd_runtime_channel_original(io) \ rsnd_runtime_channel_original_with_params(io, NULL) int rsnd_runtime_channel_original_with_params(struct rsnd_dai_stream *io, diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index f5afab631abb..44bda210256e 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -303,6 +303,8 @@ static int rsnd_ssi_master_clk_start(struct rsnd_mod *mod, if (rsnd_runtime_is_tdm_split(io)) chan = rsnd_io_converted_chan(io); + chan = rsnd_channel_normalization(chan); + main_rate = rsnd_ssi_clk_query(rdai, rate, chan, &idx); if (!main_rate) { dev_err(dev, "unsupported clock rate\n"); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 46e3ab0fced4..2403bec2fccf 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1974,10 +1974,13 @@ static void soc_check_tplg_fes(struct snd_soc_card *card) continue; /* for this machine ? */ + if (!strcmp(component->driver->ignore_machine, + card->dev->driver->name)) + goto match; if (strcmp(component->driver->ignore_machine, - card->dev->driver->name)) + dev_name(card->dev))) continue; - +match: /* machine matches, so override the rtd data */ for_each_card_prelinks(card, i, dai_link) { @@ -2828,10 +2831,21 @@ EXPORT_SYMBOL_GPL(snd_soc_register_card); static void snd_soc_unbind_card(struct snd_soc_card *card, bool unregister) { + struct snd_soc_pcm_runtime *rtd; + int order; + if (card->instantiated) { card->instantiated = false; snd_soc_dapm_shutdown(card); snd_soc_flush_all_delayed_work(card); + + /* remove all components used by DAI links on this card */ + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + soc_remove_link_components(card, rtd, order); + } + } + soc_cleanup_card_resources(card); if (!unregister) list_add(&card->list, &unbind_card_list); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 0382a47b30bd..81a7a12196ff 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -883,6 +883,7 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_pga: + case snd_soc_dapm_effect: case snd_soc_dapm_out_drv: wname_in_long_name = true; kcname_in_long_name = true; @@ -2370,6 +2371,7 @@ static ssize_t dapm_widget_show_component(struct snd_soc_component *cmpnt, case snd_soc_dapm_dac: case snd_soc_dapm_adc: case snd_soc_dapm_pga: + case snd_soc_dapm_effect: case snd_soc_dapm_out_drv: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: @@ -3197,6 +3199,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) dapm_new_mux(w); break; case snd_soc_dapm_pga: + case snd_soc_dapm_effect: case snd_soc_dapm_out_drv: dapm_new_pga(w); break; @@ -4049,7 +4052,7 @@ snd_soc_dapm_new_dai(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd, struct snd_soc_dapm_widget template; struct snd_soc_dapm_widget *w; const char **w_param_text; - unsigned long private_value; + unsigned long private_value = 0; char *link_name; int ret; diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index be80a12fba27..0a4f60c7a188 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -43,8 +43,8 @@ static bool snd_soc_dai_stream_valid(struct snd_soc_dai *dai, int stream) else codec_stream = &dai->driver->capture; - /* If the codec specifies any rate at all, it supports the stream. */ - return codec_stream->rates; + /* If the codec specifies any channels at all, it supports the stream */ + return codec_stream->channels_min; } /** @@ -1033,6 +1033,9 @@ interface_err: codec_err: for_each_rtd_codec_dai_rollback(rtd, i, codec_dai) { + if (!snd_soc_dai_stream_valid(codec_dai, substream->stream)) + continue; + if (codec_dai->driver->ops->hw_free) codec_dai->driver->ops->hw_free(substream, codec_dai); codec_dai->rate = 0; @@ -1090,6 +1093,9 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) /* now free hw params for the DAIs */ for_each_rtd_codec_dai(rtd, i, codec_dai) { + if (!snd_soc_dai_stream_valid(codec_dai, substream->stream)) + continue; + if (codec_dai->driver->ops->hw_free) codec_dai->driver->ops->hw_free(substream, codec_dai); } @@ -2166,6 +2172,10 @@ int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) } } + /* copy the fixed-up hw params for BE dai */ + memcpy(&be->dpcm[stream].hw_params, &dpcm->hw_params, + sizeof(struct snd_pcm_hw_params)); + /* only allow hw_params() if no connected FEs are running */ if (!snd_soc_dpcm_can_be_params(fe, be, stream)) continue; diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 96852d250619..3299ebb48c1a 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -30,6 +30,8 @@ #include <sound/soc-topology.h> #include <sound/tlv.h> +#define SOC_TPLG_MAGIC_BIG_ENDIAN 0x436F5341 /* ASoC in reverse */ + /* * We make several passes over the data (since it wont necessarily be ordered) * and process objects in the following order. This guarantees the component @@ -197,8 +199,8 @@ static int tplc_chan_get_reg(struct soc_tplg *tplg, int i; for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { - if (chan[i].id == map) - return chan[i].reg; + if (le32_to_cpu(chan[i].id) == map) + return le32_to_cpu(chan[i].reg); } return -EINVAL; @@ -210,8 +212,8 @@ static int tplc_chan_get_shift(struct soc_tplg *tplg, int i; for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { - if (chan[i].id == map) - return chan[i].shift; + if (le32_to_cpu(chan[i].id) == map) + return le32_to_cpu(chan[i].shift); } return -EINVAL; @@ -536,6 +538,8 @@ static void remove_dai(struct snd_soc_component *comp, if (dai->driver == dai_drv) dai->driver = NULL; + kfree(dai_drv->playback.stream_name); + kfree(dai_drv->capture.stream_name); kfree(dai_drv->name); list_del(&dobj->list); kfree(dai_drv); @@ -591,7 +595,7 @@ static int soc_tplg_kcontrol_bind_io(struct snd_soc_tplg_ctl_hdr *hdr, const struct snd_soc_tplg_bytes_ext_ops *ext_ops; int num_ops, i; - if (hdr->ops.info == SND_SOC_TPLG_CTL_BYTES + if (le32_to_cpu(hdr->ops.info) == SND_SOC_TPLG_CTL_BYTES && k->iface & SNDRV_CTL_ELEM_IFACE_MIXER && k->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE && k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { @@ -707,9 +711,9 @@ static int soc_tplg_create_tlv_db_scale(struct soc_tplg *tplg, p[0] = SNDRV_CTL_TLVT_DB_SCALE; p[1] = item_len; - p[2] = scale->min; - p[3] = (scale->step & TLV_DB_SCALE_MASK) - | (scale->mute ? TLV_DB_SCALE_MUTE : 0); + p[2] = le32_to_cpu(scale->min); + p[3] = (le32_to_cpu(scale->step) & TLV_DB_SCALE_MASK) + | (le32_to_cpu(scale->mute) ? TLV_DB_SCALE_MUTE : 0); kc->tlv.p = (void *)p; return 0; @@ -719,13 +723,14 @@ static int soc_tplg_create_tlv(struct soc_tplg *tplg, struct snd_kcontrol_new *kc, struct snd_soc_tplg_ctl_hdr *tc) { struct snd_soc_tplg_ctl_tlv *tplg_tlv; + u32 access = le32_to_cpu(tc->access); - if (!(tc->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)) + if (!(access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)) return 0; - if (!(tc->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)) { + if (!(access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)) { tplg_tlv = &tc->tlv; - switch (tplg_tlv->type) { + switch (le32_to_cpu(tplg_tlv->type)) { case SNDRV_CTL_TLVT_DB_SCALE: return soc_tplg_create_tlv_db_scale(tplg, kc, &tplg_tlv->scale); @@ -776,7 +781,7 @@ static int soc_tplg_dbytes_create(struct soc_tplg *tplg, unsigned int count, return -ENOMEM; tplg->pos += (sizeof(struct snd_soc_tplg_bytes_control) + - be->priv.size); + le32_to_cpu(be->priv.size)); dev_dbg(tplg->dev, "ASoC: adding bytes kcontrol %s with access 0x%x\n", @@ -786,9 +791,9 @@ static int soc_tplg_dbytes_create(struct soc_tplg *tplg, unsigned int count, kc.name = be->hdr.name; kc.private_value = (long)sbe; kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; - kc.access = be->hdr.access; + kc.access = le32_to_cpu(be->hdr.access); - sbe->max = be->max; + sbe->max = le32_to_cpu(be->max); sbe->dobj.type = SND_SOC_DOBJ_BYTES; sbe->dobj.ops = tplg->ops; INIT_LIST_HEAD(&sbe->dobj.list); @@ -856,7 +861,7 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, if (sm == NULL) return -ENOMEM; tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + - mc->priv.size); + le32_to_cpu(mc->priv.size)); dev_dbg(tplg->dev, "ASoC: adding mixer kcontrol %s with access 0x%x\n", @@ -866,7 +871,7 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, kc.name = mc->hdr.name; kc.private_value = (long)sm; kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; - kc.access = mc->hdr.access; + kc.access = le32_to_cpu(mc->hdr.access); /* we only support FL/FR channel mapping atm */ sm->reg = tplc_chan_get_reg(tplg, mc->channel, @@ -878,10 +883,10 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, sm->rshift = tplc_chan_get_shift(tplg, mc->channel, SNDRV_CHMAP_FR); - sm->max = mc->max; - sm->min = mc->min; - sm->invert = mc->invert; - sm->platform_max = mc->platform_max; + sm->max = le32_to_cpu(mc->max); + sm->min = le32_to_cpu(mc->min); + sm->invert = le32_to_cpu(mc->invert); + sm->platform_max = le32_to_cpu(mc->platform_max); sm->dobj.index = tplg->index; sm->dobj.ops = tplg->ops; sm->dobj.type = SND_SOC_DOBJ_MIXER; @@ -895,19 +900,20 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, continue; } + /* create any TLV data */ + soc_tplg_create_tlv(tplg, &kc, &mc->hdr); + /* pass control to driver for optional further init */ err = soc_tplg_init_kcontrol(tplg, &kc, (struct snd_soc_tplg_ctl_hdr *) mc); if (err < 0) { dev_err(tplg->dev, "ASoC: failed to init %s\n", mc->hdr.name); + soc_tplg_free_tlv(tplg, &kc); kfree(sm); continue; } - /* create any TLV data */ - soc_tplg_create_tlv(tplg, &kc, &mc->hdr); - /* register control here */ err = soc_tplg_add_kcontrol(tplg, &kc, &sm->dobj.control.kcontrol); @@ -931,7 +937,7 @@ static int soc_tplg_denum_create_texts(struct soc_enum *se, int i, ret; se->dobj.control.dtexts = - kcalloc(ec->items, sizeof(char *), GFP_KERNEL); + kcalloc(le32_to_cpu(ec->items), sizeof(char *), GFP_KERNEL); if (se->dobj.control.dtexts == NULL) return -ENOMEM; @@ -963,15 +969,22 @@ err: static int soc_tplg_denum_create_values(struct soc_enum *se, struct snd_soc_tplg_enum_control *ec) { - if (ec->items > sizeof(*ec->values)) + int i; + + if (le32_to_cpu(ec->items) > sizeof(*ec->values)) return -EINVAL; - se->dobj.control.dvalues = kmemdup(ec->values, - ec->items * sizeof(u32), + se->dobj.control.dvalues = kzalloc(le32_to_cpu(ec->items) * + sizeof(u32), GFP_KERNEL); if (!se->dobj.control.dvalues) return -ENOMEM; + /* convert from little-endian */ + for (i = 0; i < le32_to_cpu(ec->items); i++) { + se->dobj.control.dvalues[i] = le32_to_cpu(ec->values[i]); + } + return 0; } @@ -994,8 +1007,6 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, for (i = 0; i < count; i++) { ec = (struct snd_soc_tplg_enum_control *)tplg->pos; - tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + - ec->priv.size); /* validate kcontrol */ if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == @@ -1006,6 +1017,9 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, if (se == NULL) return -ENOMEM; + tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + + le32_to_cpu(ec->priv.size)); + dev_dbg(tplg->dev, "ASoC: adding enum kcontrol %s size %d\n", ec->hdr.name, ec->items); @@ -1013,7 +1027,7 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, kc.name = ec->hdr.name; kc.private_value = (long)se; kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; - kc.access = ec->hdr.access; + kc.access = le32_to_cpu(ec->hdr.access); se->reg = tplc_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL); se->shift_l = tplc_chan_get_shift(tplg, ec->channel, @@ -1021,14 +1035,14 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, se->shift_r = tplc_chan_get_shift(tplg, ec->channel, SNDRV_CHMAP_FL); - se->items = ec->items; - se->mask = ec->mask; + se->items = le32_to_cpu(ec->items); + se->mask = le32_to_cpu(ec->mask); se->dobj.index = tplg->index; se->dobj.type = SND_SOC_DOBJ_ENUM; se->dobj.ops = tplg->ops; INIT_LIST_HEAD(&se->dobj.list); - switch (ec->hdr.ops.info) { + switch (le32_to_cpu(ec->hdr.ops.info)) { case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: case SND_SOC_TPLG_CTL_ENUM_VALUE: err = soc_tplg_denum_create_values(se, ec); @@ -1101,23 +1115,24 @@ static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, int i; if (tplg->pass != SOC_TPLG_PASS_MIXER) { - tplg->pos += hdr->size + hdr->payload_size; + tplg->pos += le32_to_cpu(hdr->size) + + le32_to_cpu(hdr->payload_size); return 0; } dev_dbg(tplg->dev, "ASoC: adding %d kcontrols at 0x%lx\n", hdr->count, soc_tplg_get_offset(tplg)); - for (i = 0; i < hdr->count; i++) { + for (i = 0; i < le32_to_cpu(hdr->count); i++) { control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; - if (control_hdr->size != sizeof(*control_hdr)) { + if (le32_to_cpu(control_hdr->size) != sizeof(*control_hdr)) { dev_err(tplg->dev, "ASoC: invalid control size\n"); return -EINVAL; } - switch (control_hdr->ops.info) { + switch (le32_to_cpu(control_hdr->ops.info)) { case SND_SOC_TPLG_CTL_VOLSW: case SND_SOC_TPLG_CTL_STROBE: case SND_SOC_TPLG_CTL_VOLSW_SX: @@ -1125,17 +1140,20 @@ static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, case SND_SOC_TPLG_CTL_RANGE: case SND_SOC_TPLG_DAPM_CTL_VOLSW: case SND_SOC_TPLG_DAPM_CTL_PIN: - soc_tplg_dmixer_create(tplg, 1, hdr->payload_size); + soc_tplg_dmixer_create(tplg, 1, + le32_to_cpu(hdr->payload_size)); break; case SND_SOC_TPLG_CTL_ENUM: case SND_SOC_TPLG_CTL_ENUM_VALUE: case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: - soc_tplg_denum_create(tplg, 1, hdr->payload_size); + soc_tplg_denum_create(tplg, 1, + le32_to_cpu(hdr->payload_size)); break; case SND_SOC_TPLG_CTL_BYTES: - soc_tplg_dbytes_create(tplg, 1, hdr->payload_size); + soc_tplg_dbytes_create(tplg, 1, + le32_to_cpu(hdr->payload_size)); break; default: soc_bind_err(tplg, control_hdr, i); @@ -1163,17 +1181,22 @@ static int soc_tplg_dapm_graph_elems_load(struct soc_tplg *tplg, struct snd_soc_dapm_context *dapm = &tplg->comp->dapm; struct snd_soc_tplg_dapm_graph_elem *elem; struct snd_soc_dapm_route **routes; - int count = hdr->count, i, j; + int count, i, j; int ret = 0; + count = le32_to_cpu(hdr->count); + if (tplg->pass != SOC_TPLG_PASS_GRAPH) { - tplg->pos += hdr->size + hdr->payload_size; + tplg->pos += + le32_to_cpu(hdr->size) + + le32_to_cpu(hdr->payload_size); + return 0; } if (soc_tplg_check_elem_count(tplg, sizeof(struct snd_soc_tplg_dapm_graph_elem), - count, hdr->payload_size, "graph")) { + count, le32_to_cpu(hdr->payload_size), "graph")) { dev_err(tplg->dev, "ASoC: invalid count %d for DAPM routes\n", count); @@ -1282,14 +1305,14 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_dmixer_create( if (sm == NULL) goto err; - tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + - mc->priv.size); - /* validate kcontrol */ if (strnlen(mc->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) goto err_str; + tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + + le32_to_cpu(mc->priv.size)); + dev_dbg(tplg->dev, " adding DAPM widget mixer control %s at %d\n", mc->hdr.name, i); @@ -1325,18 +1348,19 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_dmixer_create( continue; } + /* create any TLV data */ + soc_tplg_create_tlv(tplg, &kc[i], &mc->hdr); + /* pass control to driver for optional further init */ err = soc_tplg_init_kcontrol(tplg, &kc[i], (struct snd_soc_tplg_ctl_hdr *)mc); if (err < 0) { dev_err(tplg->dev, "ASoC: failed to init %s\n", mc->hdr.name); + soc_tplg_free_tlv(tplg, &kc[i]); kfree(sm); continue; } - - /* create any TLV data */ - soc_tplg_create_tlv(tplg, &kc[i], &mc->hdr); } return kc; @@ -1374,6 +1398,9 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_denum_create( if (se == NULL) goto err; + tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + + ec->priv.size); + dev_dbg(tplg->dev, " adding DAPM widget enum control %s\n", ec->hdr.name); @@ -1397,7 +1424,7 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_denum_create( se->mask = ec->mask; se->dobj.index = tplg->index; - switch (ec->hdr.ops.info) { + switch (le32_to_cpu(ec->hdr.ops.info)) { case SND_SOC_TPLG_CTL_ENUM_VALUE: case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: err = soc_tplg_denum_create_values(se, ec); @@ -1438,9 +1465,6 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_denum_create( ec->hdr.name); goto err_se; } - - tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + - ec->priv.size); } return kc; @@ -1491,7 +1515,7 @@ static struct snd_kcontrol_new *soc_tplg_dapm_widget_dbytes_create( goto err; tplg->pos += (sizeof(struct snd_soc_tplg_bytes_control) + - be->priv.size); + le32_to_cpu(be->priv.size)); dev_dbg(tplg->dev, "ASoC: adding bytes kcontrol %s with access 0x%x\n", @@ -1563,7 +1587,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, memset(&template, 0, sizeof(template)); /* map user to kernel widget ID */ - template.id = get_widget_id(w->id); + template.id = get_widget_id(le32_to_cpu(w->id)); if (template.id < 0) return template.id; @@ -1576,18 +1600,20 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, ret = -ENOMEM; goto err; } - template.reg = w->reg; - template.shift = w->shift; - template.mask = w->mask; - template.subseq = w->subseq; + template.reg = le32_to_cpu(w->reg); + template.shift = le32_to_cpu(w->shift); + template.mask = le32_to_cpu(w->mask); + template.subseq = le32_to_cpu(w->subseq); template.on_val = w->invert ? 0 : 1; template.off_val = w->invert ? 1 : 0; - template.ignore_suspend = w->ignore_suspend; - template.event_flags = w->event_flags; + template.ignore_suspend = le32_to_cpu(w->ignore_suspend); + template.event_flags = le16_to_cpu(w->event_flags); template.dobj.index = tplg->index; tplg->pos += - (sizeof(struct snd_soc_tplg_dapm_widget) + w->priv.size); + (sizeof(struct snd_soc_tplg_dapm_widget) + + le32_to_cpu(w->priv.size)); + if (w->num_kcontrols == 0) { kcontrol_type = 0; template.num_kcontrols = 0; @@ -1598,7 +1624,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, dev_dbg(tplg->dev, "ASoC: template %s has %d controls of type %x\n", w->name, w->num_kcontrols, control_hdr->type); - switch (control_hdr->ops.info) { + switch (le32_to_cpu(control_hdr->ops.info)) { case SND_SOC_TPLG_CTL_VOLSW: case SND_SOC_TPLG_CTL_STROBE: case SND_SOC_TPLG_CTL_VOLSW_SX: @@ -1606,7 +1632,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, case SND_SOC_TPLG_CTL_RANGE: case SND_SOC_TPLG_DAPM_CTL_VOLSW: kcontrol_type = SND_SOC_TPLG_TYPE_MIXER; /* volume mixer */ - template.num_kcontrols = w->num_kcontrols; + template.num_kcontrols = le32_to_cpu(w->num_kcontrols); template.kcontrol_news = soc_tplg_dapm_widget_dmixer_create(tplg, template.num_kcontrols); @@ -1621,7 +1647,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: kcontrol_type = SND_SOC_TPLG_TYPE_ENUM; /* enumerated mixer */ - template.num_kcontrols = w->num_kcontrols; + template.num_kcontrols = le32_to_cpu(w->num_kcontrols); template.kcontrol_news = soc_tplg_dapm_widget_denum_create(tplg, template.num_kcontrols); @@ -1632,7 +1658,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, break; case SND_SOC_TPLG_CTL_BYTES: kcontrol_type = SND_SOC_TPLG_TYPE_BYTES; /* bytes control */ - template.num_kcontrols = w->num_kcontrols; + template.num_kcontrols = le32_to_cpu(w->num_kcontrols); template.kcontrol_news = soc_tplg_dapm_widget_dbytes_create(tplg, template.num_kcontrols); @@ -1644,7 +1670,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, default: dev_err(tplg->dev, "ASoC: invalid widget control type %d:%d:%d\n", control_hdr->ops.get, control_hdr->ops.put, - control_hdr->ops.info); + le32_to_cpu(control_hdr->ops.info)); ret = -EINVAL; goto hdr_err; } @@ -1694,7 +1720,9 @@ static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr) { struct snd_soc_tplg_dapm_widget *widget; - int ret, count = hdr->count, i; + int ret, count, i; + + count = le32_to_cpu(hdr->count); if (tplg->pass != SOC_TPLG_PASS_WIDGET) return 0; @@ -1703,7 +1731,7 @@ static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg, for (i = 0; i < count; i++) { widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos; - if (widget->size != sizeof(*widget)) { + if (le32_to_cpu(widget->size) != sizeof(*widget)) { dev_err(tplg->dev, "ASoC: invalid widget size\n"); return -EINVAL; } @@ -1745,13 +1773,13 @@ static void set_stream_info(struct snd_soc_pcm_stream *stream, struct snd_soc_tplg_stream_caps *caps) { stream->stream_name = kstrdup(caps->name, GFP_KERNEL); - stream->channels_min = caps->channels_min; - stream->channels_max = caps->channels_max; - stream->rates = caps->rates; - stream->rate_min = caps->rate_min; - stream->rate_max = caps->rate_max; - stream->formats = caps->formats; - stream->sig_bits = caps->sig_bits; + stream->channels_min = le32_to_cpu(caps->channels_min); + stream->channels_max = le32_to_cpu(caps->channels_max); + stream->rates = le32_to_cpu(caps->rates); + stream->rate_min = le32_to_cpu(caps->rate_min); + stream->rate_max = le32_to_cpu(caps->rate_max); + stream->formats = le64_to_cpu(caps->formats); + stream->sig_bits = le32_to_cpu(caps->sig_bits); } static void set_dai_flags(struct snd_soc_dai_driver *dai_drv, @@ -1786,7 +1814,7 @@ static int soc_tplg_dai_create(struct soc_tplg *tplg, if (strlen(pcm->dai_name)) dai_drv->name = kstrdup(pcm->dai_name, GFP_KERNEL); - dai_drv->id = pcm->dai_id; + dai_drv->id = le32_to_cpu(pcm->dai_id); if (pcm->playback) { stream = &dai_drv->playback; @@ -1807,6 +1835,9 @@ static int soc_tplg_dai_create(struct soc_tplg *tplg, ret = soc_tplg_dai_load(tplg, dai_drv, pcm, NULL); if (ret < 0) { dev_err(tplg->comp->dev, "ASoC: DAI loading failed\n"); + kfree(dai_drv->playback.stream_name); + kfree(dai_drv->capture.stream_name); + kfree(dai_drv->name); kfree(dai_drv); return ret; } @@ -1858,7 +1889,7 @@ static int soc_tplg_fe_link_create(struct soc_tplg *tplg, link->name = kstrdup(pcm->pcm_name, GFP_KERNEL); link->stream_name = kstrdup(pcm->pcm_name, GFP_KERNEL); } - link->id = pcm->pcm_id; + link->id = le32_to_cpu(pcm->pcm_id); if (strlen(pcm->dai_name)) link->cpu_dai_name = kstrdup(pcm->dai_name, GFP_KERNEL); @@ -1868,15 +1899,20 @@ static int soc_tplg_fe_link_create(struct soc_tplg *tplg, /* enable DPCM */ link->dynamic = 1; - link->dpcm_playback = pcm->playback; - link->dpcm_capture = pcm->capture; + link->dpcm_playback = le32_to_cpu(pcm->playback); + link->dpcm_capture = le32_to_cpu(pcm->capture); if (pcm->flag_mask) - set_link_flags(link, pcm->flag_mask, pcm->flags); + set_link_flags(link, + le32_to_cpu(pcm->flag_mask), + le32_to_cpu(pcm->flags)); /* pass control to component driver for optional further init */ ret = soc_tplg_dai_link_load(tplg, link, NULL); if (ret < 0) { dev_err(tplg->comp->dev, "ASoC: FE link loading failed\n"); + kfree(link->name); + kfree(link->stream_name); + kfree(link->cpu_dai_name); kfree(link); return ret; } @@ -1907,7 +1943,7 @@ static int soc_tplg_pcm_create(struct soc_tplg *tplg, static void stream_caps_new_ver(struct snd_soc_tplg_stream_caps *dest, struct snd_soc_tplg_stream_caps_v4 *src) { - dest->size = sizeof(*dest); + dest->size = cpu_to_le32(sizeof(*dest)); memcpy(dest->name, src->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); dest->formats = src->formats; dest->rates = src->rates; @@ -1941,7 +1977,7 @@ static int pcm_new_ver(struct soc_tplg *tplg, *pcm = NULL; - if (src->size != sizeof(*src_v4)) { + if (le32_to_cpu(src->size) != sizeof(*src_v4)) { dev_err(tplg->dev, "ASoC: invalid PCM size\n"); return -EINVAL; } @@ -1952,7 +1988,7 @@ static int pcm_new_ver(struct soc_tplg *tplg, if (!dest) return -ENOMEM; - dest->size = sizeof(*dest); /* size of latest abi version */ + dest->size = cpu_to_le32(sizeof(*dest)); /* size of latest abi version */ memcpy(dest->pcm_name, src_v4->pcm_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); memcpy(dest->dai_name, src_v4->dai_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); dest->pcm_id = src_v4->pcm_id; @@ -1961,7 +1997,7 @@ static int pcm_new_ver(struct soc_tplg *tplg, dest->capture = src_v4->capture; dest->compress = src_v4->compress; dest->num_streams = src_v4->num_streams; - for (i = 0; i < dest->num_streams; i++) + for (i = 0; i < le32_to_cpu(dest->num_streams); i++) memcpy(&dest->stream[i], &src_v4->stream[i], sizeof(struct snd_soc_tplg_stream)); @@ -1976,25 +2012,30 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr) { struct snd_soc_tplg_pcm *pcm, *_pcm; - int count = hdr->count; + int count; + int size; int i; bool abi_match; + count = le32_to_cpu(hdr->count); + if (tplg->pass != SOC_TPLG_PASS_PCM_DAI) return 0; /* check the element size and count */ pcm = (struct snd_soc_tplg_pcm *)tplg->pos; - if (pcm->size > sizeof(struct snd_soc_tplg_pcm) - || pcm->size < sizeof(struct snd_soc_tplg_pcm_v4)) { + size = le32_to_cpu(pcm->size); + if (size > sizeof(struct snd_soc_tplg_pcm) + || size < sizeof(struct snd_soc_tplg_pcm_v4)) { dev_err(tplg->dev, "ASoC: invalid size %d for PCM elems\n", - pcm->size); + size); return -EINVAL; } if (soc_tplg_check_elem_count(tplg, - pcm->size, count, - hdr->payload_size, "PCM DAI")) { + size, count, + le32_to_cpu(hdr->payload_size), + "PCM DAI")) { dev_err(tplg->dev, "ASoC: invalid count %d for PCM DAI elems\n", count); return -EINVAL; @@ -2002,11 +2043,12 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, for (i = 0; i < count; i++) { pcm = (struct snd_soc_tplg_pcm *)tplg->pos; + size = le32_to_cpu(pcm->size); /* check ABI version by size, create a new version of pcm * if abi not match. */ - if (pcm->size == sizeof(*pcm)) { + if (size == sizeof(*pcm)) { abi_match = true; _pcm = pcm; } else { @@ -2020,7 +2062,7 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, /* offset by version-specific struct size and * real priv data size */ - tplg->pos += pcm->size + _pcm->priv.size; + tplg->pos += size + le32_to_cpu(_pcm->priv.size); if (!abi_match) kfree(_pcm); /* free the duplicated one */ @@ -2048,12 +2090,13 @@ static void set_link_hw_format(struct snd_soc_dai_link *link, unsigned char invert_bclk, invert_fsync; int i; - for (i = 0; i < cfg->num_hw_configs; i++) { + for (i = 0; i < le32_to_cpu(cfg->num_hw_configs); i++) { hw_config = &cfg->hw_config[i]; if (hw_config->id != cfg->default_hw_config_id) continue; - link->dai_fmt = hw_config->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + link->dai_fmt = le32_to_cpu(hw_config->fmt) & + SND_SOC_DAIFMT_FORMAT_MASK; /* clock gating */ switch (hw_config->clock_gated) { @@ -2117,7 +2160,8 @@ static int link_new_ver(struct soc_tplg *tplg, *link = NULL; - if (src->size != sizeof(struct snd_soc_tplg_link_config_v4)) { + if (le32_to_cpu(src->size) != + sizeof(struct snd_soc_tplg_link_config_v4)) { dev_err(tplg->dev, "ASoC: invalid physical link config size\n"); return -EINVAL; } @@ -2129,10 +2173,10 @@ static int link_new_ver(struct soc_tplg *tplg, if (!dest) return -ENOMEM; - dest->size = sizeof(*dest); + dest->size = cpu_to_le32(sizeof(*dest)); dest->id = src_v4->id; dest->num_streams = src_v4->num_streams; - for (i = 0; i < dest->num_streams; i++) + for (i = 0; i < le32_to_cpu(dest->num_streams); i++) memcpy(&dest->stream[i], &src_v4->stream[i], sizeof(struct snd_soc_tplg_stream)); @@ -2165,7 +2209,7 @@ static int soc_tplg_link_config(struct soc_tplg *tplg, else stream_name = NULL; - link = snd_soc_find_dai_link(tplg->comp->card, cfg->id, + link = snd_soc_find_dai_link(tplg->comp->card, le32_to_cpu(cfg->id), name, stream_name); if (!link) { dev_err(tplg->dev, "ASoC: physical link %s (id %d) not exist\n", @@ -2179,7 +2223,9 @@ static int soc_tplg_link_config(struct soc_tplg *tplg, /* flags */ if (cfg->flag_mask) - set_link_flags(link, cfg->flag_mask, cfg->flags); + set_link_flags(link, + le32_to_cpu(cfg->flag_mask), + le32_to_cpu(cfg->flags)); /* pass control to component driver for optional further init */ ret = soc_tplg_dai_link_load(tplg, link, cfg); @@ -2203,27 +2249,33 @@ static int soc_tplg_link_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr) { struct snd_soc_tplg_link_config *link, *_link; - int count = hdr->count; + int count; + int size; int i, ret; bool abi_match; + count = le32_to_cpu(hdr->count); + if (tplg->pass != SOC_TPLG_PASS_LINK) { - tplg->pos += hdr->size + hdr->payload_size; + tplg->pos += le32_to_cpu(hdr->size) + + le32_to_cpu(hdr->payload_size); return 0; }; /* check the element size and count */ link = (struct snd_soc_tplg_link_config *)tplg->pos; - if (link->size > sizeof(struct snd_soc_tplg_link_config) - || link->size < sizeof(struct snd_soc_tplg_link_config_v4)) { + size = le32_to_cpu(link->size); + if (size > sizeof(struct snd_soc_tplg_link_config) + || size < sizeof(struct snd_soc_tplg_link_config_v4)) { dev_err(tplg->dev, "ASoC: invalid size %d for physical link elems\n", - link->size); + size); return -EINVAL; } if (soc_tplg_check_elem_count(tplg, - link->size, count, - hdr->payload_size, "physical link config")) { + size, count, + le32_to_cpu(hdr->payload_size), + "physical link config")) { dev_err(tplg->dev, "ASoC: invalid count %d for physical link elems\n", count); return -EINVAL; @@ -2232,7 +2284,8 @@ static int soc_tplg_link_elems_load(struct soc_tplg *tplg, /* config physical DAI links */ for (i = 0; i < count; i++) { link = (struct snd_soc_tplg_link_config *)tplg->pos; - if (link->size == sizeof(*link)) { + size = le32_to_cpu(link->size); + if (size == sizeof(*link)) { abi_match = true; _link = link; } else { @@ -2249,7 +2302,7 @@ static int soc_tplg_link_elems_load(struct soc_tplg *tplg, /* offset by version-specific struct size and * real priv data size */ - tplg->pos += link->size + _link->priv.size; + tplg->pos += size + le32_to_cpu(_link->priv.size); if (!abi_match) kfree(_link); /* free the duplicated one */ @@ -2269,13 +2322,15 @@ static int soc_tplg_link_elems_load(struct soc_tplg *tplg, static int soc_tplg_dai_config(struct soc_tplg *tplg, struct snd_soc_tplg_dai *d) { - struct snd_soc_dai_link_component dai_component = {0}; + struct snd_soc_dai_link_component dai_component; struct snd_soc_dai *dai; struct snd_soc_dai_driver *dai_drv; struct snd_soc_pcm_stream *stream; struct snd_soc_tplg_stream_caps *caps; int ret; + memset(&dai_component, 0, sizeof(dai_component)); + dai_component.dai_name = d->dai_name; dai = snd_soc_find_dai(&dai_component); if (!dai) { @@ -2284,7 +2339,7 @@ static int soc_tplg_dai_config(struct soc_tplg *tplg, return -EINVAL; } - if (d->dai_id != dai->id) { + if (le32_to_cpu(d->dai_id) != dai->id) { dev_err(tplg->dev, "ASoC: physical DAI %s id mismatch\n", d->dai_name); return -EINVAL; @@ -2307,7 +2362,9 @@ static int soc_tplg_dai_config(struct soc_tplg *tplg, } if (d->flag_mask) - set_dai_flags(dai_drv, d->flag_mask, d->flags); + set_dai_flags(dai_drv, + le32_to_cpu(d->flag_mask), + le32_to_cpu(d->flags)); /* pass control to component driver for optional further init */ ret = soc_tplg_dai_load(tplg, dai_drv, NULL, dai); @@ -2324,22 +2381,24 @@ static int soc_tplg_dai_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_hdr *hdr) { struct snd_soc_tplg_dai *dai; - int count = hdr->count; + int count; int i; + count = le32_to_cpu(hdr->count); + if (tplg->pass != SOC_TPLG_PASS_BE_DAI) return 0; /* config the existing BE DAIs */ for (i = 0; i < count; i++) { dai = (struct snd_soc_tplg_dai *)tplg->pos; - if (dai->size != sizeof(*dai)) { + if (le32_to_cpu(dai->size) != sizeof(*dai)) { dev_err(tplg->dev, "ASoC: invalid physical DAI size\n"); return -EINVAL; } soc_tplg_dai_config(tplg, dai); - tplg->pos += (sizeof(*dai) + dai->priv.size); + tplg->pos += (sizeof(*dai) + le32_to_cpu(dai->priv.size)); } dev_dbg(tplg->dev, "ASoC: Configure %d BE DAIs\n", count); @@ -2361,25 +2420,28 @@ static int manifest_new_ver(struct soc_tplg *tplg, { struct snd_soc_tplg_manifest *dest; struct snd_soc_tplg_manifest_v4 *src_v4; + int size; *manifest = NULL; - if (src->size != sizeof(*src_v4)) { + size = le32_to_cpu(src->size); + if (size != sizeof(*src_v4)) { dev_warn(tplg->dev, "ASoC: invalid manifest size %d\n", - src->size); - if (src->size) + size); + if (size) return -EINVAL; - src->size = sizeof(*src_v4); + src->size = cpu_to_le32(sizeof(*src_v4)); } dev_warn(tplg->dev, "ASoC: old version of manifest\n"); src_v4 = (struct snd_soc_tplg_manifest_v4 *)src; - dest = kzalloc(sizeof(*dest) + src_v4->priv.size, GFP_KERNEL); + dest = kzalloc(sizeof(*dest) + le32_to_cpu(src_v4->priv.size), + GFP_KERNEL); if (!dest) return -ENOMEM; - dest->size = sizeof(*dest); /* size of latest abi version */ + dest->size = cpu_to_le32(sizeof(*dest)); /* size of latest abi version */ dest->control_elems = src_v4->control_elems; dest->widget_elems = src_v4->widget_elems; dest->graph_elems = src_v4->graph_elems; @@ -2388,7 +2450,7 @@ static int manifest_new_ver(struct soc_tplg *tplg, dest->priv.size = src_v4->priv.size; if (dest->priv.size) memcpy(dest->priv.data, src_v4->priv.data, - src_v4->priv.size); + le32_to_cpu(src_v4->priv.size)); *manifest = dest; return 0; @@ -2407,7 +2469,7 @@ static int soc_tplg_manifest_load(struct soc_tplg *tplg, manifest = (struct snd_soc_tplg_manifest *)tplg->pos; /* check ABI version by size, create a new manifest if abi not match */ - if (manifest->size == sizeof(*manifest)) { + if (le32_to_cpu(manifest->size) == sizeof(*manifest)) { abi_match = true; _manifest = manifest; } else { @@ -2434,16 +2496,16 @@ static int soc_valid_header(struct soc_tplg *tplg, if (soc_tplg_get_hdr_offset(tplg) >= tplg->fw->size) return 0; - if (hdr->size != sizeof(*hdr)) { + if (le32_to_cpu(hdr->size) != sizeof(*hdr)) { dev_err(tplg->dev, "ASoC: invalid header size for type %d at offset 0x%lx size 0x%zx.\n", - hdr->type, soc_tplg_get_hdr_offset(tplg), + le32_to_cpu(hdr->type), soc_tplg_get_hdr_offset(tplg), tplg->fw->size); return -EINVAL; } /* big endian firmware objects not supported atm */ - if (hdr->magic == cpu_to_be32(SND_SOC_TPLG_MAGIC)) { + if (hdr->magic == SOC_TPLG_MAGIC_BIG_ENDIAN) { dev_err(tplg->dev, "ASoC: pass %d big endian not supported header got %x at offset 0x%lx size 0x%zx.\n", tplg->pass, hdr->magic, @@ -2451,7 +2513,7 @@ static int soc_valid_header(struct soc_tplg *tplg, return -EINVAL; } - if (hdr->magic != SND_SOC_TPLG_MAGIC) { + if (le32_to_cpu(hdr->magic) != SND_SOC_TPLG_MAGIC) { dev_err(tplg->dev, "ASoC: pass %d does not have a valid header got %x at offset 0x%lx size 0x%zx.\n", tplg->pass, hdr->magic, @@ -2460,8 +2522,8 @@ static int soc_valid_header(struct soc_tplg *tplg, } /* Support ABI from version 4 */ - if (hdr->abi > SND_SOC_TPLG_ABI_VERSION - || hdr->abi < SND_SOC_TPLG_ABI_VERSION_MIN) { + if (le32_to_cpu(hdr->abi) > SND_SOC_TPLG_ABI_VERSION || + le32_to_cpu(hdr->abi) < SND_SOC_TPLG_ABI_VERSION_MIN) { dev_err(tplg->dev, "ASoC: pass %d invalid ABI version got 0x%x need 0x%x at offset 0x%lx size 0x%zx.\n", tplg->pass, hdr->abi, @@ -2476,7 +2538,7 @@ static int soc_valid_header(struct soc_tplg *tplg, return -EINVAL; } - if (tplg->pass == hdr->type) + if (tplg->pass == le32_to_cpu(hdr->type)) dev_dbg(tplg->dev, "ASoC: Got 0x%x bytes of type %d version %d vendor %d at pass %d\n", hdr->payload_size, hdr->type, hdr->version, @@ -2492,13 +2554,13 @@ static int soc_tplg_load_header(struct soc_tplg *tplg, tplg->pos = tplg->hdr_pos + sizeof(struct snd_soc_tplg_hdr); /* check for matching ID */ - if (hdr->index != tplg->req_index && + if (le32_to_cpu(hdr->index) != tplg->req_index && tplg->req_index != SND_SOC_TPLG_INDEX_ALL) return 0; - tplg->index = hdr->index; + tplg->index = le32_to_cpu(hdr->index); - switch (hdr->type) { + switch (le32_to_cpu(hdr->type)) { case SND_SOC_TPLG_TYPE_MIXER: case SND_SOC_TPLG_TYPE_ENUM: case SND_SOC_TPLG_TYPE_BYTES: @@ -2554,7 +2616,7 @@ static int soc_tplg_process_headers(struct soc_tplg *tplg) return ret; /* goto next header */ - tplg->hdr_pos += hdr->payload_size + + tplg->hdr_pos += le32_to_cpu(hdr->payload_size) + sizeof(struct snd_soc_tplg_hdr); hdr = (struct snd_soc_tplg_hdr *)tplg->hdr_pos; } diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig new file mode 100644 index 000000000000..b204c65698f9 --- /dev/null +++ b/sound/soc/sof/Kconfig @@ -0,0 +1,156 @@ +config SND_SOC_SOF_TOPLEVEL + bool "Sound Open Firmware Support" + help + This adds support for Sound Open Firmware (SOF). SOF is a free and + generic open source audio DSP firmware for multiple devices. + Say Y if you have such a device that is supported by SOF. + If unsure select "N". + +if SND_SOC_SOF_TOPLEVEL + +config SND_SOC_SOF_PCI + tristate "SOF PCI enumeration support" + depends on PCI + select SND_SOC_SOF + select SND_SOC_ACPI if ACPI + select SND_SOC_SOF_OPTIONS + select SND_SOC_SOF_INTEL_PCI if SND_SOC_SOF_INTEL_TOPLEVEL + help + This adds support for PCI enumeration. This option is + required to enable Intel Skylake+ devices + Say Y if you need this option + If unsure select "N". + +config SND_SOC_SOF_ACPI + tristate "SOF ACPI enumeration support" + depends on ACPI || COMPILE_TEST + select SND_SOC_SOF + select SND_SOC_ACPI if ACPI + select SND_SOC_SOF_OPTIONS + select SND_SOC_SOF_INTEL_ACPI if SND_SOC_SOF_INTEL_TOPLEVEL + select IOSF_MBI if X86 && PCI + help + This adds support for ACPI enumeration. This option is required + to enable Intel Haswell/Broadwell/Baytrail/Cherrytrail devices + Say Y if you need this option + If unsure select "N". + +config SND_SOC_SOF_OPTIONS + tristate + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +if SND_SOC_SOF_OPTIONS + +config SND_SOC_SOF_NOCODEC + tristate "SOF nocodec mode Support" + help + This adds support for a dummy/nocodec machine driver fallback + option if no known codec is detected. This is typically only + enabled for developers or devices where the sound card is + controlled externally + Say Y if you need this nocodec fallback option + If unsure select "N". + +config SND_SOC_SOF_STRICT_ABI_CHECKS + bool "SOF strict ABI checks" + help + This option enables strict ABI checks for firmware and topology + files. + When these files are more recent than the kernel, the kernel + will handle the functionality it supports and may report errors + during topology creation or run-time usage if new functionality + is invoked. + This option will stop topology creation and firmware load upfront. + It is intended for SOF CI/releases and not for users or distros. + Say Y if you want strict ABI checks for an SOF release + If you are not involved in SOF releases and CI development + select "N". + +config SND_SOC_SOF_DEBUG + bool "SOF debugging features" + help + This option can be used to enable or disable individual SOF firmware + and driver debugging options. + Say Y if you are debugging SOF FW or drivers. + If unsure select "N". + +if SND_SOC_SOF_DEBUG + +config SND_SOC_SOF_FORCE_NOCODEC_MODE + bool "SOF force nocodec Mode" + depends on SND_SOC_SOF_NOCODEC + help + This forces SOF to use dummy/nocodec as machine driver, even + though there is a codec detected on the real platform. This is + typically only enabled for developers for debug purposes, before + codec/machine driver is ready, or to exclude the impact of those + drivers + Say Y if you need this force nocodec mode option + If unsure select "N". + +config SND_SOC_SOF_DEBUG_XRUN_STOP + bool "SOF stop on XRUN" + help + This option forces PCMs to stop on any XRUN event. This is useful to + preserve any trace data ond pipeline status prior to the XRUN. + Say Y if you are debugging SOF FW pipeline XRUNs. + If unsure select "N". + +config SND_SOC_SOF_DEBUG_VERBOSE_IPC + bool "SOF verbose IPC logs" + help + This option enables more verbose IPC logs, with command types in + human-readable form instead of just 32-bit hex dumps. This is useful + if you are trying to debug IPC with the DSP firmware. + If unsure select "N". + +config SND_SOC_SOF_DEBUG_FORCE_IPC_POSITION + bool "SOF force to use IPC for position update on SKL+" + help + This option force to handle stream position update IPCs and run pcm + elapse to inform ALSA about that, on platforms (e.g. Intel SKL+) that + with other approach (e.g. HDAC DPIB/posbuf) to elapse PCM. + On platforms (e.g. Intel SKL-) where position update IPC is the only + one choice, this setting won't impact anything. + if you are trying to debug pointer update with position IPCs or where + DPIB/posbuf is not ready, select "Y". + If unsure select "N". + +config SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE + bool "SOF enable debugfs caching" + help + This option enables caching of debugfs + memory -> DSP resource (memory, register, etc) + before the audio DSP is suspended. This will increase the suspend + latency and therefore should be used for debug purposes only. + Say Y if you want to enable caching the memory windows. + If unsure, select "N". + +endif ## SND_SOC_SOF_DEBUG + +endif ## SND_SOC_SOF_OPTIONS + +config SND_SOC_SOF + tristate + select SND_SOC_TOPOLOGY + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + The selection is made at the top level and does not exactly follow + module dependencies but since the module or built-in type is decided + at the top level it doesn't matter. + +config SND_SOC_SOF_PROBE_WORK_QUEUE + bool + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + When selected, the probe is handled in two steps, for example to + avoid lockdeps if request_module is used in the probe. + +source "sound/soc/sof/intel/Kconfig" +source "sound/soc/sof/xtensa/Kconfig" + +endif diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile new file mode 100644 index 000000000000..8f14c9d2950b --- /dev/null +++ b/sound/soc/sof/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) + +snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ + control.o trace.o utils.o + +snd-sof-pci-objs := sof-pci-dev.o +snd-sof-acpi-objs := sof-acpi-dev.o +snd-sof-nocodec-objs := nocodec.o + +obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o +obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o + + +obj-$(CONFIG_SND_SOC_SOF_ACPI) += sof-acpi-dev.o +obj-$(CONFIG_SND_SOC_SOF_PCI) += sof-pci-dev.o + +obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ +obj-$(CONFIG_SND_SOC_SOF_XTENSA) += xtensa/ diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c new file mode 100644 index 000000000000..11762c4580f1 --- /dev/null +++ b/sound/soc/sof/control.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +/* Mixer Controls */ + +#include <linux/pm_runtime.h> +#include "sof-priv.h" + +static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size) +{ + if (value >= size) + return volume_map[size - 1]; + + return volume_map[value]; +} + +static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (volume_map[i] >= value) + return i; + } + + return i - 1; +} + +int snd_sof_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int err, ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: volume get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* get all the mixer data from DSP */ + snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_VOLUME, + false); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = + ipc_to_mixer(cdata->chanv[i].value, + scontrol->volume_table, sm->max + 1); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: volume get failed to idle %d\n", + err); + return 0; +} + +int snd_sof_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: volume put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* update each channel */ + for (i = 0; i < channels; i++) { + cdata->chanv[i].value = + mixer_to_ipc(ucontrol->value.integer.value[i], + scontrol->volume_table, sm->max + 1); + cdata->chanv[i].channel = i; + } + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_VOLUME, + true); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: volume put failed to idle %d\n", + err); + return 0; +} + +int snd_sof_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int err, ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: switch get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* get all the mixer data from DSP */ + snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH, + false); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = cdata->chanv[i].value; + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: switch get failed to idle %d\n", + err); + return 0; +} + +int snd_sof_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *sm = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: switch put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* update each channel */ + for (i = 0; i < channels; i++) { + cdata->chanv[i].value = ucontrol->value.integer.value[i]; + cdata->chanv[i].channel = i; + } + + /* notify DSP of mixer updates */ + snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH, + true); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: switch put failed to idle %d\n", + err); + return 0; +} + +int snd_sof_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *se = + (struct soc_enum *)kcontrol->private_value; + struct snd_sof_control *scontrol = se->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int err, ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: enum get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* get all the enum data from DSP */ + snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_ENUM, + false); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.enumerated.item[i] = cdata->chanv[i].value; + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: enum get failed to idle %d\n", + err); + return 0; +} + +int snd_sof_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *se = + (struct soc_enum *)kcontrol->private_value; + struct snd_sof_control *scontrol = se->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + int ret, err; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: enum put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* update each channel */ + for (i = 0; i < channels; i++) { + cdata->chanv[i].value = ucontrol->value.enumerated.item[i]; + cdata->chanv[i].channel = i; + } + + /* notify DSP of enum updates */ + snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_ENUM, + true); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: enum put failed to idle %d\n", + err); + return 0; +} + +int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct sof_abi_hdr *data = cdata->data; + size_t size; + int ret, err; + + if (be->max > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(sdev->dev, + "error: data max %d exceeds ucontrol data array size\n", + be->max); + return -EINVAL; + } + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: bytes get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* get all the binary data from DSP */ + snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_GET_DATA, + SOF_CTRL_TYPE_DATA_GET, + scontrol->cmd, + false); + + size = data->size + sizeof(*data); + if (size > be->max) { + dev_err_ratelimited(sdev->dev, + "error: DSP sent %zu bytes max is %d\n", + size, be->max); + ret = -EINVAL; + goto out; + } + + /* copy back to kcontrol */ + memcpy(ucontrol->value.bytes.data, data, size); + +out: + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: bytes get failed to idle %d\n", + err); + return ret; +} + +int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct sof_abi_hdr *data = cdata->data; + int ret, err; + + if (be->max > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(sdev->dev, + "error: data max %d exceeds ucontrol data array size\n", + be->max); + return -EINVAL; + } + + if (data->size > be->max) { + dev_err_ratelimited(sdev->dev, + "error: size too big %d bytes max is %d\n", + data->size, be->max); + return -EINVAL; + } + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: bytes put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* copy from kcontrol */ + memcpy(data, ucontrol->value.bytes.data, data->size); + + /* notify DSP of byte control updates */ + snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_DATA, + SOF_CTRL_TYPE_DATA_SET, + scontrol->cmd, + true); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: bytes put failed to idle %d\n", + err); + return ret; +} + +int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct snd_ctl_tlv header; + const struct snd_ctl_tlv __user *tlvd = + (const struct snd_ctl_tlv __user *)binary_data; + int ret; + int err; + + /* + * The beginning of bytes data contains a header from where + * the length (as bytes) is needed to know the correct copy + * length of data from tlvd->tlv. + */ + if (copy_from_user(&header, tlvd, sizeof(const struct snd_ctl_tlv))) + return -EFAULT; + + /* be->max is coming from topology */ + if (header.length > be->max) { + dev_err_ratelimited(sdev->dev, "error: Bytes data size %d exceeds max %d.\n", + header.length, be->max); + return -EINVAL; + } + + /* Check that header id matches the command */ + if (header.numid != scontrol->cmd) { + dev_err_ratelimited(sdev->dev, + "error: incorrect numid %d\n", + header.numid); + return -EINVAL; + } + + if (copy_from_user(cdata->data, tlvd->tlv, header.length)) + return -EFAULT; + + if (cdata->data->magic != SOF_ABI_MAGIC) { + dev_err_ratelimited(sdev->dev, + "error: Wrong ABI magic 0x%08x.\n", + cdata->data->magic); + return -EINVAL; + } + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) { + dev_err_ratelimited(sdev->dev, "error: Incompatible ABI version 0x%08x.\n", + cdata->data->abi); + return -EINVAL; + } + + if (cdata->data->size + sizeof(const struct sof_abi_hdr) > be->max) { + dev_err_ratelimited(sdev->dev, "error: Mismatch in ABI data size (truncated?).\n"); + return -EINVAL; + } + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: bytes_ext put failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* notify DSP of byte control updates */ + snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_DATA, + SOF_CTRL_TYPE_DATA_SET, + scontrol->cmd, + true); + + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: bytes_ext put failed to idle %d\n", + err); + + return ret; +} + +int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, + unsigned int __user *binary_data, + unsigned int size) +{ + struct soc_bytes_ext *be = + (struct soc_bytes_ext *)kcontrol->private_value; + struct snd_sof_control *scontrol = be->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct snd_ctl_tlv header; + struct snd_ctl_tlv __user *tlvd = + (struct snd_ctl_tlv __user *)binary_data; + int data_size; + int err; + int ret; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err_ratelimited(sdev->dev, + "error: bytes_ext get failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* + * Decrement the limit by ext bytes header size to + * ensure the user space buffer is not exceeded. + */ + size -= sizeof(const struct snd_ctl_tlv); + + /* set the ABI header values */ + cdata->data->magic = SOF_ABI_MAGIC; + cdata->data->abi = SOF_ABI_VERSION; + + /* get all the component data from DSP */ + ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_GET_DATA, + SOF_CTRL_TYPE_DATA_GET, + scontrol->cmd, + false); + + /* Prevent read of other kernel data or possibly corrupt response */ + data_size = cdata->data->size + sizeof(const struct sof_abi_hdr); + + /* check data size doesn't exceed max coming from topology */ + if (data_size > be->max) { + dev_err_ratelimited(sdev->dev, "error: user data size %d exceeds max size %d.\n", + data_size, be->max); + ret = -EINVAL; + goto out; + } + + header.numid = scontrol->cmd; + header.length = data_size; + if (copy_to_user(tlvd, &header, sizeof(const struct snd_ctl_tlv))) { + ret = -EFAULT; + goto out; + } + + if (copy_to_user(tlvd->tlv, cdata->data, data_size)) + ret = -EFAULT; + +out: + pm_runtime_mark_last_busy(sdev->dev); + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err_ratelimited(sdev->dev, + "error: bytes_ext get failed to idle %d\n", + err); + return ret; +} diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c new file mode 100644 index 000000000000..32105e0fabe8 --- /dev/null +++ b/sound/soc/sof/core.c @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/module.h> +#include <asm/unaligned.h> +#include <sound/soc.h> +#include <sound/sof.h> +#include "sof-priv.h" +#include "ops.h" + +/* SOF defaults if not provided by the platform in ms */ +#define TIMEOUT_DEFAULT_IPC_MS 5 +#define TIMEOUT_DEFAULT_BOOT_MS 100 + +/* + * Generic object lookup APIs. + */ + +struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, + const char *name) +{ + struct snd_sof_pcm *spcm; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + /* match with PCM dai name */ + if (strcmp(spcm->pcm.dai_name, name) == 0) + return spcm; + + /* match with playback caps name if set */ + if (*spcm->pcm.caps[0].name && + !strcmp(spcm->pcm.caps[0].name, name)) + return spcm; + + /* match with capture caps name if set */ + if (*spcm->pcm.caps[1].name && + !strcmp(spcm->pcm.caps[1].name, name)) + return spcm; + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, + unsigned int comp_id, + int *direction) +{ + struct snd_sof_pcm *spcm; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id == comp_id) { + *direction = SNDRV_PCM_STREAM_PLAYBACK; + return spcm; + } + if (spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id == comp_id) { + *direction = SNDRV_PCM_STREAM_CAPTURE; + return spcm; + } + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, + unsigned int pcm_id) +{ + struct snd_sof_pcm *spcm; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id) + return spcm; + } + + return NULL; +} + +struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, + const char *name) +{ + struct snd_sof_widget *swidget; + + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (strcmp(name, swidget->widget->name) == 0) + return swidget; + } + + return NULL; +} + +/* find widget by stream name and direction */ +struct snd_sof_widget *snd_sof_find_swidget_sname(struct snd_sof_dev *sdev, + const char *pcm_name, int dir) +{ + struct snd_sof_widget *swidget; + enum snd_soc_dapm_type type; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + type = snd_soc_dapm_aif_in; + else + type = snd_soc_dapm_aif_out; + + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (!strcmp(pcm_name, swidget->widget->sname) && swidget->id == type) + return swidget; + } + + return NULL; +} + +struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, + const char *name) +{ + struct snd_sof_dai *dai; + + list_for_each_entry(dai, &sdev->dai_list, list) { + if (dai->name && (strcmp(name, dai->name) == 0)) + return dai; + } + + return NULL; +} + +/* + * FW Panic/fault handling. + */ + +struct sof_panic_msg { + u32 id; + const char *msg; +}; + +/* standard FW panic types */ +static const struct sof_panic_msg panic_msg[] = { + {SOF_IPC_PANIC_MEM, "out of memory"}, + {SOF_IPC_PANIC_WORK, "work subsystem init failed"}, + {SOF_IPC_PANIC_IPC, "IPC subsystem init failed"}, + {SOF_IPC_PANIC_ARCH, "arch init failed"}, + {SOF_IPC_PANIC_PLATFORM, "platform init failed"}, + {SOF_IPC_PANIC_TASK, "scheduler init failed"}, + {SOF_IPC_PANIC_EXCEPTION, "runtime exception"}, + {SOF_IPC_PANIC_DEADLOCK, "deadlock"}, + {SOF_IPC_PANIC_STACK, "stack overflow"}, + {SOF_IPC_PANIC_IDLE, "can't enter idle"}, + {SOF_IPC_PANIC_WFI, "invalid wait state"}, + {SOF_IPC_PANIC_ASSERT, "assertion failed"}, +}; + +/* + * helper to be called from .dbg_dump callbacks. No error code is + * provided, it's left as an exercise for the caller of .dbg_dump + * (typically IPC or loader) + */ +void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, + u32 tracep_code, void *oops, + struct sof_ipc_panic_info *panic_info, + void *stack, size_t stack_words) +{ + u32 code; + int i; + + /* is firmware dead ? */ + if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) { + dev_err(sdev->dev, "error: unexpected fault 0x%8.8x trace 0x%8.8x\n", + panic_code, tracep_code); + return; /* no fault ? */ + } + + code = panic_code & (SOF_IPC_PANIC_MAGIC_MASK | SOF_IPC_PANIC_CODE_MASK); + + for (i = 0; i < ARRAY_SIZE(panic_msg); i++) { + if (panic_msg[i].id == code) { + dev_err(sdev->dev, "error: %s\n", panic_msg[i].msg); + dev_err(sdev->dev, "error: trace point %8.8x\n", + tracep_code); + goto out; + } + } + + /* unknown error */ + dev_err(sdev->dev, "error: unknown reason %8.8x\n", panic_code); + dev_err(sdev->dev, "error: trace point %8.8x\n", tracep_code); + +out: + dev_err(sdev->dev, "error: panic at %s:%d\n", + panic_info->filename, panic_info->linenum); + sof_oops(sdev, oops); + sof_stack(sdev, oops, stack, stack_words); +} +EXPORT_SYMBOL(snd_sof_get_status); + +/* + * Generic buffer page table creation. + * Take the each physical page address and drop the least significant unused + * bits from each (based on PAGE_SIZE). Then pack valid page address bits + * into compressed page table. + */ + +int snd_sof_create_page_table(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size) +{ + int i, pages; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n", + dmab->area, size, pages); + + for (i = 0; i < pages; i++) { + /* + * The number of valid address bits for each page is 20. + * idx determines the byte position within page_table + * where the current page's address is stored + * in the compressed page_table. + * This can be calculated by multiplying the page number by 2.5. + */ + u32 idx = (5 * i) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u8 *pg_table; + + dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u8 *)(page_table + idx); + + /* + * pagetable compression: + * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 + * ___________pfn 0__________ __________pfn 1___________ _pfn 2... + * .... .... .... .... .... .... .... .... .... .... .... + * It is created by: + * 1. set current location to 0, PFN index i to 0 + * 2. put pfn[i] at current location in Little Endian byte order + * 3. calculate an intermediate value as + * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) + * 4. put x at offset (current location + 2) in LE byte order + * 5. increment current location by 5 bytes, increment i by 2 + * 6. continue to (2) + */ + if (i & 1) + put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, + pg_table); + else + put_unaligned_le32(pfn, pg_table); + } + + return pages; +} + +/* + * SOF Driver enumeration. + */ +static int sof_machine_check(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) + struct snd_soc_acpi_mach *machine; + int ret; +#endif + + if (plat_data->machine) + return 0; + +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) + dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); + return -ENODEV; +#else + /* fallback to nocodec mode */ + dev_warn(sdev->dev, "No ASoC machine driver found - using nocodec\n"); + machine = devm_kzalloc(sdev->dev, sizeof(*machine), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + ret = sof_nocodec_setup(sdev->dev, plat_data, machine, + plat_data->desc, plat_data->desc->ops); + if (ret < 0) + return ret; + + plat_data->machine = machine; + + return 0; +#endif +} + +static int sof_probe_continue(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const char *drv_name; + const void *mach; + int size; + int ret; + + /* probe the DSP hardware */ + ret = snd_sof_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret); + return ret; + } + + /* check machine info */ + ret = sof_machine_check(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to get machine info %d\n", + ret); + goto dbg_err; + } + + /* set up platform component driver */ + snd_sof_new_platform_drv(sdev); + + /* register any debug/trace capabilities */ + ret = snd_sof_dbg_init(sdev); + if (ret < 0) { + /* + * debugfs issues are suppressed in snd_sof_dbg_init() since + * we cannot rely on debugfs + * here we trap errors due to memory allocation only. + */ + dev_err(sdev->dev, "error: failed to init DSP trace/debug %d\n", + ret); + goto dbg_err; + } + + /* init the IPC */ + sdev->ipc = snd_sof_ipc_init(sdev); + if (!sdev->ipc) { + dev_err(sdev->dev, "error: failed to init DSP IPC %d\n", ret); + goto ipc_err; + } + + /* load the firmware */ + ret = snd_sof_load_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", + ret); + goto fw_load_err; + } + + /* boot the firmware */ + ret = snd_sof_run_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", + ret); + goto fw_run_err; + } + + /* init DMA trace */ + ret = snd_sof_init_trace(sdev); + if (ret < 0) { + /* non fatal */ + dev_warn(sdev->dev, + "warning: failed to initialize trace %d\n", ret); + } + + /* hereafter all FW boot flows are for PM reasons */ + sdev->first_boot = false; + + /* now register audio DSP platform driver and dai */ + ret = devm_snd_soc_register_component(sdev->dev, &sdev->plat_drv, + sof_ops(sdev)->drv, + sof_ops(sdev)->num_drv); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to register DSP DAI driver %d\n", ret); + goto fw_run_err; + } + + drv_name = plat_data->machine->drv_name; + mach = (const void *)plat_data->machine; + size = sizeof(*plat_data->machine); + + /* register machine driver, pass machine info as pdata */ + plat_data->pdev_mach = + platform_device_register_data(sdev->dev, drv_name, + PLATFORM_DEVID_NONE, mach, size); + + if (IS_ERR(plat_data->pdev_mach)) { + ret = PTR_ERR(plat_data->pdev_mach); + goto comp_err; + } + + dev_dbg(sdev->dev, "created machine %s\n", + dev_name(&plat_data->pdev_mach->dev)); + + if (plat_data->sof_probe_complete) + plat_data->sof_probe_complete(sdev->dev); + + return 0; + +comp_err: + snd_soc_unregister_component(sdev->dev); +fw_run_err: + snd_sof_fw_unload(sdev); +fw_load_err: + snd_sof_ipc_free(sdev); +ipc_err: + snd_sof_free_debug(sdev); +dbg_err: + snd_sof_remove(sdev); + + return ret; +} + +static void sof_probe_work(struct work_struct *work) +{ + struct snd_sof_dev *sdev = + container_of(work, struct snd_sof_dev, probe_work); + int ret; + + ret = sof_probe_continue(sdev); + if (ret < 0) { + /* errors cannot be propagated, log */ + dev_err(sdev->dev, "error: %s failed err: %d\n", __func__, ret); + } +} + +int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) +{ + struct snd_sof_dev *sdev; + + sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return -ENOMEM; + + /* initialize sof device */ + sdev->dev = dev; + + sdev->pdata = plat_data; + sdev->first_boot = true; + dev_set_drvdata(dev, sdev); + + /* check all mandatory ops */ + if (!sof_ops(sdev) || !sof_ops(sdev)->probe || !sof_ops(sdev)->run || + !sof_ops(sdev)->block_read || !sof_ops(sdev)->block_write || + !sof_ops(sdev)->send_msg || !sof_ops(sdev)->load_firmware || + !sof_ops(sdev)->ipc_msg_data || !sof_ops(sdev)->ipc_pcm_params) + return -EINVAL; + + INIT_LIST_HEAD(&sdev->pcm_list); + INIT_LIST_HEAD(&sdev->kcontrol_list); + INIT_LIST_HEAD(&sdev->widget_list); + INIT_LIST_HEAD(&sdev->dai_list); + INIT_LIST_HEAD(&sdev->route_list); + spin_lock_init(&sdev->ipc_lock); + spin_lock_init(&sdev->hw_lock); + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) + INIT_WORK(&sdev->probe_work, sof_probe_work); + + /* set default timeouts if none provided */ + if (plat_data->desc->ipc_timeout == 0) + sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC_MS; + else + sdev->ipc_timeout = plat_data->desc->ipc_timeout; + if (plat_data->desc->boot_timeout == 0) + sdev->boot_timeout = TIMEOUT_DEFAULT_BOOT_MS; + else + sdev->boot_timeout = plat_data->desc->boot_timeout; + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { + schedule_work(&sdev->probe_work); + return 0; + } + + return sof_probe_continue(sdev); +} +EXPORT_SYMBOL(snd_sof_device_probe); + +int snd_sof_device_remove(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_sof_pdata *pdata = sdev->pdata; + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) + cancel_work_sync(&sdev->probe_work); + + snd_sof_fw_unload(sdev); + snd_sof_ipc_free(sdev); + snd_sof_free_debug(sdev); + snd_sof_free_trace(sdev); + snd_sof_remove(sdev); + + /* + * Unregister machine driver. This will unbind the snd_card which + * will remove the component driver and unload the topology + * before freeing the snd_card. + */ + if (!IS_ERR_OR_NULL(pdata->pdev_mach)) + platform_device_unregister(pdata->pdev_mach); + + /* release firmware */ + release_firmware(pdata->fw); + pdata->fw = NULL; + + return 0; +} +EXPORT_SYMBOL(snd_sof_device_remove); + +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:sof-audio"); diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c new file mode 100644 index 000000000000..55f1d808dba0 --- /dev/null +++ b/sound/soc/sof/debug.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// +// Generic debug routines used to export DSP MMIO and memories to userspace +// for firmware debugging. +// + +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include "sof-priv.h" +#include "ops.h" + +static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + loff_t pos = *ppos; + size_t size_ret; + int skip = 0; + int size; + u8 *buf; + + size = dfse->size; + + /* validate position & count */ + if (pos < 0) + return -EINVAL; + if (pos >= size || !count) + return 0; + /* find the minimum. min() is not used since it adds sparse warnings */ + if (count > size - pos) + count = size - pos; + + /* align io read start to u32 multiple */ + pos = ALIGN_DOWN(pos, 4); + + /* intermediate buffer size must be u32 multiple */ + size = ALIGN(count, 4); + + /* if start position is unaligned, read extra u32 */ + if (unlikely(pos != *ppos)) { + skip = *ppos - pos; + if (pos + size + 4 < dfse->size) + size += 4; + } + + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (dfse->type == SOF_DFSENTRY_TYPE_IOMEM) { +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) + /* + * If the DSP is active: copy from IO. + * If the DSP is suspended: + * - Copy from IO if the memory is always accessible. + * - Otherwise, copy from cached buffer. + */ + if (pm_runtime_active(sdev->dev) || + dfse->access_type == SOF_DEBUGFS_ACCESS_ALWAYS) { + memcpy_fromio(buf, dfse->io_mem + pos, size); + } else { + dev_info(sdev->dev, + "Copying cached debugfs data\n"); + memcpy(buf, dfse->cache_buf + pos, size); + } +#else + /* if the DSP is in D3 */ + if (!pm_runtime_active(sdev->dev) && + dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) { + dev_err(sdev->dev, + "error: debugfs entry %s cannot be read in DSP D3\n", + dfse->dfsentry->d_name.name); + kfree(buf); + return -EINVAL; + } + + memcpy_fromio(buf, dfse->io_mem + pos, size); +#endif + } else { + memcpy(buf, ((u8 *)(dfse->buf) + pos), size); + } + + /* copy to userspace */ + size_ret = copy_to_user(buffer, buf + skip, count); + + kfree(buf); + + /* update count & position if copy succeeded */ + if (size_ret) + return -EFAULT; + + *ppos = pos + count; + + return count; +} + +static const struct file_operations sof_dfs_fops = { + .open = simple_open, + .read = sof_dfsentry_read, + .llseek = default_llseek, +}; + +/* create FS entry for debug files that can expose DSP memories, registers */ +int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, + void __iomem *base, size_t size, + const char *name, + enum sof_debugfs_access_type access_type) +{ + struct snd_sof_dfsentry *dfse; + + if (!sdev) + return -EINVAL; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_IOMEM; + dfse->io_mem = base; + dfse->size = size; + dfse->sdev = sdev; + dfse->access_type = access_type; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) + /* + * allocate cache buffer that will be used to save the mem window + * contents prior to suspend + */ + if (access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) { + dfse->cache_buf = devm_kzalloc(sdev->dev, size, GFP_KERNEL); + if (!dfse->cache_buf) + return -ENOMEM; + } +#endif + + dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root, + dfse, &sof_dfs_fops); + if (!dfse->dfsentry) { + /* can't rely on debugfs, only log error and keep going */ + dev_err(sdev->dev, "error: cannot create debugfs entry %s\n", + name); + } else { + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); + + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_sof_debugfs_io_item); + +/* create FS entry for debug files to expose kernel memory */ +int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, + void *base, size_t size, + const char *name) +{ + struct snd_sof_dfsentry *dfse; + + if (!sdev) + return -EINVAL; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->buf = base; + dfse->size = size; + dfse->sdev = sdev; + + dfse->dfsentry = debugfs_create_file(name, 0444, sdev->debugfs_root, + dfse, &sof_dfs_fops); + if (!dfse->dfsentry) { + /* can't rely on debugfs, only log error and keep going */ + dev_err(sdev->dev, "error: cannot create debugfs entry %s\n", + name); + } else { + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item); + +int snd_sof_dbg_init(struct snd_sof_dev *sdev) +{ + const struct snd_sof_dsp_ops *ops = sof_ops(sdev); + const struct snd_sof_debugfs_map *map; + int i; + int err; + + /* use "sof" as top level debugFS dir */ + sdev->debugfs_root = debugfs_create_dir("sof", NULL); + if (IS_ERR_OR_NULL(sdev->debugfs_root)) { + dev_err(sdev->dev, "error: failed to create debugfs directory\n"); + return 0; + } + + /* init dfsentry list */ + INIT_LIST_HEAD(&sdev->dfsentry_list); + + /* create debugFS files for platform specific MMIO/DSP memories */ + for (i = 0; i < ops->debug_map_count; i++) { + map = &ops->debug_map[i]; + + err = snd_sof_debugfs_io_item(sdev, sdev->bar[map->bar] + + map->offset, map->size, + map->name, map->access_type); + /* errors are only due to memory allocation, not debugfs */ + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_sof_dbg_init); + +void snd_sof_free_debug(struct snd_sof_dev *sdev) +{ + debugfs_remove_recursive(sdev->debugfs_root); +} +EXPORT_SYMBOL_GPL(snd_sof_free_debug); diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig new file mode 100644 index 000000000000..603e0db4f012 --- /dev/null +++ b/sound/soc/sof/intel/Kconfig @@ -0,0 +1,230 @@ +config SND_SOC_SOF_INTEL_TOPLEVEL + bool "SOF support for Intel audio DSPs" + depends on X86 || COMPILE_TEST + help + This adds support for Sound Open Firmware for Intel(R) platforms. + Say Y if you have such a device. + If unsure select "N". + +if SND_SOC_SOF_INTEL_TOPLEVEL + +config SND_SOC_SOF_INTEL_ACPI + tristate + select SND_SOC_SOF_BAYTRAIL if SND_SOC_SOF_BAYTRAIL_SUPPORT + select SND_SOC_SOF_BROADWELL if SND_SOC_SOF_BROADWELL_SUPPORT + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_PCI + tristate + select SND_SOC_SOF_MERRIFIELD if SND_SOC_SOF_MERRIFIELD_SUPPORT + select SND_SOC_SOF_APOLLOLAKE if SND_SOC_SOF_APOLLOLAKE_SUPPORT + select SND_SOC_SOF_GEMINILAKE if SND_SOC_SOF_GEMINILAKE_SUPPORT + select SND_SOC_SOF_CANNONLAKE if SND_SOC_SOF_CANNONLAKE_SUPPORT + select SND_SOC_SOF_COFFEELAKE if SND_SOC_SOF_COFFEELAKE_SUPPORT + select SND_SOC_SOF_ICELAKE if SND_SOC_SOF_ICELAKE_SUPPORT + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_HIFI_EP_IPC + tristate + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_ATOM_HIFI_EP + tristate + select SND_SOC_SOF_INTEL_COMMON + select SND_SOC_SOF_INTEL_HIFI_EP_IPC + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_COMMON + tristate + select SND_SOC_ACPI_INTEL_MATCH + select SND_SOC_SOF_XTENSA + select SND_SOC_INTEL_MACH + select SND_SOC_ACPI if ACPI + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +if SND_SOC_SOF_INTEL_ACPI + +config SND_SOC_SOF_BAYTRAIL_SUPPORT + bool "SOF support for Baytrail, Braswell and Cherrytrail" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Baytrail, Braswell or Cherrytrail processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_BAYTRAIL + tristate + select SND_SOC_SOF_INTEL_ATOM_HIFI_EP + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_BROADWELL_SUPPORT + bool "SOF support for Broadwell" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Broadwell processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_BROADWELL + tristate + select SND_SOC_SOF_INTEL_COMMON + select SND_SOC_SOF_INTEL_HIFI_EP_IPC + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +endif ## SND_SOC_SOF_INTEL_ACPI + +if SND_SOC_SOF_INTEL_PCI + +config SND_SOC_SOF_MERRIFIELD_SUPPORT + bool "SOF support for Tangier/Merrifield" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Tangier/Merrifield processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_MERRIFIELD + tristate + select SND_SOC_SOF_INTEL_ATOM_HIFI_EP + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_APOLLOLAKE_SUPPORT + bool "SOF support for Apollolake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Apollolake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_APOLLOLAKE + tristate + select SND_SOC_SOF_HDA_COMMON + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_GEMINILAKE_SUPPORT + bool "SOF support for GeminiLake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Geminilake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_GEMINILAKE + tristate + select SND_SOC_SOF_HDA_COMMON + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_CANNONLAKE_SUPPORT + bool "SOF support for Cannonlake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Cannonlake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_CANNONLAKE + tristate + select SND_SOC_SOF_HDA_COMMON + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_COFFEELAKE_SUPPORT + bool "SOF support for CoffeeLake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Coffeelake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_COFFEELAKE + tristate + select SND_SOC_SOF_HDA_COMMON + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_ICELAKE_SUPPORT + bool "SOF support for Icelake" + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Icelake processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_ICELAKE + tristate + select SND_SOC_SOF_HDA_COMMON + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_HDA_COMMON + tristate + select SND_SOC_SOF_INTEL_COMMON + select SND_SOC_SOF_HDA_LINK_BASELINE + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +if SND_SOC_SOF_HDA_COMMON + +config SND_SOC_SOF_HDA_LINK + bool "SOF support for HDA Links(HDA/HDMI)" + depends on SND_SOC_SOF_NOCODEC=n + select SND_SOC_SOF_PROBE_WORK_QUEUE + help + This adds support for HDA links(HDA/HDMI) with Sound Open Firmware + for Intel(R) platforms. + Say Y if you want to enable HDA links with SOF. + If unsure select "N". + +config SND_SOC_SOF_HDA_AUDIO_CODEC + bool "SOF support for HDAudio codecs" + depends on SND_SOC_SOF_HDA_LINK + help + This adds support for HDAudio codecs with Sound Open Firmware + for Intel(R) platforms. + Say Y if you want to enable HDAudio codecs with SOF. + If unsure select "N". + +endif ## SND_SOC_SOF_HDA_COMMON + +config SND_SOC_SOF_HDA_LINK_BASELINE + tristate + select SND_SOC_SOF_HDA if SND_SOC_SOF_HDA_LINK + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_HDA + tristate + select SND_HDA_EXT_CORE if SND_SOC_SOF_HDA_LINK + select SND_SOC_HDAC_HDA if SND_SOC_SOF_HDA_AUDIO_CODEC + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +endif ## SND_SOC_SOF_INTEL_PCI + +endif ## SND_SOC_SOF_INTEL_TOPLEVEL diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile new file mode 100644 index 000000000000..b8f58e006e29 --- /dev/null +++ b/sound/soc/sof/intel/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) + +snd-sof-intel-byt-objs := byt.o +snd-sof-intel-bdw-objs := bdw.o + +snd-sof-intel-ipc-objs := intel-ipc.o + +snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \ + hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \ + hda-dai.o hda-bus.o \ + apl.o cnl.o + +snd-sof-intel-hda-objs := hda-codec.o + +obj-$(CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP) += snd-sof-intel-byt.o +obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-intel-bdw.o +obj-$(CONFIG_SND_SOC_SOF_INTEL_HIFI_EP_IPC) += snd-sof-intel-ipc.o +obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o +obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c new file mode 100644 index 000000000000..f215d80dce2c --- /dev/null +++ b/sound/soc/sof/intel/apl.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Apollolake and GeminiLake + */ + +#include "../sof-priv.h" +#include "hda.h" + +static const struct snd_sof_debugfs_map apl_dsp_debugfs[] = { + {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +/* apollolake ops */ +const struct snd_sof_dsp_ops sof_apl_ops = { + /* probe and remove */ + .probe = hda_dsp_probe, + .remove = hda_dsp_remove, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_handler = hda_dsp_ipc_irq_handler, + .irq_thread = hda_dsp_ipc_irq_thread, + + /* ipc */ + .send_msg = hda_dsp_ipc_send_msg, + .fw_ready = hda_dsp_ipc_fw_ready, + + .ipc_msg_data = hda_ipc_msg_data, + .ipc_pcm_params = hda_ipc_pcm_params, + + /* debug */ + .debug_map = apl_dsp_debugfs, + .debug_map_count = ARRAY_SIZE(apl_dsp_debugfs), + .dbg_dump = hda_dsp_dump, + .ipc_dump = hda_ipc_dump, + + /* stream callbacks */ + .pcm_open = hda_dsp_pcm_open, + .pcm_close = hda_dsp_pcm_close, + .pcm_hw_params = hda_dsp_pcm_hw_params, + .pcm_trigger = hda_dsp_pcm_trigger, + .pcm_pointer = hda_dsp_pcm_pointer, + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_raw, + + /* firmware run */ + .run = hda_dsp_cl_boot_firmware, + + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + .post_fw_run = hda_dsp_post_fw_run, + + /* dsp core power up/down */ + .core_power_up = hda_dsp_enable_core, + .core_power_down = hda_dsp_core_reset_power_down, + + /* trace callback */ + .trace_init = hda_dsp_trace_init, + .trace_release = hda_dsp_trace_release, + .trace_trigger = hda_dsp_trace_trigger, + + /* DAI drivers */ + .drv = skl_dai, + .num_drv = SOF_SKL_NUM_DAIS, + + /* PM */ + .suspend = hda_dsp_suspend, + .resume = hda_dsp_resume, + .runtime_suspend = hda_dsp_runtime_suspend, + .runtime_resume = hda_dsp_runtime_resume, + .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, +}; +EXPORT_SYMBOL(sof_apl_ops); + +const struct sof_intel_dsp_desc apl_chip_info = { + /* Apollolake */ + .cores_num = 2, + .init_core_mask = 1, + .cores_mask = HDA_DSP_CORE_MASK(0) | HDA_DSP_CORE_MASK(1), + .ipc_req = HDA_DSP_REG_HIPCI, + .ipc_req_mask = HDA_DSP_REG_HIPCI_BUSY, + .ipc_ack = HDA_DSP_REG_HIPCIE, + .ipc_ack_mask = HDA_DSP_REG_HIPCIE_DONE, + .ipc_ctl = HDA_DSP_REG_HIPCCTL, + .rom_init_timeout = 150, + .ssp_count = APL_SSP_COUNT, + .ssp_base_offset = APL_SSP_BASE_OFFSET, +}; +EXPORT_SYMBOL(apl_chip_info); diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c new file mode 100644 index 000000000000..065cb868bdfa --- /dev/null +++ b/sound/soc/sof/intel/bdw.c @@ -0,0 +1,713 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Broadwell + */ + +#include <linux/module.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../ops.h" +#include "shim.h" + +/* BARs */ +#define BDW_DSP_BAR 0 +#define BDW_PCI_BAR 1 + +/* + * Debug + */ + +/* DSP memories for BDW */ +#define IRAM_OFFSET 0xA0000 +#define BDW_IRAM_SIZE (10 * 32 * 1024) +#define DRAM_OFFSET 0x00000 +#define BDW_DRAM_SIZE (20 * 32 * 1024) +#define SHIM_OFFSET 0xFB000 +#define SHIM_SIZE 0x100 +#define MBOX_OFFSET 0x9E000 +#define MBOX_SIZE 0x1000 +#define MBOX_DUMP_SIZE 0x30 +#define EXCEPT_OFFSET 0x800 + +/* DSP peripherals */ +#define DMAC0_OFFSET 0xFE000 +#define DMAC1_OFFSET 0xFF000 +#define DMAC_SIZE 0x420 +#define SSP0_OFFSET 0xFC000 +#define SSP1_OFFSET 0xFD000 +#define SSP_SIZE 0x100 + +#define BDW_STACK_DUMP_SIZE 32 + +#define BDW_PANIC_OFFSET(x) ((x) & 0xFFFF) + +static const struct snd_sof_debugfs_map bdw_debugfs[] = { + {"dmac0", BDW_DSP_BAR, DMAC0_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dmac1", BDW_DSP_BAR, DMAC1_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp0", BDW_DSP_BAR, SSP0_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp1", BDW_DSP_BAR, SSP1_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"iram", BDW_DSP_BAR, IRAM_OFFSET, BDW_IRAM_SIZE, + SOF_DEBUGFS_ACCESS_D0_ONLY}, + {"dram", BDW_DSP_BAR, DRAM_OFFSET, BDW_DRAM_SIZE, + SOF_DEBUGFS_ACCESS_D0_ONLY}, + {"shim", BDW_DSP_BAR, SHIM_OFFSET, SHIM_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +static void bdw_host_done(struct snd_sof_dev *sdev); +static void bdw_dsp_done(struct snd_sof_dev *sdev); +static void bdw_get_reply(struct snd_sof_dev *sdev); + +/* + * DSP Control. + */ + +static int bdw_run(struct snd_sof_dev *sdev) +{ + /* set opportunistic mode on engine 0,1 for all channels */ + snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_HMDC, + SHIM_HMDC_HDDA_E0_ALLCH | + SHIM_HMDC_HDDA_E1_ALLCH, 0); + + /* set DSP to RUN */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR, + SHIM_CSR_STALL, 0x0); + + /* return init core mask */ + return 1; +} + +static int bdw_reset(struct snd_sof_dev *sdev) +{ + /* put DSP into reset and stall */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR, + SHIM_CSR_RST | SHIM_CSR_STALL, + SHIM_CSR_RST | SHIM_CSR_STALL); + + /* keep in reset for 10ms */ + mdelay(10); + + /* take DSP out of reset and keep stalled for FW loading */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR, + SHIM_CSR_RST | SHIM_CSR_STALL, + SHIM_CSR_STALL); + + return 0; +} + +static int bdw_set_dsp_D0(struct snd_sof_dev *sdev) +{ + int tries = 10; + u32 reg; + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL2, + PCI_VDRTCL2_DCLCGE | + PCI_VDRTCL2_DTCGE, 0); + + /* Disable D3PG (VDRTCTL0.D3PGD = 1) */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL0, + PCI_VDRTCL0_D3PGD, PCI_VDRTCL0_D3PGD); + + /* Set D0 state */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_PMCS, + PCI_PMCS_PS_MASK, 0); + + /* check that ADSP shim is enabled */ + while (tries--) { + reg = readl(sdev->bar[BDW_PCI_BAR] + PCI_PMCS) + & PCI_PMCS_PS_MASK; + if (reg == 0) + goto finish; + + msleep(20); + } + + return -ENODEV; + +finish: + /* + * select SSP1 19.2MHz base clock, SSP clock 0, + * turn off Low Power Clock + */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR, + SHIM_CSR_S1IOCS | SHIM_CSR_SBCS1 | + SHIM_CSR_LPCS, 0x0); + + /* stall DSP core, set clk to 192/96Mhz */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, + SHIM_CSR, SHIM_CSR_STALL | + SHIM_CSR_DCS_MASK, + SHIM_CSR_STALL | + SHIM_CSR_DCS(4)); + + /* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CLKCTL, + SHIM_CLKCTL_MASK | + SHIM_CLKCTL_DCPLCG | + SHIM_CLKCTL_SCOE0, + SHIM_CLKCTL_MASK | + SHIM_CLKCTL_DCPLCG | + SHIM_CLKCTL_SCOE0); + + /* Stall and reset core, set CSR */ + bdw_reset(sdev); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL2, + PCI_VDRTCL2_DCLCGE | + PCI_VDRTCL2_DTCGE, + PCI_VDRTCL2_DCLCGE | + PCI_VDRTCL2_DTCGE); + + usleep_range(50, 55); + + /* switch on audio PLL */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL2, + PCI_VDRTCL2_APLLSE_MASK, 0); + + /* + * set default power gating control, enable power gating control for + * all blocks. that is, can't be accessed, please enable each block + * before accessing. + */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL0, + 0xfffffffC, 0x0); + + /* disable DMA finish function for SSP0 & SSP1 */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR2, + SHIM_CSR2_SDFD_SSP1, + SHIM_CSR2_SDFD_SSP1); + + /* set on-demond mode on engine 0,1 for all channels */ + snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_HMDC, + SHIM_HMDC_HDDA_E0_ALLCH | + SHIM_HMDC_HDDA_E1_ALLCH, + SHIM_HMDC_HDDA_E0_ALLCH | + SHIM_HMDC_HDDA_E1_ALLCH); + + /* Enable Interrupt from both sides */ + snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_IMRX, + (SHIM_IMRX_BUSY | SHIM_IMRX_DONE), 0x0); + snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_IMRD, + (SHIM_IMRD_DONE | SHIM_IMRD_BUSY | + SHIM_IMRD_SSP0 | SHIM_IMRD_DMAC), 0x0); + + /* clear IPC registers */ + snd_sof_dsp_write(sdev, BDW_DSP_BAR, SHIM_IPCX, 0x0); + snd_sof_dsp_write(sdev, BDW_DSP_BAR, SHIM_IPCD, 0x0); + snd_sof_dsp_write(sdev, BDW_DSP_BAR, 0x80, 0x6); + snd_sof_dsp_write(sdev, BDW_DSP_BAR, 0xe0, 0x300a); + + return 0; +} + +static void bdw_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + /* first read regsisters */ + sof_mailbox_read(sdev, sdev->dsp_oops_offset, xoops, sizeof(*xoops)); + + /* then get panic info */ + sof_mailbox_read(sdev, sdev->dsp_oops_offset + sizeof(*xoops), + panic_info, sizeof(*panic_info)); + + /* then get the stack */ + sof_mailbox_read(sdev, sdev->dsp_oops_offset + sizeof(*xoops) + + sizeof(*panic_info), stack, + stack_words * sizeof(u32)); +} + +static void bdw_dump(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[BDW_STACK_DUMP_SIZE]; + u32 status, panic; + + /* now try generic SOF status messages */ + status = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCD); + panic = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCX); + bdw_get_registers(sdev, &xoops, &panic_info, stack, + BDW_STACK_DUMP_SIZE); + snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, stack, + BDW_STACK_DUMP_SIZE); +} + +/* + * IPC Doorbell IRQ handler and thread. + */ + +static irqreturn_t bdw_irq_handler(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u32 isr; + int ret = IRQ_NONE; + + /* Interrupt arrived, check src */ + isr = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_ISRX); + if (isr & (SHIM_ISRX_DONE | SHIM_ISRX_BUSY)) + ret = IRQ_WAKE_THREAD; + + return ret; +} + +static irqreturn_t bdw_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u32 ipcx, ipcd, imrx; + + imrx = snd_sof_dsp_read64(sdev, BDW_DSP_BAR, SHIM_IMRX); + ipcx = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCX); + + /* reply message from DSP */ + if (ipcx & SHIM_IPCX_DONE && + !(imrx & SHIM_IMRX_DONE)) { + /* Mask Done interrupt before return */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, + SHIM_IMRX, SHIM_IMRX_DONE, + SHIM_IMRX_DONE); + + /* + * handle immediate reply from DSP core. If the msg is + * found, set done bit in cmd_done which is called at the + * end of message processing function, else set it here + * because the done bit can't be set in cmd_done function + * which is triggered by msg + */ + bdw_get_reply(sdev); + snd_sof_ipc_reply(sdev, ipcx); + + bdw_dsp_done(sdev); + } + + ipcd = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCD); + + /* new message from DSP */ + if (ipcd & SHIM_IPCD_BUSY && + !(imrx & SHIM_IMRX_BUSY)) { + /* Mask Busy interrupt before return */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, + SHIM_IMRX, SHIM_IMRX_BUSY, + SHIM_IMRX_BUSY); + + /* Handle messages from DSP Core */ + if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(sdev, BDW_PANIC_OFFSET(ipcx) + + MBOX_OFFSET); + } else { + snd_sof_ipc_msgs_rx(sdev); + } + + bdw_host_done(sdev); + } + + return IRQ_HANDLED; +} + +/* + * IPC Firmware ready. + */ +static void bdw_get_windows(struct snd_sof_dev *sdev) +{ + struct sof_ipc_window_elem *elem; + u32 outbox_offset = 0; + u32 stream_offset = 0; + u32 inbox_offset = 0; + u32 outbox_size = 0; + u32 stream_size = 0; + u32 inbox_size = 0; + int i; + + if (!sdev->info_window) { + dev_err(sdev->dev, "error: have no window info\n"); + return; + } + + for (i = 0; i < sdev->info_window->num_windows; i++) { + elem = &sdev->info_window->window[i]; + + switch (elem->type) { + case SOF_IPC_REGION_UPBOX: + inbox_offset = elem->offset + MBOX_OFFSET; + inbox_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[BDW_DSP_BAR] + + inbox_offset, + elem->size, "inbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DOWNBOX: + outbox_offset = elem->offset + MBOX_OFFSET; + outbox_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[BDW_DSP_BAR] + + outbox_offset, + elem->size, "outbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_TRACE: + snd_sof_debugfs_io_item(sdev, + sdev->bar[BDW_DSP_BAR] + + elem->offset + + MBOX_OFFSET, + elem->size, "etrace", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DEBUG: + snd_sof_debugfs_io_item(sdev, + sdev->bar[BDW_DSP_BAR] + + elem->offset + + MBOX_OFFSET, + elem->size, "debug", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_STREAM: + stream_offset = elem->offset + MBOX_OFFSET; + stream_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[BDW_DSP_BAR] + + stream_offset, + elem->size, "stream", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_REGS: + snd_sof_debugfs_io_item(sdev, + sdev->bar[BDW_DSP_BAR] + + elem->offset + + MBOX_OFFSET, + elem->size, "regs", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_EXCEPTION: + sdev->dsp_oops_offset = elem->offset + MBOX_OFFSET; + snd_sof_debugfs_io_item(sdev, + sdev->bar[BDW_DSP_BAR] + + elem->offset + + MBOX_OFFSET, + elem->size, "exception", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + default: + dev_err(sdev->dev, "error: get illegal window info\n"); + return; + } + } + + if (outbox_size == 0 || inbox_size == 0) { + dev_err(sdev->dev, "error: get illegal mailbox window\n"); + return; + } + + snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size, + outbox_offset, outbox_size); + sdev->stream_box.offset = stream_offset; + sdev->stream_box.size = stream_size; + + dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", + inbox_offset, inbox_size); + dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", + outbox_offset, outbox_size); + dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", + stream_offset, stream_size); +} + +/* check for ABI compatibility and create memory windows on first boot */ +static int bdw_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; + u32 offset; + int ret; + + /* mailbox must be on 4k boundary */ + offset = MBOX_OFFSET; + + dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset %d\n", + msg_id, offset); + + /* no need to re-check version/ABI for subsequent boots */ + if (!sdev->first_boot) + return 0; + + /* copy data from the DSP FW ready offset */ + sof_block_read(sdev, sdev->mmio_bar, offset, fw_ready, + sizeof(*fw_ready)); + + snd_sof_dsp_mailbox_init(sdev, fw_ready->dspbox_offset, + fw_ready->dspbox_size, + fw_ready->hostbox_offset, + fw_ready->hostbox_size); + + /* make sure ABI version is compatible */ + ret = snd_sof_ipc_valid(sdev); + if (ret < 0) + return ret; + + /* now check for extended data */ + snd_sof_fw_parse_ext_data(sdev, sdev->mmio_bar, MBOX_OFFSET + + sizeof(struct sof_ipc_fw_ready)); + + bdw_get_windows(sdev); + + return 0; +} + +/* + * IPC Mailbox IO + */ + +static int bdw_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + /* send the message */ + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + snd_sof_dsp_write(sdev, BDW_DSP_BAR, SHIM_IPCX, SHIM_IPCX_BUSY); + + return 0; +} + +static void bdw_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + unsigned long flags; + int ret = 0; + + /* + * Sometimes, there is unexpected reply ipc arriving. The reply + * ipc belongs to none of the ipcs sent from driver. + * In this case, the driver must ignore the ipc. + */ + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); + return; + } + + /* get reply */ + sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); + + spin_lock_irqsave(&sdev->ipc_lock, flags); + + if (reply.error < 0) { + memcpy(msg->reply_data, &reply, sizeof(reply)); + ret = reply.error; + } else { + /* reply correct size ? */ + if (reply.hdr.size != msg->reply_size) { + dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", + msg->reply_size, reply.hdr.size); + ret = -EINVAL; + } + + /* read the message */ + if (msg->reply_size > 0) + sof_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + msg->reply_error = ret; + + spin_unlock_irqrestore(&sdev->ipc_lock, flags); +} + +static void bdw_host_done(struct snd_sof_dev *sdev) +{ + /* clear BUSY bit and set DONE bit - accept new messages */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IPCD, + SHIM_IPCD_BUSY | SHIM_IPCD_DONE, + SHIM_IPCD_DONE); + + /* unmask busy interrupt */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_BUSY, 0); +} + +static void bdw_dsp_done(struct snd_sof_dev *sdev) +{ + /* clear DONE bit - tell DSP we have completed */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IPCX, + SHIM_IPCX_DONE, 0); + + /* unmask Done interrupt */ + snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_DONE, 0); +} + +/* + * Probe and remove. + */ +static int bdw_probe(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *pdata = sdev->pdata; + const struct sof_dev_desc *desc = pdata->desc; + struct platform_device *pdev = + container_of(sdev->dev, struct platform_device, dev); + struct resource *mmio; + u32 base, size; + int ret; + + /* LPE base */ + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_lpe_base); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get LPE base at idx %d\n", + desc->resindex_lpe_base); + return -EINVAL; + } + + dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size); + sdev->bar[BDW_DSP_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BDW_DSP_BAR]) { + dev_err(sdev->dev, + "error: failed to ioremap LPE base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[BDW_DSP_BAR]); + + /* TODO: add offsets */ + sdev->mmio_bar = BDW_DSP_BAR; + sdev->mailbox_bar = BDW_DSP_BAR; + + /* PCI base */ + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_pcicfg_base); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get PCI base at idx %d\n", + desc->resindex_pcicfg_base); + return -ENODEV; + } + + dev_dbg(sdev->dev, "PCI base at 0x%x size 0x%x", base, size); + sdev->bar[BDW_PCI_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BDW_PCI_BAR]) { + dev_err(sdev->dev, + "error: failed to ioremap PCI base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "PCI VADDR %p\n", sdev->bar[BDW_PCI_BAR]); + + /* register our IRQ */ + sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc); + if (sdev->ipc_irq < 0) { + dev_err(sdev->dev, "error: failed to get IRQ at index %d\n", + desc->irqindex_host_ipc); + return sdev->ipc_irq; + } + + dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq); + ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq, + bdw_irq_handler, bdw_irq_thread, + IRQF_SHARED, "AudioDSP", sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to register IRQ %d\n", + sdev->ipc_irq); + return ret; + } + + /* enable the DSP SHIM */ + ret = bdw_set_dsp_D0(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DSP D0\n"); + return ret; + } + + /* DSP DMA can only access low 31 bits of host memory */ + ret = dma_coerce_mask_and_coherent(sdev->dev, DMA_BIT_MASK(31)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret); + return ret; + } + + /* set default mailbox */ + snd_sof_dsp_mailbox_init(sdev, MBOX_OFFSET, MBOX_SIZE, 0, 0); + + return ret; +} + +/* Broadwell DAIs */ +static struct snd_soc_dai_driver bdw_dai[] = { +{ + .name = "ssp0-port", +}, +{ + .name = "ssp1-port", +}, +}; + +/* broadwell ops */ +const struct snd_sof_dsp_ops sof_bdw_ops = { + /*Device init */ + .probe = bdw_probe, + + /* DSP Core Control */ + .run = bdw_run, + .reset = bdw_reset, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* ipc */ + .send_msg = bdw_send_msg, + .fw_ready = bdw_fw_ready, + + .ipc_msg_data = intel_ipc_msg_data, + .ipc_pcm_params = intel_ipc_pcm_params, + + /* debug */ + .debug_map = bdw_debugfs, + .debug_map_count = ARRAY_SIZE(bdw_debugfs), + .dbg_dump = bdw_dump, + + /* stream callbacks */ + .pcm_open = intel_pcm_open, + .pcm_close = intel_pcm_close, + + /* Module loading */ + .load_module = snd_sof_parse_module_memcpy, + + /*Firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* DAI drivers */ + .drv = bdw_dai, + .num_drv = ARRAY_SIZE(bdw_dai) +}; +EXPORT_SYMBOL(sof_bdw_ops); + +const struct sof_intel_dsp_desc bdw_chip_info = { + .cores_num = 1, + .cores_mask = 1, +}; +EXPORT_SYMBOL(bdw_chip_info); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c new file mode 100644 index 000000000000..7bf9143d3106 --- /dev/null +++ b/sound/soc/sof/intel/byt.c @@ -0,0 +1,874 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Baytrail, Braswell and Cherrytrail. + */ + +#include <linux/module.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../ops.h" +#include "shim.h" + +/* DSP memories */ +#define IRAM_OFFSET 0x0C0000 +#define IRAM_SIZE (80 * 1024) +#define DRAM_OFFSET 0x100000 +#define DRAM_SIZE (160 * 1024) +#define SHIM_OFFSET 0x140000 +#define SHIM_SIZE 0x100 +#define MBOX_OFFSET 0x144000 +#define MBOX_SIZE 0x1000 +#define EXCEPT_OFFSET 0x800 + +/* DSP peripherals */ +#define DMAC0_OFFSET 0x098000 +#define DMAC1_OFFSET 0x09c000 +#define DMAC2_OFFSET 0x094000 +#define DMAC_SIZE 0x420 +#define SSP0_OFFSET 0x0a0000 +#define SSP1_OFFSET 0x0a1000 +#define SSP2_OFFSET 0x0a2000 +#define SSP3_OFFSET 0x0a4000 +#define SSP4_OFFSET 0x0a5000 +#define SSP5_OFFSET 0x0a6000 +#define SSP_SIZE 0x100 + +#define BYT_STACK_DUMP_SIZE 32 + +#define BYT_PCI_BAR_SIZE 0x200000 + +#define BYT_PANIC_OFFSET(x) (((x) & GENMASK_ULL(47, 32)) >> 32) + +/* + * Debug + */ + +#define MBOX_DUMP_SIZE 0x30 + +/* BARs */ +#define BYT_DSP_BAR 0 +#define BYT_PCI_BAR 1 +#define BYT_IMR_BAR 2 + +static const struct snd_sof_debugfs_map byt_debugfs[] = { + {"dmac0", BYT_DSP_BAR, DMAC0_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dmac1", BYT_DSP_BAR, DMAC1_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp0", BYT_DSP_BAR, SSP0_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp1", BYT_DSP_BAR, SSP1_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp2", BYT_DSP_BAR, SSP2_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"iram", BYT_DSP_BAR, IRAM_OFFSET, IRAM_SIZE, + SOF_DEBUGFS_ACCESS_D0_ONLY}, + {"dram", BYT_DSP_BAR, DRAM_OFFSET, DRAM_SIZE, + SOF_DEBUGFS_ACCESS_D0_ONLY}, + {"shim", BYT_DSP_BAR, SHIM_OFFSET, SHIM_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +static const struct snd_sof_debugfs_map cht_debugfs[] = { + {"dmac0", BYT_DSP_BAR, DMAC0_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dmac1", BYT_DSP_BAR, DMAC1_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dmac2", BYT_DSP_BAR, DMAC2_OFFSET, DMAC_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp0", BYT_DSP_BAR, SSP0_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp1", BYT_DSP_BAR, SSP1_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp2", BYT_DSP_BAR, SSP2_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp3", BYT_DSP_BAR, SSP3_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp4", BYT_DSP_BAR, SSP4_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"ssp5", BYT_DSP_BAR, SSP5_OFFSET, SSP_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, + {"iram", BYT_DSP_BAR, IRAM_OFFSET, IRAM_SIZE, + SOF_DEBUGFS_ACCESS_D0_ONLY}, + {"dram", BYT_DSP_BAR, DRAM_OFFSET, DRAM_SIZE, + SOF_DEBUGFS_ACCESS_D0_ONLY}, + {"shim", BYT_DSP_BAR, SHIM_OFFSET, SHIM_SIZE, + SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +static void byt_host_done(struct snd_sof_dev *sdev); +static void byt_dsp_done(struct snd_sof_dev *sdev); +static void byt_get_reply(struct snd_sof_dev *sdev); + +/* + * IPC Firmware ready. + */ +static void byt_get_windows(struct snd_sof_dev *sdev) +{ + struct sof_ipc_window_elem *elem; + u32 outbox_offset = 0; + u32 stream_offset = 0; + u32 inbox_offset = 0; + u32 outbox_size = 0; + u32 stream_size = 0; + u32 inbox_size = 0; + int i; + + if (!sdev->info_window) { + dev_err(sdev->dev, "error: have no window info\n"); + return; + } + + for (i = 0; i < sdev->info_window->num_windows; i++) { + elem = &sdev->info_window->window[i]; + + switch (elem->type) { + case SOF_IPC_REGION_UPBOX: + inbox_offset = elem->offset + MBOX_OFFSET; + inbox_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[BYT_DSP_BAR] + + inbox_offset, + elem->size, "inbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DOWNBOX: + outbox_offset = elem->offset + MBOX_OFFSET; + outbox_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[BYT_DSP_BAR] + + outbox_offset, + elem->size, "outbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_TRACE: + snd_sof_debugfs_io_item(sdev, + sdev->bar[BYT_DSP_BAR] + + elem->offset + + MBOX_OFFSET, + elem->size, "etrace", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DEBUG: + snd_sof_debugfs_io_item(sdev, + sdev->bar[BYT_DSP_BAR] + + elem->offset + + MBOX_OFFSET, + elem->size, "debug", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_STREAM: + stream_offset = elem->offset + MBOX_OFFSET; + stream_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[BYT_DSP_BAR] + + stream_offset, + elem->size, "stream", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_REGS: + snd_sof_debugfs_io_item(sdev, + sdev->bar[BYT_DSP_BAR] + + elem->offset + + MBOX_OFFSET, + elem->size, "regs", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_EXCEPTION: + sdev->dsp_oops_offset = elem->offset + MBOX_OFFSET; + snd_sof_debugfs_io_item(sdev, + sdev->bar[BYT_DSP_BAR] + + elem->offset + + MBOX_OFFSET, + elem->size, "exception", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + default: + dev_err(sdev->dev, "error: get illegal window info\n"); + return; + } + } + + if (outbox_size == 0 || inbox_size == 0) { + dev_err(sdev->dev, "error: get illegal mailbox window\n"); + return; + } + + snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size, + outbox_offset, outbox_size); + sdev->stream_box.offset = stream_offset; + sdev->stream_box.size = stream_size; + + dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", + inbox_offset, inbox_size); + dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", + outbox_offset, outbox_size); + dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", + stream_offset, stream_size); +} + +/* check for ABI compatibility and create memory windows on first boot */ +static int byt_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; + u32 offset; + int ret; + + /* mailbox must be on 4k boundary */ + offset = MBOX_OFFSET; + + dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n", + msg_id, offset); + + /* no need to re-check version/ABI for subsequent boots */ + if (!sdev->first_boot) + return 0; + + /* copy data from the DSP FW ready offset */ + sof_block_read(sdev, sdev->mmio_bar, offset, fw_ready, + sizeof(*fw_ready)); + + snd_sof_dsp_mailbox_init(sdev, fw_ready->dspbox_offset, + fw_ready->dspbox_size, + fw_ready->hostbox_offset, + fw_ready->hostbox_size); + + /* make sure ABI version is compatible */ + ret = snd_sof_ipc_valid(sdev); + if (ret < 0) + return ret; + + /* now check for extended data */ + snd_sof_fw_parse_ext_data(sdev, sdev->mmio_bar, MBOX_OFFSET + + sizeof(struct sof_ipc_fw_ready)); + + byt_get_windows(sdev); + + return 0; +} + +/* + * Debug + */ + +static void byt_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + /* first read regsisters */ + sof_mailbox_read(sdev, sdev->dsp_oops_offset, xoops, sizeof(*xoops)); + + /* then get panic info */ + sof_mailbox_read(sdev, sdev->dsp_oops_offset + sizeof(*xoops), + panic_info, sizeof(*panic_info)); + + /* then get the stack */ + sof_mailbox_read(sdev, sdev->dsp_oops_offset + sizeof(*xoops) + + sizeof(*panic_info), stack, + stack_words * sizeof(u32)); +} + +static void byt_dump(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[BYT_STACK_DUMP_SIZE]; + u32 status, panic; + + /* now try generic SOF status messages */ + status = snd_sof_dsp_read(sdev, BYT_DSP_BAR, SHIM_IPCD); + panic = snd_sof_dsp_read(sdev, BYT_DSP_BAR, SHIM_IPCX); + byt_get_registers(sdev, &xoops, &panic_info, stack, + BYT_STACK_DUMP_SIZE); + snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, stack, + BYT_STACK_DUMP_SIZE); +} + +/* + * IPC Doorbell IRQ handler and thread. + */ + +static irqreturn_t byt_irq_handler(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u64 isr; + int ret = IRQ_NONE; + + /* Interrupt arrived, check src */ + isr = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_ISRX); + if (isr & (SHIM_ISRX_DONE | SHIM_ISRX_BUSY)) + ret = IRQ_WAKE_THREAD; + + return ret; +} + +static irqreturn_t byt_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u64 ipcx, ipcd; + u64 imrx; + + imrx = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IMRX); + ipcx = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCX); + + /* reply message from DSP */ + if (ipcx & SHIM_BYT_IPCX_DONE && + !(imrx & SHIM_IMRX_DONE)) { + /* Mask Done interrupt before first */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, + SHIM_IMRX, + SHIM_IMRX_DONE, + SHIM_IMRX_DONE); + /* + * handle immediate reply from DSP core. If the msg is + * found, set done bit in cmd_done which is called at the + * end of message processing function, else set it here + * because the done bit can't be set in cmd_done function + * which is triggered by msg + */ + byt_get_reply(sdev); + snd_sof_ipc_reply(sdev, ipcx); + + byt_dsp_done(sdev); + } + + /* new message from DSP */ + ipcd = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCD); + if (ipcd & SHIM_BYT_IPCD_BUSY && + !(imrx & SHIM_IMRX_BUSY)) { + /* Mask Busy interrupt before return */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, + SHIM_IMRX, + SHIM_IMRX_BUSY, + SHIM_IMRX_BUSY); + + /* Handle messages from DSP Core */ + if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(sdev, BYT_PANIC_OFFSET(ipcd) + + MBOX_OFFSET); + } else { + snd_sof_ipc_msgs_rx(sdev); + } + + byt_host_done(sdev); + } + + return IRQ_HANDLED; +} + +static int byt_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + u64 cmd = msg->header; + + /* send the message */ + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + snd_sof_dsp_write64(sdev, BYT_DSP_BAR, SHIM_IPCX, + cmd | SHIM_BYT_IPCX_BUSY); + + return 0; +} + +static void byt_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + unsigned long flags; + int ret = 0; + + /* + * Sometimes, there is unexpected reply ipc arriving. The reply + * ipc belongs to none of the ipcs sent from driver. + * In this case, the driver must ignore the ipc. + */ + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); + return; + } + + /* get reply */ + sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); + + spin_lock_irqsave(&sdev->ipc_lock, flags); + + if (reply.error < 0) { + memcpy(msg->reply_data, &reply, sizeof(reply)); + ret = reply.error; + } else { + /* reply correct size ? */ + if (reply.hdr.size != msg->reply_size) { + dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", + msg->reply_size, reply.hdr.size); + ret = -EINVAL; + } + + /* read the message */ + if (msg->reply_size > 0) + sof_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + msg->reply_error = ret; + + spin_unlock_irqrestore(&sdev->ipc_lock, flags); +} + +static void byt_host_done(struct snd_sof_dev *sdev) +{ + /* clear BUSY bit and set DONE bit - accept new messages */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IPCD, + SHIM_BYT_IPCD_BUSY | + SHIM_BYT_IPCD_DONE, + SHIM_BYT_IPCD_DONE); + + /* unmask busy interrupt */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_BUSY, 0); +} + +static void byt_dsp_done(struct snd_sof_dev *sdev) +{ + /* clear DONE bit - tell DSP we have completed */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IPCX, + SHIM_BYT_IPCX_DONE, 0); + + /* unmask Done interrupt */ + snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IMRX, + SHIM_IMRX_DONE, 0); +} + +/* + * DSP control. + */ + +static int byt_run(struct snd_sof_dev *sdev) +{ + int tries = 10; + + /* release stall and wait to unstall */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR, + SHIM_BYT_CSR_STALL, 0x0); + while (tries--) { + if (!(snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_CSR) & + SHIM_BYT_CSR_PWAITMODE)) + break; + msleep(100); + } + if (tries < 0) { + dev_err(sdev->dev, "error: unable to run DSP firmware\n"); + byt_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX); + return -ENODEV; + } + + /* return init core mask */ + return 1; +} + +static int byt_reset(struct snd_sof_dev *sdev) +{ + /* put DSP into reset, set reset vector and stall */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR, + SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL | + SHIM_BYT_CSR_STALL, + SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL | + SHIM_BYT_CSR_STALL); + + usleep_range(10, 15); + + /* take DSP out of reset and keep stalled for FW loading */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR, + SHIM_BYT_CSR_RST, 0); + + return 0; +} + +/* Baytrail DAIs */ +static struct snd_soc_dai_driver byt_dai[] = { +{ + .name = "ssp0-port", +}, +{ + .name = "ssp1-port", +}, +{ + .name = "ssp2-port", +}, +{ + .name = "ssp3-port", +}, +{ + .name = "ssp4-port", +}, +{ + .name = "ssp5-port", +}, +}; + +/* + * Probe and remove. + */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD) + +static int tangier_pci_probe(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *pdata = sdev->pdata; + const struct sof_dev_desc *desc = pdata->desc; + struct pci_dev *pci = to_pci_dev(sdev->dev); + u32 base, size; + int ret; + + /* DSP DMA can only access low 31 bits of host memory */ + ret = dma_coerce_mask_and_coherent(&pci->dev, DMA_BIT_MASK(31)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret); + return ret; + } + + /* LPE base */ + base = pci_resource_start(pci, desc->resindex_lpe_base) - IRAM_OFFSET; + size = BYT_PCI_BAR_SIZE; + + dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size); + sdev->bar[BYT_DSP_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BYT_DSP_BAR]) { + dev_err(sdev->dev, "error: failed to ioremap LPE base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[BYT_DSP_BAR]); + + /* IMR base - optional */ + if (desc->resindex_imr_base == -1) + goto irq; + + base = pci_resource_start(pci, desc->resindex_imr_base); + size = pci_resource_len(pci, desc->resindex_imr_base); + + /* some BIOSes don't map IMR */ + if (base == 0x55aa55aa || base == 0x0) { + dev_info(sdev->dev, "IMR not set by BIOS. Ignoring\n"); + goto irq; + } + + dev_dbg(sdev->dev, "IMR base at 0x%x size 0x%x", base, size); + sdev->bar[BYT_IMR_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BYT_IMR_BAR]) { + dev_err(sdev->dev, "error: failed to ioremap IMR base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[BYT_IMR_BAR]); + +irq: + /* register our IRQ */ + sdev->ipc_irq = pci->irq; + dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq); + ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq, + byt_irq_handler, byt_irq_thread, + 0, "AudioDSP", sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to register IRQ %d\n", + sdev->ipc_irq); + return ret; + } + + /* enable Interrupt from both sides */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX, 0x3, 0x0); + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRD, 0x3, 0x0); + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = MBOX_OFFSET; + + return ret; +} + +const struct snd_sof_dsp_ops sof_tng_ops = { + /* device init */ + .probe = tangier_pci_probe, + + /* DSP core boot / reset */ + .run = byt_run, + .reset = byt_reset, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_handler = byt_irq_handler, + .irq_thread = byt_irq_thread, + + /* ipc */ + .send_msg = byt_send_msg, + .fw_ready = byt_fw_ready, + + .ipc_msg_data = intel_ipc_msg_data, + .ipc_pcm_params = intel_ipc_pcm_params, + + /* debug */ + .debug_map = byt_debugfs, + .debug_map_count = ARRAY_SIZE(byt_debugfs), + .dbg_dump = byt_dump, + + /* stream callbacks */ + .pcm_open = intel_pcm_open, + .pcm_close = intel_pcm_close, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + + /*Firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* DAI drivers */ + .drv = byt_dai, + .num_drv = 3, /* we have only 3 SSPs on byt*/ +}; +EXPORT_SYMBOL(sof_tng_ops); + +const struct sof_intel_dsp_desc tng_chip_info = { + .cores_num = 1, + .cores_mask = 1, +}; +EXPORT_SYMBOL(tng_chip_info); + +#endif /* CONFIG_SND_SOC_SOF_MERRIFIELD */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL) + +static int byt_acpi_probe(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *pdata = sdev->pdata; + const struct sof_dev_desc *desc = pdata->desc; + struct platform_device *pdev = + container_of(sdev->dev, struct platform_device, dev); + struct resource *mmio; + u32 base, size; + int ret; + + /* DSP DMA can only access low 31 bits of host memory */ + ret = dma_coerce_mask_and_coherent(sdev->dev, DMA_BIT_MASK(31)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret); + return ret; + } + + /* LPE base */ + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_lpe_base); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get LPE base at idx %d\n", + desc->resindex_lpe_base); + return -EINVAL; + } + + dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size); + sdev->bar[BYT_DSP_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BYT_DSP_BAR]) { + dev_err(sdev->dev, "error: failed to ioremap LPE base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[BYT_DSP_BAR]); + + /* TODO: add offsets */ + sdev->mmio_bar = BYT_DSP_BAR; + sdev->mailbox_bar = BYT_DSP_BAR; + + /* IMR base - optional */ + if (desc->resindex_imr_base == -1) + goto irq; + + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_imr_base); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get IMR base at idx %d\n", + desc->resindex_imr_base); + return -ENODEV; + } + + /* some BIOSes don't map IMR */ + if (base == 0x55aa55aa || base == 0x0) { + dev_info(sdev->dev, "IMR not set by BIOS. Ignoring\n"); + goto irq; + } + + dev_dbg(sdev->dev, "IMR base at 0x%x size 0x%x", base, size); + sdev->bar[BYT_IMR_BAR] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[BYT_IMR_BAR]) { + dev_err(sdev->dev, "error: failed to ioremap IMR base 0x%x size 0x%x\n", + base, size); + return -ENODEV; + } + dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[BYT_IMR_BAR]); + +irq: + /* register our IRQ */ + sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc); + if (sdev->ipc_irq < 0) { + dev_err(sdev->dev, "error: failed to get IRQ at index %d\n", + desc->irqindex_host_ipc); + return sdev->ipc_irq; + } + + dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq); + ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq, + byt_irq_handler, byt_irq_thread, + IRQF_SHARED, "AudioDSP", sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to register IRQ %d\n", + sdev->ipc_irq); + return ret; + } + + /* enable Interrupt from both sides */ + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX, 0x3, 0x0); + snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRD, 0x3, 0x0); + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = MBOX_OFFSET; + + return ret; +} + +/* baytrail ops */ +const struct snd_sof_dsp_ops sof_byt_ops = { + /* device init */ + .probe = byt_acpi_probe, + + /* DSP core boot / reset */ + .run = byt_run, + .reset = byt_reset, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_handler = byt_irq_handler, + .irq_thread = byt_irq_thread, + + /* ipc */ + .send_msg = byt_send_msg, + .fw_ready = byt_fw_ready, + + .ipc_msg_data = intel_ipc_msg_data, + .ipc_pcm_params = intel_ipc_pcm_params, + + /* debug */ + .debug_map = byt_debugfs, + .debug_map_count = ARRAY_SIZE(byt_debugfs), + .dbg_dump = byt_dump, + + /* stream callbacks */ + .pcm_open = intel_pcm_open, + .pcm_close = intel_pcm_close, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + + /*Firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* DAI drivers */ + .drv = byt_dai, + .num_drv = 3, /* we have only 3 SSPs on byt*/ +}; +EXPORT_SYMBOL(sof_byt_ops); + +const struct sof_intel_dsp_desc byt_chip_info = { + .cores_num = 1, + .cores_mask = 1, +}; +EXPORT_SYMBOL(byt_chip_info); + +/* cherrytrail and braswell ops */ +const struct snd_sof_dsp_ops sof_cht_ops = { + /* device init */ + .probe = byt_acpi_probe, + + /* DSP core boot / reset */ + .run = byt_run, + .reset = byt_reset, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_handler = byt_irq_handler, + .irq_thread = byt_irq_thread, + + /* ipc */ + .send_msg = byt_send_msg, + .fw_ready = byt_fw_ready, + + .ipc_msg_data = intel_ipc_msg_data, + .ipc_pcm_params = intel_ipc_pcm_params, + + /* debug */ + .debug_map = cht_debugfs, + .debug_map_count = ARRAY_SIZE(cht_debugfs), + .dbg_dump = byt_dump, + + /* stream callbacks */ + .pcm_open = intel_pcm_open, + .pcm_close = intel_pcm_close, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + + /*Firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* DAI drivers */ + .drv = byt_dai, + /* all 6 SSPs may be available for cherrytrail */ + .num_drv = ARRAY_SIZE(byt_dai), +}; +EXPORT_SYMBOL(sof_cht_ops); + +const struct sof_intel_dsp_desc cht_chip_info = { + .cores_num = 1, + .cores_mask = 1, +}; +EXPORT_SYMBOL(cht_chip_info); + +#endif /* CONFIG_SND_SOC_SOF_BAYTRAIL */ + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c new file mode 100644 index 000000000000..08a1a3d3c08d --- /dev/null +++ b/sound/soc/sof/intel/cnl.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Cannonlake. + */ + +#include "../ops.h" +#include "hda.h" + +static const struct snd_sof_debugfs_map cnl_dsp_debugfs[] = { + {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +static void cnl_ipc_host_done(struct snd_sof_dev *sdev); +static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev); + +static irqreturn_t cnl_ipc_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + u32 hipci; + u32 hipcctl; + u32 hipcida; + u32 hipctdr; + u32 hipctdd; + u32 msg; + u32 msg_ext; + irqreturn_t ret = IRQ_NONE; + + hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA); + hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCCTL); + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR); + + /* reenable IPC interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, + HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC); + + /* reply message from DSP */ + if (hipcida & CNL_DSP_REG_HIPCIDA_DONE && + hipcctl & CNL_DSP_REG_HIPCCTL_DONE) { + hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCIDR); + msg_ext = hipci & CNL_DSP_REG_HIPCIDR_MSG_MASK; + msg = hipcida & CNL_DSP_REG_HIPCIDA_MSG_MASK; + + dev_vdbg(sdev->dev, + "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n", + msg, msg_ext); + + /* mask Done interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCCTL, + CNL_DSP_REG_HIPCCTL_DONE, 0); + + /* handle immediate reply from DSP core */ + hda_dsp_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, msg); + + if (sdev->code_loading) { + sdev->code_loading = 0; + wake_up(&sdev->waitq); + } + + cnl_ipc_dsp_done(sdev); + + ret = IRQ_HANDLED; + } + + /* new message from DSP */ + if (hipctdr & CNL_DSP_REG_HIPCTDR_BUSY) { + hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCTDD); + msg = hipctdr & CNL_DSP_REG_HIPCTDR_MSG_MASK; + msg_ext = hipctdd & CNL_DSP_REG_HIPCTDD_MSG_MASK; + + dev_vdbg(sdev->dev, + "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n", + msg, msg_ext); + + /* handle messages from DSP */ + if ((hipctdr & SOF_IPC_PANIC_MAGIC_MASK) == + SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext)); + } else { + snd_sof_ipc_msgs_rx(sdev); + } + + /* + * clear busy interrupt to tell dsp controller this + * interrupt has been accepted, not trigger it again + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCTDR, + CNL_DSP_REG_HIPCTDR_BUSY, + CNL_DSP_REG_HIPCTDR_BUSY); + + cnl_ipc_host_done(sdev); + + ret = IRQ_HANDLED; + } + + return ret; +} + +static void cnl_ipc_host_done(struct snd_sof_dev *sdev) +{ + /* + * set done bit to ack dsp the msg has been + * processed and send reply msg to dsp + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCTDA, + CNL_DSP_REG_HIPCTDA_DONE, + CNL_DSP_REG_HIPCTDA_DONE); +} + +static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev) +{ + /* + * set DONE bit - tell DSP we have received the reply msg + * from DSP, and processed it, don't send more reply to host + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCIDA, + CNL_DSP_REG_HIPCIDA_DONE, + CNL_DSP_REG_HIPCIDA_DONE); + + /* unmask Done interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCCTL, + CNL_DSP_REG_HIPCCTL_DONE, + CNL_DSP_REG_HIPCCTL_DONE); +} + +static int cnl_ipc_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + u32 cmd = msg->header; + + /* send the message */ + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR, + cmd | CNL_DSP_REG_HIPCIDR_BUSY); + + return 0; +} + +static void cnl_ipc_dump(struct snd_sof_dev *sdev) +{ + u32 hipcctl; + u32 hipcida; + u32 hipctdr; + + /* read IPC status */ + hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA); + hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCCTL); + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR); + + /* dump the IPC regs */ + /* TODO: parse the raw msg */ + dev_err(sdev->dev, + "error: host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n", + hipcida, hipctdr, hipcctl); +} + +/* cannonlake ops */ +const struct snd_sof_dsp_ops sof_cnl_ops = { + /* probe and remove */ + .probe = hda_dsp_probe, + .remove = hda_dsp_remove, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* doorbell */ + .irq_handler = hda_dsp_ipc_irq_handler, + .irq_thread = cnl_ipc_irq_thread, + + /* ipc */ + .send_msg = cnl_ipc_send_msg, + .fw_ready = hda_dsp_ipc_fw_ready, + + .ipc_msg_data = hda_ipc_msg_data, + .ipc_pcm_params = hda_ipc_pcm_params, + + /* debug */ + .debug_map = cnl_dsp_debugfs, + .debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs), + .dbg_dump = hda_dsp_dump, + .ipc_dump = cnl_ipc_dump, + + /* stream callbacks */ + .pcm_open = hda_dsp_pcm_open, + .pcm_close = hda_dsp_pcm_close, + .pcm_hw_params = hda_dsp_pcm_hw_params, + .pcm_trigger = hda_dsp_pcm_trigger, + .pcm_pointer = hda_dsp_pcm_pointer, + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_raw, + + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + .post_fw_run = hda_dsp_post_fw_run, + + /* dsp core power up/down */ + .core_power_up = hda_dsp_enable_core, + .core_power_down = hda_dsp_core_reset_power_down, + + /* firmware run */ + .run = hda_dsp_cl_boot_firmware, + + /* trace callback */ + .trace_init = hda_dsp_trace_init, + .trace_release = hda_dsp_trace_release, + .trace_trigger = hda_dsp_trace_trigger, + + /* DAI drivers */ + .drv = skl_dai, + .num_drv = SOF_SKL_NUM_DAIS, + + /* PM */ + .suspend = hda_dsp_suspend, + .resume = hda_dsp_resume, + .runtime_suspend = hda_dsp_runtime_suspend, + .runtime_resume = hda_dsp_runtime_resume, + .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, +}; +EXPORT_SYMBOL(sof_cnl_ops); + +const struct sof_intel_dsp_desc cnl_chip_info = { + /* Cannonlake */ + .cores_num = 4, + .init_core_mask = 1, + .cores_mask = HDA_DSP_CORE_MASK(0) | + HDA_DSP_CORE_MASK(1) | + HDA_DSP_CORE_MASK(2) | + HDA_DSP_CORE_MASK(3), + .ipc_req = CNL_DSP_REG_HIPCIDR, + .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY, + .ipc_ack = CNL_DSP_REG_HIPCIDA, + .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, + .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_init_timeout = 300, + .ssp_count = CNL_SSP_COUNT, + .ssp_base_offset = CNL_SSP_BASE_OFFSET, +}; +EXPORT_SYMBOL(cnl_chip_info); diff --git a/sound/soc/sof/intel/hda-bus.c b/sound/soc/sof/intel/hda-bus.c new file mode 100644 index 000000000000..a7e6d8227df6 --- /dev/null +++ b/sound/soc/sof/intel/hda-bus.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Keyon Jie <yang.jie@linux.intel.com> + +#include <linux/io.h> +#include <sound/hdaudio.h> +#include "../sof-priv.h" +#include "hda.h" + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + +static const struct hdac_bus_ops bus_ops = { + .command = snd_hdac_bus_send_cmd, + .get_response = snd_hdac_bus_get_response, +}; + +#endif + +static void sof_hda_writel(u32 value, u32 __iomem *addr) +{ + writel(value, addr); +} + +static u32 sof_hda_readl(u32 __iomem *addr) +{ + return readl(addr); +} + +static void sof_hda_writew(u16 value, u16 __iomem *addr) +{ + writew(value, addr); +} + +static u16 sof_hda_readw(u16 __iomem *addr) +{ + return readw(addr); +} + +static void sof_hda_writeb(u8 value, u8 __iomem *addr) +{ + writeb(value, addr); +} + +static u8 sof_hda_readb(u8 __iomem *addr) +{ + return readb(addr); +} + +static int sof_hda_dma_alloc_pages(struct hdac_bus *bus, int type, + size_t size, struct snd_dma_buffer *buf) +{ + return snd_dma_alloc_pages(type, bus->dev, size, buf); +} + +static void sof_hda_dma_free_pages(struct hdac_bus *bus, + struct snd_dma_buffer *buf) +{ + snd_dma_free_pages(buf); +} + +static const struct hdac_io_ops io_ops = { + .reg_writel = sof_hda_writel, + .reg_readl = sof_hda_readl, + .reg_writew = sof_hda_writew, + .reg_readw = sof_hda_readw, + .reg_writeb = sof_hda_writeb, + .reg_readb = sof_hda_readb, + .dma_alloc_pages = sof_hda_dma_alloc_pages, + .dma_free_pages = sof_hda_dma_free_pages, +}; + +/* + * This can be used for both with/without hda link support. + */ +void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev, + const struct hdac_ext_bus_ops *ext_ops) +{ + memset(bus, 0, sizeof(*bus)); + bus->dev = dev; + + bus->io_ops = &io_ops; + INIT_LIST_HEAD(&bus->stream_list); + + bus->irq = -1; + bus->ext_ops = ext_ops; + + /* + * There is only one HDA bus atm. keep the index as 0. + * Need to fix when there are more than one HDA bus. + */ + bus->idx = 0; + + spin_lock_init(&bus->reg_lock); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + INIT_LIST_HEAD(&bus->codec_list); + INIT_LIST_HEAD(&bus->hlink_list); + + mutex_init(&bus->cmd_mutex); + mutex_init(&bus->lock); + bus->ops = &bus_ops; + INIT_WORK(&bus->unsol_work, snd_hdac_bus_process_unsol_events); + bus->cmd_dma_state = true; +#endif + +} diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c new file mode 100644 index 000000000000..b8b37f082309 --- /dev/null +++ b/sound/soc/sof/intel/hda-codec.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Keyon Jie <yang.jie@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_codec.h> +#include <sound/hda_i915.h> +#include <sound/sof.h> +#include "../ops.h" +#include "hda.h" +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +#include "../../codecs/hdac_hda.h" +#endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +#define IDISP_VID_INTEL 0x80860000 + +/* load the legacy HDA codec driver */ +#ifdef MODULE +static void hda_codec_load_module(struct hda_codec *codec) +{ + char alias[MODULE_NAME_LEN]; + const char *module = alias; + + snd_hdac_codec_modalias(&codec->core, alias, sizeof(alias)); + dev_dbg(&codec->core.dev, "loading codec module: %s\n", module); + request_module(module); +} +#else +static void hda_codec_load_module(struct hda_codec *codec) {} +#endif + +#endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */ + +/* probe individual codec */ +static int hda_codec_probe(struct snd_sof_dev *sdev, int address) +{ + struct hda_bus *hbus = sof_to_hbus(sdev); + struct hdac_device *hdev; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) + struct hdac_hda_priv *hda_priv; +#endif + u32 hda_cmd = (address << 28) | (AC_NODE_ROOT << 20) | + (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; + u32 resp = -1; + int ret; + + mutex_lock(&hbus->core.cmd_mutex); + snd_hdac_bus_send_cmd(&hbus->core, hda_cmd); + snd_hdac_bus_get_response(&hbus->core, address, &resp); + mutex_unlock(&hbus->core.cmd_mutex); + if (resp == -1) + return -EIO; + dev_dbg(sdev->dev, "HDA codec #%d probed OK: response: %x\n", + address, resp); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) + /* snd_hdac_ext_bus_device_exit will use kfree to free hdev */ + hda_priv = kzalloc(sizeof(*hda_priv), GFP_KERNEL); + if (!hda_priv) + return -ENOMEM; + + hda_priv->codec.bus = hbus; + hdev = &hda_priv->codec.core; + + ret = snd_hdac_ext_bus_device_init(&hbus->core, address, hdev); + if (ret < 0) + return ret; + + /* use legacy bus only for HDA codecs, idisp uses ext bus */ + if ((resp & 0xFFFF0000) != IDISP_VID_INTEL) { + hdev->type = HDA_DEV_LEGACY; + hda_codec_load_module(&hda_priv->codec); + } + + return 0; +#else + /* snd_hdac_ext_bus_device_exit will use kfree to free hdev */ + hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); + if (!hdev) + return -ENOMEM; + + ret = snd_hdac_ext_bus_device_init(&hbus->core, address, hdev); + + return ret; +#endif +} + +/* Codec initialization */ +int hda_codec_probe_bus(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + int i, ret; + + /* probe codecs in avail slots */ + for (i = 0; i < HDA_MAX_CODECS; i++) { + + if (!(bus->codec_mask & (1 << i))) + continue; + + ret = hda_codec_probe(sdev, i); + if (ret < 0) { + dev_err(bus->dev, "error: codec #%d probe error, ret: %d\n", + i, ret); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL(hda_codec_probe_bus); + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) + +void hda_codec_i915_get(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + + dev_dbg(bus->dev, "Turning i915 HDAC power on\n"); + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true); +} +EXPORT_SYMBOL(hda_codec_i915_get); + +void hda_codec_i915_put(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + + dev_dbg(bus->dev, "Turning i915 HDAC power off\n"); + snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false); +} +EXPORT_SYMBOL(hda_codec_i915_put); + +int hda_codec_i915_init(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + int ret; + + /* i915 exposes a HDA codec for HDMI audio */ + ret = snd_hdac_i915_init(bus); + if (ret < 0) + return ret; + + hda_codec_i915_get(sdev); + + return 0; +} +EXPORT_SYMBOL(hda_codec_i915_init); + +int hda_codec_i915_exit(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + int ret; + + hda_codec_i915_put(sdev); + + ret = snd_hdac_i915_exit(bus); + + return ret; +} +EXPORT_SYMBOL(hda_codec_i915_exit); + +#endif /* CONFIG_SND_SOC_HDAC_HDMI */ + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c new file mode 100644 index 000000000000..2c3645736e1f --- /dev/null +++ b/sound/soc/sof/intel/hda-ctrl.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <sound/hdaudio_ext.h> +#include <sound/hda_register.h> +#include "../ops.h" +#include "hda.h" + +/* + * HDA Operations. + */ + +int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset) +{ + unsigned long timeout; + u32 gctl = 0; + u32 val; + + /* 0 to enter reset and 1 to exit reset */ + val = reset ? 0 : SOF_HDA_GCTL_RESET; + + /* enter/exit HDA controller reset */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL, + SOF_HDA_GCTL_RESET, val); + + /* wait to enter/exit reset */ + timeout = jiffies + msecs_to_jiffies(HDA_DSP_CTRL_RESET_TIMEOUT); + while (time_before(jiffies, timeout)) { + gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL); + if ((gctl & SOF_HDA_GCTL_RESET) == val) + return 0; + usleep_range(500, 1000); + } + + /* enter/exit reset failed */ + dev_err(sdev->dev, "error: failed to %s HDA controller gctl 0x%x\n", + reset ? "reset" : "ready", gctl); + return -EIO; +} + +int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + u32 cap, offset, feature; + int count = 0; + + offset = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_LLCH); + + do { + cap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, offset); + + dev_dbg(sdev->dev, "checking for capabilities at offset 0x%x\n", + offset & SOF_HDA_CAP_NEXT_MASK); + + feature = (cap & SOF_HDA_CAP_ID_MASK) >> SOF_HDA_CAP_ID_OFF; + + switch (feature) { + case SOF_HDA_PP_CAP_ID: + dev_dbg(sdev->dev, "found DSP capability at 0x%x\n", + offset); + bus->ppcap = bus->remap_addr + offset; + sdev->bar[HDA_DSP_PP_BAR] = bus->ppcap; + break; + case SOF_HDA_SPIB_CAP_ID: + dev_dbg(sdev->dev, "found SPIB capability at 0x%x\n", + offset); + bus->spbcap = bus->remap_addr + offset; + sdev->bar[HDA_DSP_SPIB_BAR] = bus->spbcap; + break; + case SOF_HDA_DRSM_CAP_ID: + dev_dbg(sdev->dev, "found DRSM capability at 0x%x\n", + offset); + bus->drsmcap = bus->remap_addr + offset; + sdev->bar[HDA_DSP_DRSM_BAR] = bus->drsmcap; + break; + case SOF_HDA_GTS_CAP_ID: + dev_dbg(sdev->dev, "found GTS capability at 0x%x\n", + offset); + bus->gtscap = bus->remap_addr + offset; + break; + case SOF_HDA_ML_CAP_ID: + dev_dbg(sdev->dev, "found ML capability at 0x%x\n", + offset); + bus->mlcap = bus->remap_addr + offset; + break; + default: + dev_vdbg(sdev->dev, "found capability %d at 0x%x\n", + feature, offset); + break; + } + + offset = cap & SOF_HDA_CAP_NEXT_MASK; + } while (count++ <= SOF_HDA_MAX_CAPS && offset); + + return 0; +} + +void hda_dsp_ctrl_ppcap_enable(struct snd_sof_dev *sdev, bool enable) +{ + u32 val = enable ? SOF_HDA_PPCTL_GPROCEN : 0; + + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_GPROCEN, val); +} + +void hda_dsp_ctrl_ppcap_int_enable(struct snd_sof_dev *sdev, bool enable) +{ + u32 val = enable ? SOF_HDA_PPCTL_PIE : 0; + + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_PIE, val); +} + +void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable) +{ + u32 val = enable ? PCI_CGCTL_MISCBDCGE_MASK : 0; + + snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_MISCBDCGE_MASK, val); +} + +/* + * enable/disable audio dsp clock gating and power gating bits. + * This allows the HW to opportunistically power and clock gate + * the audio dsp when it is idle + */ +int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable) +{ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + struct hdac_bus *bus = sof_to_bus(sdev); +#endif + u32 val; + + /* enable/disable audio dsp clock gating */ + val = enable ? PCI_CGCTL_ADSPDCGE : 0; + snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* enable/disable L1 support */ + val = enable ? SOF_HDA_VS_EM2_L1SEN : 0; + snd_hdac_chip_updatel(bus, VS_EM2, SOF_HDA_VS_EM2_L1SEN, val); +#endif + + /* enable/disable audio dsp power gating */ + val = enable ? 0 : PCI_PGCTL_ADSPPGD; + snd_sof_pci_update_bits(sdev, PCI_PGCTL, PCI_PGCTL_ADSPPGD, val); + + return 0; +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +/* + * While performing reset, controller may not come back properly and causing + * issues, so recommendation is to set CGCTL.MISCBDCGE to 0 then do reset + * (init chip) and then again set CGCTL.MISCBDCGE to 1 + */ +int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + int ret; + + hda_dsp_ctrl_misc_clock_gating(sdev, false); + ret = snd_hdac_bus_init_chip(bus, full_reset); + hda_dsp_ctrl_misc_clock_gating(sdev, true); + + return ret; +} +#endif diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c new file mode 100644 index 000000000000..e1decf25aeac --- /dev/null +++ b/sound/soc/sof/intel/hda-dai.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Keyon Jie <yang.jie@linux.intel.com> +// + +#include <sound/pcm_params.h> +#include <sound/hdaudio_ext.h> +#include "../sof-priv.h" +#include "hda.h" + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + +struct hda_pipe_params { + u8 host_dma_id; + u8 link_dma_id; + u32 ch; + u32 s_freq; + u32 s_fmt; + u8 linktype; + snd_pcm_format_t format; + int link_index; + int stream; + unsigned int host_bps; + unsigned int link_bps; +}; + +/* + * Unlike GP dma, there is a set of stream registers in hda controller + * to control the link dma channels. Each register controls one link + * dma channel and the relation is fixed. To make sure FW uses correct + * link dma channels, host allocates stream registers and sends the + * corresponding link dma channels to FW to allocate link dma channel + * + * FIXME: this API is abused in the sense that tx_num and rx_num are + * passed as arguments, not returned. We need to find a better way to + * retrieve the stream tag allocated for the link DMA + */ +static int hda_link_dma_get_channels(struct snd_soc_dai *dai, + unsigned int *tx_num, + unsigned int *tx_slot, + unsigned int *rx_num, + unsigned int *rx_slot) +{ + struct hdac_bus *bus; + struct hdac_ext_stream *stream; + struct snd_pcm_substream substream; + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(dai->component); + + bus = sof_to_bus(sdev); + + memset(&substream, 0, sizeof(substream)); + if (*tx_num == 1) { + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + stream = snd_hdac_ext_stream_assign(bus, &substream, + HDAC_EXT_STREAM_TYPE_LINK); + if (!stream) { + dev_err(bus->dev, "error: failed to find a free hda ext stream for playback"); + return -EBUSY; + } + + snd_soc_dai_set_dma_data(dai, &substream, stream); + *tx_slot = hdac_stream(stream)->stream_tag - 1; + + dev_dbg(bus->dev, "link dma channel %d for playback", *tx_slot); + } + + if (*rx_num == 1) { + substream.stream = SNDRV_PCM_STREAM_CAPTURE; + stream = snd_hdac_ext_stream_assign(bus, &substream, + HDAC_EXT_STREAM_TYPE_LINK); + if (!stream) { + dev_err(bus->dev, "error: failed to find a free hda ext stream for capture"); + return -EBUSY; + } + + snd_soc_dai_set_dma_data(dai, &substream, stream); + *rx_slot = hdac_stream(stream)->stream_tag - 1; + + dev_dbg(bus->dev, "link dma channel %d for capture", *rx_slot); + } + + return 0; +} + +static int hda_link_dma_params(struct hdac_ext_stream *stream, + struct hda_pipe_params *params) +{ + struct hdac_stream *hstream = &stream->hstream; + unsigned char stream_tag = hstream->stream_tag; + struct hdac_bus *bus = hstream->bus; + struct hdac_ext_link *link; + unsigned int format_val; + + snd_hdac_ext_stream_decouple(bus, stream, true); + snd_hdac_ext_link_stream_reset(stream); + + format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch, + params->format, + params->link_bps, 0); + + dev_dbg(bus->dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", + format_val, params->s_freq, params->ch, params->format); + + snd_hdac_ext_link_stream_setup(stream, format_val); + + if (stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) { + list_for_each_entry(link, &bus->hlink_list, list) { + if (link->index == params->link_index) + snd_hdac_ext_link_set_stream_id(link, + stream_tag); + } + } + + stream->link_prepared = 1; + + return 0; +} + +static int hda_link_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct hdac_bus *bus = hstream->bus; + struct hdac_ext_stream *link_dev; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct sof_intel_hda_stream *hda_stream; + struct hda_pipe_params p_params = {0}; + struct hdac_ext_link *link; + int stream_tag; + + link_dev = snd_soc_dai_get_dma_data(dai, substream); + + hda_stream = container_of(link_dev, struct sof_intel_hda_stream, + hda_stream); + hda_stream->hw_params_upon_resume = 0; + + link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); + if (!link) + return -EINVAL; + + stream_tag = hdac_stream(link_dev)->stream_tag; + + /* set the stream tag in the codec dai dma params */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dai_set_tdm_slot(codec_dai, stream_tag, 0, 0, 0); + else + snd_soc_dai_set_tdm_slot(codec_dai, 0, stream_tag, 0, 0); + + p_params.s_fmt = snd_pcm_format_width(params_format(params)); + p_params.ch = params_channels(params); + p_params.s_freq = params_rate(params); + p_params.stream = substream->stream; + p_params.link_dma_id = stream_tag - 1; + p_params.link_index = link->index; + p_params.format = params_format(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_params.link_bps = codec_dai->driver->playback.sig_bits; + else + p_params.link_bps = codec_dai->driver->capture.sig_bits; + + return hda_link_dma_params(link_dev, &p_params); +} + +static int hda_link_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *link_dev = + snd_soc_dai_get_dma_data(dai, substream); + struct sof_intel_hda_stream *hda_stream; + struct snd_sof_dev *sdev = + snd_soc_component_get_drvdata(dai->component); + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + int stream = substream->stream; + + hda_stream = container_of(link_dev, struct sof_intel_hda_stream, + hda_stream); + + /* setup hw_params again only if resuming from system suspend */ + if (!hda_stream->hw_params_upon_resume) + return 0; + + dev_dbg(sdev->dev, "hda: prepare stream dir %d\n", substream->stream); + + return hda_link_hw_params(substream, &rtd->dpcm[stream].hw_params, + dai); +} + +static int hda_link_pcm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *link_dev = + snd_soc_dai_get_dma_data(dai, substream); + int ret; + + dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd); + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + /* set up hw_params */ + ret = hda_link_pcm_prepare(substream, dai); + if (ret < 0) { + dev_err(dai->dev, + "error: setting up hw_params during resume\n"); + return ret; + } + + /* fallthrough */ + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_hdac_ext_link_stream_start(link_dev); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + snd_hdac_ext_link_stream_clear(link_dev); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * FIXME: This API is also abused since it's used for two purposes. + * when the substream argument is NULL this function is used for cleanups + * that aren't necessarily required, and called explicitly by handling + * ASoC core structures, which is not recommended. + * This part will be reworked in follow-up patches. + */ +static int hda_link_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + const char *name; + unsigned int stream_tag; + struct hdac_bus *bus; + struct hdac_ext_link *link; + struct hdac_stream *hstream; + struct hdac_ext_stream *stream; + struct snd_soc_pcm_runtime *rtd; + struct hdac_ext_stream *link_dev; + struct snd_pcm_substream pcm_substream; + + memset(&pcm_substream, 0, sizeof(pcm_substream)); + if (substream) { + hstream = substream->runtime->private_data; + bus = hstream->bus; + rtd = snd_pcm_substream_chip(substream); + link_dev = snd_soc_dai_get_dma_data(dai, substream); + snd_hdac_ext_stream_decouple(bus, link_dev, false); + name = rtd->codec_dai->component->name; + link = snd_hdac_ext_bus_get_link(bus, name); + if (!link) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_tag = hdac_stream(link_dev)->stream_tag; + snd_hdac_ext_link_clear_stream_id(link, stream_tag); + } + + link_dev->link_prepared = 0; + } else { + /* release all hda streams when dai link is unloaded */ + pcm_substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + stream = snd_soc_dai_get_dma_data(dai, &pcm_substream); + if (stream) { + snd_soc_dai_set_dma_data(dai, &pcm_substream, NULL); + snd_hdac_ext_stream_release(stream, + HDAC_EXT_STREAM_TYPE_LINK); + } + + pcm_substream.stream = SNDRV_PCM_STREAM_CAPTURE; + stream = snd_soc_dai_get_dma_data(dai, &pcm_substream); + if (stream) { + snd_soc_dai_set_dma_data(dai, &pcm_substream, NULL); + snd_hdac_ext_stream_release(stream, + HDAC_EXT_STREAM_TYPE_LINK); + } + } + + return 0; +} + +static const struct snd_soc_dai_ops hda_link_dai_ops = { + .hw_params = hda_link_hw_params, + .hw_free = hda_link_hw_free, + .trigger = hda_link_pcm_trigger, + .prepare = hda_link_pcm_prepare, + .get_channel_map = hda_link_dma_get_channels, +}; +#endif + +/* + * common dai driver for skl+ platforms. + * some products who use this DAI array only physically have a subset of + * the DAIs, but no harm is done here by adding the whole set. + */ +struct snd_soc_dai_driver skl_dai[] = { +{ + .name = "SSP0 Pin", +}, +{ + .name = "SSP1 Pin", +}, +{ + .name = "SSP2 Pin", +}, +{ + .name = "SSP3 Pin", +}, +{ + .name = "SSP4 Pin", +}, +{ + .name = "SSP5 Pin", +}, +{ + .name = "DMIC01 Pin", +}, +{ + .name = "DMIC16k Pin", +}, +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +{ + .name = "iDisp1 Pin", + .ops = &hda_link_dai_ops, +}, +{ + .name = "iDisp2 Pin", + .ops = &hda_link_dai_ops, +}, +{ + .name = "iDisp3 Pin", + .ops = &hda_link_dai_ops, +}, +{ + .name = "Analog CPU DAI", + .ops = &hda_link_dai_ops, +}, +{ + .name = "Digital CPU DAI", + .ops = &hda_link_dai_ops, +}, +{ + .name = "Alt Analog CPU DAI", + .ops = &hda_link_dai_ops, +}, +#endif +}; diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c new file mode 100644 index 000000000000..5b73115a0b78 --- /dev/null +++ b/sound/soc/sof/intel/hda-dsp.c @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <sound/hdaudio_ext.h> +#include <sound/hda_register.h> +#include "../ops.h" +#include "hda.h" + +/* + * DSP Core control. + */ + +int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + u32 adspcs; + u32 reset; + int ret; + + /* set reset bits for cores */ + reset = HDA_DSP_ADSPCS_CRST_MASK(core_mask); + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, + reset, reset), + + /* poll with timeout to check if operation successful */ + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, adspcs, + ((adspcs & reset) == reset), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_RESET_TIMEOUT_US); + + /* has core entered reset ? */ + adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS); + if ((adspcs & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) != + HDA_DSP_ADSPCS_CRST_MASK(core_mask)) { + dev_err(sdev->dev, + "error: reset enter failed: core_mask %x adspcs 0x%x\n", + core_mask, adspcs); + ret = -EIO; + } + + return ret; +} + +int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + unsigned int crst; + u32 adspcs; + int ret; + + /* clear reset bits for cores */ + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_CRST_MASK(core_mask), + 0); + + /* poll with timeout to check if operation successful */ + crst = HDA_DSP_ADSPCS_CRST_MASK(core_mask); + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, adspcs, + !(adspcs & crst), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_RESET_TIMEOUT_US); + + /* has core left reset ? */ + adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS); + if ((adspcs & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) != 0) { + dev_err(sdev->dev, + "error: reset leave failed: core_mask %x adspcs 0x%x\n", + core_mask, adspcs); + ret = -EIO; + } + + return ret; +} + +int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + /* stall core */ + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_CSTALL_MASK(core_mask), + HDA_DSP_ADSPCS_CSTALL_MASK(core_mask)); + + /* set reset state */ + return hda_dsp_core_reset_enter(sdev, core_mask); +} + +int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + int ret; + + /* leave reset state */ + ret = hda_dsp_core_reset_leave(sdev, core_mask); + if (ret < 0) + return ret; + + /* run core */ + dev_dbg(sdev->dev, "unstall/run core: core_mask = %x\n", core_mask); + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_CSTALL_MASK(core_mask), + 0); + + /* is core now running ? */ + if (!hda_dsp_core_is_enabled(sdev, core_mask)) { + hda_dsp_core_stall_reset(sdev, core_mask); + dev_err(sdev->dev, "error: DSP start core failed: core_mask %x\n", + core_mask); + ret = -EIO; + } + + return ret; +} + +/* + * Power Management. + */ + +int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + unsigned int cpa; + u32 adspcs; + int ret; + + /* update bits */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_SPA_MASK(core_mask), + HDA_DSP_ADSPCS_SPA_MASK(core_mask)); + + /* poll with timeout to check if operation successful */ + cpa = HDA_DSP_ADSPCS_CPA_MASK(core_mask); + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, adspcs, + (adspcs & cpa) == cpa, + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_RESET_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "error: timeout on core powerup\n"); + + /* did core power up ? */ + adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS); + if ((adspcs & HDA_DSP_ADSPCS_CPA_MASK(core_mask)) != + HDA_DSP_ADSPCS_CPA_MASK(core_mask)) { + dev_err(sdev->dev, + "error: power up core failed core_mask %xadspcs 0x%x\n", + core_mask, adspcs); + ret = -EIO; + } + + return ret; +} + +int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + u32 adspcs; + + /* update bits */ + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_SPA_MASK(core_mask), 0); + + return snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPCS, adspcs, + !(adspcs & HDA_DSP_ADSPCS_SPA_MASK(core_mask)), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC); +} + +bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, + unsigned int core_mask) +{ + int val; + bool is_enable; + + val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS); + + is_enable = ((val & HDA_DSP_ADSPCS_CPA_MASK(core_mask)) && + (val & HDA_DSP_ADSPCS_SPA_MASK(core_mask)) && + !(val & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) && + !(val & HDA_DSP_ADSPCS_CSTALL_MASK(core_mask))); + + dev_dbg(sdev->dev, "DSP core(s) enabled? %d : core_mask %x\n", + is_enable, core_mask); + + return is_enable; +} + +int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + int ret; + + /* return if core is already enabled */ + if (hda_dsp_core_is_enabled(sdev, core_mask)) + return 0; + + /* power up */ + ret = hda_dsp_core_power_up(sdev, core_mask); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core power up failed: core_mask %x\n", + core_mask); + return ret; + } + + return hda_dsp_core_run(sdev, core_mask); +} + +int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev, + unsigned int core_mask) +{ + int ret; + + /* place core in reset prior to power down */ + ret = hda_dsp_core_stall_reset(sdev, core_mask); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core reset failed: core_mask %x\n", + core_mask); + return ret; + } + + /* power down core */ + ret = hda_dsp_core_power_down(sdev, core_mask); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core power down fail mask %x: %d\n", + core_mask, ret); + return ret; + } + + /* make sure we are in OFF state */ + if (hda_dsp_core_is_enabled(sdev, core_mask)) { + dev_err(sdev->dev, "error: dsp core disable fail mask %x: %d\n", + core_mask, ret); + ret = -EIO; + } + + return ret; +} + +void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + + /* enable IPC DONE and BUSY interrupts */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, + HDA_DSP_REG_HIPCCTL_DONE | HDA_DSP_REG_HIPCCTL_BUSY, + HDA_DSP_REG_HIPCCTL_DONE | HDA_DSP_REG_HIPCCTL_BUSY); + + /* enable IPC interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, + HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC); +} + +void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + + /* disable IPC interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, + HDA_DSP_ADSPIC_IPC, 0); + + /* disable IPC BUSY and DONE interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, + HDA_DSP_REG_HIPCCTL_BUSY | HDA_DSP_REG_HIPCCTL_DONE, 0); +} + +static int hda_suspend(struct snd_sof_dev *sdev, int state) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + struct hdac_bus *bus = sof_to_bus(sdev); +#endif + int ret; + + /* disable IPC interrupts */ + hda_dsp_ipc_int_disable(sdev); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* power down all hda link */ + snd_hdac_ext_bus_link_power_down_all(bus); +#endif + + /* power down DSP */ + ret = hda_dsp_core_reset_power_down(sdev, chip->cores_mask); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to power down core during suspend\n"); + return ret; + } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* disable ppcap interrupt */ + snd_hdac_ext_bus_ppcap_int_enable(bus, false); + snd_hdac_ext_bus_ppcap_enable(bus, false); + + /* disable hda bus irq and i/o */ + snd_hdac_bus_stop_chip(bus); +#else + /* disable ppcap interrupt */ + hda_dsp_ctrl_ppcap_enable(sdev, false); + hda_dsp_ctrl_ppcap_int_enable(sdev, false); + + /* disable hda bus irq */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, + 0); +#endif + + /* disable LP retention mode */ + snd_sof_pci_update_bits(sdev, PCI_PGCTL, + PCI_PGCTL_LSRMD_MASK, PCI_PGCTL_LSRMD_MASK); + + /* reset controller */ + ret = hda_dsp_ctrl_link_reset(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to reset controller during suspend\n"); + return ret; + } + + return 0; +} + +static int hda_resume(struct snd_sof_dev *sdev) +{ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_ext_link *hlink = NULL; +#endif + int ret; + + /* + * clear TCSEL to clear playback on some HD Audio + * codecs. PCI TCSEL is defined in the Intel manuals. + */ + snd_sof_pci_update_bits(sdev, PCI_TCSEL, 0x07, 0); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* reset and start hda controller */ + ret = hda_dsp_ctrl_init_chip(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to start controller after resume\n"); + return ret; + } + + hda_dsp_ctrl_misc_clock_gating(sdev, false); + + /* Reset stream-to-link mapping */ + list_for_each_entry(hlink, &bus->hlink_list, list) + bus->io_ops->reg_writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV); + + hda_dsp_ctrl_misc_clock_gating(sdev, true); + + /* enable ppcap interrupt */ + snd_hdac_ext_bus_ppcap_enable(bus, true); + snd_hdac_ext_bus_ppcap_int_enable(bus, true); +#else + + hda_dsp_ctrl_misc_clock_gating(sdev, false); + + /* reset controller */ + ret = hda_dsp_ctrl_link_reset(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to reset controller during resume\n"); + return ret; + } + + /* take controller out of reset */ + ret = hda_dsp_ctrl_link_reset(sdev, false); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to ready controller during resume\n"); + return ret; + } + + /* enable hda bus irq */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN); + + hda_dsp_ctrl_misc_clock_gating(sdev, true); + + /* enable ppcap interrupt */ + hda_dsp_ctrl_ppcap_enable(sdev, true); + hda_dsp_ctrl_ppcap_int_enable(sdev, true); +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* turn off the links that were off before suspend */ + list_for_each_entry(hlink, &bus->hlink_list, list) { + if (!hlink->ref_count) + snd_hdac_ext_bus_link_power_down(hlink); + } + + /* check dma status and clean up CORB/RIRB buffers */ + if (!bus->cmd_dma_state) + snd_hdac_bus_stop_cmd_io(bus); +#endif + + return 0; +} + +int hda_dsp_resume(struct snd_sof_dev *sdev) +{ + /* init hda controller. DSP cores will be powered up during fw boot */ + return hda_resume(sdev); +} + +int hda_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + /* init hda controller. DSP cores will be powered up during fw boot */ + return hda_resume(sdev); +} + +int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev, int state) +{ + /* stop hda controller and power dsp off */ + return hda_suspend(sdev, state); +} + +int hda_dsp_suspend(struct snd_sof_dev *sdev, int state) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + int ret; + + /* stop hda controller and power dsp off */ + ret = hda_suspend(sdev, state); + if (ret < 0) { + dev_err(bus->dev, "error: suspending dsp\n"); + return ret; + } + + return 0; +} + +void hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct sof_intel_hda_stream *hda_stream; + struct hdac_ext_stream *stream; + struct hdac_stream *s; + + /* set internal flag for BE */ + list_for_each_entry(s, &bus->stream_list, list) { + stream = stream_to_hdac_ext_stream(s); + hda_stream = container_of(stream, struct sof_intel_hda_stream, + hda_stream); + hda_stream->hw_params_upon_resume = 1; + } +} diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c new file mode 100644 index 000000000000..73ead7070cde --- /dev/null +++ b/sound/soc/sof/intel/hda-ipc.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include "../ops.h" +#include "hda.h" + +static void hda_dsp_ipc_host_done(struct snd_sof_dev *sdev) +{ + /* + * tell DSP cmd is done - clear busy + * interrupt and send reply msg to dsp + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCT, + HDA_DSP_REG_HIPCT_BUSY, + HDA_DSP_REG_HIPCT_BUSY); + + /* unmask BUSY interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_BUSY, + HDA_DSP_REG_HIPCCTL_BUSY); +} + +static void hda_dsp_ipc_dsp_done(struct snd_sof_dev *sdev) +{ + /* + * set DONE bit - tell DSP we have received the reply msg + * from DSP, and processed it, don't send more reply to host + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCIE, + HDA_DSP_REG_HIPCIE_DONE, + HDA_DSP_REG_HIPCIE_DONE); + + /* unmask Done interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_DONE, + HDA_DSP_REG_HIPCCTL_DONE); +} + +int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + u32 cmd = msg->header; + + /* send IPC message to DSP */ + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI, + cmd | HDA_DSP_REG_HIPCI_BUSY); + + return 0; +} + +void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + struct sof_ipc_cmd_hdr *hdr; + unsigned long flags; + int ret = 0; + + /* + * Sometimes, there is unexpected reply ipc arriving. The reply + * ipc belongs to none of the ipcs sent from driver. + * In this case, the driver must ignore the ipc. + */ + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); + return; + } + spin_lock_irqsave(&sdev->ipc_lock, flags); + + hdr = msg->msg_data; + if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE)) { + /* + * memory windows are powered off before sending IPC reply, + * so we can't read the mailbox for CTX_SAVE reply. + */ + reply.error = 0; + reply.hdr.cmd = SOF_IPC_GLB_REPLY; + reply.hdr.size = sizeof(reply); + memcpy(msg->reply_data, &reply, sizeof(reply)); + goto out; + } + + /* get IPC reply from DSP in the mailbox */ + sof_mailbox_read(sdev, sdev->host_box.offset, &reply, + sizeof(reply)); + + if (reply.error < 0) { + memcpy(msg->reply_data, &reply, sizeof(reply)); + ret = reply.error; + } else { + /* reply correct size ? */ + if (reply.hdr.size != msg->reply_size) { + dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", + msg->reply_size, reply.hdr.size); + ret = -EINVAL; + } + + /* read the message */ + if (msg->reply_size > 0) + sof_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + +out: + msg->reply_error = ret; + + spin_unlock_irqrestore(&sdev->ipc_lock, flags); +} + +static bool hda_dsp_ipc_is_sof(uint32_t msg) +{ + return (msg & (HDA_DSP_IPC_PURGE_FW | 0xf << 9)) != msg || + (msg & HDA_DSP_IPC_PURGE_FW) != HDA_DSP_IPC_PURGE_FW; +} + +/* IPC handler thread */ +irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + irqreturn_t ret = IRQ_NONE; + u32 hipci; + u32 hipcie; + u32 hipct; + u32 hipcte; + u32 hipcctl; + u32 msg; + u32 msg_ext; + + /* read IPC status */ + hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCIE); + hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); + hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL); + + /* reenable IPC interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, + HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC); + + /* is this a reply message from the DSP */ + if (hipcie & HDA_DSP_REG_HIPCIE_DONE && + hipcctl & HDA_DSP_REG_HIPCCTL_DONE) { + hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCI); + msg = hipci & HDA_DSP_REG_HIPCI_MSG_MASK; + msg_ext = hipcie & HDA_DSP_REG_HIPCIE_MSG_MASK; + + dev_vdbg(sdev->dev, + "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n", + msg, msg_ext); + + /* mask Done interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_DONE, 0); + + /* handle immediate reply from DSP core - ignore ROM messages */ + if (hda_dsp_ipc_is_sof(msg)) { + hda_dsp_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, msg); + } + + /* wake up sleeper if we are loading code */ + if (sdev->code_loading) { + sdev->code_loading = 0; + wake_up(&sdev->waitq); + } + + /* set the done bit */ + hda_dsp_ipc_dsp_done(sdev); + + ret = IRQ_HANDLED; + } + + /* is this a new message from DSP */ + if (hipct & HDA_DSP_REG_HIPCT_BUSY && + hipcctl & HDA_DSP_REG_HIPCCTL_BUSY) { + + hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCTE); + msg = hipct & HDA_DSP_REG_HIPCT_MSG_MASK; + msg_ext = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK; + + dev_vdbg(sdev->dev, + "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n", + msg, msg_ext); + + /* mask BUSY interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_BUSY, 0); + + /* handle messages from DSP */ + if ((hipct & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + /* this is a PANIC message !! */ + snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext)); + } else { + /* normal message - process normally */ + snd_sof_ipc_msgs_rx(sdev); + } + + hda_dsp_ipc_host_done(sdev); + + ret = IRQ_HANDLED; + } + + return ret; +} + +/* is this IRQ for ADSP ? - we only care about IPC here */ +irqreturn_t hda_dsp_ipc_irq_handler(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + int ret = IRQ_NONE; + u32 irq_status; + + spin_lock(&sdev->hw_lock); + + /* store status */ + irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS); + dev_vdbg(sdev->dev, "irq handler: irq_status:0x%x\n", irq_status); + + /* invalid message ? */ + if (irq_status == 0xffffffff) + goto out; + + /* IPC message ? */ + if (irq_status & HDA_DSP_ADSPIS_IPC) { + /* disable IPC interrupt */ + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + HDA_DSP_REG_ADSPIC, + HDA_DSP_ADSPIC_IPC, 0); + ret = IRQ_WAKE_THREAD; + } + +out: + spin_unlock(&sdev->hw_lock); + return ret; +} + +/* IPC Firmware ready */ + +static void ipc_get_windows(struct snd_sof_dev *sdev) +{ + struct sof_ipc_window_elem *elem; + u32 outbox_offset = 0; + u32 stream_offset = 0; + u32 inbox_offset = 0; + u32 outbox_size = 0; + u32 stream_size = 0; + u32 inbox_size = 0; + int i; + + if (!sdev->info_window) { + dev_err(sdev->dev, "error: have no window info\n"); + return; + } + + for (i = 0; i < sdev->info_window->num_windows; i++) { + elem = &sdev->info_window->window[i]; + + switch (elem->type) { + case SOF_IPC_REGION_UPBOX: + inbox_offset = + elem->offset + SRAM_WINDOW_OFFSET(elem->id); + inbox_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[HDA_DSP_BAR] + + inbox_offset, + elem->size, "inbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DOWNBOX: + outbox_offset = + elem->offset + SRAM_WINDOW_OFFSET(elem->id); + outbox_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[HDA_DSP_BAR] + + outbox_offset, + elem->size, "outbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_TRACE: + snd_sof_debugfs_io_item(sdev, + sdev->bar[HDA_DSP_BAR] + + elem->offset + + SRAM_WINDOW_OFFSET + (elem->id), + elem->size, "etrace", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DEBUG: + snd_sof_debugfs_io_item(sdev, + sdev->bar[HDA_DSP_BAR] + + elem->offset + + SRAM_WINDOW_OFFSET + (elem->id), + elem->size, "debug", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_STREAM: + stream_offset = + elem->offset + SRAM_WINDOW_OFFSET(elem->id); + stream_size = elem->size; + snd_sof_debugfs_io_item(sdev, + sdev->bar[HDA_DSP_BAR] + + elem->offset + + SRAM_WINDOW_OFFSET + (elem->id), + elem->size, "stream", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_REGS: + snd_sof_debugfs_io_item(sdev, + sdev->bar[HDA_DSP_BAR] + + elem->offset + + SRAM_WINDOW_OFFSET + (elem->id), + elem->size, "regs", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_EXCEPTION: + sdev->dsp_oops_offset = elem->offset + + SRAM_WINDOW_OFFSET(elem->id); + snd_sof_debugfs_io_item(sdev, + sdev->bar[HDA_DSP_BAR] + + elem->offset + + SRAM_WINDOW_OFFSET + (elem->id), + elem->size, "exception", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + default: + dev_err(sdev->dev, "error: get illegal window info\n"); + return; + } + } + + if (outbox_size == 0 || inbox_size == 0) { + dev_err(sdev->dev, "error: get illegal mailbox window\n"); + return; + } + + snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size, + outbox_offset, outbox_size); + sdev->stream_box.offset = stream_offset; + sdev->stream_box.size = stream_size; + + dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", + inbox_offset, inbox_size); + dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", + outbox_offset, outbox_size); + dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", + stream_offset, stream_size); +} + +/* check for ABI compatibility and create memory windows on first boot */ +int hda_dsp_ipc_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; + u32 offset; + int ret; + + /* mailbox must be on 4k boundary */ + offset = HDA_DSP_MBOX_UPLINK_OFFSET; + + dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n", + msg_id, offset); + + /* no need to re-check version/ABI for subsequent boots */ + if (!sdev->first_boot) + return 0; + + /* copy data from the DSP FW ready offset */ + sof_block_read(sdev, sdev->mmio_bar, offset, fw_ready, + sizeof(*fw_ready)); + + /* make sure ABI version is compatible */ + ret = snd_sof_ipc_valid(sdev); + if (ret < 0) + return ret; + + /* now check for extended data */ + snd_sof_fw_parse_ext_data(sdev, sdev->mmio_bar, + HDA_DSP_MBOX_UPLINK_OFFSET + + sizeof(struct sof_ipc_fw_ready)); + + ipc_get_windows(sdev); + + return 0; +} + +void hda_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + if (!substream || !sdev->stream_box.size) { + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); + } else { + struct hdac_stream *hstream = substream->runtime->private_data; + struct sof_intel_hda_stream *hda_stream; + + hda_stream = container_of(hstream, + struct sof_intel_hda_stream, + hda_stream.hstream); + + /* The stream might already be closed */ + if (hstream) + sof_mailbox_read(sdev, hda_stream->stream.posn_offset, + p, sz); + } +} + +int hda_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct sof_intel_hda_stream *hda_stream; + /* validate offset */ + size_t posn_offset = reply->posn_offset; + + hda_stream = container_of(hstream, struct sof_intel_hda_stream, + hda_stream.hstream); + + /* check for unaligned offset or overflow */ + if (posn_offset > sdev->stream_box.size || + posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) + return -EINVAL; + + hda_stream->stream.posn_offset = sdev->stream_box.offset + posn_offset; + + dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu", + substream->stream, hda_stream->stream.posn_offset); + + return 0; +} diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c new file mode 100644 index 000000000000..6427f0b3a2f1 --- /dev/null +++ b/sound/soc/sof/intel/hda-loader.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for HDA DSP code loader + */ + +#include <linux/firmware.h> +#include <sound/hdaudio_ext.h> +#include <sound/sof.h> +#include "../ops.h" +#include "hda.h" + +#define HDA_FW_BOOT_ATTEMPTS 3 + +static int cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format, + unsigned int size, struct snd_dma_buffer *dmab, + int direction) +{ + struct hdac_ext_stream *dsp_stream; + struct hdac_stream *hstream; + struct pci_dev *pci = to_pci_dev(sdev->dev); + int ret; + + if (direction != SNDRV_PCM_STREAM_PLAYBACK) { + dev_err(sdev->dev, "error: code loading DMA is playback only\n"); + return -EINVAL; + } + + dsp_stream = hda_dsp_stream_get(sdev, direction); + + if (!dsp_stream) { + dev_err(sdev->dev, "error: no stream available\n"); + return -ENODEV; + } + hstream = &dsp_stream->hstream; + + /* allocate DMA buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, &pci->dev, size, dmab); + if (ret < 0) { + dev_err(sdev->dev, "error: memory alloc failed: %x\n", ret); + goto error; + } + + hstream->period_bytes = 0;/* initialize period_bytes */ + hstream->format_val = format; + hstream->bufsize = size; + + ret = hda_dsp_stream_hw_params(sdev, dsp_stream, dmab, NULL); + if (ret < 0) { + dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + goto error; + } + + hda_dsp_stream_spib_config(sdev, dsp_stream, HDA_DSP_SPIB_ENABLE, size); + + return hstream->stream_tag; + +error: + hda_dsp_stream_put(sdev, direction, hstream->stream_tag); + snd_dma_free_pages(dmab); + return ret; +} + +/* + * first boot sequence has some extra steps. core 0 waits for power + * status on core 1, so power up core 1 also momentarily, keep it in + * reset/stall and then turn it off + */ +static int cl_dsp_init(struct snd_sof_dev *sdev, const void *fwdata, + u32 fwsize, int stream_tag) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + unsigned int status; + int ret; + int i; + + /* step 1: power up corex */ + ret = hda_dsp_core_power_up(sdev, chip->cores_mask); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core 0/1 power up failed\n"); + goto err; + } + + /* DSP is powered up, set all SSPs to slave mode */ + for (i = 0; i < chip->ssp_count; i++) { + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + chip->ssp_base_offset + + i * SSP_DEV_MEM_SIZE + + SSP_SSC1_OFFSET, + SSP_SET_SLAVE, + SSP_SET_SLAVE); + } + + /* step 2: purge FW request */ + snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, + chip->ipc_req_mask | (HDA_DSP_IPC_PURGE_FW | + ((stream_tag - 1) << 9))); + + /* step 3: unset core 0 reset state & unstall/run core 0 */ + ret = hda_dsp_core_run(sdev, HDA_DSP_CORE_MASK(0)); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core start failed %d\n", ret); + ret = -EIO; + goto err; + } + + /* step 4: wait for IPC DONE bit from ROM */ + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + chip->ipc_ack, status, + ((status & chip->ipc_ack_mask) + == chip->ipc_ack_mask), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_INIT_TIMEOUT_US); + + if (ret < 0) { + dev_err(sdev->dev, "error: waiting for HIPCIE done\n"); + goto err; + } + + /* step 5: power down corex */ + ret = hda_dsp_core_power_down(sdev, + chip->cores_mask & ~(HDA_DSP_CORE_MASK(0))); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core x power down failed\n"); + goto err; + } + + /* step 6: enable IPC interrupts */ + hda_dsp_ipc_int_enable(sdev); + + /* step 7: wait for ROM init */ + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_ROM_STATUS, status, + ((status & HDA_DSP_ROM_STS_MASK) + == HDA_DSP_ROM_INIT), + HDA_DSP_REG_POLL_INTERVAL_US, + chip->rom_init_timeout * + USEC_PER_MSEC); + if (!ret) + return 0; + +err: + hda_dsp_dump(sdev, SOF_DBG_REGS | SOF_DBG_PCI | SOF_DBG_MBOX); + hda_dsp_core_reset_power_down(sdev, chip->cores_mask); + + return ret; +} + +static int cl_trigger(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, int cmd) +{ + struct hdac_stream *hstream = &stream->hstream; + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + + /* code loader is special case that reuses stream ops */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + wait_event_timeout(sdev->waitq, !sdev->code_loading, + HDA_DSP_CL_TRIGGER_TIMEOUT); + + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + 1 << hstream->index, + 1 << hstream->index); + + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK); + + hstream->running = true; + return 0; + default: + return hda_dsp_stream_trigger(sdev, stream, cmd); + } +} + +static struct hdac_ext_stream *get_stream_with_tag(struct snd_sof_dev *sdev, + int tag) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_stream *s; + + /* get stream with tag */ + list_for_each_entry(s, &bus->stream_list, list) { + if (s->direction == SNDRV_PCM_STREAM_PLAYBACK && + s->stream_tag == tag) { + return stream_to_hdac_ext_stream(s); + } + } + + return NULL; +} + +static int cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct hdac_ext_stream *stream) +{ + struct hdac_stream *hstream = &stream->hstream; + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret; + + ret = hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0); + + hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_PLAYBACK, + hstream->stream_tag); + hstream->running = 0; + hstream->substream = NULL; + + /* reset BDL address */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, 0); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, 0); + + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, sd_offset, 0); + snd_dma_free_pages(dmab); + dmab->area = NULL; + hstream->bufsize = 0; + hstream->format_val = 0; + + return ret; +} + +static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream) +{ + unsigned int reg; + int ret, status; + + ret = cl_trigger(sdev, stream, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(sdev->dev, "error: DMA trigger start failed\n"); + return ret; + } + + status = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_ROM_STATUS, reg, + ((reg & HDA_DSP_ROM_STS_MASK) + == HDA_DSP_ROM_FW_ENTERED), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_BASEFW_TIMEOUT_US); + + ret = cl_trigger(sdev, stream, SNDRV_PCM_TRIGGER_STOP); + if (ret < 0) { + dev_err(sdev->dev, "error: DMA trigger stop failed\n"); + return ret; + } + + return status; +} + +int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const struct sof_dev_desc *desc = plat_data->desc; + const struct sof_intel_dsp_desc *chip_info; + struct hdac_ext_stream *stream; + struct firmware stripped_firmware; + int ret, ret1, tag, i; + + chip_info = desc->chip_info; + + stripped_firmware.data = plat_data->fw->data; + stripped_firmware.size = plat_data->fw->size; + + /* init for booting wait */ + init_waitqueue_head(&sdev->boot_wait); + sdev->boot_complete = false; + + /* prepare DMA for code loader stream */ + tag = cl_stream_prepare(sdev, 0x40, stripped_firmware.size, + &sdev->dmab, SNDRV_PCM_STREAM_PLAYBACK); + + if (tag < 0) { + dev_err(sdev->dev, "error: dma prepare for fw loading err: %x\n", + tag); + return tag; + } + + /* get stream with tag */ + stream = get_stream_with_tag(sdev, tag); + if (!stream) { + dev_err(sdev->dev, + "error: could not get stream with stream tag %d\n", + tag); + ret = -ENODEV; + goto err; + } + + memcpy(sdev->dmab.area, stripped_firmware.data, + stripped_firmware.size); + + /* try ROM init a few times before giving up */ + for (i = 0; i < HDA_FW_BOOT_ATTEMPTS; i++) { + ret = cl_dsp_init(sdev, stripped_firmware.data, + stripped_firmware.size, tag); + + /* don't retry anymore if successful */ + if (!ret) + break; + + dev_err(sdev->dev, "error: Error code=0x%x: FW status=0x%x\n", + snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_ROM_ERROR), + snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_ROM_STATUS)); + dev_err(sdev->dev, "error: iteration %d of Core En/ROM load failed: %d\n", + i, ret); + } + + if (i == HDA_FW_BOOT_ATTEMPTS) { + dev_err(sdev->dev, "error: dsp init failed after %d attempts with err: %d\n", + i, ret); + goto cleanup; + } + + /* + * at this point DSP ROM has been initialized and + * should be ready for code loading and firmware boot + */ + ret = cl_copy_fw(sdev, stream); + if (!ret) + dev_dbg(sdev->dev, "Firmware download successful, booting...\n"); + else + dev_err(sdev->dev, "error: load fw failed ret: %d\n", ret); + +cleanup: + /* + * Perform codeloader stream cleanup. + * This should be done even if firmware loading fails. + */ + ret1 = cl_cleanup(sdev, &sdev->dmab, stream); + if (ret1 < 0) { + dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n"); + + /* set return value to indicate cleanup failure */ + ret = ret1; + } + + /* + * return master core id if both fw copy + * and stream clean up are successful + */ + if (!ret) + return chip_info->init_core_mask; + + /* dump dsp registers and disable DSP upon error */ +err: + hda_dsp_dump(sdev, SOF_DBG_REGS | SOF_DBG_PCI | SOF_DBG_MBOX); + + /* disable DSP */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, + SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_GPROCEN, 0); + return ret; +} + +/* pre fw run operations */ +int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev) +{ + /* disable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, false); +} + +/* post fw run operations */ +int hda_dsp_post_fw_run(struct snd_sof_dev *sdev) +{ + /* re-enable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, true); +} diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c new file mode 100644 index 000000000000..9b730f183529 --- /dev/null +++ b/sound/soc/sof/intel/hda-pcm.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <sound/hda_register.h> +#include <sound/pcm_params.h> +#include "../ops.h" +#include "hda.h" + +#define SDnFMT_BASE(x) ((x) << 14) +#define SDnFMT_MULT(x) (((x) - 1) << 11) +#define SDnFMT_DIV(x) (((x) - 1) << 8) +#define SDnFMT_BITS(x) ((x) << 4) +#define SDnFMT_CHAN(x) ((x) << 0) + +static inline u32 get_mult_div(struct snd_sof_dev *sdev, int rate) +{ + switch (rate) { + case 8000: + return SDnFMT_DIV(6); + case 9600: + return SDnFMT_DIV(5); + case 11025: + return SDnFMT_BASE(1) | SDnFMT_DIV(4); + case 16000: + return SDnFMT_DIV(3); + case 22050: + return SDnFMT_BASE(1) | SDnFMT_DIV(2); + case 32000: + return SDnFMT_DIV(3) | SDnFMT_MULT(2); + case 44100: + return SDnFMT_BASE(1); + case 48000: + return 0; + case 88200: + return SDnFMT_BASE(1) | SDnFMT_MULT(2); + case 96000: + return SDnFMT_MULT(2); + case 176400: + return SDnFMT_BASE(1) | SDnFMT_MULT(4); + case 192000: + return SDnFMT_MULT(4); + default: + dev_warn(sdev->dev, "can't find div rate %d using 48kHz\n", + rate); + return 0; /* use 48KHz if not found */ + } +}; + +static inline u32 get_bits(struct snd_sof_dev *sdev, int sample_bits) +{ + switch (sample_bits) { + case 8: + return SDnFMT_BITS(0); + case 16: + return SDnFMT_BITS(1); + case 20: + return SDnFMT_BITS(2); + case 24: + return SDnFMT_BITS(3); + case 32: + return SDnFMT_BITS(4); + default: + dev_warn(sdev->dev, "can't find %d bits using 16bit\n", + sample_bits); + return SDnFMT_BITS(1); /* use 16bits format if not found */ + } +}; + +int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream); + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct snd_dma_buffer *dmab; + int ret; + u32 size, rate, bits; + + size = params_buffer_bytes(params); + rate = get_mult_div(sdev, params_rate(params)); + bits = get_bits(sdev, params_width(params)); + + hstream->substream = substream; + + dmab = substream->runtime->dma_buffer_p; + + hstream->format_val = rate | bits | (params_channels(params) - 1); + hstream->bufsize = size; + hstream->period_bytes = params_period_bytes(params); + hstream->no_period_wakeup = + (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) && + (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP); + + ret = hda_dsp_stream_hw_params(sdev, stream, dmab, params); + if (ret < 0) { + dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + return ret; + } + + /* disable SPIB, to enable buffer wrap for stream */ + hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0); + + /* set host_period_bytes to 0 if no IPC position */ + if (hda && hda->no_ipc_position) + ipc_params->host_period_bytes = 0; + + ipc_params->stream_tag = hstream->stream_tag; + + return 0; +} + +int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, int cmd) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream); + + return hda_dsp_stream_trigger(sdev, stream, cmd); +} + +snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hdac_stream *hstream = substream->runtime->private_data; + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct snd_sof_pcm *spcm; + snd_pcm_uframes_t pos; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) { + dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return 0; + } + + if (hda && !hda->no_ipc_position) { + /* read position from IPC position */ + pos = spcm->stream[substream->stream].posn.host_posn; + goto found; + } + + /* + * DPIB/posbuf position mode: + * For Playback, Use DPIB register from HDA space which + * reflects the actual data transferred. + * For Capture, Use the position buffer for pointer, as DPIB + * is not accurate enough, its update may be completed + * earlier than the data written to DDR. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pos = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hstream->index)); + } else { + /* + * For capture stream, we need more workaround to fix the + * position incorrect issue: + * + * 1. Wait at least 20us before reading position buffer after + * the interrupt generated(IOC), to make sure position update + * happens on frame boundary i.e. 20.833uSec for 48KHz. + * 2. Perform a dummy Read to DPIB register to flush DMA + * position value. + * 3. Read the DMA Position from posbuf. Now the readback + * value should be >= period boundary. + */ + usleep_range(20, 21); + snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hstream->index)); + pos = snd_hdac_stream_get_pos_posbuf(hstream); + } + + if (pos >= hstream->bufsize) + pos = 0; + +found: + pos = bytes_to_frames(substream->runtime, pos); + + dev_vdbg(sdev->dev, "PCM: stream %d dir %d position %lu\n", + hstream->index, substream->stream, pos); + return pos; +} + +int hda_dsp_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *dsp_stream; + int direction = substream->stream; + + dsp_stream = hda_dsp_stream_get(sdev, direction); + + if (!dsp_stream) { + dev_err(sdev->dev, "error: no stream available\n"); + return -ENODEV; + } + + /* binding pcm substream to hda stream */ + substream->runtime->private_data = &dsp_stream->hstream; + return 0; +} + +int hda_dsp_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + int direction = substream->stream; + int ret; + + ret = hda_dsp_stream_put(sdev, direction, hstream->stream_tag); + + if (ret) { + dev_dbg(sdev->dev, "stream %s not opened!\n", substream->name); + return -ENODEV; + } + + /* unbinding pcm substream to hda stream */ + substream->runtime->private_data = NULL; + return 0; +} diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c new file mode 100644 index 000000000000..c92006f89499 --- /dev/null +++ b/sound/soc/sof/intel/hda-stream.c @@ -0,0 +1,701 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <linux/pm_runtime.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_register.h> +#include <sound/sof.h> +#include "../ops.h" +#include "hda.h" + +/* + * set up one of BDL entries for a stream + */ +static int hda_setup_bdle(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + struct hdac_stream *stream, + struct sof_intel_dsp_bdl **bdlp, + int offset, int size, int ioc) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct sof_intel_dsp_bdl *bdl = *bdlp; + + while (size > 0) { + dma_addr_t addr; + int chunk; + + if (stream->frags >= HDA_DSP_MAX_BDL_ENTRIES) { + dev_err(sdev->dev, "error: stream frags exceeded\n"); + return -EINVAL; + } + + addr = snd_sgbuf_get_addr(dmab, offset); + /* program BDL addr */ + bdl->addr_l = cpu_to_le32(lower_32_bits(addr)); + bdl->addr_h = cpu_to_le32(upper_32_bits(addr)); + /* program BDL size */ + chunk = snd_sgbuf_get_chunk_size(dmab, offset, size); + /* one BDLE should not cross 4K boundary */ + if (bus->align_bdle_4k) { + u32 remain = 0x1000 - (offset & 0xfff); + + if (chunk > remain) + chunk = remain; + } + bdl->size = cpu_to_le32(chunk); + /* only program IOC when the whole segment is processed */ + size -= chunk; + bdl->ioc = (size || !ioc) ? 0 : cpu_to_le32(0x01); + bdl++; + stream->frags++; + offset += chunk; + + dev_vdbg(sdev->dev, "bdl, frags:%d, chunk size:0x%x;\n", + stream->frags, chunk); + } + + *bdlp = bdl; + return offset; +} + +/* + * set up Buffer Descriptor List (BDL) for host memory transfer + * BDL describes the location of the individual buffers and is little endian. + */ +int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + struct hdac_stream *stream) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct sof_intel_dsp_bdl *bdl; + int i, offset, period_bytes, periods; + int remain, ioc; + + period_bytes = stream->period_bytes; + dev_dbg(sdev->dev, "period_bytes:0x%x\n", period_bytes); + if (!period_bytes) + period_bytes = stream->bufsize; + + periods = stream->bufsize / period_bytes; + + dev_dbg(sdev->dev, "periods:%d\n", periods); + + remain = stream->bufsize % period_bytes; + if (remain) + periods++; + + /* program the initial BDL entries */ + bdl = (struct sof_intel_dsp_bdl *)stream->bdl.area; + offset = 0; + stream->frags = 0; + + /* + * set IOC if don't use position IPC + * and period_wakeup needed. + */ + ioc = hda->no_ipc_position ? + !stream->no_period_wakeup : 0; + + for (i = 0; i < periods; i++) { + if (i == (periods - 1) && remain) + /* set the last small entry */ + offset = hda_setup_bdle(sdev, dmab, + stream, &bdl, offset, + remain, 0); + else + offset = hda_setup_bdle(sdev, dmab, + stream, &bdl, offset, + period_bytes, ioc); + } + + return offset; +} + +int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, + int enable, u32 size) +{ + struct hdac_stream *hstream = &stream->hstream; + u32 mask; + + if (!sdev->bar[HDA_DSP_SPIB_BAR]) { + dev_err(sdev->dev, "error: address of spib capability is NULL\n"); + return -EINVAL; + } + + mask = (1 << hstream->index); + + /* enable/disable SPIB for the stream */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_SPIB_BAR, + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, mask, + enable << hstream->index); + + /* set the SPIB value */ + sof_io_write(sdev, stream->spib_addr, size); + + return 0; +} + +/* get next unused stream */ +struct hdac_ext_stream * +hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_ext_stream *stream = NULL; + struct hdac_stream *s; + + spin_lock_irq(&bus->reg_lock); + + /* get an unused stream */ + list_for_each_entry(s, &bus->stream_list, list) { + if (s->direction == direction && !s->opened) { + s->opened = true; + stream = stream_to_hdac_ext_stream(s); + break; + } + } + + spin_unlock_irq(&bus->reg_lock); + + /* stream found ? */ + if (!stream) + dev_err(sdev->dev, "error: no free %s streams\n", + direction == SNDRV_PCM_STREAM_PLAYBACK ? + "playback" : "capture"); + + return stream; +} + +/* free a stream */ +int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_stream *s; + + spin_lock_irq(&bus->reg_lock); + + /* find used stream */ + list_for_each_entry(s, &bus->stream_list, list) { + if (s->direction == direction && + s->opened && s->stream_tag == stream_tag) { + s->opened = false; + spin_unlock_irq(&bus->reg_lock); + return 0; + } + } + + spin_unlock_irq(&bus->reg_lock); + + dev_dbg(sdev->dev, "stream_tag %d not opened!\n", stream_tag); + return -ENODEV; +} + +int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, int cmd) +{ + struct hdac_stream *hstream = &stream->hstream; + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + + /* cmd must be for audio stream */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_START: + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + 1 << hstream->index, + 1 << hstream->index); + + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK); + + hstream->running = true; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset, + SOF_HDA_SD_CTL_DMA_START | + SOF_HDA_CL_DMA_SD_INT_MASK, 0x0); + + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, sd_offset + + SOF_HDA_ADSP_REG_CL_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK); + + hstream->running = false; + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + 1 << hstream->index, 0x0); + break; + default: + dev_err(sdev->dev, "error: unknown command: %d\n", cmd); + return -EINVAL; + } + + return 0; +} + +/* + * prepare for common hdac registers settings, for both code loader + * and normal stream. + */ +int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, + struct snd_dma_buffer *dmab, + struct snd_pcm_hw_params *params) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_stream *hstream = &stream->hstream; + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret, timeout = HDA_DSP_STREAM_RESET_TIMEOUT; + u32 val, mask; + + if (!stream) { + dev_err(sdev->dev, "error: no stream available\n"); + return -ENODEV; + } + + /* decouple host and link DMA */ + mask = 0x1 << hstream->index; + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, mask); + + if (!dmab) { + dev_err(sdev->dev, "error: no dma buffer allocated!\n"); + return -ENODEV; + } + + /* clear stream status */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_CL_DMA_SD_INT_MASK | + SOF_HDA_SD_CTL_DMA_START, 0); + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_CL_DMA_SD_INT_MASK); + + /* stream reset */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1, + 0x1); + udelay(3); + do { + val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + sd_offset); + if (val & 0x1) + break; + } while (--timeout); + if (timeout == 0) { + dev_err(sdev->dev, "error: stream reset failed\n"); + return -ETIMEDOUT; + } + + timeout = HDA_DSP_STREAM_RESET_TIMEOUT; + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1, + 0x0); + + /* wait for hardware to report that stream is out of reset */ + udelay(3); + do { + val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + sd_offset); + if ((val & 0x1) == 0) + break; + } while (--timeout); + if (timeout == 0) { + dev_err(sdev->dev, "error: timeout waiting for stream reset\n"); + return -ETIMEDOUT; + } + + if (hstream->posbuf) + *hstream->posbuf = 0; + + /* reset BDL address */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, + 0x0); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, + 0x0); + + /* clear stream status */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_CL_DMA_SD_INT_MASK | + SOF_HDA_SD_CTL_DMA_START, 0); + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_CL_DMA_SD_INT_MASK); + + hstream->frags = 0; + + ret = hda_dsp_stream_setup_bdl(sdev, dmab, hstream); + if (ret < 0) { + dev_err(sdev->dev, "error: set up of BDL failed\n"); + return ret; + } + + /* program stream tag to set up stream descriptor for DMA */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_CL_SD_CTL_STREAM_TAG_MASK, + hstream->stream_tag << + SOF_HDA_CL_SD_CTL_STREAM_TAG_SHIFT); + + /* program cyclic buffer length */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CBL, + hstream->bufsize); + + /* + * Recommended hardware programming sequence for HDAudio DMA format + * + * 1. Put DMA into coupled mode by clearing PPCTL.PROCEN bit + * for corresponding stream index before the time of writing + * format to SDxFMT register. + * 2. Write SDxFMT + * 3. Set PPCTL.PROCEN bit for corresponding stream index to + * enable decoupled mode + */ + + /* couple host and link DMA, disable DSP features */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, 0); + + /* program stream format */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + + SOF_HDA_ADSP_REG_CL_SD_FORMAT, + 0xffff, hstream->format_val); + + /* decouple host and link DMA, enable DSP features */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, mask); + + /* program last valid index */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_LVI, + 0xffff, (hstream->frags - 1)); + + /* program BDL address */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, + (u32)hstream->bdl.addr); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, + upper_32_bits(hstream->bdl.addr)); + + /* enable position buffer */ + if (!(snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE) + & SOF_HDA_ADSP_DPLBASE_ENABLE)) { + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPUBASE, + upper_32_bits(bus->posbuf.addr)); + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE, + (u32)bus->posbuf.addr | + SOF_HDA_ADSP_DPLBASE_ENABLE); + } + + /* set interrupt enable bits */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_CL_DMA_SD_INT_MASK); + + /* read FIFO size */ + if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) { + hstream->fifo_size = + snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + sd_offset + + SOF_HDA_ADSP_REG_CL_SD_FIFOSIZE); + hstream->fifo_size &= 0xffff; + hstream->fifo_size += 1; + } else { + hstream->fifo_size = 0; + } + + return ret; +} + +irqreturn_t hda_dsp_stream_interrupt(int irq, void *context) +{ + struct hdac_bus *bus = context; + struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + u32 stream_mask; + u32 status; + + if (!pm_runtime_active(bus->dev)) + return IRQ_NONE; + + spin_lock(&bus->reg_lock); + + status = snd_hdac_chip_readl(bus, INTSTS); + stream_mask = GENMASK(sof_hda->stream_max - 1, 0) | AZX_INT_CTRL_EN; + + /* Not stream interrupt or register inaccessible, ignore it.*/ + if (!(status & stream_mask) || status == 0xffffffff) { + spin_unlock(&bus->reg_lock); + return IRQ_NONE; + } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* clear rirb int */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { + if (status & RIRB_INT_RESPONSE) + snd_hdac_bus_update_rirb(bus); + snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK); + } +#endif + + spin_unlock(&bus->reg_lock); + + return snd_hdac_chip_readl(bus, INTSTS) ? IRQ_WAKE_THREAD : IRQ_HANDLED; +} + +irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context) +{ + struct hdac_bus *bus = context; + struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + u32 status = snd_hdac_chip_readl(bus, INTSTS); + struct hdac_stream *s; + u32 sd_status; + + /* check streams */ + list_for_each_entry(s, &bus->stream_list, list) { + if (status & (1 << s->index) && s->opened) { + sd_status = snd_hdac_stream_readb(s, SD_STS); + + dev_vdbg(bus->dev, "stream %d status 0x%x\n", + s->index, sd_status); + + snd_hdac_stream_writeb(s, SD_STS, SD_INT_MASK); + + if (!s->substream || + !s->running || + (sd_status & SOF_HDA_CL_DMA_SD_INT_COMPLETE) == 0) + continue; + + /* Inform ALSA only in case not do that with IPC */ + if (sof_hda->no_ipc_position) + snd_sof_pcm_period_elapsed(s->substream); + + } + } + + return IRQ_HANDLED; +} + +int hda_dsp_stream_init(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_ext_stream *stream; + struct hdac_stream *hstream; + struct pci_dev *pci = to_pci_dev(sdev->dev); + struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + int sd_offset; + int i, num_playback, num_capture, num_total, ret; + u32 gcap; + + gcap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCAP); + dev_dbg(sdev->dev, "hda global caps = 0x%x\n", gcap); + + /* get stream count from GCAP */ + num_capture = (gcap >> 8) & 0x0f; + num_playback = (gcap >> 12) & 0x0f; + num_total = num_playback + num_capture; + + dev_dbg(sdev->dev, "detected %d playback and %d capture streams\n", + num_playback, num_capture); + + if (num_playback >= SOF_HDA_PLAYBACK_STREAMS) { + dev_err(sdev->dev, "error: too many playback streams %d\n", + num_playback); + return -EINVAL; + } + + if (num_capture >= SOF_HDA_CAPTURE_STREAMS) { + dev_err(sdev->dev, "error: too many capture streams %d\n", + num_playback); + return -EINVAL; + } + + /* + * mem alloc for the position buffer + * TODO: check position buffer update + */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + SOF_HDA_DPIB_ENTRY_SIZE * num_total, + &bus->posbuf); + if (ret < 0) { + dev_err(sdev->dev, "error: posbuffer dma alloc failed\n"); + return -ENOMEM; + } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* mem alloc for the CORB/RIRB ringbuffers */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + PAGE_SIZE, &bus->rb); + if (ret < 0) { + dev_err(sdev->dev, "error: RB alloc failed\n"); + return -ENOMEM; + } +#endif + + /* create capture streams */ + for (i = 0; i < num_capture; i++) { + struct sof_intel_hda_stream *hda_stream; + + hda_stream = devm_kzalloc(sdev->dev, sizeof(*hda_stream), + GFP_KERNEL); + if (!hda_stream) + return -ENOMEM; + + stream = &hda_stream->hda_stream; + + stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; + + stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + + SOF_HDA_PPLC_INTERVAL * i; + + /* do we support SPIB */ + if (sdev->bar[HDA_DSP_SPIB_BAR]) { + stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + + SOF_HDA_SPIB_SPIB; + + stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + + SOF_HDA_SPIB_MAXFIFO; + } + + hstream = &stream->hstream; + hstream->bus = bus; + hstream->sd_int_sta_mask = 1 << i; + hstream->index = i; + sd_offset = SOF_STREAM_SD_OFFSET(hstream); + hstream->sd_addr = sdev->bar[HDA_DSP_HDA_BAR] + sd_offset; + hstream->stream_tag = i + 1; + hstream->opened = false; + hstream->running = false; + hstream->direction = SNDRV_PCM_STREAM_CAPTURE; + + /* memory alloc for stream BDL */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + HDA_DSP_BDL_SIZE, &hstream->bdl); + if (ret < 0) { + dev_err(sdev->dev, "error: stream bdl dma alloc failed\n"); + return -ENOMEM; + } + hstream->posbuf = (__le32 *)(bus->posbuf.area + + (hstream->index) * 8); + + list_add_tail(&hstream->list, &bus->stream_list); + } + + /* create playback streams */ + for (i = num_capture; i < num_total; i++) { + struct sof_intel_hda_stream *hda_stream; + + hda_stream = devm_kzalloc(sdev->dev, sizeof(*hda_stream), + GFP_KERNEL); + if (!hda_stream) + return -ENOMEM; + + stream = &hda_stream->hda_stream; + + /* we always have DSP support */ + stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; + + stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + + SOF_HDA_PPLC_INTERVAL * i; + + /* do we support SPIB */ + if (sdev->bar[HDA_DSP_SPIB_BAR]) { + stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + + SOF_HDA_SPIB_SPIB; + + stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + + SOF_HDA_SPIB_MAXFIFO; + } + + hstream = &stream->hstream; + hstream->bus = bus; + hstream->sd_int_sta_mask = 1 << i; + hstream->index = i; + sd_offset = SOF_STREAM_SD_OFFSET(hstream); + hstream->sd_addr = sdev->bar[HDA_DSP_HDA_BAR] + sd_offset; + hstream->stream_tag = i - num_capture + 1; + hstream->opened = false; + hstream->running = false; + hstream->direction = SNDRV_PCM_STREAM_PLAYBACK; + + /* mem alloc for stream BDL */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev, + HDA_DSP_BDL_SIZE, &hstream->bdl); + if (ret < 0) { + dev_err(sdev->dev, "error: stream bdl dma alloc failed\n"); + return -ENOMEM; + } + + hstream->posbuf = (__le32 *)(bus->posbuf.area + + (hstream->index) * 8); + + list_add_tail(&hstream->list, &bus->stream_list); + } + + /* store total stream count (playback + capture) from GCAP */ + sof_hda->stream_max = num_total; + + return 0; +} + +void hda_dsp_stream_free(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_stream *s, *_s; + struct hdac_ext_stream *stream; + struct sof_intel_hda_stream *hda_stream; + + /* free position buffer */ + if (bus->posbuf.area) + snd_dma_free_pages(&bus->posbuf); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* free position buffer */ + if (bus->rb.area) + snd_dma_free_pages(&bus->rb); +#endif + + list_for_each_entry_safe(s, _s, &bus->stream_list, list) { + /* TODO: decouple */ + + /* free bdl buffer */ + if (s->bdl.area) + snd_dma_free_pages(&s->bdl); + list_del(&s->list); + stream = stream_to_hdac_ext_stream(s); + hda_stream = container_of(stream, struct sof_intel_hda_stream, + hda_stream); + devm_kfree(sdev->dev, hda_stream); + } +} diff --git a/sound/soc/sof/intel/hda-trace.c b/sound/soc/sof/intel/hda-trace.c new file mode 100644 index 000000000000..33b23bd6a01e --- /dev/null +++ b/sound/soc/sof/intel/hda-trace.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <sound/hdaudio_ext.h> +#include "../ops.h" +#include "hda.h" + +static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct hdac_ext_stream *stream = hda->dtrace_stream; + struct hdac_stream *hstream = &stream->hstream; + struct snd_dma_buffer *dmab = &sdev->dmatb; + int ret; + + hstream->period_bytes = 0;/* initialize period_bytes */ + hstream->bufsize = sdev->dmatb.bytes; + + ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL); + if (ret < 0) + dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + + return ret; +} + +int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + int ret; + + hda->dtrace_stream = hda_dsp_stream_get(sdev, + SNDRV_PCM_STREAM_CAPTURE); + + if (!hda->dtrace_stream) { + dev_err(sdev->dev, + "error: no available capture stream for DMA trace\n"); + return -ENODEV; + } + + *stream_tag = hda->dtrace_stream->hstream.stream_tag; + + /* + * initialize capture stream, set BDL address and return corresponding + * stream tag which will be sent to the firmware by IPC message. + */ + ret = hda_dsp_trace_prepare(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: hdac trace init failed: %x\n", ret); + hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, *stream_tag); + hda->dtrace_stream = NULL; + *stream_tag = 0; + } + + return ret; +} + +int hda_dsp_trace_release(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct hdac_stream *hstream; + + if (hda->dtrace_stream) { + hstream = &hda->dtrace_stream->hstream; + hda_dsp_stream_put(sdev, + SNDRV_PCM_STREAM_CAPTURE, + hstream->stream_tag); + hda->dtrace_stream = NULL; + return 0; + } + + dev_dbg(sdev->dev, "DMA trace stream is not opened!\n"); + return -ENODEV; +} + +int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + + return hda_dsp_stream_trigger(sdev, hda->dtrace_stream, cmd); +} diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c new file mode 100644 index 000000000000..7e3980a2f7ba --- /dev/null +++ b/sound/soc/sof/intel/hda.c @@ -0,0 +1,689 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Rander Wang <rander.wang@intel.com> +// Keyon Jie <yang.jie@linux.intel.com> +// + +/* + * Hardware interface for generic Intel audio DSP HDA IP + */ + +#include <linux/module.h> +#include <sound/hdaudio_ext.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../ops.h" +#include "hda.h" +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +#include "../../codecs/hdac_hda.h" +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +#include <sound/soc-acpi-intel-match.h> +#endif + +/* platform specific devices */ +#include "shim.h" + +/* + * Debug + */ + +struct hda_dsp_msg_code { + u32 code; + const char *msg; +}; + +static const struct hda_dsp_msg_code hda_dsp_rom_msg[] = { + {HDA_DSP_ROM_FW_MANIFEST_LOADED, "status: manifest loaded"}, + {HDA_DSP_ROM_FW_FW_LOADED, "status: fw loaded"}, + {HDA_DSP_ROM_FW_ENTERED, "status: fw entered"}, + {HDA_DSP_ROM_CSE_ERROR, "error: cse error"}, + {HDA_DSP_ROM_CSE_WRONG_RESPONSE, "error: cse wrong response"}, + {HDA_DSP_ROM_IMR_TO_SMALL, "error: IMR too small"}, + {HDA_DSP_ROM_BASE_FW_NOT_FOUND, "error: base fw not found"}, + {HDA_DSP_ROM_CSE_VALIDATION_FAILED, "error: signature verification failed"}, + {HDA_DSP_ROM_IPC_FATAL_ERROR, "error: ipc fatal error"}, + {HDA_DSP_ROM_L2_CACHE_ERROR, "error: L2 cache error"}, + {HDA_DSP_ROM_LOAD_OFFSET_TO_SMALL, "error: load offset too small"}, + {HDA_DSP_ROM_API_PTR_INVALID, "error: API ptr invalid"}, + {HDA_DSP_ROM_BASEFW_INCOMPAT, "error: base fw incompatible"}, + {HDA_DSP_ROM_UNHANDLED_INTERRUPT, "error: unhandled interrupt"}, + {HDA_DSP_ROM_MEMORY_HOLE_ECC, "error: ECC memory hole"}, + {HDA_DSP_ROM_KERNEL_EXCEPTION, "error: kernel exception"}, + {HDA_DSP_ROM_USER_EXCEPTION, "error: user exception"}, + {HDA_DSP_ROM_UNEXPECTED_RESET, "error: unexpected reset"}, + {HDA_DSP_ROM_NULL_FW_ENTRY, "error: null FW entry point"}, +}; + +static void hda_dsp_get_status_skl(struct snd_sof_dev *sdev) +{ + u32 status; + int i; + + status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_ADSP_FW_STATUS_SKL); + + for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) { + if (status == hda_dsp_rom_msg[i].code) { + dev_err(sdev->dev, "%s - code %8.8x\n", + hda_dsp_rom_msg[i].msg, status); + return; + } + } + + /* not for us, must be generic sof message */ + dev_dbg(sdev->dev, "unknown ROM status value %8.8x\n", status); +} + +static void hda_dsp_get_status(struct snd_sof_dev *sdev) +{ + u32 status; + int i; + + status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_ROM_STATUS); + + for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) { + if (status == hda_dsp_rom_msg[i].code) { + dev_err(sdev->dev, "%s - code %8.8x\n", + hda_dsp_rom_msg[i].msg, status); + return; + } + } + + /* not for us, must be generic sof message */ + dev_dbg(sdev->dev, "unknown ROM status value %8.8x\n", status); +} + +static void hda_dsp_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + /* first read registers */ + sof_block_read(sdev, sdev->mmio_bar, sdev->dsp_oops_offset, xoops, + sizeof(*xoops)); + + /* then get panic info */ + sof_block_read(sdev, sdev->mmio_bar, sdev->dsp_oops_offset + + sizeof(*xoops), panic_info, sizeof(*panic_info)); + + /* then get the stack */ + sof_block_read(sdev, sdev->mmio_bar, sdev->dsp_oops_offset + + sizeof(*xoops) + sizeof(*panic_info), stack, + stack_words * sizeof(u32)); +} + +void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[HDA_DSP_STACK_DUMP_SIZE]; + u32 status, panic; + + /* try APL specific status message types first */ + hda_dsp_get_status_skl(sdev); + + /* now try generic SOF status messages */ + status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_ADSP_ERROR_CODE_SKL); + + /*TODO: Check: there is no define in spec, but it is used in the code*/ + panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_ADSP_ERROR_CODE_SKL + 0x4); + + if (sdev->boot_complete) { + hda_dsp_get_registers(sdev, &xoops, &panic_info, stack, + HDA_DSP_STACK_DUMP_SIZE); + snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, + stack, HDA_DSP_STACK_DUMP_SIZE); + } else { + dev_err(sdev->dev, "error: status = 0x%8.8x panic = 0x%8.8x\n", + status, panic); + hda_dsp_get_status_skl(sdev); + } +} + +void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags) +{ + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[HDA_DSP_STACK_DUMP_SIZE]; + u32 status, panic; + + /* try APL specific status message types first */ + hda_dsp_get_status(sdev); + + /* now try generic SOF status messages */ + status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_SRAM_REG_FW_STATUS); + panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_TRACEP); + + if (sdev->boot_complete) { + hda_dsp_get_registers(sdev, &xoops, &panic_info, stack, + HDA_DSP_STACK_DUMP_SIZE); + snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, + stack, HDA_DSP_STACK_DUMP_SIZE); + } else { + dev_err(sdev->dev, "error: status = 0x%8.8x panic = 0x%8.8x\n", + status, panic); + hda_dsp_get_status(sdev); + } +} + +void hda_ipc_dump(struct snd_sof_dev *sdev) +{ + u32 hipcie; + u32 hipct; + u32 hipcctl; + + /* read IPC status */ + hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE); + hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); + hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL); + + /* dump the IPC regs */ + /* TODO: parse the raw msg */ + dev_err(sdev->dev, + "error: host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n", + hipcie, hipct, hipcctl); +} + +static int hda_init(struct snd_sof_dev *sdev) +{ + struct hda_bus *hbus; + struct hdac_bus *bus; + struct hdac_ext_bus_ops *ext_ops = NULL; + struct pci_dev *pci = to_pci_dev(sdev->dev); + int ret; + + hbus = sof_to_hbus(sdev); + bus = sof_to_bus(sdev); + + /* HDA bus init */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) + ext_ops = snd_soc_hdac_hda_get_ops(); +#endif + sof_hda_bus_init(bus, &pci->dev, ext_ops); + bus->use_posbuf = 1; + bus->bdl_pos_adj = 0; + + mutex_init(&hbus->prepare_mutex); + hbus->pci = pci; + hbus->mixer_assigned = -1; + hbus->modelname = "sofbus"; + + /* initialise hdac bus */ + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pci_ioremap_bar(pci, 0); + if (!bus->remap_addr) { + dev_err(bus->dev, "error: ioremap error\n"); + return -ENXIO; + } + + /* HDA base */ + sdev->bar[HDA_DSP_HDA_BAR] = bus->remap_addr; + + /* get controller capabilities */ + ret = hda_dsp_ctrl_get_caps(sdev); + if (ret < 0) + dev_err(sdev->dev, "error: get caps error\n"); + + return ret; +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + +static const char *fixup_tplg_name(struct snd_sof_dev *sdev, + const char *sof_tplg_filename) +{ + const char *tplg_filename = NULL; + char *filename; + char *split_ext; + + filename = devm_kstrdup(sdev->dev, sof_tplg_filename, GFP_KERNEL); + if (!filename) + return NULL; + + /* this assumes a .tplg extension */ + split_ext = strsep(&filename, "."); + if (split_ext) { + tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, + "%s-idisp.tplg", split_ext); + if (!tplg_filename) + return NULL; + } + return tplg_filename; +} + +static int hda_init_caps(struct snd_sof_dev *sdev) +{ + struct hdac_bus *bus = sof_to_bus(sdev); + struct hdac_ext_link *hlink; + struct snd_soc_acpi_mach_params *mach_params; + struct snd_soc_acpi_mach *hda_mach; + struct snd_sof_pdata *pdata = sdev->pdata; + struct snd_soc_acpi_mach *mach; + const char *tplg_filename; + int codec_num = 0; + int ret = 0; + int i; + + device_disable_async_suspend(bus->dev); + + /* check if dsp is there */ + if (bus->ppcap) + dev_dbg(sdev->dev, "PP capability, will probe DSP later.\n"); + + if (bus->mlcap) + snd_hdac_ext_bus_get_ml_capabilities(bus); + + /* init i915 and HDMI codecs */ + ret = hda_codec_i915_init(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: no HDMI audio devices found\n"); + return ret; + } + + ret = hda_dsp_ctrl_init_chip(sdev, true); + if (ret < 0) { + dev_err(bus->dev, "error: init chip failed with ret: %d\n", ret); + goto out; + } + + /* codec detection */ + if (!bus->codec_mask) { + dev_info(bus->dev, "no hda codecs found!\n"); + } else { + dev_info(bus->dev, "hda codecs found, mask %lx\n", + bus->codec_mask); + + for (i = 0; i < HDA_MAX_CODECS; i++) { + if (bus->codec_mask & (1 << i)) + codec_num++; + } + + /* + * If no machine driver is found, then: + * + * hda machine driver is used if : + * 1. there is one HDMI codec and one external HDAudio codec + * 2. only HDMI codec + */ + if (!pdata->machine && codec_num <= 2 && + HDA_IDISP_CODEC(bus->codec_mask)) { + hda_mach = snd_soc_acpi_intel_hda_machines; + pdata->machine = hda_mach; + + /* topology: use the info from hda_machines */ + pdata->tplg_filename = + hda_mach->sof_tplg_filename; + + /* firmware: pick the first in machine list */ + mach = pdata->desc->machines; + pdata->fw_filename = mach->sof_fw_filename; + + dev_info(bus->dev, "using HDA machine driver %s now\n", + hda_mach->drv_name); + + /* fixup topology file for HDMI only platforms */ + if (codec_num == 1) { + /* use local variable for readability */ + tplg_filename = pdata->tplg_filename; + tplg_filename = fixup_tplg_name(sdev, tplg_filename); + if (!tplg_filename) + goto out; + pdata->tplg_filename = tplg_filename; + } + } + } + + /* used by hda machine driver to create dai links */ + if (pdata->machine) { + mach_params = (struct snd_soc_acpi_mach_params *) + &pdata->machine->mach_params; + mach_params->codec_mask = bus->codec_mask; + mach_params->platform = dev_name(sdev->dev); + } + + /* create codec instances */ + hda_codec_probe_bus(sdev); + + hda_codec_i915_put(sdev); + + /* + * we are done probing so decrement link counts + */ + list_for_each_entry(hlink, &bus->hlink_list, list) + snd_hdac_ext_bus_link_put(bus, hlink); + + return 0; + +out: + hda_codec_i915_exit(sdev); + return ret; +} + +#else + +static int hda_init_caps(struct snd_sof_dev *sdev) +{ + /* + * set CGCTL.MISCBDCGE to 0 during reset and set back to 1 + * when reset finished. + * TODO: maybe no need for init_caps? + */ + hda_dsp_ctrl_misc_clock_gating(sdev, 0); + + /* clear WAKESTS */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS, + SOF_HDA_WAKESTS_INT_MASK, + SOF_HDA_WAKESTS_INT_MASK); + + return 0; +} + +#endif + +static const struct sof_intel_dsp_desc + *get_chip_info(struct snd_sof_pdata *pdata) +{ + const struct sof_dev_desc *desc = pdata->desc; + const struct sof_intel_dsp_desc *chip_info; + + chip_info = desc->chip_info; + + return chip_info; +} + +int hda_dsp_probe(struct snd_sof_dev *sdev) +{ + struct pci_dev *pci = to_pci_dev(sdev->dev); + struct sof_intel_hda_dev *hdev; + struct hdac_bus *bus; + struct hdac_stream *stream; + const struct sof_intel_dsp_desc *chip; + int sd_offset, ret = 0; + + /* + * detect DSP by checking class/subclass/prog-id information + * class=04 subclass 03 prog-if 00: no DSP, legacy driver is required + * class=04 subclass 01 prog-if 00: DSP is present + * (and may be required e.g. for DMIC or SSP support) + * class=04 subclass 03 prog-if 80: either of DSP or legacy mode works + */ + if (pci->class == 0x040300) { + dev_err(sdev->dev, "error: the DSP is not enabled on this platform, aborting probe\n"); + return -ENODEV; + } else if (pci->class != 0x040100 && pci->class != 0x040380) { + dev_err(sdev->dev, "error: unknown PCI class/subclass/prog-if 0x%06x found, aborting probe\n", pci->class); + return -ENODEV; + } + dev_info(sdev->dev, "DSP detected with PCI class/subclass/prog-if 0x%06x\n", pci->class); + + chip = get_chip_info(sdev->pdata); + if (!chip) { + dev_err(sdev->dev, "error: no such device supported, chip id:%x\n", + pci->device); + ret = -EIO; + goto err; + } + + hdev = devm_kzalloc(sdev->dev, sizeof(*hdev), GFP_KERNEL); + if (!hdev) + return -ENOMEM; + sdev->pdata->hw_pdata = hdev; + hdev->desc = chip; + + hdev->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec", + PLATFORM_DEVID_NONE, + NULL, 0); + if (IS_ERR(hdev->dmic_dev)) { + dev_err(sdev->dev, "error: failed to create DMIC device\n"); + return PTR_ERR(hdev->dmic_dev); + } + + /* + * use position update IPC if either it is forced + * or we don't have other choice + */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_FORCE_IPC_POSITION) + hdev->no_ipc_position = 0; +#else + hdev->no_ipc_position = sof_ops(sdev)->pcm_pointer ? 1 : 0; +#endif + + /* set up HDA base */ + bus = sof_to_bus(sdev); + ret = hda_init(sdev); + if (ret < 0) + goto hdac_bus_unmap; + + /* DSP base */ + sdev->bar[HDA_DSP_BAR] = pci_ioremap_bar(pci, HDA_DSP_BAR); + if (!sdev->bar[HDA_DSP_BAR]) { + dev_err(sdev->dev, "error: ioremap error\n"); + ret = -ENXIO; + goto hdac_bus_unmap; + } + + sdev->mmio_bar = HDA_DSP_BAR; + sdev->mailbox_bar = HDA_DSP_BAR; + + /* allow 64bit DMA address if supported by H/W */ + if (!dma_set_mask(&pci->dev, DMA_BIT_MASK(64))) { + dev_dbg(sdev->dev, "DMA mask is 64 bit\n"); + dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(64)); + } else { + dev_dbg(sdev->dev, "DMA mask is 32 bit\n"); + dma_set_mask(&pci->dev, DMA_BIT_MASK(32)); + dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)); + } + + /* init streams */ + ret = hda_dsp_stream_init(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to init streams\n"); + /* + * not all errors are due to memory issues, but trying + * to free everything does not harm + */ + goto free_streams; + } + + /* + * register our IRQ + * let's try to enable msi firstly + * if it fails, use legacy interrupt mode + * TODO: support interrupt mode selection with kernel parameter + * support msi multiple vectors + */ + ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_MSI); + if (ret < 0) { + dev_info(sdev->dev, "use legacy interrupt mode\n"); + /* + * in IO-APIC mode, hda->irq and ipc_irq are using the same + * irq number of pci->irq + */ + hdev->irq = pci->irq; + sdev->ipc_irq = pci->irq; + sdev->msi_enabled = 0; + } else { + dev_info(sdev->dev, "use msi interrupt mode\n"); + hdev->irq = pci_irq_vector(pci, 0); + /* ipc irq number is the same of hda irq */ + sdev->ipc_irq = hdev->irq; + sdev->msi_enabled = 1; + } + + dev_dbg(sdev->dev, "using HDA IRQ %d\n", hdev->irq); + ret = request_threaded_irq(hdev->irq, hda_dsp_stream_interrupt, + hda_dsp_stream_threaded_handler, + IRQF_SHARED, "AudioHDA", bus); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to register HDA IRQ %d\n", + hdev->irq); + goto free_irq_vector; + } + + dev_dbg(sdev->dev, "using IPC IRQ %d\n", sdev->ipc_irq); + ret = request_threaded_irq(sdev->ipc_irq, hda_dsp_ipc_irq_handler, + sof_ops(sdev)->irq_thread, IRQF_SHARED, + "AudioDSP", sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to register IPC IRQ %d\n", + sdev->ipc_irq); + goto free_hda_irq; + } + + pci_set_master(pci); + synchronize_irq(pci->irq); + + /* + * clear TCSEL to clear playback on some HD Audio + * codecs. PCI TCSEL is defined in the Intel manuals. + */ + snd_sof_pci_update_bits(sdev, PCI_TCSEL, 0x07, 0); + + /* init HDA capabilities */ + ret = hda_init_caps(sdev); + if (ret < 0) + goto free_ipc_irq; + + /* reset HDA controller */ + ret = hda_dsp_ctrl_link_reset(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to reset HDA controller\n"); + goto free_ipc_irq; + } + + /* exit HDA controller reset */ + ret = hda_dsp_ctrl_link_reset(sdev, false); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to exit HDA controller reset\n"); + goto free_ipc_irq; + } + + /* clear stream status */ + list_for_each_entry(stream, &bus->stream_list, list) { + sd_offset = SOF_STREAM_SD_OFFSET(stream); + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + + SOF_HDA_ADSP_REG_CL_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK, + SOF_HDA_CL_DMA_SD_INT_MASK); + } + + /* clear WAKESTS */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS, + SOF_HDA_WAKESTS_INT_MASK, + SOF_HDA_WAKESTS_INT_MASK); + + /* clear interrupt status register */ + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM); + + /* enable CIE and GIE interrupts */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN); + + /* re-enable CGCTL.MISCBDCGE after reset */ + hda_dsp_ctrl_misc_clock_gating(sdev, true); + + device_disable_async_suspend(&pci->dev); + + /* enable DSP features */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_GPROCEN, SOF_HDA_PPCTL_GPROCEN); + + /* enable DSP IRQ */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_PIE, SOF_HDA_PPCTL_PIE); + + /* initialize waitq for code loading */ + init_waitqueue_head(&sdev->waitq); + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = HDA_DSP_MBOX_UPLINK_OFFSET; + + return 0; + +free_ipc_irq: + free_irq(sdev->ipc_irq, sdev); +free_hda_irq: + free_irq(hdev->irq, bus); +free_irq_vector: + if (sdev->msi_enabled) + pci_free_irq_vectors(pci); +free_streams: + hda_dsp_stream_free(sdev); +/* dsp_unmap: not currently used */ + iounmap(sdev->bar[HDA_DSP_BAR]); +hdac_bus_unmap: + iounmap(bus->remap_addr); +err: + return ret; +} + +int hda_dsp_remove(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct hdac_bus *bus = sof_to_bus(sdev); + struct pci_dev *pci = to_pci_dev(sdev->dev); + const struct sof_intel_dsp_desc *chip = hda->desc; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + /* codec removal, invoke bus_device_remove */ + snd_hdac_ext_bus_device_remove(bus); +#endif + + if (!IS_ERR_OR_NULL(hda->dmic_dev)) + platform_device_unregister(hda->dmic_dev); + + /* disable DSP IRQ */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_PIE, 0); + + /* disable CIE and GIE interrupts */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, + SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, 0); + + /* disable cores */ + if (chip) + hda_dsp_core_reset_power_down(sdev, chip->cores_mask); + + /* disable DSP */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_GPROCEN, 0); + + free_irq(sdev->ipc_irq, sdev); + free_irq(hda->irq, bus); + if (sdev->msi_enabled) + pci_free_irq_vectors(pci); + + hda_dsp_stream_free(sdev); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + snd_hdac_link_free_all(bus); +#endif + + iounmap(sdev->bar[HDA_DSP_BAR]); + iounmap(bus->remap_addr); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + snd_hdac_ext_bus_exit(bus); +#endif + hda_codec_i915_exit(sdev); + + return 0; +} + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h new file mode 100644 index 000000000000..92d45c43b4b1 --- /dev/null +++ b/sound/soc/sof/intel/hda.h @@ -0,0 +1,583 @@ +/* SPDX-License-Identifier: (GPL-2.0 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) 2017 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + */ + +#ifndef __SOF_INTEL_HDA_H +#define __SOF_INTEL_HDA_H + +#include <sound/hda_codec.h> +#include <sound/hdaudio_ext.h> +#include "shim.h" + +/* PCI registers */ +#define PCI_TCSEL 0x44 +#define PCI_PGCTL PCI_TCSEL +#define PCI_CGCTL 0x48 + +/* PCI_PGCTL bits */ +#define PCI_PGCTL_ADSPPGD BIT(2) +#define PCI_PGCTL_LSRMD_MASK BIT(4) + +/* PCI_CGCTL bits */ +#define PCI_CGCTL_MISCBDCGE_MASK BIT(6) +#define PCI_CGCTL_ADSPDCGE BIT(1) + +/* Legacy HDA registers and bits used - widths are variable */ +#define SOF_HDA_GCAP 0x0 +#define SOF_HDA_GCTL 0x8 +/* accept unsol. response enable */ +#define SOF_HDA_GCTL_UNSOL BIT(8) +#define SOF_HDA_LLCH 0x14 +#define SOF_HDA_INTCTL 0x20 +#define SOF_HDA_INTSTS 0x24 +#define SOF_HDA_WAKESTS 0x0E +#define SOF_HDA_WAKESTS_INT_MASK ((1 << 8) - 1) +#define SOF_HDA_RIRBSTS 0x5d +#define SOF_HDA_VS_EM2_L1SEN BIT(13) + +/* SOF_HDA_GCTL register bist */ +#define SOF_HDA_GCTL_RESET BIT(0) + +/* SOF_HDA_INCTL and SOF_HDA_INTSTS regs */ +#define SOF_HDA_INT_GLOBAL_EN BIT(31) +#define SOF_HDA_INT_CTRL_EN BIT(30) +#define SOF_HDA_INT_ALL_STREAM 0xff + +#define SOF_HDA_MAX_CAPS 10 +#define SOF_HDA_CAP_ID_OFF 16 +#define SOF_HDA_CAP_ID_MASK GENMASK(SOF_HDA_CAP_ID_OFF + 11,\ + SOF_HDA_CAP_ID_OFF) +#define SOF_HDA_CAP_NEXT_MASK 0xFFFF + +#define SOF_HDA_GTS_CAP_ID 0x1 +#define SOF_HDA_ML_CAP_ID 0x2 + +#define SOF_HDA_PP_CAP_ID 0x3 +#define SOF_HDA_REG_PP_PPCH 0x10 +#define SOF_HDA_REG_PP_PPCTL 0x04 +#define SOF_HDA_PPCTL_PIE BIT(31) +#define SOF_HDA_PPCTL_GPROCEN BIT(30) + +/* DPIB entry size: 8 Bytes = 2 DWords */ +#define SOF_HDA_DPIB_ENTRY_SIZE 0x8 + +#define SOF_HDA_SPIB_CAP_ID 0x4 +#define SOF_HDA_DRSM_CAP_ID 0x5 + +#define SOF_HDA_SPIB_BASE 0x08 +#define SOF_HDA_SPIB_INTERVAL 0x08 +#define SOF_HDA_SPIB_SPIB 0x00 +#define SOF_HDA_SPIB_MAXFIFO 0x04 + +#define SOF_HDA_PPHC_BASE 0x10 +#define SOF_HDA_PPHC_INTERVAL 0x10 + +#define SOF_HDA_PPLC_BASE 0x10 +#define SOF_HDA_PPLC_MULTI 0x10 +#define SOF_HDA_PPLC_INTERVAL 0x10 + +#define SOF_HDA_DRSM_BASE 0x08 +#define SOF_HDA_DRSM_INTERVAL 0x08 + +/* Descriptor error interrupt */ +#define SOF_HDA_CL_DMA_SD_INT_DESC_ERR 0x10 + +/* FIFO error interrupt */ +#define SOF_HDA_CL_DMA_SD_INT_FIFO_ERR 0x08 + +/* Buffer completion interrupt */ +#define SOF_HDA_CL_DMA_SD_INT_COMPLETE 0x04 + +#define SOF_HDA_CL_DMA_SD_INT_MASK \ + (SOF_HDA_CL_DMA_SD_INT_DESC_ERR | \ + SOF_HDA_CL_DMA_SD_INT_FIFO_ERR | \ + SOF_HDA_CL_DMA_SD_INT_COMPLETE) +#define SOF_HDA_SD_CTL_DMA_START 0x02 /* Stream DMA start bit */ + +/* Intel HD Audio Code Loader DMA Registers */ +#define SOF_HDA_ADSP_LOADER_BASE 0x80 +#define SOF_HDA_ADSP_DPLBASE 0x70 +#define SOF_HDA_ADSP_DPUBASE 0x74 +#define SOF_HDA_ADSP_DPLBASE_ENABLE 0x01 + +/* Stream Registers */ +#define SOF_HDA_ADSP_REG_CL_SD_CTL 0x00 +#define SOF_HDA_ADSP_REG_CL_SD_STS 0x03 +#define SOF_HDA_ADSP_REG_CL_SD_LPIB 0x04 +#define SOF_HDA_ADSP_REG_CL_SD_CBL 0x08 +#define SOF_HDA_ADSP_REG_CL_SD_LVI 0x0C +#define SOF_HDA_ADSP_REG_CL_SD_FIFOW 0x0E +#define SOF_HDA_ADSP_REG_CL_SD_FIFOSIZE 0x10 +#define SOF_HDA_ADSP_REG_CL_SD_FORMAT 0x12 +#define SOF_HDA_ADSP_REG_CL_SD_FIFOL 0x14 +#define SOF_HDA_ADSP_REG_CL_SD_BDLPL 0x18 +#define SOF_HDA_ADSP_REG_CL_SD_BDLPU 0x1C +#define SOF_HDA_ADSP_SD_ENTRY_SIZE 0x20 + +/* CL: Software Position Based FIFO Capability Registers */ +#define SOF_DSP_REG_CL_SPBFIFO \ + (SOF_HDA_ADSP_LOADER_BASE + 0x20) +#define SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCH 0x0 +#define SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL 0x4 +#define SOF_HDA_ADSP_REG_CL_SPBFIFO_SPIB 0x8 +#define SOF_HDA_ADSP_REG_CL_SPBFIFO_MAXFIFOS 0xc + +/* Stream Number */ +#define SOF_HDA_CL_SD_CTL_STREAM_TAG_SHIFT 20 +#define SOF_HDA_CL_SD_CTL_STREAM_TAG_MASK \ + GENMASK(SOF_HDA_CL_SD_CTL_STREAM_TAG_SHIFT + 3,\ + SOF_HDA_CL_SD_CTL_STREAM_TAG_SHIFT) + +#define HDA_DSP_HDA_BAR 0 +#define HDA_DSP_PP_BAR 1 +#define HDA_DSP_SPIB_BAR 2 +#define HDA_DSP_DRSM_BAR 3 +#define HDA_DSP_BAR 4 + +#define SRAM_WINDOW_OFFSET(x) (0x80000 + (x) * 0x20000) + +#define HDA_DSP_MBOX_OFFSET SRAM_WINDOW_OFFSET(0) + +#define HDA_DSP_PANIC_OFFSET(x) \ + (((x) & 0xFFFFFF) + HDA_DSP_MBOX_OFFSET) + +/* SRAM window 0 FW "registers" */ +#define HDA_DSP_SRAM_REG_ROM_STATUS (HDA_DSP_MBOX_OFFSET + 0x0) +#define HDA_DSP_SRAM_REG_ROM_ERROR (HDA_DSP_MBOX_OFFSET + 0x4) +/* FW and ROM share offset 4 */ +#define HDA_DSP_SRAM_REG_FW_STATUS (HDA_DSP_MBOX_OFFSET + 0x4) +#define HDA_DSP_SRAM_REG_FW_TRACEP (HDA_DSP_MBOX_OFFSET + 0x8) +#define HDA_DSP_SRAM_REG_FW_END (HDA_DSP_MBOX_OFFSET + 0xc) + +#define HDA_DSP_MBOX_UPLINK_OFFSET 0x81000 + +#define HDA_DSP_STREAM_RESET_TIMEOUT 300 +#define HDA_DSP_CL_TRIGGER_TIMEOUT 300 + +#define HDA_DSP_SPIB_ENABLE 1 +#define HDA_DSP_SPIB_DISABLE 0 + +#define SOF_HDA_MAX_BUFFER_SIZE (32 * PAGE_SIZE) + +#define HDA_DSP_STACK_DUMP_SIZE 32 + +/* ROM status/error values */ +#define HDA_DSP_ROM_STS_MASK 0xf +#define HDA_DSP_ROM_INIT 0x1 +#define HDA_DSP_ROM_FW_MANIFEST_LOADED 0x3 +#define HDA_DSP_ROM_FW_FW_LOADED 0x4 +#define HDA_DSP_ROM_FW_ENTERED 0x5 +#define HDA_DSP_ROM_RFW_START 0xf +#define HDA_DSP_ROM_CSE_ERROR 40 +#define HDA_DSP_ROM_CSE_WRONG_RESPONSE 41 +#define HDA_DSP_ROM_IMR_TO_SMALL 42 +#define HDA_DSP_ROM_BASE_FW_NOT_FOUND 43 +#define HDA_DSP_ROM_CSE_VALIDATION_FAILED 44 +#define HDA_DSP_ROM_IPC_FATAL_ERROR 45 +#define HDA_DSP_ROM_L2_CACHE_ERROR 46 +#define HDA_DSP_ROM_LOAD_OFFSET_TO_SMALL 47 +#define HDA_DSP_ROM_API_PTR_INVALID 50 +#define HDA_DSP_ROM_BASEFW_INCOMPAT 51 +#define HDA_DSP_ROM_UNHANDLED_INTERRUPT 0xBEE00000 +#define HDA_DSP_ROM_MEMORY_HOLE_ECC 0xECC00000 +#define HDA_DSP_ROM_KERNEL_EXCEPTION 0xCAFE0000 +#define HDA_DSP_ROM_USER_EXCEPTION 0xBEEF0000 +#define HDA_DSP_ROM_UNEXPECTED_RESET 0xDECAF000 +#define HDA_DSP_ROM_NULL_FW_ENTRY 0x4c4c4e55 +#define HDA_DSP_IPC_PURGE_FW 0x01004000 + +/* various timeout values */ +#define HDA_DSP_PU_TIMEOUT 50 +#define HDA_DSP_PD_TIMEOUT 50 +#define HDA_DSP_RESET_TIMEOUT_US 50000 +#define HDA_DSP_BASEFW_TIMEOUT_US 3000000 +#define HDA_DSP_INIT_TIMEOUT_US 500000 +#define HDA_DSP_CTRL_RESET_TIMEOUT 100 +#define HDA_DSP_WAIT_TIMEOUT 500 /* 500 msec */ +#define HDA_DSP_REG_POLL_INTERVAL_US 500 /* 0.5 msec */ + +#define HDA_DSP_ADSPIC_IPC 1 +#define HDA_DSP_ADSPIS_IPC 1 + +/* Intel HD Audio General DSP Registers */ +#define HDA_DSP_GEN_BASE 0x0 +#define HDA_DSP_REG_ADSPCS (HDA_DSP_GEN_BASE + 0x04) +#define HDA_DSP_REG_ADSPIC (HDA_DSP_GEN_BASE + 0x08) +#define HDA_DSP_REG_ADSPIS (HDA_DSP_GEN_BASE + 0x0C) +#define HDA_DSP_REG_ADSPIC2 (HDA_DSP_GEN_BASE + 0x10) +#define HDA_DSP_REG_ADSPIS2 (HDA_DSP_GEN_BASE + 0x14) + +/* Intel HD Audio Inter-Processor Communication Registers */ +#define HDA_DSP_IPC_BASE 0x40 +#define HDA_DSP_REG_HIPCT (HDA_DSP_IPC_BASE + 0x00) +#define HDA_DSP_REG_HIPCTE (HDA_DSP_IPC_BASE + 0x04) +#define HDA_DSP_REG_HIPCI (HDA_DSP_IPC_BASE + 0x08) +#define HDA_DSP_REG_HIPCIE (HDA_DSP_IPC_BASE + 0x0C) +#define HDA_DSP_REG_HIPCCTL (HDA_DSP_IPC_BASE + 0x10) + +/* HIPCI */ +#define HDA_DSP_REG_HIPCI_BUSY BIT(31) +#define HDA_DSP_REG_HIPCI_MSG_MASK 0x7FFFFFFF + +/* HIPCIE */ +#define HDA_DSP_REG_HIPCIE_DONE BIT(30) +#define HDA_DSP_REG_HIPCIE_MSG_MASK 0x3FFFFFFF + +/* HIPCCTL */ +#define HDA_DSP_REG_HIPCCTL_DONE BIT(1) +#define HDA_DSP_REG_HIPCCTL_BUSY BIT(0) + +/* HIPCT */ +#define HDA_DSP_REG_HIPCT_BUSY BIT(31) +#define HDA_DSP_REG_HIPCT_MSG_MASK 0x7FFFFFFF + +/* HIPCTE */ +#define HDA_DSP_REG_HIPCTE_MSG_MASK 0x3FFFFFFF + +#define HDA_DSP_ADSPIC_CL_DMA 0x2 +#define HDA_DSP_ADSPIS_CL_DMA 0x2 + +/* Delay before scheduling D0i3 entry */ +#define BXT_D0I3_DELAY 5000 + +#define FW_CL_STREAM_NUMBER 0x1 + +/* ADSPCS - Audio DSP Control & Status */ + +/* + * Core Reset - asserted high + * CRST Mask for a given core mask pattern, cm + */ +#define HDA_DSP_ADSPCS_CRST_SHIFT 0 +#define HDA_DSP_ADSPCS_CRST_MASK(cm) ((cm) << HDA_DSP_ADSPCS_CRST_SHIFT) + +/* + * Core run/stall - when set to '1' core is stalled + * CSTALL Mask for a given core mask pattern, cm + */ +#define HDA_DSP_ADSPCS_CSTALL_SHIFT 8 +#define HDA_DSP_ADSPCS_CSTALL_MASK(cm) ((cm) << HDA_DSP_ADSPCS_CSTALL_SHIFT) + +/* + * Set Power Active - when set to '1' turn cores on + * SPA Mask for a given core mask pattern, cm + */ +#define HDA_DSP_ADSPCS_SPA_SHIFT 16 +#define HDA_DSP_ADSPCS_SPA_MASK(cm) ((cm) << HDA_DSP_ADSPCS_SPA_SHIFT) + +/* + * Current Power Active - power status of cores, set by hardware + * CPA Mask for a given core mask pattern, cm + */ +#define HDA_DSP_ADSPCS_CPA_SHIFT 24 +#define HDA_DSP_ADSPCS_CPA_MASK(cm) ((cm) << HDA_DSP_ADSPCS_CPA_SHIFT) + +/* Mask for a given core index, c = 0.. number of supported cores - 1 */ +#define HDA_DSP_CORE_MASK(c) BIT(c) + +/* + * Mask for a given number of cores + * nc = number of supported cores + */ +#define SOF_DSP_CORES_MASK(nc) GENMASK(((nc) - 1), 0) + +/* Intel HD Audio Inter-Processor Communication Registers for Cannonlake*/ +#define CNL_DSP_IPC_BASE 0xc0 +#define CNL_DSP_REG_HIPCTDR (CNL_DSP_IPC_BASE + 0x00) +#define CNL_DSP_REG_HIPCTDA (CNL_DSP_IPC_BASE + 0x04) +#define CNL_DSP_REG_HIPCTDD (CNL_DSP_IPC_BASE + 0x08) +#define CNL_DSP_REG_HIPCIDR (CNL_DSP_IPC_BASE + 0x10) +#define CNL_DSP_REG_HIPCIDA (CNL_DSP_IPC_BASE + 0x14) +#define CNL_DSP_REG_HIPCCTL (CNL_DSP_IPC_BASE + 0x28) + +/* HIPCI */ +#define CNL_DSP_REG_HIPCIDR_BUSY BIT(31) +#define CNL_DSP_REG_HIPCIDR_MSG_MASK 0x7FFFFFFF + +/* HIPCIE */ +#define CNL_DSP_REG_HIPCIDA_DONE BIT(31) +#define CNL_DSP_REG_HIPCIDA_MSG_MASK 0x7FFFFFFF + +/* HIPCCTL */ +#define CNL_DSP_REG_HIPCCTL_DONE BIT(1) +#define CNL_DSP_REG_HIPCCTL_BUSY BIT(0) + +/* HIPCT */ +#define CNL_DSP_REG_HIPCTDR_BUSY BIT(31) +#define CNL_DSP_REG_HIPCTDR_MSG_MASK 0x7FFFFFFF + +/* HIPCTDA */ +#define CNL_DSP_REG_HIPCTDA_DONE BIT(31) +#define CNL_DSP_REG_HIPCTDA_MSG_MASK 0x7FFFFFFF + +/* HIPCTDD */ +#define CNL_DSP_REG_HIPCTDD_MSG_MASK 0x7FFFFFFF + +/* BDL */ +#define HDA_DSP_BDL_SIZE 4096 +#define HDA_DSP_MAX_BDL_ENTRIES \ + (HDA_DSP_BDL_SIZE / sizeof(struct sof_intel_dsp_bdl)) + +/* Number of DAIs */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +#define SOF_SKL_NUM_DAIS 14 +#else +#define SOF_SKL_NUM_DAIS 8 +#endif + +/* Intel HD Audio SRAM Window 0*/ +#define HDA_ADSP_SRAM0_BASE_SKL 0x8000 + +/* Firmware status window */ +#define HDA_ADSP_FW_STATUS_SKL HDA_ADSP_SRAM0_BASE_SKL +#define HDA_ADSP_ERROR_CODE_SKL (HDA_ADSP_FW_STATUS_SKL + 0x4) + +/* Host Device Memory Space */ +#define APL_SSP_BASE_OFFSET 0x2000 +#define CNL_SSP_BASE_OFFSET 0x10000 + +/* Host Device Memory Size of a Single SSP */ +#define SSP_DEV_MEM_SIZE 0x1000 + +/* SSP Count of the Platform */ +#define APL_SSP_COUNT 6 +#define CNL_SSP_COUNT 3 + +/* SSP Registers */ +#define SSP_SSC1_OFFSET 0x4 +#define SSP_SET_SCLK_SLAVE BIT(25) +#define SSP_SET_SFRM_SLAVE BIT(24) +#define SSP_SET_SLAVE (SSP_SET_SCLK_SLAVE | SSP_SET_SFRM_SLAVE) + +#define HDA_IDISP_CODEC(x) ((x) & BIT(2)) + +struct sof_intel_dsp_bdl { + __le32 addr_l; + __le32 addr_h; + __le32 size; + __le32 ioc; +} __attribute((packed)); + +#define SOF_HDA_PLAYBACK_STREAMS 16 +#define SOF_HDA_CAPTURE_STREAMS 16 +#define SOF_HDA_PLAYBACK 0 +#define SOF_HDA_CAPTURE 1 + +/* represents DSP HDA controller frontend - i.e. host facing control */ +struct sof_intel_hda_dev { + + struct hda_bus hbus; + + /* hw config */ + const struct sof_intel_dsp_desc *desc; + + /* trace */ + struct hdac_ext_stream *dtrace_stream; + + /* if position update IPC needed */ + u32 no_ipc_position; + + /* the maximum number of streams (playback + capture) supported */ + u32 stream_max; + + int irq; + + /* DMIC device */ + struct platform_device *dmic_dev; +}; + +static inline struct hdac_bus *sof_to_bus(struct snd_sof_dev *s) +{ + struct sof_intel_hda_dev *hda = s->pdata->hw_pdata; + + return &hda->hbus.core; +} + +static inline struct hda_bus *sof_to_hbus(struct snd_sof_dev *s) +{ + struct sof_intel_hda_dev *hda = s->pdata->hw_pdata; + + return &hda->hbus; +} + +struct sof_intel_hda_stream { + struct hdac_ext_stream hda_stream; + struct sof_intel_stream stream; + int hw_params_upon_resume; /* set up hw_params upon resume */ +}; + +#define bus_to_sof_hda(bus) \ + container_of(bus, struct sof_intel_hda_dev, hbus.core) + +#define SOF_STREAM_SD_OFFSET(s) \ + (SOF_HDA_ADSP_SD_ENTRY_SIZE * ((s)->index) \ + + SOF_HDA_ADSP_LOADER_BASE) + +/* + * DSP Core services. + */ +int hda_dsp_probe(struct snd_sof_dev *sdev); +int hda_dsp_remove(struct snd_sof_dev *sdev); +int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, + unsigned int core_mask); +int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, + unsigned int core_mask); +int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask); +int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask); +int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask); +int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask); +int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask); +bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, + unsigned int core_mask); +int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev, + unsigned int core_mask); +void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev); +void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev); + +int hda_dsp_suspend(struct snd_sof_dev *sdev, int state); +int hda_dsp_resume(struct snd_sof_dev *sdev); +int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev, int state); +int hda_dsp_runtime_resume(struct snd_sof_dev *sdev); +void hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev); +void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags); +void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags); +void hda_ipc_dump(struct snd_sof_dev *sdev); + +/* + * DSP PCM Operations. + */ +int hda_dsp_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); +int hda_dsp_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); +int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params); +int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, int cmd); +snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); + +/* + * DSP Stream Operations. + */ + +int hda_dsp_stream_init(struct snd_sof_dev *sdev); +void hda_dsp_stream_free(struct snd_sof_dev *sdev); +int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, + struct snd_dma_buffer *dmab, + struct snd_pcm_hw_params *params); +int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, int cmd); +irqreturn_t hda_dsp_stream_interrupt(int irq, void *context); +irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context); +int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + struct hdac_stream *stream); + +struct hdac_ext_stream * + hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction); +int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag); +int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, + struct hdac_ext_stream *stream, + int enable, u32 size); + +void hda_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz); +int hda_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply); + +/* + * DSP IPC Operations. + */ +int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg); +void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev); +int hda_dsp_ipc_fw_ready(struct snd_sof_dev *sdev, u32 msg_id); +irqreturn_t hda_dsp_ipc_irq_handler(int irq, void *context); +irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context); +int hda_dsp_ipc_cmd_done(struct snd_sof_dev *sdev, int dir); + +/* + * DSP Code loader. + */ +int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev); +int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev); + +/* pre and post fw run ops */ +int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev); +int hda_dsp_post_fw_run(struct snd_sof_dev *sdev); + +/* + * HDA Controller Operations. + */ +int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev); +void hda_dsp_ctrl_ppcap_enable(struct snd_sof_dev *sdev, bool enable); +void hda_dsp_ctrl_ppcap_int_enable(struct snd_sof_dev *sdev, bool enable); +int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset); +void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable); +int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable); +int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset); + +/* + * HDA bus operations. + */ +void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev, + const struct hdac_ext_bus_ops *ext_ops); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +/* + * HDA Codec operations. + */ +int hda_codec_probe_bus(struct snd_sof_dev *sdev); + +#endif /* CONFIG_SND_SOC_SOF_HDA */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) && IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) + +void hda_codec_i915_get(struct snd_sof_dev *sdev); +void hda_codec_i915_put(struct snd_sof_dev *sdev); +int hda_codec_i915_init(struct snd_sof_dev *sdev); +int hda_codec_i915_exit(struct snd_sof_dev *sdev); + +#else + +static inline void hda_codec_i915_get(struct snd_sof_dev *sdev) { } +static inline void hda_codec_i915_put(struct snd_sof_dev *sdev) { } +static inline int hda_codec_i915_init(struct snd_sof_dev *sdev) { return 0; } +static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; } + +#endif /* CONFIG_SND_SOC_SOF_HDA && CONFIG_SND_SOC_HDAC_HDMI */ + +/* + * Trace Control. + */ +int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag); +int hda_dsp_trace_release(struct snd_sof_dev *sdev); +int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd); + +/* common dai driver */ +extern struct snd_soc_dai_driver skl_dai[]; + +/* + * Platform Specific HW abstraction Ops. + */ +extern const struct snd_sof_dsp_ops sof_apl_ops; +extern const struct snd_sof_dsp_ops sof_cnl_ops; +extern const struct snd_sof_dsp_ops sof_skl_ops; + +extern const struct sof_intel_dsp_desc apl_chip_info; +extern const struct sof_intel_dsp_desc cnl_chip_info; +extern const struct sof_intel_dsp_desc skl_chip_info; + +#endif diff --git a/sound/soc/sof/intel/intel-ipc.c b/sound/soc/sof/intel/intel-ipc.c new file mode 100644 index 000000000000..4edd92151fd5 --- /dev/null +++ b/sound/soc/sof/intel/intel-ipc.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2019 Intel Corporation. All rights reserved. +// +// Authors: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com> + +/* Intel-specific SOF IPC code */ + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/types.h> + +#include <sound/pcm.h> +#include <sound/sof/stream.h> + +#include "../ops.h" +#include "../sof-priv.h" + +struct intel_stream { + size_t posn_offset; +}; + +/* Mailbox-based Intel IPC implementation */ +void intel_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + if (!substream || !sdev->stream_box.size) { + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); + } else { + struct intel_stream *stream = substream->runtime->private_data; + + /* The stream might already be closed */ + if (stream) + sof_mailbox_read(sdev, stream->posn_offset, p, sz); + } +} +EXPORT_SYMBOL(intel_ipc_msg_data); + +int intel_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + struct intel_stream *stream = substream->runtime->private_data; + size_t posn_offset = reply->posn_offset; + + /* check if offset is overflow or it is not aligned */ + if (posn_offset > sdev->stream_box.size || + posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) + return -EINVAL; + + stream->posn_offset = sdev->stream_box.offset + posn_offset; + + dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu", + substream->stream, stream->posn_offset); + + return 0; +} +EXPORT_SYMBOL(intel_ipc_pcm_params); + +int intel_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct intel_stream *stream = kmalloc(sizeof(*stream), GFP_KERNEL); + + if (!stream) + return -ENOMEM; + + /* binding pcm substream to hda stream */ + substream->runtime->private_data = stream; + + return 0; +} +EXPORT_SYMBOL(intel_pcm_open); + +int intel_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct intel_stream *stream = substream->runtime->private_data; + + substream->runtime->private_data = NULL; + kfree(stream); + + return 0; +} +EXPORT_SYMBOL(intel_pcm_close); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/shim.h b/sound/soc/sof/intel/shim.h new file mode 100644 index 000000000000..f7a3f62e45d4 --- /dev/null +++ b/sound/soc/sof/intel/shim.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: (GPL-2.0 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) 2017 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + */ + +#ifndef __SOF_INTEL_SHIM_H +#define __SOF_INTEL_SHIM_H + +/* + * SHIM registers for BYT, BSW, CHT, HSW, BDW + */ + +#define SHIM_CSR (SHIM_OFFSET + 0x00) +#define SHIM_PISR (SHIM_OFFSET + 0x08) +#define SHIM_PIMR (SHIM_OFFSET + 0x10) +#define SHIM_ISRX (SHIM_OFFSET + 0x18) +#define SHIM_ISRD (SHIM_OFFSET + 0x20) +#define SHIM_IMRX (SHIM_OFFSET + 0x28) +#define SHIM_IMRD (SHIM_OFFSET + 0x30) +#define SHIM_IPCX (SHIM_OFFSET + 0x38) +#define SHIM_IPCD (SHIM_OFFSET + 0x40) +#define SHIM_ISRSC (SHIM_OFFSET + 0x48) +#define SHIM_ISRLPESC (SHIM_OFFSET + 0x50) +#define SHIM_IMRSC (SHIM_OFFSET + 0x58) +#define SHIM_IMRLPESC (SHIM_OFFSET + 0x60) +#define SHIM_IPCSC (SHIM_OFFSET + 0x68) +#define SHIM_IPCLPESC (SHIM_OFFSET + 0x70) +#define SHIM_CLKCTL (SHIM_OFFSET + 0x78) +#define SHIM_CSR2 (SHIM_OFFSET + 0x80) +#define SHIM_LTRC (SHIM_OFFSET + 0xE0) +#define SHIM_HMDC (SHIM_OFFSET + 0xE8) + +#define SHIM_PWMCTRL 0x1000 + +/* + * SST SHIM register bits for BYT, BSW, CHT HSW, BDW + * Register bit naming and functionaility can differ between devices. + */ + +/* CSR / CS */ +#define SHIM_CSR_RST BIT(1) +#define SHIM_CSR_SBCS0 BIT(2) +#define SHIM_CSR_SBCS1 BIT(3) +#define SHIM_CSR_DCS(x) ((x) << 4) +#define SHIM_CSR_DCS_MASK (0x7 << 4) +#define SHIM_CSR_STALL BIT(10) +#define SHIM_CSR_S0IOCS BIT(21) +#define SHIM_CSR_S1IOCS BIT(23) +#define SHIM_CSR_LPCS BIT(31) +#define SHIM_CSR_24MHZ_LPCS \ + (SHIM_CSR_SBCS0 | SHIM_CSR_SBCS1 | SHIM_CSR_LPCS) +#define SHIM_CSR_24MHZ_NO_LPCS (SHIM_CSR_SBCS0 | SHIM_CSR_SBCS1) +#define SHIM_BYT_CSR_RST BIT(0) +#define SHIM_BYT_CSR_VECTOR_SEL BIT(1) +#define SHIM_BYT_CSR_STALL BIT(2) +#define SHIM_BYT_CSR_PWAITMODE BIT(3) + +/* ISRX / ISC */ +#define SHIM_ISRX_BUSY BIT(1) +#define SHIM_ISRX_DONE BIT(0) +#define SHIM_BYT_ISRX_REQUEST BIT(1) + +/* ISRD / ISD */ +#define SHIM_ISRD_BUSY BIT(1) +#define SHIM_ISRD_DONE BIT(0) + +/* IMRX / IMC */ +#define SHIM_IMRX_BUSY BIT(1) +#define SHIM_IMRX_DONE BIT(0) +#define SHIM_BYT_IMRX_REQUEST BIT(1) + +/* IMRD / IMD */ +#define SHIM_IMRD_DONE BIT(0) +#define SHIM_IMRD_BUSY BIT(1) +#define SHIM_IMRD_SSP0 BIT(16) +#define SHIM_IMRD_DMAC0 BIT(21) +#define SHIM_IMRD_DMAC1 BIT(22) +#define SHIM_IMRD_DMAC (SHIM_IMRD_DMAC0 | SHIM_IMRD_DMAC1) + +/* IPCX / IPCC */ +#define SHIM_IPCX_DONE BIT(30) +#define SHIM_IPCX_BUSY BIT(31) +#define SHIM_BYT_IPCX_DONE BIT_ULL(62) +#define SHIM_BYT_IPCX_BUSY BIT_ULL(63) + +/* IPCD */ +#define SHIM_IPCD_DONE BIT(30) +#define SHIM_IPCD_BUSY BIT(31) +#define SHIM_BYT_IPCD_DONE BIT_ULL(62) +#define SHIM_BYT_IPCD_BUSY BIT_ULL(63) + +/* CLKCTL */ +#define SHIM_CLKCTL_SMOS(x) ((x) << 24) +#define SHIM_CLKCTL_MASK (3 << 24) +#define SHIM_CLKCTL_DCPLCG BIT(18) +#define SHIM_CLKCTL_SCOE1 BIT(17) +#define SHIM_CLKCTL_SCOE0 BIT(16) + +/* CSR2 / CS2 */ +#define SHIM_CSR2_SDFD_SSP0 BIT(1) +#define SHIM_CSR2_SDFD_SSP1 BIT(2) + +/* LTRC */ +#define SHIM_LTRC_VAL(x) ((x) << 0) + +/* HMDC */ +#define SHIM_HMDC_HDDA0(x) ((x) << 0) +#define SHIM_HMDC_HDDA1(x) ((x) << 7) +#define SHIM_HMDC_HDDA_E0_CH0 1 +#define SHIM_HMDC_HDDA_E0_CH1 2 +#define SHIM_HMDC_HDDA_E0_CH2 4 +#define SHIM_HMDC_HDDA_E0_CH3 8 +#define SHIM_HMDC_HDDA_E1_CH0 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH0) +#define SHIM_HMDC_HDDA_E1_CH1 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH1) +#define SHIM_HMDC_HDDA_E1_CH2 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH2) +#define SHIM_HMDC_HDDA_E1_CH3 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH3) +#define SHIM_HMDC_HDDA_E0_ALLCH \ + (SHIM_HMDC_HDDA_E0_CH0 | SHIM_HMDC_HDDA_E0_CH1 | \ + SHIM_HMDC_HDDA_E0_CH2 | SHIM_HMDC_HDDA_E0_CH3) +#define SHIM_HMDC_HDDA_E1_ALLCH \ + (SHIM_HMDC_HDDA_E1_CH0 | SHIM_HMDC_HDDA_E1_CH1 | \ + SHIM_HMDC_HDDA_E1_CH2 | SHIM_HMDC_HDDA_E1_CH3) + +/* Audio DSP PCI registers */ +#define PCI_VDRTCTL0 0xa0 +#define PCI_VDRTCTL1 0xa4 +#define PCI_VDRTCTL2 0xa8 +#define PCI_VDRTCTL3 0xaC + +/* VDRTCTL0 */ +#define PCI_VDRTCL0_D3PGD BIT(0) +#define PCI_VDRTCL0_D3SRAMPGD BIT(1) +#define PCI_VDRTCL0_DSRAMPGE_SHIFT 12 +#define PCI_VDRTCL0_DSRAMPGE_MASK GENMASK(PCI_VDRTCL0_DSRAMPGE_SHIFT + 19,\ + PCI_VDRTCL0_DSRAMPGE_SHIFT) +#define PCI_VDRTCL0_ISRAMPGE_SHIFT 2 +#define PCI_VDRTCL0_ISRAMPGE_MASK GENMASK(PCI_VDRTCL0_ISRAMPGE_SHIFT + 9,\ + PCI_VDRTCL0_ISRAMPGE_SHIFT) + +/* VDRTCTL2 */ +#define PCI_VDRTCL2_DCLCGE BIT(1) +#define PCI_VDRTCL2_DTCGE BIT(10) +#define PCI_VDRTCL2_APLLSE_MASK BIT(31) + +/* PMCS */ +#define PCI_PMCS 0x84 +#define PCI_PMCS_PS_MASK 0x3 + +/* DSP hardware descriptor */ +struct sof_intel_dsp_desc { + int cores_num; + int cores_mask; + int init_core_mask; /* cores available after fw boot */ + int ipc_req; + int ipc_req_mask; + int ipc_ack; + int ipc_ack_mask; + int ipc_ctl; + int rom_init_timeout; + int ssp_count; /* ssp count of the platform */ + int ssp_base_offset; /* base address of the SSPs */ +}; + +extern const struct snd_sof_dsp_ops sof_tng_ops; +extern const struct snd_sof_dsp_ops sof_byt_ops; +extern const struct snd_sof_dsp_ops sof_cht_ops; +extern const struct snd_sof_dsp_ops sof_hsw_ops; +extern const struct snd_sof_dsp_ops sof_bdw_ops; + +extern const struct sof_intel_dsp_desc byt_chip_info; +extern const struct sof_intel_dsp_desc cht_chip_info; +extern const struct sof_intel_dsp_desc bdw_chip_info; +extern const struct sof_intel_dsp_desc hsw_chip_info; +extern const struct sof_intel_dsp_desc tng_chip_info; + +struct sof_intel_stream { + size_t posn_offset; +}; + +#endif diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c new file mode 100644 index 000000000000..f0b9d3c53f6f --- /dev/null +++ b/sound/soc/sof/ipc.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// +// Generic IPC layer that can work over MMIO and SPI/I2C. PHY layer provided +// by platform driver code. +// + +#include <linux/mutex.h> +#include <linux/types.h> + +#include "sof-priv.h" +#include "ops.h" + +/* + * IPC message default size and timeout (ms). + * TODO: allow platforms to set size and timeout. + */ +#define IPC_TIMEOUT_MS 300 + +static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id); +static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd); + +/* + * IPC message Tx/Rx message handling. + */ + +/* SOF generic IPC data */ +struct snd_sof_ipc { + struct snd_sof_dev *sdev; + + /* protects messages and the disable flag */ + struct mutex tx_mutex; + /* disables further sending of ipc's */ + bool disable_ipc_tx; + + struct snd_sof_ipc_msg msg; +}; + +struct sof_ipc_ctrl_data_params { + size_t msg_bytes; + size_t hdr_bytes; + size_t pl_size; + size_t elems; + u32 num_msg; + u8 *src; + u8 *dst; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC) +static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) +{ + u8 *str; + u8 *str2 = NULL; + u32 glb; + u32 type; + + glb = cmd & SOF_GLB_TYPE_MASK; + type = cmd & SOF_CMD_TYPE_MASK; + + switch (glb) { + case SOF_IPC_GLB_REPLY: + str = "GLB_REPLY"; break; + case SOF_IPC_GLB_COMPOUND: + str = "GLB_COMPOUND"; break; + case SOF_IPC_GLB_TPLG_MSG: + str = "GLB_TPLG_MSG"; + switch (type) { + case SOF_IPC_TPLG_COMP_NEW: + str2 = "COMP_NEW"; break; + case SOF_IPC_TPLG_COMP_FREE: + str2 = "COMP_FREE"; break; + case SOF_IPC_TPLG_COMP_CONNECT: + str2 = "COMP_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_NEW: + str2 = "PIPE_NEW"; break; + case SOF_IPC_TPLG_PIPE_FREE: + str2 = "PIPE_FREE"; break; + case SOF_IPC_TPLG_PIPE_CONNECT: + str2 = "PIPE_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_COMPLETE: + str2 = "PIPE_COMPLETE"; break; + case SOF_IPC_TPLG_BUFFER_NEW: + str2 = "BUFFER_NEW"; break; + case SOF_IPC_TPLG_BUFFER_FREE: + str2 = "BUFFER_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_PM_MSG: + str = "GLB_PM_MSG"; + switch (type) { + case SOF_IPC_PM_CTX_SAVE: + str2 = "CTX_SAVE"; break; + case SOF_IPC_PM_CTX_RESTORE: + str2 = "CTX_RESTORE"; break; + case SOF_IPC_PM_CTX_SIZE: + str2 = "CTX_SIZE"; break; + case SOF_IPC_PM_CLK_SET: + str2 = "CLK_SET"; break; + case SOF_IPC_PM_CLK_GET: + str2 = "CLK_GET"; break; + case SOF_IPC_PM_CLK_REQ: + str2 = "CLK_REQ"; break; + case SOF_IPC_PM_CORE_ENABLE: + str2 = "CORE_ENABLE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_COMP_MSG: + str = "GLB_COMP_MSG: SET_VALUE"; + switch (type) { + case SOF_IPC_COMP_SET_VALUE: + str2 = "SET_VALUE"; break; + case SOF_IPC_COMP_GET_VALUE: + str2 = "GET_VALUE"; break; + case SOF_IPC_COMP_SET_DATA: + str2 = "SET_DATA"; break; + case SOF_IPC_COMP_GET_DATA: + str2 = "GET_DATA"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_STREAM_MSG: + str = "GLB_STREAM_MSG"; + switch (type) { + case SOF_IPC_STREAM_PCM_PARAMS: + str2 = "PCM_PARAMS"; break; + case SOF_IPC_STREAM_PCM_PARAMS_REPLY: + str2 = "PCM_REPLY"; break; + case SOF_IPC_STREAM_PCM_FREE: + str2 = "PCM_FREE"; break; + case SOF_IPC_STREAM_TRIG_START: + str2 = "TRIG_START"; break; + case SOF_IPC_STREAM_TRIG_STOP: + str2 = "TRIG_STOP"; break; + case SOF_IPC_STREAM_TRIG_PAUSE: + str2 = "TRIG_PAUSE"; break; + case SOF_IPC_STREAM_TRIG_RELEASE: + str2 = "TRIG_RELEASE"; break; + case SOF_IPC_STREAM_TRIG_DRAIN: + str2 = "TRIG_DRAIN"; break; + case SOF_IPC_STREAM_TRIG_XRUN: + str2 = "TRIG_XRUN"; break; + case SOF_IPC_STREAM_POSITION: + str2 = "POSITION"; break; + case SOF_IPC_STREAM_VORBIS_PARAMS: + str2 = "VORBIS_PARAMS"; break; + case SOF_IPC_STREAM_VORBIS_FREE: + str2 = "VORBIS_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_FW_READY: + str = "FW_READY"; break; + case SOF_IPC_GLB_DAI_MSG: + str = "GLB_DAI_MSG"; + switch (type) { + case SOF_IPC_DAI_CONFIG: + str2 = "CONFIG"; break; + case SOF_IPC_DAI_LOOPBACK: + str2 = "LOOPBACK"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_TRACE_MSG: + str = "GLB_TRACE_MSG"; break; + default: + str = "unknown GLB command"; break; + } + + if (str2) + dev_dbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2); + else + dev_dbg(dev, "%s: 0x%x: %s\n", text, cmd, str); +} +#else +static inline void ipc_log_header(struct device *dev, u8 *text, u32 cmd) +{ + dev_dbg(dev, "%s: 0x%x\n", text, cmd); +} +#endif + +/* wait for IPC message reply */ +static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, + void *reply_data) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_cmd_hdr *hdr = msg->msg_data; + int ret; + + /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(IPC_TIMEOUT_MS)); + + if (ret == 0) { + dev_err(sdev->dev, "error: ipc timed out for 0x%x size %d\n", + hdr->cmd, hdr->size); + snd_sof_dsp_dbg_dump(ipc->sdev, SOF_DBG_REGS | SOF_DBG_MBOX); + snd_sof_ipc_dump(ipc->sdev); + snd_sof_trace_notify_for_error(ipc->sdev); + ret = -ETIMEDOUT; + } else { + /* copy the data returned from DSP */ + ret = msg->reply_error; + if (msg->reply_size) + memcpy(reply_data, msg->reply_data, msg->reply_size); + if (ret < 0) + dev_err(sdev->dev, "error: ipc error for 0x%x size %zu\n", + hdr->cmd, msg->reply_size); + else + ipc_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd); + } + + return ret; +} + +/* send IPC message from host to DSP */ +static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) +{ + struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc_msg *msg; + int ret; + + if (ipc->disable_ipc_tx) + return -ENODEV; + + /* + * The spin-lock is also still needed to protect message objects against + * other atomic contexts. + */ + spin_lock_irq(&sdev->ipc_lock); + + /* initialise the message */ + msg = &ipc->msg; + + msg->header = header; + msg->msg_size = msg_bytes; + msg->reply_size = reply_bytes; + msg->reply_error = 0; + + /* attach any data */ + if (msg_bytes) + memcpy(msg->msg_data, msg_data, msg_bytes); + + sdev->msg = msg; + + ret = snd_sof_dsp_send_msg(sdev, msg); + /* Next reply that we receive will be related to this message */ + if (!ret) + msg->ipc_complete = false; + + spin_unlock_irq(&sdev->ipc_lock); + + if (ret < 0) { + /* So far IPC TX never fails, consider making the above void */ + dev_err_ratelimited(sdev->dev, + "error: ipc tx failed with error %d\n", + ret); + return ret; + } + + ipc_log_header(sdev->dev, "ipc tx", msg->header); + + /* now wait for completion */ + if (!ret) + ret = tx_wait_done(ipc, msg, reply_data); + + return ret; +} + +/* send IPC message from host to DSP */ +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, void *reply_data, + size_t reply_bytes) +{ + int ret; + + if (msg_bytes > SOF_IPC_MSG_MAX_SIZE || + reply_bytes > SOF_IPC_MSG_MAX_SIZE) + return -ENOBUFS; + + /* Serialise IPC TX */ + mutex_lock(&ipc->tx_mutex); + + ret = sof_ipc_tx_message_unlocked(ipc, header, msg_data, msg_bytes, + reply_data, reply_bytes); + + mutex_unlock(&ipc->tx_mutex); + + return ret; +} +EXPORT_SYMBOL(sof_ipc_tx_message); + +/* handle reply message from DSP */ +int snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; + unsigned long flags; + + /* + * Protect against a theoretical race with sof_ipc_tx_message(): if the + * DSP is fast enough to receive an IPC message, reply to it, and the + * host interrupt processing calls this function on a different core + * from the one, where the sending is taking place, the message might + * not yet be marked as expecting a reply. + */ + spin_lock_irqsave(&sdev->ipc_lock, flags); + + if (msg->ipc_complete) { + spin_unlock_irqrestore(&sdev->ipc_lock, flags); + dev_err(sdev->dev, "error: no reply expected, received 0x%x", + msg_id); + return -EINVAL; + } + + /* wake up and return the error if we have waiters on this message ? */ + msg->ipc_complete = true; + wake_up(&msg->waitq); + + spin_unlock_irqrestore(&sdev->ipc_lock, flags); + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_reply); + +/* DSP firmware has sent host a message */ +void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) +{ + struct sof_ipc_cmd_hdr hdr; + u32 cmd, type; + int err = 0; + + /* read back header */ + snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); + ipc_log_header(sdev->dev, "ipc rx", hdr.cmd); + + cmd = hdr.cmd & SOF_GLB_TYPE_MASK; + type = hdr.cmd & SOF_CMD_TYPE_MASK; + + /* check message type */ + switch (cmd) { + case SOF_IPC_GLB_REPLY: + dev_err(sdev->dev, "error: ipc reply unknown\n"); + break; + case SOF_IPC_FW_READY: + /* check for FW boot completion */ + if (!sdev->boot_complete) { + err = sof_ops(sdev)->fw_ready(sdev, cmd); + if (err < 0) { + /* + * this indicates a mismatch in ABI + * between the driver and fw + */ + dev_err(sdev->dev, "error: ABI mismatch %d\n", + err); + } else { + /* firmware boot completed OK */ + sdev->boot_complete = true; + } + + /* wake up firmware loader */ + wake_up(&sdev->boot_wait); + } + break; + case SOF_IPC_GLB_COMPOUND: + case SOF_IPC_GLB_TPLG_MSG: + case SOF_IPC_GLB_PM_MSG: + case SOF_IPC_GLB_COMP_MSG: + break; + case SOF_IPC_GLB_STREAM_MSG: + /* need to pass msg id into the function */ + ipc_stream_message(sdev, hdr.cmd); + break; + case SOF_IPC_GLB_TRACE_MSG: + ipc_trace_message(sdev, type); + break; + default: + dev_err(sdev->dev, "error: unknown DSP message 0x%x\n", cmd); + break; + } + + ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd); +} +EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); + +/* + * IPC trace mechanism. + */ + +static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct sof_ipc_dma_trace_posn posn; + + switch (msg_id) { + case SOF_IPC_TRACE_DMA_POSITION: + /* read back full message */ + snd_sof_ipc_msg_data(sdev, NULL, &posn, sizeof(posn)); + snd_sof_trace_update_pos(sdev, &posn); + break; + default: + dev_err(sdev->dev, "error: unhandled trace message %x\n", + msg_id); + break; + } +} + +/* + * IPC stream position. + */ + +static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_sof_pcm_stream *stream; + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + int direction; + + spcm = snd_sof_find_spcm_comp(sdev, msg_id, &direction); + if (!spcm) { + dev_err(sdev->dev, + "error: period elapsed for unknown stream, msg_id %d\n", + msg_id); + return; + } + + stream = &spcm->stream[direction]; + snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + + dev_dbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n", + posn.host_posn, posn.dai_posn, posn.wallclock); + + memcpy(&stream->posn, &posn, sizeof(posn)); + + /* only inform ALSA for period_wakeup mode */ + if (!stream->substream->runtime->no_period_wakeup) + snd_sof_pcm_period_elapsed(stream->substream); +} + +/* DSP notifies host of an XRUN within FW */ +static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_sof_pcm_stream *stream; + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + int direction; + + spcm = snd_sof_find_spcm_comp(sdev, msg_id, &direction); + if (!spcm) { + dev_err(sdev->dev, "error: XRUN for unknown stream, msg_id %d\n", + msg_id); + return; + } + + stream = &spcm->stream[direction]; + snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + + dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n", + posn.host_posn, posn.xrun_comp_id, posn.xrun_size); + +#if defined(CONFIG_SND_SOC_SOF_DEBUG_XRUN_STOP) + /* stop PCM on XRUN - used for pipeline debug */ + memcpy(&stream->posn, &posn, sizeof(posn)); + snd_pcm_stop_xrun(stream->substream); +#endif +} + +/* stream notifications from DSP FW */ +static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd) +{ + /* get msg cmd type and msd id */ + u32 msg_type = msg_cmd & SOF_CMD_TYPE_MASK; + u32 msg_id = SOF_IPC_MESSAGE_ID(msg_cmd); + + switch (msg_type) { + case SOF_IPC_STREAM_POSITION: + ipc_period_elapsed(sdev, msg_id); + break; + case SOF_IPC_STREAM_TRIG_XRUN: + ipc_xrun(sdev, msg_id); + break; + default: + dev_err(sdev->dev, "error: unhandled stream message %x\n", + msg_id); + break; + } +} + +/* get stream position IPC - use faster MMIO method if available on platform */ +int snd_sof_ipc_stream_posn(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, int direction, + struct sof_ipc_stream_posn *posn) +{ + struct sof_ipc_stream stream; + int err; + + /* read position via slower IPC */ + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_POSITION; + stream.comp_id = spcm->stream[direction].comp_id; + + /* send IPC to the DSP */ + err = sof_ipc_tx_message(sdev->ipc, + stream.hdr.cmd, &stream, sizeof(stream), &posn, + sizeof(*posn)); + if (err < 0) { + dev_err(sdev->dev, "error: failed to get stream %d position\n", + stream.comp_id); + return err; + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_stream_posn); + +static int sof_get_ctrl_copy_params(enum sof_ipc_ctrl_type ctrl_type, + struct sof_ipc_ctrl_data *src, + struct sof_ipc_ctrl_data *dst, + struct sof_ipc_ctrl_data_params *sparams) +{ + switch (ctrl_type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + sparams->src = (u8 *)src->chanv; + sparams->dst = (u8 *)dst->chanv; + break; + case SOF_CTRL_TYPE_VALUE_COMP_GET: + case SOF_CTRL_TYPE_VALUE_COMP_SET: + sparams->src = (u8 *)src->compv; + sparams->dst = (u8 *)dst->compv; + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + sparams->src = (u8 *)src->data->data; + sparams->dst = (u8 *)dst->data->data; + break; + default: + return -EINVAL; + } + + /* calculate payload size and number of messages */ + sparams->pl_size = SOF_IPC_MSG_MAX_SIZE - sparams->hdr_bytes; + sparams->num_msg = DIV_ROUND_UP(sparams->msg_bytes, sparams->pl_size); + + return 0; +} + +static int sof_set_get_large_ctrl_data(struct snd_sof_dev *sdev, + struct sof_ipc_ctrl_data *cdata, + struct sof_ipc_ctrl_data_params *sparams, + bool send) +{ + struct sof_ipc_ctrl_data *partdata; + size_t send_bytes; + size_t offset = 0; + size_t msg_bytes; + size_t pl_size; + int err; + int i; + + /* allocate max ipc size because we have at least one */ + partdata = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!partdata) + return -ENOMEM; + + if (send) + err = sof_get_ctrl_copy_params(cdata->type, cdata, partdata, + sparams); + else + err = sof_get_ctrl_copy_params(cdata->type, partdata, cdata, + sparams); + if (err < 0) + return err; + + msg_bytes = sparams->msg_bytes; + pl_size = sparams->pl_size; + + /* copy the header data */ + memcpy(partdata, cdata, sparams->hdr_bytes); + + /* Serialise IPC TX */ + mutex_lock(&sdev->ipc->tx_mutex); + + /* copy the payload data in a loop */ + for (i = 0; i < sparams->num_msg; i++) { + send_bytes = min(msg_bytes, pl_size); + partdata->num_elems = send_bytes; + partdata->rhdr.hdr.size = sparams->hdr_bytes + send_bytes; + partdata->msg_index = i; + msg_bytes -= send_bytes; + partdata->elems_remaining = msg_bytes; + + if (send) + memcpy(sparams->dst, sparams->src + offset, send_bytes); + + err = sof_ipc_tx_message_unlocked(sdev->ipc, + partdata->rhdr.hdr.cmd, + partdata, + partdata->rhdr.hdr.size, + partdata, + partdata->rhdr.hdr.size); + if (err < 0) + break; + + if (!send) + memcpy(sparams->dst + offset, sparams->src, send_bytes); + + offset += pl_size; + } + + mutex_unlock(&sdev->ipc->tx_mutex); + + kfree(partdata); + return err; +} + +/* + * IPC get()/set() for kcontrols. + */ +int snd_sof_ipc_set_get_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, + u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd, + bool send) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct snd_sof_dev *sdev = ipc->sdev; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + struct sof_ipc_ctrl_data_params sparams; + size_t send_bytes; + int err; + + /* read or write firmware volume */ + if (scontrol->readback_offset != 0) { + /* write/read value header via mmaped region */ + send_bytes = sizeof(struct sof_ipc_ctrl_value_chan) * + cdata->num_elems; + if (send) + snd_sof_dsp_block_write(sdev, sdev->mmio_bar, + scontrol->readback_offset, + cdata->chanv, send_bytes); + + else + snd_sof_dsp_block_read(sdev, sdev->mmio_bar, + scontrol->readback_offset, + cdata->chanv, send_bytes); + return 0; + } + + cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; + cdata->cmd = ctrl_cmd; + cdata->type = ctrl_type; + cdata->comp_id = scontrol->comp_id; + cdata->msg_index = 0; + + /* calculate header and data size */ + switch (cdata->type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + sparams.msg_bytes = scontrol->num_channels * + sizeof(struct sof_ipc_ctrl_value_chan); + sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data); + sparams.elems = scontrol->num_channels; + break; + case SOF_CTRL_TYPE_VALUE_COMP_GET: + case SOF_CTRL_TYPE_VALUE_COMP_SET: + sparams.msg_bytes = scontrol->num_channels * + sizeof(struct sof_ipc_ctrl_value_comp); + sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data); + sparams.elems = scontrol->num_channels; + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + sparams.msg_bytes = cdata->data->size; + sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_abi_hdr); + sparams.elems = cdata->data->size; + break; + default: + return -EINVAL; + } + + cdata->rhdr.hdr.size = sparams.msg_bytes + sparams.hdr_bytes; + cdata->num_elems = sparams.elems; + cdata->elems_remaining = 0; + + /* send normal size ipc in one part */ + if (cdata->rhdr.hdr.size <= SOF_IPC_MSG_MAX_SIZE) { + err = sof_ipc_tx_message(sdev->ipc, cdata->rhdr.hdr.cmd, cdata, + cdata->rhdr.hdr.size, cdata, + cdata->rhdr.hdr.size); + + if (err < 0) + dev_err(sdev->dev, "error: set/get ctrl ipc comp %d\n", + cdata->comp_id); + + return err; + } + + /* data is bigger than max ipc size, chop into smaller pieces */ + dev_dbg(sdev->dev, "large ipc size %u, control size %u\n", + cdata->rhdr.hdr.size, scontrol->size); + + /* large messages is only supported from ABI 3.3.0 onwards */ + if (v->abi_version < SOF_ABI_VER(3, 3, 0)) { + dev_err(sdev->dev, "error: incompatible FW ABI version\n"); + return -EINVAL; + } + + err = sof_set_get_large_ctrl_data(sdev, cdata, &sparams, send); + + if (err < 0) + dev_err(sdev->dev, "error: set/get large ctrl ipc comp %d\n", + cdata->comp_id); + + return err; +} +EXPORT_SYMBOL(snd_sof_ipc_set_get_comp_data); + +/* + * IPC layer enumeration. + */ + +int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox, + size_t dspbox_size, u32 hostbox, + size_t hostbox_size) +{ + sdev->dsp_box.offset = dspbox; + sdev->dsp_box.size = dspbox_size; + sdev->host_box.offset = hostbox; + sdev->host_box.size = hostbox_size; + return 0; +} +EXPORT_SYMBOL(snd_sof_dsp_mailbox_init); + +int snd_sof_ipc_valid(struct snd_sof_dev *sdev) +{ + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + + dev_info(sdev->dev, + "Firmware info: version %d:%d:%d-%s\n", v->major, v->minor, + v->micro, v->tag); + dev_info(sdev->dev, + "Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", + SOF_ABI_VERSION_MAJOR(v->abi_version), + SOF_ABI_VERSION_MINOR(v->abi_version), + SOF_ABI_VERSION_PATCH(v->abi_version), + SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) { + dev_err(sdev->dev, "error: incompatible FW ABI version\n"); + return -EINVAL; + } + + if (v->abi_version > SOF_ABI_VERSION) { + if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) { + dev_warn(sdev->dev, "warn: FW ABI is more recent than kernel\n"); + } else { + dev_err(sdev->dev, "error: FW ABI is more recent than kernel\n"); + return -EINVAL; + } + } + + if (ready->debug.bits.build) { + dev_info(sdev->dev, + "Firmware debug build %d on %s-%s - options:\n" + " GDB: %s\n" + " lock debug: %s\n" + " lock vdebug: %s\n", + v->build, v->date, v->time, + ready->debug.bits.gdb ? "enabled" : "disabled", + ready->debug.bits.locks ? "enabled" : "disabled", + ready->debug.bits.locks_verbose ? "enabled" : "disabled"); + } + + /* copy the fw_version into debugfs at first boot */ + memcpy(&sdev->fw_version, v, sizeof(*v)); + + return 0; +} +EXPORT_SYMBOL(snd_sof_ipc_valid); + +struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc *ipc; + struct snd_sof_ipc_msg *msg; + + /* check if mandatory ops required for ipc are defined */ + if (!sof_ops(sdev)->fw_ready) { + dev_err(sdev->dev, "error: ipc mandatory ops not defined\n"); + return NULL; + } + + ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return NULL; + + mutex_init(&ipc->tx_mutex); + ipc->sdev = sdev; + msg = &ipc->msg; + + /* indicate that we aren't sending a message ATM */ + msg->ipc_complete = true; + + /* pre-allocate message data */ + msg->msg_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, + GFP_KERNEL); + if (!msg->msg_data) + return NULL; + + msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, + GFP_KERNEL); + if (!msg->reply_data) + return NULL; + + init_waitqueue_head(&msg->waitq); + + return ipc; +} +EXPORT_SYMBOL(snd_sof_ipc_init); + +void snd_sof_ipc_free(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc *ipc = sdev->ipc; + + /* disable sending of ipc's */ + mutex_lock(&ipc->tx_mutex); + ipc->disable_ipc_tx = true; + mutex_unlock(&ipc->tx_mutex); +} +EXPORT_SYMBOL(snd_sof_ipc_free); diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c new file mode 100644 index 000000000000..81c7452aae17 --- /dev/null +++ b/sound/soc/sof/loader.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// +// Generic firmware loader. +// + +#include <linux/firmware.h> +#include <sound/sof.h> +#include "ops.h" + +static int get_ext_windows(struct snd_sof_dev *sdev, + struct sof_ipc_ext_data_hdr *ext_hdr) +{ + struct sof_ipc_window *w = + container_of(ext_hdr, struct sof_ipc_window, ext_hdr); + size_t size; + + if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS) + return -EINVAL; + + size = sizeof(*w) + sizeof(struct sof_ipc_window_elem) * w->num_windows; + + /* keep a local copy of the data */ + sdev->info_window = kmemdup(w, size, GFP_KERNEL); + if (!sdev->info_window) + return -ENOMEM; + + return 0; +} + +/* parse the extended FW boot data structures from FW boot message */ +int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) +{ + struct sof_ipc_ext_data_hdr *ext_hdr; + void *ext_data; + int ret = 0; + + ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!ext_data) + return -ENOMEM; + + /* get first header */ + snd_sof_dsp_block_read(sdev, bar, offset, ext_data, + sizeof(*ext_hdr)); + ext_hdr = ext_data; + + while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { + /* read in ext structure */ + offset += sizeof(*ext_hdr); + snd_sof_dsp_block_read(sdev, bar, offset, + (void *)((u8 *)ext_data + sizeof(*ext_hdr)), + ext_hdr->hdr.size - sizeof(*ext_hdr)); + + dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n", + ext_hdr->type, ext_hdr->hdr.size); + + /* process structure data */ + switch (ext_hdr->type) { + case SOF_IPC_EXT_DMA_BUFFER: + break; + case SOF_IPC_EXT_WINDOW: + ret = get_ext_windows(sdev, ext_hdr); + break; + default: + break; + } + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to parse ext data type %d\n", + ext_hdr->type); + break; + } + + /* move to next header */ + offset += ext_hdr->hdr.size; + snd_sof_dsp_block_read(sdev, bar, offset, ext_data, + sizeof(*ext_hdr)); + ext_hdr = ext_data; + } + + kfree(ext_data); + return ret; +} +EXPORT_SYMBOL(snd_sof_fw_parse_ext_data); + +/* generic module parser for mmaped DSPs */ +int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, + struct snd_sof_mod_hdr *module) +{ + struct snd_sof_blk_hdr *block; + int count; + u32 offset; + size_t remaining; + + dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n", + module->size, module->num_blocks, module->type); + + block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module)); + + /* module->size doesn't include header size */ + remaining = module->size; + for (count = 0; count < module->num_blocks; count++) { + /* check for wrap */ + if (remaining < sizeof(*block)) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + + /* minus header size of block */ + remaining -= sizeof(*block); + + if (block->size == 0) { + dev_warn(sdev->dev, + "warning: block %d size zero\n", count); + dev_warn(sdev->dev, " type 0x%x offset 0x%x\n", + block->type, block->offset); + continue; + } + + switch (block->type) { + case SOF_FW_BLK_TYPE_RSRVD0: + case SOF_FW_BLK_TYPE_SRAM...SOF_FW_BLK_TYPE_RSRVD14: + continue; /* not handled atm */ + case SOF_FW_BLK_TYPE_IRAM: + case SOF_FW_BLK_TYPE_DRAM: + offset = block->offset; + break; + default: + dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n", + block->type, count); + return -EINVAL; + } + + dev_dbg(sdev->dev, + "block %d type 0x%x size 0x%x ==> offset 0x%x\n", + count, block->type, block->size, offset); + + /* checking block->size to avoid unaligned access */ + if (block->size % sizeof(u32)) { + dev_err(sdev->dev, "error: invalid block size 0x%x\n", + block->size); + return -EINVAL; + } + snd_sof_dsp_block_write(sdev, sdev->mmio_bar, offset, + block + 1, block->size); + + if (remaining < block->size) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + + /* minus body size of block */ + remaining -= block->size; + /* next block */ + block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block) + + block->size); + } + + return 0; +} +EXPORT_SYMBOL(snd_sof_parse_module_memcpy); + +static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw) +{ + struct snd_sof_fw_header *header; + + /* Read the header information from the data pointer */ + header = (struct snd_sof_fw_header *)fw->data; + + /* verify FW sig */ + if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) { + dev_err(sdev->dev, "error: invalid firmware signature\n"); + return -EINVAL; + } + + /* check size is valid */ + if (fw->size != header->file_size + sizeof(*header)) { + dev_err(sdev->dev, "error: invalid filesize mismatch got 0x%zx expected 0x%zx\n", + fw->size, header->file_size + sizeof(*header)); + return -EINVAL; + } + + dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n", + header->file_size, header->num_modules, + header->abi, sizeof(*header)); + + return 0; +} + +static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw) +{ + struct snd_sof_fw_header *header; + struct snd_sof_mod_hdr *module; + int (*load_module)(struct snd_sof_dev *sof_dev, + struct snd_sof_mod_hdr *hdr); + int ret, count; + size_t remaining; + + header = (struct snd_sof_fw_header *)fw->data; + load_module = sof_ops(sdev)->load_module; + if (!load_module) + return -EINVAL; + + /* parse each module */ + module = (struct snd_sof_mod_hdr *)((u8 *)(fw->data) + sizeof(*header)); + remaining = fw->size - sizeof(*header); + /* check for wrap */ + if (remaining > fw->size) { + dev_err(sdev->dev, "error: fw size smaller than header size\n"); + return -EINVAL; + } + + for (count = 0; count < header->num_modules; count++) { + /* check for wrap */ + if (remaining < sizeof(*module)) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + + /* minus header size of module */ + remaining -= sizeof(*module); + + /* module */ + ret = load_module(sdev, module); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid module %d\n", count); + return ret; + } + + if (remaining < module->size) { + dev_err(sdev->dev, "error: not enough data remaining\n"); + return -EINVAL; + } + + /* minus body size of module */ + remaining -= module->size; + module = (struct snd_sof_mod_hdr *)((u8 *)module + + sizeof(*module) + module->size); + } + + return 0; +} + +int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const char *fw_filename; + int ret; + + /* set code loading condition to true */ + sdev->code_loading = 1; + + /* Don't request firmware again if firmware is already requested */ + if (plat_data->fw) + return 0; + + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, + plat_data->fw_filename); + if (!fw_filename) + return -ENOMEM; + + ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev); + + if (ret < 0) { + dev_err(sdev->dev, "error: request firmware %s failed err: %d\n", + fw_filename, ret); + } + + kfree(fw_filename); + + return ret; +} +EXPORT_SYMBOL(snd_sof_load_firmware_raw); + +int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + int ret; + + ret = snd_sof_load_firmware_raw(sdev); + if (ret < 0) + return ret; + + /* make sure the FW header and file is valid */ + ret = check_header(sdev, plat_data->fw); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid FW header\n"); + goto error; + } + + /* prepare the DSP for FW loading */ + ret = snd_sof_dsp_reset(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to reset DSP\n"); + goto error; + } + + /* parse and load firmware modules to DSP */ + ret = load_modules(sdev, plat_data->fw); + if (ret < 0) { + dev_err(sdev->dev, "error: invalid FW modules\n"); + goto error; + } + + return 0; + +error: + release_firmware(plat_data->fw); + plat_data->fw = NULL; + return ret; + +} +EXPORT_SYMBOL(snd_sof_load_firmware_memcpy); + +int snd_sof_load_firmware(struct snd_sof_dev *sdev) +{ + dev_dbg(sdev->dev, "loading firmware\n"); + + if (sof_ops(sdev)->load_firmware) + return sof_ops(sdev)->load_firmware(sdev); + return 0; +} +EXPORT_SYMBOL(snd_sof_load_firmware); + +int snd_sof_run_firmware(struct snd_sof_dev *sdev) +{ + int ret; + int init_core_mask; + + init_waitqueue_head(&sdev->boot_wait); + sdev->boot_complete = false; + + /* create fw_version debugfs to store boot version info */ + if (sdev->first_boot) { + ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version, + sizeof(sdev->fw_version), + "fw_version"); + /* errors are only due to memory allocation, not debugfs */ + if (ret < 0) { + dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n"); + return ret; + } + } + + /* perform pre fw run operations */ + ret = snd_sof_dsp_pre_fw_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed pre fw run op\n"); + return ret; + } + + dev_dbg(sdev->dev, "booting DSP firmware\n"); + + /* boot the firmware on the DSP */ + ret = snd_sof_dsp_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to reset DSP\n"); + return ret; + } + + init_core_mask = ret; + + /* now wait for the DSP to boot */ + ret = wait_event_timeout(sdev->boot_wait, sdev->boot_complete, + msecs_to_jiffies(sdev->boot_timeout)); + if (ret == 0) { + dev_err(sdev->dev, "error: firmware boot failure\n"); + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX | + SOF_DBG_TEXT | SOF_DBG_PCI); + return -EIO; + } + + dev_info(sdev->dev, "firmware boot complete\n"); + + /* perform post fw run operations */ + ret = snd_sof_dsp_post_fw_run(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed post fw run op\n"); + return ret; + } + + /* fw boot is complete. Update the active cores mask */ + sdev->enabled_cores_mask = init_core_mask; + + return 0; +} +EXPORT_SYMBOL(snd_sof_run_firmware); + +void snd_sof_fw_unload(struct snd_sof_dev *sdev) +{ + /* TODO: support module unloading at runtime */ +} +EXPORT_SYMBOL(snd_sof_fw_unload); diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c new file mode 100644 index 000000000000..f84b4344dcc3 --- /dev/null +++ b/sound/soc/sof/nocodec.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/sof.h> +#include "sof-priv.h" + +static struct snd_soc_card sof_nocodec_card = { + .name = "nocodec", /* the sof- prefix is added by the core */ +}; + +static int sof_nocodec_bes_setup(struct device *dev, + const struct snd_sof_dsp_ops *ops, + struct snd_soc_dai_link *links, + int link_num, struct snd_soc_card *card) +{ + int i; + + if (!ops || !links || !card) + return -EINVAL; + + /* set up BE dai_links */ + for (i = 0; i < link_num; i++) { + links[i].name = devm_kasprintf(dev, GFP_KERNEL, + "NoCodec-%d", i); + if (!links[i].name) + return -ENOMEM; + + links[i].id = i; + links[i].no_pcm = 1; + links[i].cpu_dai_name = ops->drv[i].name; + links[i].platform_name = dev_name(dev); + links[i].codec_dai_name = "snd-soc-dummy-dai"; + links[i].codec_name = "snd-soc-dummy"; + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + } + + card->dai_link = links; + card->num_links = link_num; + + return 0; +} + +int sof_nocodec_setup(struct device *dev, + struct snd_sof_pdata *sof_pdata, + struct snd_soc_acpi_mach *mach, + const struct sof_dev_desc *desc, + const struct snd_sof_dsp_ops *ops) +{ + struct snd_soc_dai_link *links; + int ret; + + if (!mach) + return -EINVAL; + + sof_pdata->drv_name = "sof-nocodec"; + + mach->drv_name = "sof-nocodec"; + sof_pdata->fw_filename = desc->nocodec_fw_filename; + sof_pdata->tplg_filename = desc->nocodec_tplg_filename; + + /* create dummy BE dai_links */ + links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * + ops->num_drv, GFP_KERNEL); + if (!links) + return -ENOMEM; + + ret = sof_nocodec_bes_setup(dev, ops, links, ops->num_drv, + &sof_nocodec_card); + return ret; +} +EXPORT_SYMBOL(sof_nocodec_setup); + +static int sof_nocodec_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &sof_nocodec_card; + + card->dev = &pdev->dev; + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static int sof_nocodec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver sof_nocodec_audio = { + .probe = sof_nocodec_probe, + .remove = sof_nocodec_remove, + .driver = { + .name = "sof-nocodec", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(sof_nocodec_audio) + +MODULE_DESCRIPTION("ASoC sof nocodec"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:sof-nocodec"); diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c new file mode 100644 index 000000000000..7a27c3b719e7 --- /dev/null +++ b/sound/soc/sof/ops.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/pci.h> +#include "ops.h" + +static +bool snd_sof_pci_update_bits_unlocked(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value) +{ + struct pci_dev *pci = to_pci_dev(sdev->dev); + unsigned int old, new; + u32 ret = 0; + + pci_read_config_dword(pci, offset, &ret); + old = ret; + dev_dbg(sdev->dev, "Debug PCIR: %8.8x at %8.8x\n", old & mask, offset); + + new = (old & ~mask) | (value & mask); + + if (old == new) + return false; + + pci_write_config_dword(pci, offset, new); + dev_dbg(sdev->dev, "Debug PCIW: %8.8x at %8.8x\n", value, + offset); + + return true; +} + +bool snd_sof_pci_update_bits(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sdev->hw_lock, flags); + change = snd_sof_pci_update_bits_unlocked(sdev, offset, mask, value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_sof_pci_update_bits); + +bool snd_sof_dsp_update_bits_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value) +{ + unsigned int old, new; + u32 ret; + + ret = snd_sof_dsp_read(sdev, bar, offset); + + old = ret; + new = (old & ~mask) | (value & mask); + + if (old == new) + return false; + + snd_sof_dsp_write(sdev, bar, offset, new); + + return true; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits_unlocked); + +bool snd_sof_dsp_update_bits64_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 mask, u64 value) +{ + u64 old, new; + + old = snd_sof_dsp_read64(sdev, bar, offset); + + new = (old & ~mask) | (value & mask); + + if (old == new) + return false; + + snd_sof_dsp_write64(sdev, bar, offset, new); + + return true; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits64_unlocked); + +/* This is for registers bits with attribute RWC */ +bool snd_sof_dsp_update_bits(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sdev->hw_lock, flags); + change = snd_sof_dsp_update_bits_unlocked(sdev, bar, offset, mask, + value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits); + +bool snd_sof_dsp_update_bits64(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u64 mask, u64 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sdev->hw_lock, flags); + change = snd_sof_dsp_update_bits64_unlocked(sdev, bar, offset, mask, + value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits64); + +static +void snd_sof_dsp_update_bits_forced_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value) +{ + unsigned int old, new; + u32 ret; + + ret = snd_sof_dsp_read(sdev, bar, offset); + + old = ret; + new = (old & ~mask) | (value & mask); + + snd_sof_dsp_write(sdev, bar, offset, new); +} + +/* This is for registers bits with attribute RWC */ +void snd_sof_dsp_update_bits_forced(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(&sdev->hw_lock, flags); + snd_sof_dsp_update_bits_forced_unlocked(sdev, bar, offset, mask, value); + spin_unlock_irqrestore(&sdev->hw_lock, flags); +} +EXPORT_SYMBOL(snd_sof_dsp_update_bits_forced); + +void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset) +{ + dev_err(sdev->dev, "error : DSP panic!\n"); + + /* + * check if DSP is not ready and did not set the dsp_oops_offset. + * if the dsp_oops_offset is not set, set it from the panic message. + * Also add a check to memory window setting with panic message. + */ + if (!sdev->dsp_oops_offset) + sdev->dsp_oops_offset = offset; + else + dev_dbg(sdev->dev, "panic: dsp_oops_offset %zu offset %d\n", + sdev->dsp_oops_offset, offset); + + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX); + snd_sof_trace_notify_for_error(sdev); +} +EXPORT_SYMBOL(snd_sof_dsp_panic); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h new file mode 100644 index 000000000000..80fc3b374c2b --- /dev/null +++ b/sound/soc/sof/ops.h @@ -0,0 +1,411 @@ +/* SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + */ + +#ifndef __SOUND_SOC_SOF_IO_H +#define __SOUND_SOC_SOF_IO_H + +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <sound/pcm.h> +#include "sof-priv.h" + +#define sof_ops(sdev) \ + ((sdev)->pdata->desc->ops) + +/* Mandatory operations are verified during probing */ + +/* init */ +static inline int snd_sof_probe(struct snd_sof_dev *sdev) +{ + return sof_ops(sdev)->probe(sdev); +} + +static inline int snd_sof_remove(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->remove) + return sof_ops(sdev)->remove(sdev); + + return 0; +} + +/* control */ + +/* + * snd_sof_dsp_run returns the core mask of the cores that are available + * after successful fw boot + */ +static inline int snd_sof_dsp_run(struct snd_sof_dev *sdev) +{ + return sof_ops(sdev)->run(sdev); +} + +static inline int snd_sof_dsp_stall(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->stall) + return sof_ops(sdev)->stall(sdev); + + return 0; +} + +static inline int snd_sof_dsp_reset(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->reset) + return sof_ops(sdev)->reset(sdev); + + return 0; +} + +/* dsp core power up/power down */ +static inline int snd_sof_dsp_core_power_up(struct snd_sof_dev *sdev, + unsigned int core_mask) +{ + if (sof_ops(sdev)->core_power_up) + return sof_ops(sdev)->core_power_up(sdev, core_mask); + + return 0; +} + +static inline int snd_sof_dsp_core_power_down(struct snd_sof_dev *sdev, + unsigned int core_mask) +{ + if (sof_ops(sdev)->core_power_down) + return sof_ops(sdev)->core_power_down(sdev, core_mask); + + return 0; +} + +/* pre/post fw load */ +static inline int snd_sof_dsp_pre_fw_run(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->pre_fw_run) + return sof_ops(sdev)->pre_fw_run(sdev); + + return 0; +} + +static inline int snd_sof_dsp_post_fw_run(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->post_fw_run) + return sof_ops(sdev)->post_fw_run(sdev); + + return 0; +} + +/* power management */ +static inline int snd_sof_dsp_resume(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->resume) + return sof_ops(sdev)->resume(sdev); + + return 0; +} + +static inline int snd_sof_dsp_suspend(struct snd_sof_dev *sdev, int state) +{ + if (sof_ops(sdev)->suspend) + return sof_ops(sdev)->suspend(sdev, state); + + return 0; +} + +static inline int snd_sof_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->runtime_resume) + return sof_ops(sdev)->runtime_resume(sdev); + + return 0; +} + +static inline int snd_sof_dsp_runtime_suspend(struct snd_sof_dev *sdev, + int state) +{ + if (sof_ops(sdev)->runtime_suspend) + return sof_ops(sdev)->runtime_suspend(sdev, state); + + return 0; +} + +static inline void snd_sof_dsp_hw_params_upon_resume(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->set_hw_params_upon_resume) + sof_ops(sdev)->set_hw_params_upon_resume(sdev); +} + +static inline int snd_sof_dsp_set_clk(struct snd_sof_dev *sdev, u32 freq) +{ + if (sof_ops(sdev)->set_clk) + return sof_ops(sdev)->set_clk(sdev, freq); + + return 0; +} + +/* debug */ +static inline void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags) +{ + if (sof_ops(sdev)->dbg_dump) + return sof_ops(sdev)->dbg_dump(sdev, flags); +} + +static inline void snd_sof_ipc_dump(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->ipc_dump) + return sof_ops(sdev)->ipc_dump(sdev); +} + +/* register IO */ +static inline void snd_sof_dsp_write(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 value) +{ + if (sof_ops(sdev)->write) { + sof_ops(sdev)->write(sdev, sdev->bar[bar] + offset, value); + return; + } + + dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__); +} + +static inline void snd_sof_dsp_write64(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 value) +{ + if (sof_ops(sdev)->write64) { + sof_ops(sdev)->write64(sdev, sdev->bar[bar] + offset, value); + return; + } + + dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__); +} + +static inline u32 snd_sof_dsp_read(struct snd_sof_dev *sdev, u32 bar, + u32 offset) +{ + if (sof_ops(sdev)->read) + return sof_ops(sdev)->read(sdev, sdev->bar[bar] + offset); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +static inline u64 snd_sof_dsp_read64(struct snd_sof_dev *sdev, u32 bar, + u32 offset) +{ + if (sof_ops(sdev)->read64) + return sof_ops(sdev)->read64(sdev, sdev->bar[bar] + offset); + + dev_err(sdev->dev, "error: %s not defined\n", __func__); + return -ENOTSUPP; +} + +/* block IO */ +static inline void snd_sof_dsp_block_read(struct snd_sof_dev *sdev, u32 bar, + u32 offset, void *dest, size_t bytes) +{ + sof_ops(sdev)->block_read(sdev, bar, offset, dest, bytes); +} + +static inline void snd_sof_dsp_block_write(struct snd_sof_dev *sdev, u32 bar, + u32 offset, void *src, size_t bytes) +{ + sof_ops(sdev)->block_write(sdev, bar, offset, src, bytes); +} + +/* ipc */ +static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + return sof_ops(sdev)->send_msg(sdev, msg); +} + +/* host DMA trace */ +static inline int snd_sof_dma_trace_init(struct snd_sof_dev *sdev, + u32 *stream_tag) +{ + if (sof_ops(sdev)->trace_init) + return sof_ops(sdev)->trace_init(sdev, stream_tag); + + return 0; +} + +static inline int snd_sof_dma_trace_release(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->trace_release) + return sof_ops(sdev)->trace_release(sdev); + + return 0; +} + +static inline int snd_sof_dma_trace_trigger(struct snd_sof_dev *sdev, int cmd) +{ + if (sof_ops(sdev)->trace_trigger) + return sof_ops(sdev)->trace_trigger(sdev, cmd); + + return 0; +} + +/* host PCM ops */ +static inline int +snd_sof_pcm_platform_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_open) + return sof_ops(sdev)->pcm_open(sdev, substream); + + return 0; +} + +/* disconnect pcm substream to a host stream */ +static inline int +snd_sof_pcm_platform_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_close) + return sof_ops(sdev)->pcm_close(sdev, substream); + + return 0; +} + +/* host stream hw params */ +static inline int +snd_sof_pcm_platform_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_hw_params) + return sof_ops(sdev)->pcm_hw_params(sdev, substream, + params, ipc_params); + + return 0; +} + +/* host stream trigger */ +static inline int +snd_sof_pcm_platform_trigger(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, int cmd) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_trigger) + return sof_ops(sdev)->pcm_trigger(sdev, substream, cmd); + + return 0; +} + +/* host DSP message data */ +static inline void snd_sof_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + sof_ops(sdev)->ipc_msg_data(sdev, substream, p, sz); +} + +/* host configure DSP HW parameters */ +static inline int +snd_sof_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + return sof_ops(sdev)->ipc_pcm_params(sdev, substream, reply); +} + +/* host stream pointer */ +static inline snd_pcm_uframes_t +snd_sof_pcm_platform_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->pcm_pointer) + return sof_ops(sdev)->pcm_pointer(sdev, substream); + + return 0; +} + +static inline const struct snd_sof_dsp_ops +*sof_get_ops(const struct sof_dev_desc *d, + const struct sof_ops_table mach_ops[], int asize) +{ + int i; + + for (i = 0; i < asize; i++) { + if (d == mach_ops[i].desc) + return mach_ops[i].ops; + } + + /* not found */ + return NULL; +} + +/** + * snd_sof_dsp_register_poll_timeout - Periodically poll an address + * until a condition is met or a timeout occurs + * @op: accessor function (takes @addr as its only argument) + * @addr: Address to poll + * @val: Variable to read the value into + * @cond: Break condition (usually involving @val) + * @sleep_us: Maximum time to sleep between reads in us (0 + * tight-loops). Should be less than ~20ms since usleep_range + * is used (see Documentation/timers/timers-howto.txt). + * @timeout_us: Timeout in us, 0 means never timeout + * + * Returns 0 on success and -ETIMEDOUT upon a timeout. In either + * case, the last read value at @addr is stored in @val. Must not + * be called from atomic context if sleep_us or timeout_us are used. + * + * This is modelled after the readx_poll_timeout macros in linux/iopoll.h. + */ +#define snd_sof_dsp_read_poll_timeout(sdev, bar, offset, val, cond, sleep_us, timeout_us) \ +({ \ + u64 __timeout_us = (timeout_us); \ + unsigned long __sleep_us = (sleep_us); \ + ktime_t __timeout = ktime_add_us(ktime_get(), __timeout_us); \ + might_sleep_if((__sleep_us) != 0); \ + for (;;) { \ + (val) = snd_sof_dsp_read(sdev, bar, offset); \ + if (cond) { \ + dev_dbg(sdev->dev, \ + "FW Poll Status: reg=%#x successful\n", (val)); \ + break; \ + } \ + if (__timeout_us && \ + ktime_compare(ktime_get(), __timeout) > 0) { \ + (val) = snd_sof_dsp_read(sdev, bar, offset); \ + dev_dbg(sdev->dev, \ + "FW Poll Status: reg=%#x timedout\n", (val)); \ + break; \ + } \ + if (__sleep_us) \ + usleep_range((__sleep_us >> 2) + 1, __sleep_us); \ + } \ + (cond) ? 0 : -ETIMEDOUT; \ +}) + +/* This is for registers bits with attribute RWC */ +bool snd_sof_pci_update_bits(struct snd_sof_dev *sdev, u32 offset, + u32 mask, u32 value); + +bool snd_sof_dsp_update_bits_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value); + +bool snd_sof_dsp_update_bits64_unlocked(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 mask, u64 value); + +bool snd_sof_dsp_update_bits(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 value); + +bool snd_sof_dsp_update_bits64(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u64 mask, u64 value); + +void snd_sof_dsp_update_bits_forced(struct snd_sof_dev *sdev, u32 bar, + u32 offset, u32 mask, u32 value); + +int snd_sof_dsp_register_poll(struct snd_sof_dev *sdev, u32 bar, u32 offset, + u32 mask, u32 target, u32 timeout_ms, + u32 interval_us); + +void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset); +#endif diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c new file mode 100644 index 000000000000..649968841dad --- /dev/null +++ b/sound/soc/sof/pcm.c @@ -0,0 +1,767 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// +// PCM Layer, interface between ALSA and IPC. +// + +#include <linux/pm_runtime.h> +#include <sound/pcm_params.h> +#include <sound/sof.h> +#include "sof-priv.h" +#include "ops.h" + +#define DRV_NAME "sof-audio-component" + +/* Create DMA buffer page table for DSP */ +static int create_page_table(struct snd_pcm_substream *substream, + unsigned char *dma_area, size_t size) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); + int stream = substream->stream; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + return snd_sof_create_page_table(sdev, dmab, + spcm->stream[stream].page_table.area, size); +} + +static int sof_pcm_dsp_params(struct snd_sof_pcm *spcm, struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + struct snd_sof_dev *sdev = spcm->sdev; + /* validate offset */ + int ret = snd_sof_ipc_pcm_params(sdev, substream, reply); + + if (ret < 0) + dev_err(sdev->dev, "error: got wrong reply for PCM %d\n", + spcm->pcm.pcm_id); + + return ret; +} + +/* + * sof pcm period elapse work + */ +static void sof_pcm_period_elapsed_work(struct work_struct *work) +{ + struct snd_sof_pcm_stream *sps = + container_of(work, struct snd_sof_pcm_stream, + period_elapsed_work); + + snd_pcm_period_elapsed(sps->substream); +} + +/* + * sof pcm period elapse, this could be called at irq thread context. + */ +void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) { + dev_err(sdev->dev, + "error: period elapsed for unknown stream!\n"); + return; + } + + /* + * snd_pcm_period_elapsed() can be called in interrupt context + * before IRQ_HANDLED is returned. Inside snd_pcm_period_elapsed(), + * when the PCM is done draining or xrun happened, a STOP IPC will + * then be sent and this IPC will hit IPC timeout. + * To avoid sending IPC before the previous IPC is handled, we + * schedule delayed work here to call the snd_pcm_period_elapsed(). + */ + schedule_work(&spcm->stream[substream->stream].period_elapsed_work); +} +EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); + +/* this may get called several times by oss emulation */ +static int sof_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct sof_ipc_pcm_params pcm; + struct sof_ipc_pcm_params_reply ipc_params_reply; + int ret; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->dev, "pcm: hw params stream %d dir %d\n", + spcm->pcm.pcm_id, substream->stream); + + memset(&pcm, 0, sizeof(pcm)); + + /* allocate audio buffer pages */ + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) { + dev_err(sdev->dev, "error: could not allocate %d bytes for PCM %d\n", + params_buffer_bytes(params), spcm->pcm.pcm_id); + return ret; + } + if (ret) { + /* + * ret == 1 means the buffer is changed + * create compressed page table for audio firmware + * ret == 0 means the buffer is not changed + * so no need to regenerate the page table + */ + ret = create_page_table(substream, runtime->dma_area, + runtime->dma_bytes); + if (ret < 0) + return ret; + } + + /* number of pages should be rounded up */ + pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes); + + /* set IPC PCM parameters */ + pcm.hdr.size = sizeof(pcm); + pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; + pcm.comp_id = spcm->stream[substream->stream].comp_id; + pcm.params.hdr.size = sizeof(pcm.params); + pcm.params.buffer.phy_addr = + spcm->stream[substream->stream].page_table.addr; + pcm.params.buffer.size = runtime->dma_bytes; + pcm.params.direction = substream->stream; + pcm.params.sample_valid_bytes = params_width(params) >> 3; + pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + pcm.params.rate = params_rate(params); + pcm.params.channels = params_channels(params); + pcm.params.host_period_bytes = params_period_bytes(params); + + /* container size */ + ret = snd_pcm_format_physical_width(params_format(params)); + if (ret < 0) + return ret; + pcm.params.sample_container_bytes = ret >> 3; + + /* format */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16: + pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24: + pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; + break; + case SNDRV_PCM_FORMAT_S32: + pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; + break; + case SNDRV_PCM_FORMAT_FLOAT: + pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT; + break; + default: + return -EINVAL; + } + + /* firmware already configured host stream */ + ret = snd_sof_pcm_platform_hw_params(sdev, + substream, + params, + &pcm.params); + if (ret < 0) { + dev_err(sdev->dev, "error: platform hw params failed\n"); + return ret; + } + + dev_dbg(sdev->dev, "stream_tag %d", pcm.params.stream_tag); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), + &ipc_params_reply, sizeof(ipc_params_reply)); + if (ret < 0) { + dev_err(sdev->dev, "error: hw params ipc failed for stream %d\n", + pcm.params.stream_tag); + return ret; + } + + ret = sof_pcm_dsp_params(spcm, substream, &ipc_params_reply); + if (ret < 0) + return ret; + + /* save pcm hw_params */ + memcpy(&spcm->params[substream->stream], params, sizeof(*params)); + + INIT_WORK(&spcm->stream[substream->stream].period_elapsed_work, + sof_pcm_period_elapsed_work); + + return ret; +} + +static int sof_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); + + snd_pcm_lib_free_pages(substream); + + cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work); + + return ret; +} + +static int sof_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + int ret; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + /* + * check if hw_params needs to be set-up again. + * This is only needed when resuming from system sleep. + */ + if (!spcm->hw_params_upon_resume[substream->stream]) + return 0; + + dev_dbg(sdev->dev, "pcm: prepare stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + /* set hw_params */ + ret = sof_pcm_hw_params(substream, &spcm->params[substream->stream]); + if (ret < 0) { + dev_err(sdev->dev, "error: set pcm hw_params after resume\n"); + return ret; + } + + return 0; +} + +/* + * FE dai link trigger actions are always executed in non-atomic context because + * they involve IPC's. + */ +static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->dev, "pcm: trigger stream %d dir %d cmd %d\n", + spcm->pcm.pcm_id, substream->stream, cmd); + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE; + break; + case SNDRV_PCM_TRIGGER_RESUME: + /* set up hw_params */ + ret = sof_pcm_prepare(substream); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to set up hw_params upon resume\n"); + return ret; + } + + /* fallthrough */ + case SNDRV_PCM_TRIGGER_START: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; + break; + default: + dev_err(sdev->dev, "error: unhandled trigger cmd %d\n", cmd); + return -EINVAL; + } + + snd_sof_pcm_platform_trigger(sdev, substream, cmd); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); + + if (ret < 0 || cmd != SNDRV_PCM_TRIGGER_SUSPEND) + return ret; + + /* + * The hw_free op is usually called when the pcm stream is closed. + * Since the stream is not closed during suspend, the DSP needs to be + * notified explicitly to free pcm to prevent errors upon resume. + */ + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + /* send IPC to the DSP */ + return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); +} + +static snd_pcm_uframes_t sof_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + snd_pcm_uframes_t host, dai; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + /* use dsp ops pointer callback directly if set */ + if (sof_ops(sdev)->pcm_pointer) + return sof_ops(sdev)->pcm_pointer(sdev, substream); + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + /* read position from DSP */ + host = bytes_to_frames(substream->runtime, + spcm->stream[substream->stream].posn.host_posn); + dai = bytes_to_frames(substream->runtime, + spcm->stream[substream->stream].posn.dai_posn); + + dev_dbg(sdev->dev, "PCM: stream %d dir %d DMA position %lu DAI position %lu\n", + spcm->pcm.pcm_id, substream->stream, host, dai); + + return host; +} + +static int sof_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct snd_soc_tplg_stream_caps *caps; + int ret; + int err; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->dev, "pcm: open stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + /* clear hw_params_upon_resume flag */ + spcm->hw_params_upon_resume[substream->stream] = 0; + + caps = &spcm->pcm.caps[substream->stream]; + + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: pcm open failed to resume %d\n", + ret); + pm_runtime_put_noidle(sdev->dev); + return ret; + } + + /* set any runtime constraints based on topology */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + le32_to_cpu(caps->period_size_min)); + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + le32_to_cpu(caps->period_size_min)); + + /* set runtime config */ + runtime->hw.info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP; + runtime->hw.formats = le64_to_cpu(caps->formats); + runtime->hw.period_bytes_min = le32_to_cpu(caps->period_size_min); + runtime->hw.period_bytes_max = le32_to_cpu(caps->period_size_max); + runtime->hw.periods_min = le32_to_cpu(caps->periods_min); + runtime->hw.periods_max = le32_to_cpu(caps->periods_max); + + /* + * caps->buffer_size_min is not used since the + * snd_pcm_hardware structure only defines buffer_bytes_max + */ + runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max); + + dev_dbg(sdev->dev, "period min %zd max %zd bytes\n", + runtime->hw.period_bytes_min, + runtime->hw.period_bytes_max); + dev_dbg(sdev->dev, "period count %d max %d\n", + runtime->hw.periods_min, + runtime->hw.periods_max); + dev_dbg(sdev->dev, "buffer max %zd bytes\n", + runtime->hw.buffer_bytes_max); + + /* set wait time - TODO: come from topology */ + substream->wait_time = 500; + + spcm->stream[substream->stream].posn.host_posn = 0; + spcm->stream[substream->stream].posn.dai_posn = 0; + spcm->stream[substream->stream].substream = substream; + + ret = snd_sof_pcm_platform_open(sdev, substream); + if (ret < 0) { + dev_err(sdev->dev, "error: pcm open failed %d\n", + ret); + + pm_runtime_mark_last_busy(sdev->dev); + + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: pcm close failed to idle %d\n", + err); + } + + return ret; +} + +static int sof_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + int err; + + /* nothing to do for BE */ + if (rtd->dai_link->no_pcm) + return 0; + + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) + return -EINVAL; + + dev_dbg(sdev->dev, "pcm: close stream %d dir %d\n", spcm->pcm.pcm_id, + substream->stream); + + err = snd_sof_pcm_platform_close(sdev, substream); + if (err < 0) { + dev_err(sdev->dev, "error: pcm close failed %d\n", + err); + /* + * keep going, no point in preventing the close + * from happening + */ + } + + pm_runtime_mark_last_busy(sdev->dev); + + err = pm_runtime_put_autosuspend(sdev->dev); + if (err < 0) + dev_err(sdev->dev, "error: pcm close failed to idle %d\n", + err); + + return 0; +} + +static struct snd_pcm_ops sof_pcm_ops = { + .open = sof_pcm_open, + .close = sof_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sof_pcm_hw_params, + .prepare = sof_pcm_prepare, + .hw_free = sof_pcm_hw_free, + .trigger = sof_pcm_trigger, + .pointer = sof_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* + * Pre-allocate playback/capture audio buffer pages. + * no need to explicitly release memory preallocated by sof_pcm_new in pcm_free + * snd_pcm_lib_preallocate_free_for_all() is called by the core. + */ +static int sof_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_tplg_stream_caps *caps; + int stream = SNDRV_PCM_STREAM_PLAYBACK; + + /* find SOF PCM for this RTD */ + spcm = snd_sof_find_spcm_dai(sdev, rtd); + if (!spcm) { + dev_warn(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return 0; + } + + dev_dbg(sdev->dev, "creating new PCM %s\n", spcm->pcm.pcm_name); + + /* do we need to pre-allocate playback audio buffer pages */ + if (!spcm->pcm.playback) + goto capture; + + caps = &spcm->pcm.caps[stream]; + + /* pre-allocate playback audio buffer pages */ + dev_dbg(sdev->dev, "spcm: allocate %s playback DMA buffer size 0x%x max 0x%x\n", + caps->name, caps->buffer_size_min, caps->buffer_size_max); + + snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream, + SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + le32_to_cpu(caps->buffer_size_min), + le32_to_cpu(caps->buffer_size_max)); +capture: + stream = SNDRV_PCM_STREAM_CAPTURE; + + /* do we need to pre-allocate capture audio buffer pages */ + if (!spcm->pcm.capture) + return 0; + + caps = &spcm->pcm.caps[stream]; + + /* pre-allocate capture audio buffer pages */ + dev_dbg(sdev->dev, "spcm: allocate %s capture DMA buffer size 0x%x max 0x%x\n", + caps->name, caps->buffer_size_min, caps->buffer_size_max); + + snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream, + SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + le32_to_cpu(caps->buffer_size_min), + le32_to_cpu(caps->buffer_size_max)); + + return 0; +} + +/* fixup the BE DAI link to match any values from topology */ +static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_dai *dai = + snd_sof_find_dai(sdev, (char *)rtd->dai_link->name); + + /* no topology exists for this BE, try a common configuration */ + if (!dai) { + dev_warn(sdev->dev, "warning: no topology found for BE DAI %s config\n", + rtd->dai_link->name); + + /* set 48k, stereo, 16bits by default */ + rate->min = 48000; + rate->max = 48000; + + channels->min = 2; + channels->max = 2; + + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + + return 0; + } + + /* read format from topology */ + snd_mask_none(fmt); + + switch (dai->comp_dai.config.frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + break; + case SOF_IPC_FRAME_S24_4LE: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + break; + case SOF_IPC_FRAME_S32_LE: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + break; + default: + dev_err(sdev->dev, "error: No available DAI format!\n"); + return -EINVAL; + } + + /* read rate and channels from topology */ + switch (dai->dai_config->type) { + case SOF_DAI_INTEL_SSP: + rate->min = dai->dai_config->ssp.fsync_rate; + rate->max = dai->dai_config->ssp.fsync_rate; + channels->min = dai->dai_config->ssp.tdm_slots; + channels->max = dai->dai_config->ssp.tdm_slots; + + dev_dbg(sdev->dev, + "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(sdev->dev, + "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + + break; + case SOF_DAI_INTEL_DMIC: + /* DMIC only supports 16 or 32 bit formats */ + if (dai->comp_dai.config.frame_fmt == SOF_IPC_FRAME_S24_4LE) { + dev_err(sdev->dev, + "error: invalid fmt %d for DAI type %d\n", + dai->comp_dai.config.frame_fmt, + dai->dai_config->type); + } + break; + case SOF_DAI_INTEL_HDA: + /* do nothing for HDA dai_link */ + break; + default: + dev_err(sdev->dev, "error: invalid DAI type %d\n", + dai->dai_config->type); + break; + } + + return 0; +} + +static int sof_pcm_probe(struct snd_soc_component *component) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pdata *plat_data = sdev->pdata; + const char *tplg_filename; + int ret; + + /* load the default topology */ + sdev->component = component; + + tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, + "%s/%s", + plat_data->tplg_filename_prefix, + plat_data->tplg_filename); + if (!tplg_filename) + return -ENOMEM; + + ret = snd_sof_load_topology(sdev, tplg_filename); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load DSP topology %d\n", + ret); + return ret; + } + + /* + * Some platforms in SOF, ex: BYT, may not have their platform PM + * callbacks set. Increment the usage count so as to + * prevent the device from entering runtime suspend. + */ + if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume) + pm_runtime_get_noresume(sdev->dev); + + return ret; +} + +static void sof_pcm_remove(struct snd_soc_component *component) +{ + /* remove topology */ + snd_soc_tplg_component_remove(component, SND_SOC_TPLG_INDEX_ALL); +} + +void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) +{ + struct snd_soc_component_driver *pd = &sdev->plat_drv; + struct snd_sof_pdata *plat_data = sdev->pdata; + const char *drv_name; + + drv_name = plat_data->machine->drv_name; + + pd->name = "sof-audio-component"; + pd->probe = sof_pcm_probe; + pd->remove = sof_pcm_remove; + pd->ops = &sof_pcm_ops; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) + pd->compr_ops = &sof_compressed_ops; +#endif + pd->pcm_new = sof_pcm_new; + pd->ignore_machine = drv_name; + pd->be_hw_params_fixup = sof_pcm_dai_link_fixup; + pd->be_pcm_base = SOF_BE_PCM_BASE; + pd->use_dai_pcm_id = true; + pd->topology_name_prefix = "sof"; + + /* increment module refcount when a pcm is opened */ + pd->module_get_upon_open = 1; +} diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c new file mode 100644 index 000000000000..8ef1d51025d8 --- /dev/null +++ b/sound/soc/sof/pm.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include "ops.h" +#include "sof-priv.h" + +static int sof_restore_kcontrols(struct snd_sof_dev *sdev) +{ + struct snd_sof_control *scontrol; + int ipc_cmd, ctrl_type; + int ret = 0; + + /* restore kcontrol values */ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + /* reset readback offset for scontrol after resuming */ + scontrol->readback_offset = 0; + + /* notify DSP of kcontrol values */ + switch (scontrol->cmd) { + case SOF_CTRL_CMD_VOLUME: + case SOF_CTRL_CMD_ENUM: + case SOF_CTRL_CMD_SWITCH: + ipc_cmd = SOF_IPC_COMP_SET_VALUE; + ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; + ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + ipc_cmd, ctrl_type, + scontrol->cmd, + true); + break; + case SOF_CTRL_CMD_BINARY: + ipc_cmd = SOF_IPC_COMP_SET_DATA; + ctrl_type = SOF_CTRL_TYPE_DATA_SET; + ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol, + ipc_cmd, ctrl_type, + scontrol->cmd, + true); + break; + + default: + break; + } + + if (ret < 0) { + dev_err(sdev->dev, + "error: failed kcontrol value set for widget: %d\n", + scontrol->comp_id); + + return ret; + } + } + + return 0; +} + +static int sof_restore_pipelines(struct snd_sof_dev *sdev) +{ + struct snd_sof_widget *swidget; + struct snd_sof_route *sroute; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_dai *dai; + struct sof_ipc_comp_dai *comp_dai; + struct sof_ipc_cmd_hdr *hdr; + int ret; + + /* restore pipeline components */ + list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { + struct sof_ipc_comp_reply r; + + /* skip if there is no private data */ + if (!swidget->private) + continue; + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + dai = swidget->private; + comp_dai = &dai->comp_dai; + ret = sof_ipc_tx_message(sdev->ipc, + comp_dai->comp.hdr.cmd, + comp_dai, sizeof(*comp_dai), + &r, sizeof(r)); + break; + case snd_soc_dapm_scheduler: + + /* + * During suspend, all DSP cores are powered off. + * Therefore upon resume, create the pipeline comp + * and power up the core that the pipeline is + * scheduled on. + */ + pipeline = swidget->private; + ret = sof_load_pipeline_ipc(sdev, pipeline, &r); + break; + default: + hdr = swidget->private; + ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, + swidget->private, hdr->size, + &r, sizeof(r)); + break; + } + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to load widget type %d with ID: %d\n", + swidget->widget->id, swidget->comp_id); + + return ret; + } + } + + /* restore pipeline connections */ + list_for_each_entry_reverse(sroute, &sdev->route_list, list) { + struct sof_ipc_pipe_comp_connect *connect; + struct sof_ipc_reply reply; + + /* skip if there's no private data */ + if (!sroute->private) + continue; + + connect = sroute->private; + + /* send ipc */ + ret = sof_ipc_tx_message(sdev->ipc, + connect->hdr.cmd, + connect, sizeof(*connect), + &reply, sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to load route sink %s control %s source %s\n", + sroute->route->sink, + sroute->route->control ? sroute->route->control + : "none", + sroute->route->source); + + return ret; + } + } + + /* restore dai links */ + list_for_each_entry_reverse(dai, &sdev->dai_list, list) { + struct sof_ipc_reply reply; + struct sof_ipc_dai_config *config = dai->dai_config; + + if (!config) { + dev_err(sdev->dev, "error: no config for DAI %s\n", + dai->name); + continue; + } + + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, + config->hdr.size, + &reply, sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to set dai config for %s\n", + dai->name); + + return ret; + } + } + + /* complete pipeline */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + switch (swidget->id) { + case snd_soc_dapm_scheduler: + swidget->complete = + snd_sof_complete_pipeline(sdev, swidget); + break; + default: + break; + } + } + + /* restore pipeline kcontrols */ + ret = sof_restore_kcontrols(sdev); + if (ret < 0) + dev_err(sdev->dev, + "error: restoring kcontrols after resume\n"); + + return ret; +} + +static int sof_send_pm_ipc(struct snd_sof_dev *sdev, int cmd) +{ + struct sof_ipc_pm_ctx pm_ctx; + struct sof_ipc_reply reply; + + memset(&pm_ctx, 0, sizeof(pm_ctx)); + + /* configure ctx save ipc message */ + pm_ctx.hdr.size = sizeof(pm_ctx); + pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd; + + /* send ctx save ipc to dsp */ + return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx, + sizeof(pm_ctx), &reply, sizeof(reply)); +} + +static void sof_set_hw_params_upon_resume(struct snd_sof_dev *sdev) +{ + struct snd_pcm_substream *substream; + struct snd_sof_pcm *spcm; + snd_pcm_state_t state; + int dir; + + /* + * SOF requires hw_params to be set-up internally upon resume. + * So, set the flag to indicate this for those streams that + * have been suspended. + */ + list_for_each_entry(spcm, &sdev->pcm_list, list) { + for (dir = 0; dir <= SNDRV_PCM_STREAM_CAPTURE; dir++) { + substream = spcm->stream[dir].substream; + if (!substream || !substream->runtime) + continue; + + state = substream->runtime->status->state; + if (state == SNDRV_PCM_STATE_SUSPENDED) + spcm->hw_params_upon_resume[dir] = 1; + } + } + + /* set internal flag for BE */ + snd_sof_dsp_hw_params_upon_resume(sdev); +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) +static void sof_cache_debugfs(struct snd_sof_dev *sdev) +{ + struct snd_sof_dfsentry *dfse; + + list_for_each_entry(dfse, &sdev->dfsentry_list, list) { + + /* nothing to do if debugfs buffer is not IO mem */ + if (dfse->type == SOF_DFSENTRY_TYPE_BUF) + continue; + + /* cache memory that is only accessible in D0 */ + if (dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) + memcpy_fromio(dfse->cache_buf, dfse->io_mem, + dfse->size); + } +} +#endif + +static int sof_resume(struct device *dev, bool runtime_resume) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + int ret; + + /* do nothing if dsp resume callbacks are not set */ + if (!sof_ops(sdev)->resume || !sof_ops(sdev)->runtime_resume) + return 0; + + /* + * if the runtime_resume flag is set, call the runtime_resume routine + * or else call the system resume routine + */ + if (runtime_resume) + ret = snd_sof_dsp_runtime_resume(sdev); + else + ret = snd_sof_dsp_resume(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to power up DSP after resume\n"); + return ret; + } + + /* load the firmware */ + ret = snd_sof_load_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to load DSP firmware after resume %d\n", + ret); + return ret; + } + + /* boot the firmware */ + ret = snd_sof_run_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to boot DSP firmware after resume %d\n", + ret); + return ret; + } + + /* resume DMA trace, only need send ipc */ + ret = snd_sof_init_trace_ipc(sdev); + if (ret < 0) { + /* non fatal */ + dev_warn(sdev->dev, + "warning: failed to init trace after resume %d\n", + ret); + } + + /* restore pipelines */ + ret = sof_restore_pipelines(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to restore pipeline after resume %d\n", + ret); + return ret; + } + + /* notify DSP of system resume */ + ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); + if (ret < 0) + dev_err(sdev->dev, + "error: ctx_restore ipc error during resume %d\n", + ret); + + return ret; +} + +static int sof_suspend(struct device *dev, bool runtime_suspend) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + int ret; + + /* do nothing if dsp suspend callback is not set */ + if (!sof_ops(sdev)->suspend) + return 0; + + /* release trace */ + snd_sof_release_trace(sdev); + + /* set restore_stream for all streams during system suspend */ + if (!runtime_suspend) + sof_set_hw_params_upon_resume(sdev); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) + /* cache debugfs contents during runtime suspend */ + if (runtime_suspend) + sof_cache_debugfs(sdev); +#endif + /* notify DSP of upcoming power down */ + ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_SAVE); + if (ret < 0) { + dev_err(sdev->dev, + "error: ctx_save ipc error during suspend %d\n", + ret); + return ret; + } + + /* power down all DSP cores */ + if (runtime_suspend) + ret = snd_sof_dsp_runtime_suspend(sdev, 0); + else + ret = snd_sof_dsp_suspend(sdev, 0); + if (ret < 0) + dev_err(sdev->dev, + "error: failed to power down DSP during suspend %d\n", + ret); + + return ret; +} + +int snd_sof_runtime_suspend(struct device *dev) +{ + return sof_suspend(dev, true); +} +EXPORT_SYMBOL(snd_sof_runtime_suspend); + +int snd_sof_runtime_resume(struct device *dev) +{ + return sof_resume(dev, true); +} +EXPORT_SYMBOL(snd_sof_runtime_resume); + +int snd_sof_resume(struct device *dev) +{ + return sof_resume(dev, false); +} +EXPORT_SYMBOL(snd_sof_resume); + +int snd_sof_suspend(struct device *dev) +{ + return sof_suspend(dev, false); +} +EXPORT_SYMBOL(snd_sof_suspend); diff --git a/sound/soc/sof/sof-acpi-dev.c b/sound/soc/sof/sof-acpi-dev.c new file mode 100644 index 000000000000..e9cf69874b5b --- /dev/null +++ b/sound/soc/sof/sof-acpi-dev.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/acpi.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> +#include <sound/sof.h> +#ifdef CONFIG_X86 +#include <asm/iosf_mbi.h> +#endif + +#include "ops.h" + +/* platform specific devices */ +#include "intel/shim.h" + +static char *fw_path; +module_param(fw_path, charp, 0444); +MODULE_PARM_DESC(fw_path, "alternate path for SOF firmware."); + +static char *tplg_path; +module_param(tplg_path, charp, 0444); +MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HASWELL) +static const struct sof_dev_desc sof_acpi_haswell_desc = { + .machines = snd_soc_acpi_intel_haswell_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_imr_base = -1, + .irqindex_host_ipc = 0, + .chip_info = &hsw_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-hsw.ri", + .nocodec_tplg_filename = "sof-hsw-nocodec.tplg", + .ops = &sof_hsw_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) +static const struct sof_dev_desc sof_acpi_broadwell_desc = { + .machines = snd_soc_acpi_intel_broadwell_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_imr_base = -1, + .irqindex_host_ipc = 0, + .chip_info = &bdw_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-bdw.ri", + .nocodec_tplg_filename = "sof-bdw-nocodec.tplg", + .ops = &sof_bdw_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL) + +/* BYTCR uses different IRQ index */ +static const struct sof_dev_desc sof_acpi_baytrailcr_desc = { + .machines = snd_soc_acpi_intel_baytrail_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_imr_base = 2, + .irqindex_host_ipc = 0, + .chip_info = &byt_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-byt.ri", + .nocodec_tplg_filename = "sof-byt-nocodec.tplg", + .ops = &sof_byt_ops, + .arch_ops = &sof_xtensa_arch_ops +}; + +static const struct sof_dev_desc sof_acpi_baytrail_desc = { + .machines = snd_soc_acpi_intel_baytrail_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_imr_base = 2, + .irqindex_host_ipc = 5, + .chip_info = &byt_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-byt.ri", + .nocodec_tplg_filename = "sof-byt-nocodec.tplg", + .ops = &sof_byt_ops, + .arch_ops = &sof_xtensa_arch_ops +}; + +#ifdef CONFIG_X86 /* TODO: move this to common helper */ + +static bool is_byt_cr(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int status; + + if (iosf_mbi_available()) { + u32 bios_status; + status = iosf_mbi_read(BT_MBI_UNIT_PMC, /* 0x04 PUNIT */ + MBI_REG_READ, /* 0x10 */ + 0x006, /* BIOS_CONFIG */ + &bios_status); + + if (status) { + dev_err(dev, "could not read PUNIT BIOS_CONFIG\n"); + } else { + /* bits 26:27 mirror PMIC options */ + bios_status = (bios_status >> 26) & 3; + + if (bios_status == 1 || bios_status == 3) { + dev_info(dev, "Detected Baytrail-CR platform\n"); + return true; + } + + dev_info(dev, "BYT-CR not detected\n"); + } + } else { + dev_info(dev, "IOSF_MBI not available, no BYT-CR detection\n"); + } + + if (platform_get_resource(pdev, IORESOURCE_IRQ, 5) == NULL) { + /* + * Some devices detected as BYT-T have only a single IRQ listed, + * causing platform_get_irq with index 5 to return -ENXIO. + * The correct IRQ in this case is at index 0, as on BYT-CR. + */ + dev_info(dev, "Falling back to Baytrail-CR platform\n"); + return true; + } + + return false; +} +#else +static int is_byt_cr(struct platform_device *pdev) +{ + return 0; +} +#endif + +static const struct sof_dev_desc sof_acpi_cherrytrail_desc = { + .machines = snd_soc_acpi_intel_cherrytrail_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_imr_base = 2, + .irqindex_host_ipc = 5, + .chip_info = &cht_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-cht.ri", + .nocodec_tplg_filename = "sof-cht-nocodec.tplg", + .ops = &sof_cht_ops, + .arch_ops = &sof_xtensa_arch_ops +}; + +#endif + +static const struct dev_pm_ops sof_acpi_pm = { + SET_SYSTEM_SLEEP_PM_OPS(snd_sof_suspend, snd_sof_resume) + SET_RUNTIME_PM_OPS(snd_sof_runtime_suspend, snd_sof_runtime_resume, + NULL) +}; + +static void sof_acpi_probe_complete(struct device *dev) +{ + /* allow runtime_pm */ + pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); +} + +static int sof_acpi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct sof_dev_desc *desc; + struct snd_soc_acpi_mach *mach; + struct snd_sof_pdata *sof_pdata; + const struct snd_sof_dsp_ops *ops; + int ret; + + dev_dbg(&pdev->dev, "ACPI DSP detected"); + + sof_pdata = devm_kzalloc(dev, sizeof(*sof_pdata), GFP_KERNEL); + if (!sof_pdata) + return -ENOMEM; + + desc = device_get_match_data(dev); + if (!desc) + return -ENODEV; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL) + if (desc == &sof_acpi_baytrail_desc && is_byt_cr(pdev)) + desc = &sof_acpi_baytrailcr_desc; +#endif + + /* get ops for platform */ + ops = desc->ops; + if (!ops) { + dev_err(dev, "error: no matching ACPI descriptor ops\n"); + return -ENODEV; + } + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE) + /* force nocodec mode */ + dev_warn(dev, "Force to use nocodec mode\n"); + mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL); + if (!mach) + return -ENOMEM; + ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops); + if (ret < 0) + return ret; +#else + /* find machine */ + mach = snd_soc_acpi_find_machine(desc->machines); + if (!mach) { + dev_warn(dev, "warning: No matching ASoC machine driver found\n"); + } else { + sof_pdata->fw_filename = mach->sof_fw_filename; + sof_pdata->tplg_filename = mach->sof_tplg_filename; + } +#endif + + if (mach) { + mach->mach_params.platform = dev_name(dev); + mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc; + } + + sof_pdata->machine = mach; + sof_pdata->desc = desc; + sof_pdata->dev = &pdev->dev; + sof_pdata->platform = dev_name(dev); + + /* alternate fw and tplg filenames ? */ + if (fw_path) + sof_pdata->fw_filename_prefix = fw_path; + else + sof_pdata->fw_filename_prefix = + sof_pdata->desc->default_fw_path; + + if (tplg_path) + sof_pdata->tplg_filename_prefix = tplg_path; + else + sof_pdata->tplg_filename_prefix = + sof_pdata->desc->default_tplg_path; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) + /* set callback to enable runtime_pm */ + sof_pdata->sof_probe_complete = sof_acpi_probe_complete; +#endif + /* call sof helper for DSP hardware probe */ + ret = snd_sof_device_probe(dev, sof_pdata); + if (ret) { + dev_err(dev, "error: failed to probe DSP hardware!\n"); + return ret; + } + +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) + sof_acpi_probe_complete(dev); +#endif + + return ret; +} + +static int sof_acpi_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + /* call sof helper for DSP hardware remove */ + snd_sof_device_remove(&pdev->dev); + + return 0; +} + +static const struct acpi_device_id sof_acpi_match[] = { +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HASWELL) + { "INT33C8", (unsigned long)&sof_acpi_haswell_desc }, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL) + { "INT3438", (unsigned long)&sof_acpi_broadwell_desc }, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL) + { "80860F28", (unsigned long)&sof_acpi_baytrail_desc }, + { "808622A8", (unsigned long)&sof_acpi_cherrytrail_desc }, +#endif + { } +}; +MODULE_DEVICE_TABLE(acpi, sof_acpi_match); + +/* acpi_driver definition */ +static struct platform_driver snd_sof_acpi_driver = { + .probe = sof_acpi_probe, + .remove = sof_acpi_remove, + .driver = { + .name = "sof-audio-acpi", + .pm = &sof_acpi_pm, + .acpi_match_table = ACPI_PTR(sof_acpi_match), + }, +}; +module_platform_driver(snd_sof_acpi_driver); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c new file mode 100644 index 000000000000..b778dffb2d25 --- /dev/null +++ b/sound/soc/sof/sof-pci-dev.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pm_runtime.h> +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> +#include <sound/sof.h> +#include "ops.h" + +/* platform specific devices */ +#include "intel/shim.h" +#include "intel/hda.h" + +static char *fw_path; +module_param(fw_path, charp, 0444); +MODULE_PARM_DESC(fw_path, "alternate path for SOF firmware."); + +static char *tplg_path; +module_param(tplg_path, charp, 0444); +MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE) +static const struct sof_dev_desc bxt_desc = { + .machines = snd_soc_acpi_intel_bxt_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &apl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-apl.ri", + .nocodec_tplg_filename = "sof-apl-nocodec.tplg", + .ops = &sof_apl_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE) +static const struct sof_dev_desc glk_desc = { + .machines = snd_soc_acpi_intel_glk_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &apl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-glk.ri", + .nocodec_tplg_filename = "sof-glk-nocodec.tplg", + .ops = &sof_apl_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD) +static struct snd_soc_acpi_mach sof_tng_machines[] = { + { + .id = "INT343A", + .drv_name = "edison", + .sof_fw_filename = "sof-byt.ri", + .sof_tplg_filename = "sof-byt.tplg", + }, + {} +}; + +static const struct sof_dev_desc tng_desc = { + .machines = sof_tng_machines, + .resindex_lpe_base = 3, /* IRAM, but subtract IRAM offset */ + .resindex_pcicfg_base = -1, + .resindex_imr_base = 0, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &tng_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-byt.ri", + .nocodec_tplg_filename = "sof-byt.tplg", + .ops = &sof_tng_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_CANNONLAKE) +static const struct sof_dev_desc cnl_desc = { + .machines = snd_soc_acpi_intel_cnl_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &cnl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-cnl.ri", + .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", + .ops = &sof_cnl_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE) +static const struct sof_dev_desc cfl_desc = { + .machines = snd_soc_acpi_intel_cnl_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &cnl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-cnl.ri", + .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", + .ops = &sof_cnl_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE) +static const struct sof_dev_desc icl_desc = { + .machines = snd_soc_acpi_intel_icl_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &cnl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-icl.ri", + .nocodec_tplg_filename = "sof-icl-nocodec.tplg", + .ops = &sof_cnl_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_SKYLAKE) +static const struct sof_dev_desc skl_desc = { + .machines = snd_soc_acpi_intel_skl_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &skl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-skl.ri", + .nocodec_tplg_filename = "sof-skl-nocodec.tplg", + .ops = &sof_skl_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_KABYLAKE) +static const struct sof_dev_desc kbl_desc = { + .machines = snd_soc_acpi_intel_kbl_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .chip_info = &skl_chip_info, + .default_fw_path = "intel/sof", + .default_tplg_path = "intel/sof-tplg", + .nocodec_fw_filename = "sof-kbl.ri", + .nocodec_tplg_filename = "sof-kbl-nocodec.tplg", + .ops = &sof_skl_ops, + .arch_ops = &sof_xtensa_arch_ops +}; +#endif + +static const struct dev_pm_ops sof_pci_pm = { + SET_SYSTEM_SLEEP_PM_OPS(snd_sof_suspend, snd_sof_resume) + SET_RUNTIME_PM_OPS(snd_sof_runtime_suspend, snd_sof_runtime_resume, + NULL) +}; + +static void sof_pci_probe_complete(struct device *dev) +{ + dev_dbg(dev, "Completing SOF PCI probe"); + + /* allow runtime_pm */ + pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + /* + * runtime pm for pci device is "forbidden" by default. + * so call pm_runtime_allow() to enable it. + */ + pm_runtime_allow(dev); + + /* follow recommendation in pci-driver.c to decrement usage counter */ + pm_runtime_put_noidle(dev); +} + +static int sof_pci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct device *dev = &pci->dev; + const struct sof_dev_desc *desc = + (const struct sof_dev_desc *)pci_id->driver_data; + struct snd_soc_acpi_mach *mach; + struct snd_sof_pdata *sof_pdata; + const struct snd_sof_dsp_ops *ops; + int ret; + + dev_dbg(&pci->dev, "PCI DSP detected"); + + /* get ops for platform */ + ops = desc->ops; + if (!ops) { + dev_err(dev, "error: no matching PCI descriptor ops\n"); + return -ENODEV; + } + + sof_pdata = devm_kzalloc(dev, sizeof(*sof_pdata), GFP_KERNEL); + if (!sof_pdata) + return -ENOMEM; + + ret = pcim_enable_device(pci); + if (ret < 0) + return ret; + + ret = pci_request_regions(pci, "Audio DSP"); + if (ret < 0) + return ret; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE) + /* force nocodec mode */ + dev_warn(dev, "Force to use nocodec mode\n"); + mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL); + if (!mach) { + ret = -ENOMEM; + goto release_regions; + } + ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops); + if (ret < 0) + goto release_regions; + +#else + /* find machine */ + mach = snd_soc_acpi_find_machine(desc->machines); + if (!mach) { + dev_warn(dev, "warning: No matching ASoC machine driver found\n"); + } else { + mach->mach_params.platform = dev_name(dev); + sof_pdata->fw_filename = mach->sof_fw_filename; + sof_pdata->tplg_filename = mach->sof_tplg_filename; + } +#endif /* CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE */ + + sof_pdata->name = pci_name(pci); + sof_pdata->machine = mach; + sof_pdata->desc = (struct sof_dev_desc *)pci_id->driver_data; + sof_pdata->dev = dev; + sof_pdata->platform = dev_name(dev); + + /* alternate fw and tplg filenames ? */ + if (fw_path) + sof_pdata->fw_filename_prefix = fw_path; + else + sof_pdata->fw_filename_prefix = + sof_pdata->desc->default_fw_path; + + if (tplg_path) + sof_pdata->tplg_filename_prefix = tplg_path; + else + sof_pdata->tplg_filename_prefix = + sof_pdata->desc->default_tplg_path; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) + /* set callback to enable runtime_pm */ + sof_pdata->sof_probe_complete = sof_pci_probe_complete; +#endif + /* call sof helper for DSP hardware probe */ + ret = snd_sof_device_probe(dev, sof_pdata); + if (ret) { + dev_err(dev, "error: failed to probe DSP hardware!\n"); + goto release_regions; + } + +#if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) + sof_pci_probe_complete(dev); +#endif + + return ret; + +release_regions: + pci_release_regions(pci); + + return ret; +} + +static void sof_pci_remove(struct pci_dev *pci) +{ + /* call sof helper for DSP hardware remove */ + snd_sof_device_remove(&pci->dev); + + /* follow recommendation in pci-driver.c to increment usage counter */ + pm_runtime_get_noresume(&pci->dev); + + /* release pci regions and disable device */ + pci_release_regions(pci); +} + +/* PCI IDs */ +static const struct pci_device_id sof_pci_ids[] = { +#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD) + { PCI_DEVICE(0x8086, 0x119a), + .driver_data = (unsigned long)&tng_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE) + /* BXT-P & Apollolake */ + { PCI_DEVICE(0x8086, 0x5a98), + .driver_data = (unsigned long)&bxt_desc}, + { PCI_DEVICE(0x8086, 0x1a98), + .driver_data = (unsigned long)&bxt_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE) + { PCI_DEVICE(0x8086, 0x3198), + .driver_data = (unsigned long)&glk_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_CANNONLAKE) + { PCI_DEVICE(0x8086, 0x9dc8), + .driver_data = (unsigned long)&cnl_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE) + { PCI_DEVICE(0x8086, 0xa348), + .driver_data = (unsigned long)&cfl_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_KABYLAKE) + { PCI_DEVICE(0x8086, 0x9d71), + .driver_data = (unsigned long)&kbl_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_SKYLAKE) + { PCI_DEVICE(0x8086, 0x9d70), + .driver_data = (unsigned long)&skl_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE) + { PCI_DEVICE(0x8086, 0x34C8), + .driver_data = (unsigned long)&icl_desc}, +#endif + { 0, } +}; +MODULE_DEVICE_TABLE(pci, sof_pci_ids); + +/* pci_driver definition */ +static struct pci_driver snd_sof_pci_driver = { + .name = "sof-audio-pci", + .id_table = sof_pci_ids, + .probe = sof_pci_probe, + .remove = sof_pci_remove, + .driver = { + .pm = &sof_pci_pm, + }, +}; +module_pci_driver(snd_sof_pci_driver); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h new file mode 100644 index 000000000000..1e85d6f9c5c3 --- /dev/null +++ b/sound/soc/sof/sof-priv.h @@ -0,0 +1,635 @@ +/* SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. + * + * Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + */ + +#ifndef __SOUND_SOC_SOF_PRIV_H +#define __SOUND_SOC_SOF_PRIV_H + +#include <linux/device.h> + +#include <sound/hdaudio.h> +#include <sound/soc.h> + +#include <sound/sof.h> +#include <sound/sof/stream.h> /* needs to be included before control.h */ +#include <sound/sof/control.h> +#include <sound/sof/dai.h> +#include <sound/sof/info.h> +#include <sound/sof/pm.h> +#include <sound/sof/topology.h> +#include <sound/sof/trace.h> + +#include <uapi/sound/sof/fw.h> + +/* debug flags */ +#define SOF_DBG_REGS BIT(1) +#define SOF_DBG_MBOX BIT(2) +#define SOF_DBG_TEXT BIT(3) +#define SOF_DBG_PCI BIT(4) + +/* max BARs mmaped devices can use */ +#define SND_SOF_BARS 8 + +/* time in ms for runtime suspend delay */ +#define SND_SOF_SUSPEND_DELAY_MS 2000 + +/* DMA buffer size for trace */ +#define DMA_BUF_SIZE_FOR_TRACE (PAGE_SIZE * 16) + +/* max number of FE PCMs before BEs */ +#define SOF_BE_PCM_BASE 16 + +#define SOF_IPC_DSP_REPLY 0 +#define SOF_IPC_HOST_REPLY 1 + +/* convenience constructor for DAI driver streams */ +#define SOF_DAI_STREAM(sname, scmin, scmax, srates, sfmt) \ + {.stream_name = sname, .channels_min = scmin, .channels_max = scmax, \ + .rates = srates, .formats = sfmt} + +#define SOF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_FLOAT) + +struct snd_sof_dev; +struct snd_sof_ipc_msg; +struct snd_sof_ipc; +struct snd_sof_debugfs_map; +struct snd_soc_tplg_ops; +struct snd_soc_component; +struct snd_sof_pdata; + +/* + * SOF DSP HW abstraction operations. + * Used to abstract DSP HW architecture and any IO busses between host CPU + * and DSP device(s). + */ +struct snd_sof_dsp_ops { + + /* probe and remove */ + int (*probe)(struct snd_sof_dev *sof_dev); /* mandatory */ + int (*remove)(struct snd_sof_dev *sof_dev); /* optional */ + + /* DSP core boot / reset */ + int (*run)(struct snd_sof_dev *sof_dev); /* mandatory */ + int (*stall)(struct snd_sof_dev *sof_dev); /* optional */ + int (*reset)(struct snd_sof_dev *sof_dev); /* optional */ + int (*core_power_up)(struct snd_sof_dev *sof_dev, + unsigned int core_mask); /* optional */ + int (*core_power_down)(struct snd_sof_dev *sof_dev, + unsigned int core_mask); /* optional */ + + /* + * Register IO: only used by respective drivers themselves, + * TODO: consider removing these operations and calling respective + * implementations directly + */ + void (*write)(struct snd_sof_dev *sof_dev, void __iomem *addr, + u32 value); /* optional */ + u32 (*read)(struct snd_sof_dev *sof_dev, + void __iomem *addr); /* optional */ + void (*write64)(struct snd_sof_dev *sof_dev, void __iomem *addr, + u64 value); /* optional */ + u64 (*read64)(struct snd_sof_dev *sof_dev, + void __iomem *addr); /* optional */ + + /* memcpy IO */ + void (*block_read)(struct snd_sof_dev *sof_dev, u32 bar, + u32 offset, void *dest, + size_t size); /* mandatory */ + void (*block_write)(struct snd_sof_dev *sof_dev, u32 bar, + u32 offset, void *src, + size_t size); /* mandatory */ + + /* doorbell */ + irqreturn_t (*irq_handler)(int irq, void *context); /* optional */ + irqreturn_t (*irq_thread)(int irq, void *context); /* optional */ + + /* ipc */ + int (*send_msg)(struct snd_sof_dev *sof_dev, + struct snd_sof_ipc_msg *msg); /* mandatory */ + + /* FW loading */ + int (*load_firmware)(struct snd_sof_dev *sof_dev); /* mandatory */ + int (*load_module)(struct snd_sof_dev *sof_dev, + struct snd_sof_mod_hdr *hdr); /* optional */ + /* + * FW ready checks for ABI compatibility and creates + * memory windows at first boot + */ + int (*fw_ready)(struct snd_sof_dev *sdev, u32 msg_id); /* optional */ + + /* connect pcm substream to a host stream */ + int (*pcm_open)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); /* optional */ + /* disconnect pcm substream to a host stream */ + int (*pcm_close)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); /* optional */ + + /* host stream hw params */ + int (*pcm_hw_params)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params); /* optional */ + + /* host stream trigger */ + int (*pcm_trigger)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + int cmd); /* optional */ + + /* host stream pointer */ + snd_pcm_uframes_t (*pcm_pointer)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); /* optional */ + + /* host read DSP stream data */ + void (*ipc_msg_data)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz); /* mandatory */ + + /* host configure DSP HW parameters */ + int (*ipc_pcm_params)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply); /* mandatory */ + + /* pre/post firmware run */ + int (*pre_fw_run)(struct snd_sof_dev *sof_dev); /* optional */ + int (*post_fw_run)(struct snd_sof_dev *sof_dev); /* optional */ + + /* DSP PM */ + int (*suspend)(struct snd_sof_dev *sof_dev, int state); /* optional */ + int (*resume)(struct snd_sof_dev *sof_dev); /* optional */ + int (*runtime_suspend)(struct snd_sof_dev *sof_dev, + int state); /* optional */ + int (*runtime_resume)(struct snd_sof_dev *sof_dev); /* optional */ + void (*set_hw_params_upon_resume)(struct snd_sof_dev *sdev); /* optional */ + + /* DSP clocking */ + int (*set_clk)(struct snd_sof_dev *sof_dev, u32 freq); /* optional */ + + /* debug */ + const struct snd_sof_debugfs_map *debug_map; /* optional */ + int debug_map_count; /* optional */ + void (*dbg_dump)(struct snd_sof_dev *sof_dev, + u32 flags); /* optional */ + void (*ipc_dump)(struct snd_sof_dev *sof_dev); /* optional */ + + /* host DMA trace initialization */ + int (*trace_init)(struct snd_sof_dev *sdev, + u32 *stream_tag); /* optional */ + int (*trace_release)(struct snd_sof_dev *sdev); /* optional */ + int (*trace_trigger)(struct snd_sof_dev *sdev, + int cmd); /* optional */ + + /* DAI ops */ + struct snd_soc_dai_driver *drv; + int num_drv; +}; + +/* DSP architecture specific callbacks for oops and stack dumps */ +struct sof_arch_ops { + void (*dsp_oops)(struct snd_sof_dev *sdev, void *oops); + void (*dsp_stack)(struct snd_sof_dev *sdev, void *oops, + u32 *stack, u32 stack_words); +}; + +#define sof_arch_ops(sdev) ((sdev)->pdata->desc->arch_ops) + +/* DSP device HW descriptor mapping between bus ID and ops */ +struct sof_ops_table { + const struct sof_dev_desc *desc; + const struct snd_sof_dsp_ops *ops; +}; + +enum sof_dfsentry_type { + SOF_DFSENTRY_TYPE_IOMEM = 0, + SOF_DFSENTRY_TYPE_BUF, +}; + +enum sof_debugfs_access_type { + SOF_DEBUGFS_ACCESS_ALWAYS = 0, + SOF_DEBUGFS_ACCESS_D0_ONLY, +}; + +/* FS entry for debug files that can expose DSP memories, registers */ +struct snd_sof_dfsentry { + struct dentry *dfsentry; + size_t size; + enum sof_dfsentry_type type; + /* + * access_type specifies if the + * memory -> DSP resource (memory, register etc) is always accessible + * or if it is accessible only when the DSP is in D0. + */ + enum sof_debugfs_access_type access_type; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) + char *cache_buf; /* buffer to cache the contents of debugfs memory */ +#endif + struct snd_sof_dev *sdev; + struct list_head list; /* list in sdev dfsentry list */ + union { + void __iomem *io_mem; + void *buf; + }; +}; + +/* Debug mapping for any DSP memory or registers that can used for debug */ +struct snd_sof_debugfs_map { + const char *name; + u32 bar; + u32 offset; + u32 size; + /* + * access_type specifies if the memory is always accessible + * or if it is accessible only when the DSP is in D0. + */ + enum sof_debugfs_access_type access_type; +}; + +/* mailbox descriptor, used for host <-> DSP IPC */ +struct snd_sof_mailbox { + u32 offset; + size_t size; +}; + +/* IPC message descriptor for host <-> DSP IO */ +struct snd_sof_ipc_msg { + /* message data */ + u32 header; + void *msg_data; + void *reply_data; + size_t msg_size; + size_t reply_size; + int reply_error; + + wait_queue_head_t waitq; + bool ipc_complete; +}; + +/* PCM stream, mapped to FW component */ +struct snd_sof_pcm_stream { + u32 comp_id; + struct snd_dma_buffer page_table; + struct sof_ipc_stream_posn posn; + struct snd_pcm_substream *substream; + struct work_struct period_elapsed_work; +}; + +/* ALSA SOF PCM device */ +struct snd_sof_pcm { + struct snd_sof_dev *sdev; + struct snd_soc_tplg_pcm pcm; + struct snd_sof_pcm_stream stream[2]; + struct list_head list; /* list in sdev pcm list */ + struct snd_pcm_hw_params params[2]; + int hw_params_upon_resume[2]; /* set up hw_params upon resume */ +}; + +/* ALSA SOF Kcontrol device */ +struct snd_sof_control { + struct snd_sof_dev *sdev; + int comp_id; + int num_channels; + u32 readback_offset; /* offset to mmaped data if used */ + struct sof_ipc_ctrl_data *control_data; + u32 size; /* cdata size */ + enum sof_ipc_ctrl_cmd cmd; + u32 *volume_table; /* volume table computed from tlv data*/ + + struct list_head list; /* list in sdev control list */ +}; + +/* ASoC SOF DAPM widget */ +struct snd_sof_widget { + struct snd_sof_dev *sdev; + int comp_id; + int pipeline_id; + int complete; + int id; + + struct snd_soc_dapm_widget *widget; + struct list_head list; /* list in sdev widget list */ + + void *private; /* core does not touch this */ +}; + +/* ASoC SOF DAPM route */ +struct snd_sof_route { + struct snd_sof_dev *sdev; + + struct snd_soc_dapm_route *route; + struct list_head list; /* list in sdev route list */ + + void *private; +}; + +/* ASoC DAI device */ +struct snd_sof_dai { + struct snd_sof_dev *sdev; + const char *name; + + struct sof_ipc_comp_dai comp_dai; + struct sof_ipc_dai_config *dai_config; + struct list_head list; /* list in sdev dai list */ +}; + +/* + * SOF Device Level. + */ +struct snd_sof_dev { + struct device *dev; + spinlock_t ipc_lock; /* lock for IPC users */ + spinlock_t hw_lock; /* lock for HW IO access */ + + /* + * ASoC components. plat_drv fields are set dynamically so + * can't use const + */ + struct snd_soc_component_driver plat_drv; + + /* DSP firmware boot */ + wait_queue_head_t boot_wait; + u32 boot_complete; + u32 first_boot; + + /* work queue in case the probe is implemented in two steps */ + struct work_struct probe_work; + + /* DSP HW differentiation */ + struct snd_sof_pdata *pdata; + + /* IPC */ + struct snd_sof_ipc *ipc; + struct snd_sof_mailbox dsp_box; /* DSP initiated IPC */ + struct snd_sof_mailbox host_box; /* Host initiated IPC */ + struct snd_sof_mailbox stream_box; /* Stream position update */ + struct snd_sof_ipc_msg *msg; + int ipc_irq; + u32 next_comp_id; /* monotonic - reset during S3 */ + + /* memory bases for mmaped DSPs - set by dsp_init() */ + void __iomem *bar[SND_SOF_BARS]; /* DSP base address */ + int mmio_bar; + int mailbox_bar; + size_t dsp_oops_offset; + + /* debug */ + struct dentry *debugfs_root; + struct list_head dfsentry_list; + + /* firmware loader */ + struct snd_dma_buffer dmab; + struct snd_dma_buffer dmab_bdl; + struct sof_ipc_fw_ready fw_ready; + struct sof_ipc_fw_version fw_version; + + /* topology */ + struct snd_soc_tplg_ops *tplg_ops; + struct list_head pcm_list; + struct list_head kcontrol_list; + struct list_head widget_list; + struct list_head dai_list; + struct list_head route_list; + struct snd_soc_component *component; + u32 enabled_cores_mask; /* keep track of enabled cores */ + + /* FW configuration */ + struct sof_ipc_dma_buffer_data *info_buffer; + struct sof_ipc_window *info_window; + + /* IPC timeouts in ms */ + int ipc_timeout; + int boot_timeout; + + /* Wait queue for code loading */ + wait_queue_head_t waitq; + int code_loading; + + /* DMA for Trace */ + struct snd_dma_buffer dmatb; + struct snd_dma_buffer dmatp; + int dma_trace_pages; + wait_queue_head_t trace_sleep; + u32 host_offset; + u32 dtrace_is_enabled; + u32 dtrace_error; + u32 msi_enabled; + + void *private; /* core does not touch this */ +}; + +/* + * Device Level. + */ + +int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data); +int snd_sof_device_remove(struct device *dev); + +int snd_sof_runtime_suspend(struct device *dev); +int snd_sof_runtime_resume(struct device *dev); +int snd_sof_resume(struct device *dev); +int snd_sof_suspend(struct device *dev); + +void snd_sof_new_platform_drv(struct snd_sof_dev *sdev); + +int snd_sof_create_page_table(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size); + +/* + * Firmware loading. + */ +int snd_sof_load_firmware(struct snd_sof_dev *sdev); +int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev); +int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev); +int snd_sof_run_firmware(struct snd_sof_dev *sdev); +int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, + struct snd_sof_mod_hdr *module); +void snd_sof_fw_unload(struct snd_sof_dev *sdev); +int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset); + +/* + * IPC low level APIs. + */ +struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev); +void snd_sof_ipc_free(struct snd_sof_dev *sdev); +int snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id); +void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev); +int snd_sof_ipc_stream_pcm_params(struct snd_sof_dev *sdev, + struct sof_ipc_pcm_params *params); +int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox, + size_t dspbox_size, u32 hostbox, + size_t hostbox_size); +int snd_sof_ipc_valid(struct snd_sof_dev *sdev); +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, void *reply_data, + size_t reply_bytes); +struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, + const char *name); +struct snd_sof_widget *snd_sof_find_swidget_sname(struct snd_sof_dev *sdev, + const char *pcm_name, + int dir); +struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, + const char *name); + +static inline +struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_sof_pcm *spcm = NULL; + + list_for_each_entry(spcm, &sdev->pcm_list, list) { + if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id) + return spcm; + } + + return NULL; +} + +struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, + const char *name); +struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, + unsigned int comp_id, + int *direction); +struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, + unsigned int pcm_id); +void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); + +/* + * Stream IPC + */ +int snd_sof_ipc_stream_posn(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, int direction, + struct sof_ipc_stream_posn *posn); + +/* + * Mixer IPC + */ +int snd_sof_ipc_set_get_comp_data(struct snd_sof_ipc *ipc, + struct snd_sof_control *scontrol, u32 ipc_cmd, + enum sof_ipc_ctrl_type ctrl_type, + enum sof_ipc_ctrl_cmd ctrl_cmd, + bool send); + +/* + * Topology. + * There is no snd_sof_free_topology since topology components will + * be freed by snd_soc_unregister_component, + */ +int snd_sof_init_topology(struct snd_sof_dev *sdev, + struct snd_soc_tplg_ops *ops); +int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file); +int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget); + +int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, + struct sof_ipc_pipe_new *pipeline, + struct sof_ipc_comp_reply *r); + +/* + * Trace/debug + */ +int snd_sof_init_trace(struct snd_sof_dev *sdev); +void snd_sof_release_trace(struct snd_sof_dev *sdev); +void snd_sof_free_trace(struct snd_sof_dev *sdev); +int snd_sof_dbg_init(struct snd_sof_dev *sdev); +void snd_sof_free_debug(struct snd_sof_dev *sdev); +int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, + void __iomem *base, size_t size, + const char *name, + enum sof_debugfs_access_type access_type); +int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, + void *base, size_t size, + const char *name); +int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn); +void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev); +void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, + u32 tracep_code, void *oops, + struct sof_ipc_panic_info *panic_info, + void *stack, size_t stack_words); +int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); + +/* + * Platform specific ops. + */ +extern struct snd_compr_ops sof_compressed_ops; + +/* + * Kcontrols. + */ + +int snd_sof_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *binary_data, + unsigned int size); +int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, + unsigned int __user *binary_data, + unsigned int size); + +/* + * DSP Architectures. + */ +static inline void sof_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, + u32 stack_words) +{ + if (sof_arch_ops(sdev)->dsp_stack) + sof_arch_ops(sdev)->dsp_stack(sdev, oops, stack, stack_words); +} + +static inline void sof_oops(struct snd_sof_dev *sdev, void *oops) +{ + if (sof_arch_ops(sdev)->dsp_oops) + sof_arch_ops(sdev)->dsp_oops(sdev, oops); +} + +extern const struct sof_arch_ops sof_xtensa_arch_ops; + +/* + * Utilities + */ +void sof_io_write(struct snd_sof_dev *sdev, void __iomem *addr, u32 value); +void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value); +u32 sof_io_read(struct snd_sof_dev *sdev, void __iomem *addr); +u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr); +void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes); +void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes); +void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src, + size_t size); +void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest, + size_t size); + +void intel_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz); +int intel_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply); + +int intel_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); +int intel_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); + +#endif diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c new file mode 100644 index 000000000000..c88afa872a58 --- /dev/null +++ b/sound/soc/sof/topology.c @@ -0,0 +1,3179 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/firmware.h> +#include <sound/tlv.h> +#include <sound/pcm_params.h> +#include <uapi/sound/sof/tokens.h> +#include "sof-priv.h" +#include "ops.h" + +#define COMP_ID_UNASSIGNED 0xffffffff +/* + * Constants used in the computation of linear volume gain + * from dB gain 20th root of 10 in Q1.16 fixed-point notation + */ +#define VOL_TWENTIETH_ROOT_OF_TEN 73533 +/* 40th root of 10 in Q1.16 fixed-point notation*/ +#define VOL_FORTIETH_ROOT_OF_TEN 69419 +/* + * Volume fractional word length define to 16 sets + * the volume linear gain value to use Qx.16 format + */ +#define VOLUME_FWL 16 +/* 0.5 dB step value in topology TLV */ +#define VOL_HALF_DB_STEP 50 +/* Full volume for default values */ +#define VOL_ZERO_DB BIT(VOLUME_FWL) + +/* TLV data items */ +#define TLV_ITEMS 3 +#define TLV_MIN 0 +#define TLV_STEP 1 +#define TLV_MUTE 2 + +/* size of tplg abi in byte */ +#define SOF_TPLG_ABI_SIZE 3 + +/* send pcm params ipc */ +static int ipc_pcm_params(struct snd_sof_widget *swidget, int dir) +{ + struct sof_ipc_pcm_params_reply ipc_params_reply; + struct snd_sof_dev *sdev = swidget->sdev; + struct sof_ipc_pcm_params pcm; + struct snd_pcm_hw_params *params; + struct snd_sof_pcm *spcm; + int ret = 0; + + memset(&pcm, 0, sizeof(pcm)); + + /* get runtime PCM params using widget's stream name */ + spcm = snd_sof_find_spcm_name(sdev, swidget->widget->sname); + if (!spcm) { + dev_err(sdev->dev, "error: cannot find PCM for %s\n", + swidget->widget->name); + return -EINVAL; + } + + params = &spcm->params[dir]; + + /* set IPC PCM params */ + pcm.hdr.size = sizeof(pcm); + pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; + pcm.comp_id = swidget->comp_id; + pcm.params.hdr.size = sizeof(pcm.params); + pcm.params.direction = dir; + pcm.params.sample_valid_bytes = params_width(params) >> 3; + pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + pcm.params.rate = params_rate(params); + pcm.params.channels = params_channels(params); + pcm.params.host_period_bytes = params_period_bytes(params); + + /* set format */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16: + pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24: + pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; + break; + case SNDRV_PCM_FORMAT_S32: + pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; + break; + default: + return -EINVAL; + } + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), + &ipc_params_reply, sizeof(ipc_params_reply)); + if (ret < 0) + dev_err(sdev->dev, "error: pcm params failed for %s\n", + swidget->widget->name); + + return ret; +} + + /* send stream trigger ipc */ +static int ipc_trigger(struct snd_sof_widget *swidget, int cmd) +{ + struct snd_sof_dev *sdev = swidget->sdev; + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret = 0; + + /* set IPC stream params */ + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | cmd; + stream.comp_id = swidget->comp_id; + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, + sizeof(stream), &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "error: failed to trigger %s\n", + swidget->widget->name); + + return ret; +} + +static int sof_keyword_dapm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_sof_widget *swidget = w->dobj.private; + struct snd_sof_dev *sdev; + int ret = 0; + + if (!swidget) + return 0; + + sdev = swidget->sdev; + + dev_dbg(sdev->dev, "received event %d for widget %s\n", + event, w->name); + + /* process events */ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* set pcm params */ + ret = ipc_pcm_params(swidget, SOF_IPC_STREAM_CAPTURE); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to set pcm params for widget %s\n", + swidget->widget->name); + break; + } + + /* start trigger */ + ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_START); + if (ret < 0) + dev_err(sdev->dev, + "error: failed to trigger widget %s\n", + swidget->widget->name); + break; + case SND_SOC_DAPM_POST_PMD: + /* stop trigger */ + ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_STOP); + if (ret < 0) + dev_err(sdev->dev, + "error: failed to trigger widget %s\n", + swidget->widget->name); + + /* pcm free */ + ret = ipc_trigger(swidget, SOF_IPC_STREAM_PCM_FREE); + if (ret < 0) + dev_err(sdev->dev, + "error: failed to trigger widget %s\n", + swidget->widget->name); + break; + default: + break; + } + + return ret; +} + +/* event handlers for keyword detect component */ +static const struct snd_soc_tplg_widget_events sof_kwd_events[] = { + {SOF_KEYWORD_DETECT_DAPM_EVENT, sof_keyword_dapm_event}, +}; + +static inline int get_tlv_data(const int *p, int tlv[TLV_ITEMS]) +{ + /* we only support dB scale TLV type at the moment */ + if ((int)p[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE) + return -EINVAL; + + /* min value in topology tlv data is multiplied by 100 */ + tlv[TLV_MIN] = (int)p[SNDRV_CTL_TLVO_DB_SCALE_MIN] / 100; + + /* volume steps */ + tlv[TLV_STEP] = (int)(p[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & + TLV_DB_SCALE_MASK); + + /* mute ON/OFF */ + if ((p[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & + TLV_DB_SCALE_MUTE) == 0) + tlv[TLV_MUTE] = 0; + else + tlv[TLV_MUTE] = 1; + + return 0; +} + +/* + * Function to truncate an unsigned 64-bit number + * by x bits and return 32-bit unsigned number. This + * function also takes care of rounding while truncating + */ +static inline u32 vol_shift_64(u64 i, u32 x) +{ + /* do not truncate more than 32 bits */ + if (x > 32) + x = 32; + + if (x == 0) + return (u32)i; + + return (u32)(((i >> (x - 1)) + 1) >> 1); +} + +/* + * Function to compute a ^ exp where, + * a is a fractional number represented by a fixed-point + * integer with a fractional world length of "fwl" + * exp is an integer + * fwl is the fractional word length + * Return value is a fractional number represented by a + * fixed-point integer with a fractional word length of "fwl" + */ +static u32 vol_pow32(u32 a, int exp, u32 fwl) +{ + int i, iter; + u32 power = 1 << fwl; + u64 numerator; + + /* if exponent is 0, return 1 */ + if (exp == 0) + return power; + + /* determine the number of iterations based on the exponent */ + if (exp < 0) + iter = exp * -1; + else + iter = exp; + + /* mutiply a "iter" times to compute power */ + for (i = 0; i < iter; i++) { + /* + * Product of 2 Qx.fwl fixed-point numbers yields a Q2*x.2*fwl + * Truncate product back to fwl fractional bits with rounding + */ + power = vol_shift_64((u64)power * a, fwl); + } + + if (exp > 0) { + /* if exp is positive, return the result */ + return power; + } + + /* if exp is negative, return the multiplicative inverse */ + numerator = (u64)1 << (fwl << 1); + do_div(numerator, power); + + return (u32)numerator; +} + +/* + * Function to calculate volume gain from TLV data. + * This function can only handle gain steps that are multiples of 0.5 dB + */ +static u32 vol_compute_gain(u32 value, int *tlv) +{ + int dB_gain; + u32 linear_gain; + int f_step; + + /* mute volume */ + if (value == 0 && tlv[TLV_MUTE]) + return 0; + + /* + * compute dB gain from tlv. tlv_step + * in topology is multiplied by 100 + */ + dB_gain = tlv[TLV_MIN] + (value * tlv[TLV_STEP]) / 100; + + /* + * compute linear gain represented by fixed-point + * int with VOLUME_FWL fractional bits + */ + linear_gain = vol_pow32(VOL_TWENTIETH_ROOT_OF_TEN, dB_gain, VOLUME_FWL); + + /* extract the fractional part of volume step */ + f_step = tlv[TLV_STEP] - (tlv[TLV_STEP] / 100); + + /* if volume step is an odd multiple of 0.5 dB */ + if (f_step == VOL_HALF_DB_STEP && (value & 1)) + linear_gain = vol_shift_64((u64)linear_gain * + VOL_FORTIETH_ROOT_OF_TEN, + VOLUME_FWL); + + return linear_gain; +} + +/* + * Set up volume table for kcontrols from tlv data + * "size" specifies the number of entries in the table + */ +static int set_up_volume_table(struct snd_sof_control *scontrol, + int tlv[TLV_ITEMS], int size) +{ + int j; + + /* init the volume table */ + scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL); + if (!scontrol->volume_table) + return -ENOMEM; + + /* populate the volume table */ + for (j = 0; j < size ; j++) + scontrol->volume_table[j] = vol_compute_gain(j, tlv); + + return 0; +} + +struct sof_dai_types { + const char *name; + enum sof_ipc_dai_type type; +}; + +static const struct sof_dai_types sof_dais[] = { + {"SSP", SOF_DAI_INTEL_SSP}, + {"HDA", SOF_DAI_INTEL_HDA}, + {"DMIC", SOF_DAI_INTEL_DMIC}, +}; + +static enum sof_ipc_dai_type find_dai(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_dais); i++) { + if (strcmp(name, sof_dais[i].name) == 0) + return sof_dais[i].type; + } + + return SOF_DAI_INTEL_NONE; +} + +/* + * Supported Frame format types and lookup, add new ones to end of list. + */ + +struct sof_frame_types { + const char *name; + enum sof_ipc_frame frame; +}; + +static const struct sof_frame_types sof_frames[] = { + {"s16le", SOF_IPC_FRAME_S16_LE}, + {"s24le", SOF_IPC_FRAME_S24_4LE}, + {"s32le", SOF_IPC_FRAME_S32_LE}, + {"float", SOF_IPC_FRAME_FLOAT}, +}; + +static enum sof_ipc_frame find_format(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_frames); i++) { + if (strcmp(name, sof_frames[i].name) == 0) + return sof_frames[i].frame; + } + + /* use s32le if nothing is specified */ + return SOF_IPC_FRAME_S32_LE; +} + +struct sof_process_types { + const char *name; + enum sof_ipc_process_type type; + enum sof_comp_type comp_type; +}; + +static const struct sof_process_types sof_process[] = { + {"EQFIR", SOF_PROCESS_EQFIR, SOF_COMP_EQ_FIR}, + {"EQIIR", SOF_PROCESS_EQIIR, SOF_COMP_EQ_IIR}, + {"KEYWORD_DETECT", SOF_PROCESS_KEYWORD_DETECT, SOF_COMP_KEYWORD_DETECT}, + {"KPB", SOF_PROCESS_KPB, SOF_COMP_KPB}, + {"CHAN_SELECTOR", SOF_PROCESS_CHAN_SELECTOR, SOF_COMP_SELECTOR}, +}; + +static enum sof_ipc_process_type find_process(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_process); i++) { + if (strcmp(name, sof_process[i].name) == 0) + return sof_process[i].type; + } + + return SOF_PROCESS_NONE; +} + +static enum sof_comp_type find_process_comp_type(enum sof_ipc_process_type type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_process); i++) { + if (sof_process[i].type == type) + return sof_process[i].comp_type; + } + + return SOF_COMP_NONE; +} + +/* + * Standard Kcontrols. + */ + +static int sof_control_load_volume(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_mixer_control *mc = + container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); + struct sof_ipc_ctrl_data *cdata; + int tlv[TLV_ITEMS]; + unsigned int i; + int ret; + + /* validate topology data */ + if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + + /* init the volume get/put data */ + scontrol->size = sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_ipc_ctrl_value_chan) * + le32_to_cpu(mc->num_channels); + scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); + if (!scontrol->control_data) + return -ENOMEM; + + scontrol->comp_id = sdev->next_comp_id; + scontrol->num_channels = le32_to_cpu(mc->num_channels); + + /* set cmd for mixer control */ + if (le32_to_cpu(mc->max) == 1) { + scontrol->cmd = SOF_CTRL_CMD_SWITCH; + goto out; + } + + scontrol->cmd = SOF_CTRL_CMD_VOLUME; + + /* extract tlv data */ + if (get_tlv_data(kc->tlv.p, tlv) < 0) { + dev_err(sdev->dev, "error: invalid TLV data\n"); + return -EINVAL; + } + + /* set up volume table */ + ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); + if (ret < 0) { + dev_err(sdev->dev, "error: setting up volume table\n"); + return ret; + } + + /* set default volume values to 0dB in control */ + cdata = scontrol->control_data; + for (i = 0; i < scontrol->num_channels; i++) { + cdata->chanv[i].channel = i; + cdata->chanv[i].value = VOL_ZERO_DB; + } + +out: + dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n", + scontrol->comp_id, scontrol->num_channels); + + return 0; +} + +static int sof_control_load_enum(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_enum_control *ec = + container_of(hdr, struct snd_soc_tplg_enum_control, hdr); + + /* validate topology data */ + if (le32_to_cpu(ec->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + + /* init the enum get/put data */ + scontrol->size = sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_ipc_ctrl_value_chan) * + le32_to_cpu(ec->num_channels); + scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); + if (!scontrol->control_data) + return -ENOMEM; + + scontrol->comp_id = sdev->next_comp_id; + scontrol->num_channels = le32_to_cpu(ec->num_channels); + + scontrol->cmd = SOF_CTRL_CMD_ENUM; + + dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d comp_id %d\n", + scontrol->comp_id, scontrol->num_channels, scontrol->comp_id); + + return 0; +} + +static int sof_control_load_bytes(struct snd_soc_component *scomp, + struct snd_sof_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_ctrl_data *cdata; + struct snd_soc_tplg_bytes_control *control = + container_of(hdr, struct snd_soc_tplg_bytes_control, hdr); + struct soc_bytes_ext *sbe = (struct soc_bytes_ext *)kc->private_value; + int max_size = sbe->max; + + if (le32_to_cpu(control->priv.size) > max_size) { + dev_err(sdev->dev, "err: bytes data size %d exceeds max %d.\n", + control->priv.size, max_size); + return -EINVAL; + } + + /* init the get/put bytes data */ + scontrol->size = sizeof(struct sof_ipc_ctrl_data) + + le32_to_cpu(control->priv.size); + scontrol->control_data = kzalloc(max_size, GFP_KERNEL); + cdata = scontrol->control_data; + if (!scontrol->control_data) + return -ENOMEM; + + scontrol->comp_id = sdev->next_comp_id; + scontrol->cmd = SOF_CTRL_CMD_BINARY; + + dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n", + scontrol->comp_id, scontrol->num_channels); + + if (le32_to_cpu(control->priv.size) > 0) { + memcpy(cdata->data, control->priv.data, + le32_to_cpu(control->priv.size)); + + if (cdata->data->magic != SOF_ABI_MAGIC) { + dev_err(sdev->dev, "error: Wrong ABI magic 0x%08x.\n", + cdata->data->magic); + return -EINVAL; + } + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, + cdata->data->abi)) { + dev_err(sdev->dev, + "error: Incompatible ABI version 0x%08x.\n", + cdata->data->abi); + return -EINVAL; + } + if (cdata->data->size + sizeof(const struct sof_abi_hdr) != + le32_to_cpu(control->priv.size)) { + dev_err(sdev->dev, + "error: Conflict in bytes vs. priv size.\n"); + return -EINVAL; + } + } + return 0; +} + +/* + * Topology Token Parsing. + * New tokens should be added to headers and parsing tables below. + */ + +struct sof_topology_token { + u32 token; + u32 type; + int (*get_token)(void *elem, void *object, u32 offset, u32 size); + u32 offset; + u32 size; +}; + +static int get_token_u32(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_value_elem *velem = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = le32_to_cpu(velem->value); + return 0; +} + +static int get_token_u16(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_value_elem *velem = elem; + u16 *val = (u16 *)((u8 *)object + offset); + + *val = (u16)le32_to_cpu(velem->value); + return 0; +} + +static int get_token_comp_format(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_string_elem *velem = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = find_format(velem->string); + return 0; +} + +static int get_token_dai_type(void *elem, void *object, u32 offset, u32 size) +{ + struct snd_soc_tplg_vendor_string_elem *velem = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = find_dai(velem->string); + return 0; +} + +static int get_token_process_type(void *elem, void *object, u32 offset, + u32 size) +{ + struct snd_soc_tplg_vendor_string_elem *velem = elem; + u32 *val = (u32 *)((u8 *)object + offset); + + *val = find_process(velem->string); + return 0; +} + +/* Buffers */ +static const struct sof_topology_token buffer_tokens[] = { + {SOF_TKN_BUF_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_buffer, size), 0}, + {SOF_TKN_BUF_CAPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_buffer, caps), 0}, +}; + +/* DAI */ +static const struct sof_topology_token dai_tokens[] = { + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct sof_ipc_comp_dai, type), 0}, + {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_dai, dai_index), 0}, + {SOF_TKN_DAI_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_dai, direction), 0}, +}; + +/* BE DAI link */ +static const struct sof_topology_token dai_link_tokens[] = { + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct sof_ipc_dai_config, type), 0}, + {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_config, dai_index), 0}, +}; + +/* scheduling */ +static const struct sof_topology_token sched_tokens[] = { + {SOF_TKN_SCHED_PERIOD, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, period), 0}, + {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, priority), 0}, + {SOF_TKN_SCHED_MIPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, period_mips), 0}, + {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, core), 0}, + {SOF_TKN_SCHED_FRAMES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, frames_per_sched), 0}, + {SOF_TKN_SCHED_TIME_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, time_domain), 0}, +}; + +/* volume */ +static const struct sof_topology_token volume_tokens[] = { + {SOF_TKN_VOLUME_RAMP_STEP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, offsetof(struct sof_ipc_comp_volume, ramp), 0}, + {SOF_TKN_VOLUME_RAMP_STEP_MS, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_volume, initial_ramp), 0}, +}; + +/* SRC */ +static const struct sof_topology_token src_tokens[] = { + {SOF_TKN_SRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_src, source_rate), 0}, + {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_src, sink_rate), 0}, +}; + +/* Tone */ +static const struct sof_topology_token tone_tokens[] = { +}; + +/* EFFECT */ +static const struct sof_topology_token process_tokens[] = { + {SOF_TKN_PROCESS_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, + get_token_process_type, + offsetof(struct sof_ipc_comp_process, type), 0}, +}; + +/* PCM */ +static const struct sof_topology_token pcm_tokens[] = { + {SOF_TKN_PCM_DMAC_CONFIG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_host, dmac_config), 0}, +}; + +/* Generic components */ +static const struct sof_topology_token comp_tokens[] = { + {SOF_TKN_COMP_PERIOD_SINK_COUNT, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_config, periods_sink), 0}, + {SOF_TKN_COMP_PERIOD_SOURCE_COUNT, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_config, periods_source), 0}, + {SOF_TKN_COMP_FORMAT, + SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format, + offsetof(struct sof_ipc_comp_config, frame_fmt), 0}, +}; + +/* SSP */ +static const struct sof_topology_token ssp_tokens[] = { + {SOF_TKN_INTEL_SSP_CLKS_CONTROL, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, clks_control), 0}, + {SOF_TKN_INTEL_SSP_MCLK_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, mclk_id), 0}, + {SOF_TKN_INTEL_SSP_SAMPLE_BITS, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, sample_valid_bits), 0}, + {SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, + get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, frame_pulse_width), 0}, + {SOF_TKN_INTEL_SSP_QUIRKS, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, quirks), 0}, + {SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT, SND_SOC_TPLG_TUPLE_TYPE_BOOL, + get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, + tdm_per_slot_padding_flag), 0}, + +}; + +/* DMIC */ +static const struct sof_topology_token dmic_tokens[] = { + {SOF_TKN_INTEL_DMIC_DRIVER_VERSION, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, driver_ipc_version), + 0}, + {SOF_TKN_INTEL_DMIC_CLK_MIN, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, pdmclk_min), 0}, + {SOF_TKN_INTEL_DMIC_CLK_MAX, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, pdmclk_max), 0}, + {SOF_TKN_INTEL_DMIC_SAMPLE_RATE, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, fifo_fs), 0}, + {SOF_TKN_INTEL_DMIC_DUTY_MIN, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, duty_min), 0}, + {SOF_TKN_INTEL_DMIC_DUTY_MAX, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, duty_max), 0}, + {SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, + num_pdm_active), 0}, + {SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, fifo_bits), 0}, +}; + +/* + * DMIC PDM Tokens + * SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token + * as it increments the index while parsing the array of pdm tokens + * and determines the correct offset + */ +static const struct sof_topology_token dmic_pdm_tokens[] = { + {SOF_TKN_INTEL_DMIC_PDM_CTRL_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, id), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_a), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_b), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_POLARITY_A, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_a), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_POLARITY_B, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_b), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, clk_edge), + 0}, + {SOF_TKN_INTEL_DMIC_PDM_SKEW, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, skew), + 0}, +}; + +/* HDA */ +static const struct sof_topology_token hda_tokens[] = { +}; + +static void sof_parse_uuid_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array) +{ + struct snd_soc_tplg_vendor_uuid_elem *elem; + int i, j; + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + elem = &array->uuid[i]; + + /* search for token */ + for (j = 0; j < count; j++) { + /* match token type */ + if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_UUID) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* matched - now load token */ + tokens[j].get_token(elem, object, tokens[j].offset, + tokens[j].size); + } + } +} + +static void sof_parse_string_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array) +{ + struct snd_soc_tplg_vendor_string_elem *elem; + int i, j; + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + elem = &array->string[i]; + + /* search for token */ + for (j = 0; j < count; j++) { + /* match token type */ + if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_STRING) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* matched - now load token */ + tokens[j].get_token(elem, object, tokens[j].offset, + tokens[j].size); + } + } +} + +static void sof_parse_word_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_vendor_value_elem *elem; + size_t size = sizeof(struct sof_ipc_dai_dmic_pdm_ctrl); + int i, j; + u32 offset; + u32 *index = NULL; + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + elem = &array->value[i]; + + /* search for token */ + for (j = 0; j < count; j++) { + /* match token type */ + if (!(tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD || + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT)) + continue; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + /* pdm config array index */ + if (sdev->private) + index = sdev->private; + + /* matched - determine offset */ + switch (tokens[j].token) { + case SOF_TKN_INTEL_DMIC_PDM_CTRL_ID: + + /* inc number of pdm array index */ + if (index) + (*index)++; + /* fallthrough */ + case SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable: + case SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable: + case SOF_TKN_INTEL_DMIC_PDM_POLARITY_A: + case SOF_TKN_INTEL_DMIC_PDM_POLARITY_B: + case SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE: + case SOF_TKN_INTEL_DMIC_PDM_SKEW: + + /* check if array index is valid */ + if (!index || *index == 0) { + dev_err(sdev->dev, + "error: invalid array offset\n"); + continue; + } else { + /* offset within the pdm config array */ + offset = size * (*index - 1); + } + break; + default: + offset = 0; + break; + } + + /* load token */ + tokens[j].get_token(elem, object, + offset + tokens[j].offset, + tokens[j].size); + } + } +} + +static int sof_parse_tokens(struct snd_soc_component *scomp, + void *object, + const struct sof_topology_token *tokens, + int count, + struct snd_soc_tplg_vendor_array *array, + int priv_size) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + int asize; + + while (priv_size > 0) { + asize = le32_to_cpu(array->size); + + /* validate asize */ + if (asize < 0) { /* FIXME: A zero-size array makes no sense */ + dev_err(sdev->dev, "error: invalid array size 0x%x\n", + asize); + return -EINVAL; + } + + /* make sure there is enough data before parsing */ + priv_size -= asize; + if (priv_size < 0) { + dev_err(sdev->dev, "error: invalid array size 0x%x\n", + asize); + return -EINVAL; + } + + /* call correct parser depending on type */ + switch (le32_to_cpu(array->type)) { + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + sof_parse_uuid_tokens(scomp, object, tokens, count, + array); + break; + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + sof_parse_string_tokens(scomp, object, tokens, count, + array); + break; + case SND_SOC_TPLG_TUPLE_TYPE_BOOL: + case SND_SOC_TPLG_TUPLE_TYPE_BYTE: + case SND_SOC_TPLG_TUPLE_TYPE_WORD: + case SND_SOC_TPLG_TUPLE_TYPE_SHORT: + sof_parse_word_tokens(scomp, object, tokens, count, + array); + break; + default: + dev_err(sdev->dev, "error: unknown token type %d\n", + array->type); + return -EINVAL; + } + + /* next array */ + array = (struct snd_soc_tplg_vendor_array *)((u8 *)array + + asize); + } + return 0; +} + +static void sof_dbg_comp_config(struct snd_soc_component *scomp, + struct sof_ipc_comp_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + + dev_dbg(sdev->dev, " config: periods snk %d src %d fmt %d\n", + config->periods_sink, config->periods_source, + config->frame_fmt); +} + +/* external kcontrol init - used for any driver specific init */ +static int sof_control_load(struct snd_soc_component *scomp, int index, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct soc_mixer_control *sm; + struct soc_bytes_ext *sbe; + struct soc_enum *se; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dobj *dobj; + struct snd_sof_control *scontrol; + int ret = -EINVAL; + + dev_dbg(sdev->dev, "tplg: load control type %d name : %s\n", + hdr->type, hdr->name); + + scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL); + if (!scontrol) + return -ENOMEM; + + scontrol->sdev = sdev; + + switch (le32_to_cpu(hdr->ops.info)) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + sm = (struct soc_mixer_control *)kc->private_value; + dobj = &sm->dobj; + ret = sof_control_load_volume(scomp, scontrol, kc, hdr); + break; + case SND_SOC_TPLG_CTL_BYTES: + sbe = (struct soc_bytes_ext *)kc->private_value; + dobj = &sbe->dobj; + ret = sof_control_load_bytes(scomp, scontrol, kc, hdr); + break; + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + se = (struct soc_enum *)kc->private_value; + dobj = &se->dobj; + ret = sof_control_load_enum(scomp, scontrol, kc, hdr); + break; + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_CTL_STROBE: + case SND_SOC_TPLG_DAPM_CTL_VOLSW: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_PIN: + default: + dev_warn(sdev->dev, "control type not supported %d:%d:%d\n", + hdr->ops.get, hdr->ops.put, hdr->ops.info); + kfree(scontrol); + return 0; + } + + dobj->private = scontrol; + list_add(&scontrol->list, &sdev->kcontrol_list); + return ret; +} + +static int sof_control_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_free fcomp; + struct snd_sof_control *scontrol = dobj->private; + + dev_dbg(sdev->dev, "tplg: unload control name : %s\n", scomp->name); + + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_FREE; + fcomp.hdr.size = sizeof(fcomp); + fcomp.id = scontrol->comp_id; + + kfree(scontrol->control_data); + list_del(&scontrol->list); + kfree(scontrol); + /* send IPC to the DSP */ + return sof_ipc_tx_message(sdev->ipc, + fcomp.hdr.cmd, &fcomp, sizeof(fcomp), + NULL, 0); +} + +/* + * DAI Topology + */ + +static int sof_connect_dai_widget(struct snd_soc_component *scomp, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tw, + struct snd_sof_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_card *card = scomp->card; + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { + dev_vdbg(sdev->dev, "tplg: check widget: %s stream: %s dai stream: %s\n", + w->name, w->sname, rtd->dai_link->stream_name); + + if (!w->sname || !rtd->dai_link->stream_name) + continue; + + /* does stream match DAI link ? */ + if (strcmp(w->sname, rtd->dai_link->stream_name)) + continue; + + switch (w->id) { + case snd_soc_dapm_dai_out: + rtd->cpu_dai->capture_widget = w; + dai->name = rtd->dai_link->name; + dev_dbg(sdev->dev, "tplg: connected widget %s -> DAI link %s\n", + w->name, rtd->dai_link->name); + break; + case snd_soc_dapm_dai_in: + rtd->cpu_dai->playback_widget = w; + dai->name = rtd->dai_link->name; + dev_dbg(sdev->dev, "tplg: connected widget %s -> DAI link %s\n", + w->name, rtd->dai_link->name); + break; + default: + break; + } + } + + /* check we have a connection */ + if (!dai->name) { + dev_err(sdev->dev, "error: can't connect DAI %s stream %s\n", + w->name, w->sname); + return -EINVAL; + } + + return 0; +} + +static int sof_widget_load_dai(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r, + struct snd_sof_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_dai comp_dai; + int ret; + + /* configure dai IPC message */ + memset(&comp_dai, 0, sizeof(comp_dai)); + comp_dai.comp.hdr.size = sizeof(comp_dai); + comp_dai.comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + comp_dai.comp.id = swidget->comp_id; + comp_dai.comp.type = SOF_COMP_DAI; + comp_dai.comp.pipeline_id = index; + comp_dai.config.hdr.size = sizeof(comp_dai.config); + + ret = sof_parse_tokens(scomp, &comp_dai, dai_tokens, + ARRAY_SIZE(dai_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse dai tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + ret = sof_parse_tokens(scomp, &comp_dai.config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse dai.cfg tokens failed %d\n", + private->size); + return ret; + } + + dev_dbg(sdev->dev, "dai %s: type %d index %d\n", + swidget->widget->name, comp_dai.type, comp_dai.dai_index); + sof_dbg_comp_config(scomp, &comp_dai.config); + + ret = sof_ipc_tx_message(sdev->ipc, comp_dai.comp.hdr.cmd, + &comp_dai, sizeof(comp_dai), r, sizeof(*r)); + + if (ret == 0 && dai) { + dai->sdev = sdev; + memcpy(&dai->comp_dai, &comp_dai, sizeof(comp_dai)); + } + + return ret; +} + +/* + * Buffer topology + */ + +static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_buffer *buffer; + int ret; + + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + /* configure dai IPC message */ + buffer->comp.hdr.size = sizeof(*buffer); + buffer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_BUFFER_NEW; + buffer->comp.id = swidget->comp_id; + buffer->comp.type = SOF_COMP_BUFFER; + buffer->comp.pipeline_id = index; + + ret = sof_parse_tokens(scomp, buffer, buffer_tokens, + ARRAY_SIZE(buffer_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse buffer tokens failed %d\n", + private->size); + kfree(buffer); + return ret; + } + + dev_dbg(sdev->dev, "buffer %s: size %d caps 0x%x\n", + swidget->widget->name, buffer->size, buffer->caps); + + swidget->private = buffer; + + ret = sof_ipc_tx_message(sdev->ipc, buffer->comp.hdr.cmd, buffer, + sizeof(*buffer), r, sizeof(*r)); + if (ret < 0) { + dev_err(sdev->dev, "error: buffer %s load failed\n", + swidget->widget->name); + kfree(buffer); + } + + return ret; +} + +/* bind PCM ID to host component ID */ +static int spcm_bind(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, + int dir) +{ + struct snd_sof_widget *host_widget; + + host_widget = snd_sof_find_swidget_sname(sdev, + spcm->pcm.caps[dir].name, + dir); + if (!host_widget) { + dev_err(sdev->dev, "can't find host comp to bind pcm\n"); + return -EINVAL; + } + + spcm->stream[dir].comp_id = host_widget->comp_id; + + return 0; +} + +/* + * PCM Topology + */ + +static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + enum sof_ipc_stream_direction dir, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_host *host; + int ret; + + host = kzalloc(sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + /* configure host comp IPC message */ + host->comp.hdr.size = sizeof(*host); + host->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + host->comp.id = swidget->comp_id; + host->comp.type = SOF_COMP_HOST; + host->comp.pipeline_id = index; + host->direction = dir; + host->config.hdr.size = sizeof(host->config); + + ret = sof_parse_tokens(scomp, host, pcm_tokens, + ARRAY_SIZE(pcm_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse host tokens failed %d\n", + private->size); + goto err; + } + + ret = sof_parse_tokens(scomp, &host->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse host.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(sdev->dev, "loaded host %s\n", swidget->widget->name); + sof_dbg_comp_config(scomp, &host->config); + + swidget->private = host; + + ret = sof_ipc_tx_message(sdev->ipc, host->comp.hdr.cmd, host, + sizeof(*host), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(host); + return ret; +} + +/* + * Pipeline Topology + */ +int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, + struct sof_ipc_pipe_new *pipeline, + struct sof_ipc_comp_reply *r) +{ + struct sof_ipc_pm_core_config pm_core_config; + int ret; + + ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline, + sizeof(*pipeline), r, sizeof(*r)); + if (ret < 0) { + dev_err(sdev->dev, "error: load pipeline ipc failure\n"); + return ret; + } + + /* power up the core that this pipeline is scheduled on */ + ret = snd_sof_dsp_core_power_up(sdev, 1 << pipeline->core); + if (ret < 0) { + dev_err(sdev->dev, "error: powering up pipeline schedule core %d\n", + pipeline->core); + return ret; + } + + /* update enabled cores mask */ + sdev->enabled_cores_mask |= 1 << pipeline->core; + + /* + * Now notify DSP that the core that this pipeline is scheduled on + * has been powered up + */ + memset(&pm_core_config, 0, sizeof(pm_core_config)); + pm_core_config.enable_mask = sdev->enabled_cores_mask; + + /* configure CORE_ENABLE ipc message */ + pm_core_config.hdr.size = sizeof(pm_core_config); + pm_core_config.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE; + + /* send ipc */ + ret = sof_ipc_tx_message(sdev->ipc, pm_core_config.hdr.cmd, + &pm_core_config, sizeof(pm_core_config), + &pm_core_config, sizeof(pm_core_config)); + if (ret < 0) + dev_err(sdev->dev, "error: core enable ipc failure\n"); + + return ret; +} + +static int sof_widget_load_pipeline(struct snd_soc_component *scomp, + int index, struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_widget *comp_swidget; + int ret; + + pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL); + if (!pipeline) + return -ENOMEM; + + /* configure dai IPC message */ + pipeline->hdr.size = sizeof(*pipeline); + pipeline->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_NEW; + pipeline->pipeline_id = index; + pipeline->comp_id = swidget->comp_id; + + /* component at start of pipeline is our stream id */ + comp_swidget = snd_sof_find_swidget(sdev, tw->sname); + if (!comp_swidget) { + dev_err(sdev->dev, "error: widget %s refers to non existent widget %s\n", + tw->name, tw->sname); + ret = -EINVAL; + goto err; + } + + pipeline->sched_id = comp_swidget->comp_id; + + dev_dbg(sdev->dev, "tplg: pipeline id %d comp %d scheduling comp id %d\n", + pipeline->pipeline_id, pipeline->comp_id, pipeline->sched_id); + + ret = sof_parse_tokens(scomp, pipeline, sched_tokens, + ARRAY_SIZE(sched_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse pipeline tokens failed %d\n", + private->size); + goto err; + } + + dev_dbg(sdev->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d\n", + swidget->widget->name, pipeline->period, pipeline->priority, + pipeline->period_mips, pipeline->core, pipeline->frames_per_sched); + + swidget->private = pipeline; + + /* send ipc's to create pipeline comp and power up schedule core */ + ret = sof_load_pipeline_ipc(sdev, pipeline, r); + if (ret >= 0) + return ret; +err: + kfree(pipeline); + return ret; +} + +/* + * Mixer topology + */ + +static int sof_widget_load_mixer(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_mixer *mixer; + int ret; + + mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); + if (!mixer) + return -ENOMEM; + + /* configure mixer IPC message */ + mixer->comp.hdr.size = sizeof(*mixer); + mixer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + mixer->comp.id = swidget->comp_id; + mixer->comp.type = SOF_COMP_MIXER; + mixer->comp.pipeline_id = index; + mixer->config.hdr.size = sizeof(mixer->config); + + ret = sof_parse_tokens(scomp, &mixer->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse mixer.cfg tokens failed %d\n", + private->size); + kfree(mixer); + return ret; + } + + sof_dbg_comp_config(scomp, &mixer->config); + + swidget->private = mixer; + + ret = sof_ipc_tx_message(sdev->ipc, mixer->comp.hdr.cmd, mixer, + sizeof(*mixer), r, sizeof(*r)); + if (ret < 0) + kfree(mixer); + + return ret; +} + +/* + * Mux topology + */ +static int sof_widget_load_mux(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_mux *mux; + int ret; + + mux = kzalloc(sizeof(*mux), GFP_KERNEL); + if (!mux) + return -ENOMEM; + + /* configure mux IPC message */ + mux->comp.hdr.size = sizeof(*mux); + mux->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + mux->comp.id = swidget->comp_id; + mux->comp.type = SOF_COMP_MUX; + mux->comp.pipeline_id = index; + mux->config.hdr.size = sizeof(mux->config); + + ret = sof_parse_tokens(scomp, &mux->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse mux.cfg tokens failed %d\n", + private->size); + kfree(mux); + return ret; + } + + sof_dbg_comp_config(scomp, &mux->config); + + swidget->private = mux; + + ret = sof_ipc_tx_message(sdev->ipc, mux->comp.hdr.cmd, mux, + sizeof(*mux), r, sizeof(*r)); + if (ret < 0) + kfree(mux); + + return ret; +} + +/* + * PGA Topology + */ + +static int sof_widget_load_pga(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_volume *volume; + int ret; + + volume = kzalloc(sizeof(*volume), GFP_KERNEL); + if (!volume) + return -ENOMEM; + + if (le32_to_cpu(tw->num_kcontrols) != 1) { + dev_err(sdev->dev, "error: invalid kcontrol count %d for volume\n", + tw->num_kcontrols); + ret = -EINVAL; + goto err; + } + + /* configure volume IPC message */ + volume->comp.hdr.size = sizeof(*volume); + volume->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + volume->comp.id = swidget->comp_id; + volume->comp.type = SOF_COMP_VOLUME; + volume->comp.pipeline_id = index; + volume->config.hdr.size = sizeof(volume->config); + + ret = sof_parse_tokens(scomp, volume, volume_tokens, + ARRAY_SIZE(volume_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse volume tokens failed %d\n", + private->size); + goto err; + } + ret = sof_parse_tokens(scomp, &volume->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse volume.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + sof_dbg_comp_config(scomp, &volume->config); + + swidget->private = volume; + + ret = sof_ipc_tx_message(sdev->ipc, volume->comp.hdr.cmd, volume, + sizeof(*volume), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(volume); + return ret; +} + +/* + * SRC Topology + */ + +static int sof_widget_load_src(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_src *src; + int ret; + + src = kzalloc(sizeof(*src), GFP_KERNEL); + if (!src) + return -ENOMEM; + + /* configure src IPC message */ + src->comp.hdr.size = sizeof(*src); + src->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + src->comp.id = swidget->comp_id; + src->comp.type = SOF_COMP_SRC; + src->comp.pipeline_id = index; + src->config.hdr.size = sizeof(src->config); + + ret = sof_parse_tokens(scomp, src, src_tokens, + ARRAY_SIZE(src_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse src tokens failed %d\n", + private->size); + goto err; + } + + ret = sof_parse_tokens(scomp, &src->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse src.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(sdev->dev, "src %s: source rate %d sink rate %d\n", + swidget->widget->name, src->source_rate, src->sink_rate); + sof_dbg_comp_config(scomp, &src->config); + + swidget->private = src; + + ret = sof_ipc_tx_message(sdev->ipc, src->comp.hdr.cmd, src, + sizeof(*src), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(src); + return ret; +} + +/* + * Signal Generator Topology + */ + +static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_tone *tone; + int ret; + + tone = kzalloc(sizeof(*tone), GFP_KERNEL); + if (!tone) + return -ENOMEM; + + /* configure siggen IPC message */ + tone->comp.hdr.size = sizeof(*tone); + tone->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + tone->comp.id = swidget->comp_id; + tone->comp.type = SOF_COMP_TONE; + tone->comp.pipeline_id = index; + tone->config.hdr.size = sizeof(tone->config); + + ret = sof_parse_tokens(scomp, tone, tone_tokens, + ARRAY_SIZE(tone_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse tone tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + ret = sof_parse_tokens(scomp, &tone->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse tone.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + dev_dbg(sdev->dev, "tone %s: frequency %d amplitude %d\n", + swidget->widget->name, tone->frequency, tone->amplitude); + sof_dbg_comp_config(scomp, &tone->config); + + swidget->private = tone; + + ret = sof_ipc_tx_message(sdev->ipc, tone->comp.hdr.cmd, tone, + sizeof(*tone), r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(tone); + return ret; +} + +static int sof_process_load(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r, + int type) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct snd_soc_dapm_widget *widget = swidget->widget; + const struct snd_kcontrol_new *kc; + struct soc_bytes_ext *sbe; + struct soc_mixer_control *sm; + struct soc_enum *se; + struct snd_sof_control *scontrol = NULL; + struct sof_abi_hdr *pdata = NULL; + struct sof_ipc_comp_process *process; + size_t ipc_size, ipc_data_size = 0; + int ret, i, offset = 0; + + if (type == SOF_COMP_NONE) { + dev_err(sdev->dev, "error: invalid process comp type %d\n", + type); + return -EINVAL; + } + + /* + * get possible component controls - get size of all pdata, + * then memcpy with headers + */ + for (i = 0; i < widget->num_kcontrols; i++) { + + kc = &widget->kcontrol_news[i]; + + switch (widget->dobj.widget.kcontrol_type) { + case SND_SOC_TPLG_TYPE_MIXER: + sm = (struct soc_mixer_control *)kc->private_value; + scontrol = sm->dobj.private; + break; + case SND_SOC_TPLG_TYPE_BYTES: + sbe = (struct soc_bytes_ext *)kc->private_value; + scontrol = sbe->dobj.private; + break; + case SND_SOC_TPLG_TYPE_ENUM: + se = (struct soc_enum *)kc->private_value; + scontrol = se->dobj.private; + break; + default: + dev_err(sdev->dev, "error: unknown kcontrol type %d in widget %s\n", + widget->dobj.widget.kcontrol_type, + widget->name); + return -EINVAL; + } + + if (!scontrol) { + dev_err(sdev->dev, "error: no scontrol for widget %s\n", + widget->name); + return -EINVAL; + } + + /* don't include if no private data */ + pdata = scontrol->control_data->data; + if (!pdata) + continue; + + /* make sure data is valid - data can be updated at runtime */ + if (pdata->magic != SOF_ABI_MAGIC) + continue; + + ipc_data_size += pdata->size; + } + + ipc_size = sizeof(struct sof_ipc_comp_process) + + le32_to_cpu(private->size) + + ipc_data_size; + + process = kzalloc(ipc_size, GFP_KERNEL); + if (!process) + return -ENOMEM; + + /* configure iir IPC message */ + process->comp.hdr.size = ipc_size; + process->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + process->comp.id = swidget->comp_id; + process->comp.type = type; + process->comp.pipeline_id = index; + process->config.hdr.size = sizeof(process->config); + + ret = sof_parse_tokens(scomp, &process->config, comp_tokens, + ARRAY_SIZE(comp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse process.cfg tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + sof_dbg_comp_config(scomp, &process->config); + + /* + * found private data in control, so copy it. + * get possible component controls - get size of all pdata, + * then memcpy with headers + */ + for (i = 0; i < widget->num_kcontrols; i++) { + kc = &widget->kcontrol_news[i]; + + switch (widget->dobj.widget.kcontrol_type) { + case SND_SOC_TPLG_TYPE_MIXER: + sm = (struct soc_mixer_control *)kc->private_value; + scontrol = sm->dobj.private; + break; + case SND_SOC_TPLG_TYPE_BYTES: + sbe = (struct soc_bytes_ext *)kc->private_value; + scontrol = sbe->dobj.private; + break; + case SND_SOC_TPLG_TYPE_ENUM: + se = (struct soc_enum *)kc->private_value; + scontrol = se->dobj.private; + break; + default: + dev_err(sdev->dev, "error: unknown kcontrol type %d in widget %s\n", + widget->dobj.widget.kcontrol_type, + widget->name); + return -EINVAL; + } + + /* don't include if no private data */ + pdata = scontrol->control_data->data; + if (!pdata) + continue; + + /* make sure data is valid - data can be updated at runtime */ + if (pdata->magic != SOF_ABI_MAGIC) + continue; + + memcpy(&process->data + offset, pdata->data, pdata->size); + offset += pdata->size; + } + + process->size = ipc_data_size; + swidget->private = process; + + ret = sof_ipc_tx_message(sdev->ipc, process->comp.hdr.cmd, process, + ipc_size, r, sizeof(*r)); + if (ret >= 0) + return ret; +err: + kfree(process); + return ret; +} + +/* + * Processing Component Topology - can be "effect", "codec", or general + * "processing". + */ + +static int sof_widget_load_process(struct snd_soc_component *scomp, int index, + struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + struct sof_ipc_comp_reply *r) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &tw->priv; + struct sof_ipc_comp_process config; + int ret; + + /* check we have some tokens - we need at least process type */ + if (le32_to_cpu(private->size) == 0) { + dev_err(sdev->dev, "error: process tokens not found\n"); + return -EINVAL; + } + + memset(&config, 0, sizeof(config)); + + /* get the process token */ + ret = sof_parse_tokens(scomp, &config, process_tokens, + ARRAY_SIZE(process_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse process tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* now load process specific data and send IPC */ + ret = sof_process_load(scomp, index, swidget, tw, r, + find_process_comp_type(config.type)); + if (ret < 0) { + dev_err(sdev->dev, "error: process loading failed\n"); + return ret; + } + + return 0; +} + +static int sof_widget_bind_event(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget, + u16 event_type) +{ + struct sof_ipc_comp *ipc_comp; + + /* validate widget event type */ + switch (event_type) { + case SOF_KEYWORD_DETECT_DAPM_EVENT: + /* only KEYWORD_DETECT comps should handle this */ + if (swidget->id != snd_soc_dapm_effect) + break; + + ipc_comp = swidget->private; + if (ipc_comp && ipc_comp->type != SOF_COMP_KEYWORD_DETECT) + break; + + /* bind event to keyword detect comp */ + return snd_soc_tplg_widget_bind_event(swidget->widget, + sof_kwd_events, + ARRAY_SIZE(sof_kwd_events), + event_type); + default: + break; + } + + dev_err(sdev->dev, + "error: invalid event type %d for widget %s\n", + event_type, swidget->widget->name); + return -EINVAL; +} + +/* external widget init - used for any driver specific init */ +static int sof_widget_ready(struct snd_soc_component *scomp, int index, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tw) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *swidget; + struct snd_sof_dai *dai; + struct sof_ipc_comp_reply reply; + struct snd_sof_control *scontrol; + int ret = 0; + + swidget = kzalloc(sizeof(*swidget), GFP_KERNEL); + if (!swidget) + return -ENOMEM; + + swidget->sdev = sdev; + swidget->widget = w; + swidget->comp_id = sdev->next_comp_id++; + swidget->complete = 0; + swidget->id = w->id; + swidget->pipeline_id = index; + swidget->private = NULL; + memset(&reply, 0, sizeof(reply)); + + dev_dbg(sdev->dev, "tplg: ready widget id %d pipe %d type %d name : %s stream %s\n", + swidget->comp_id, index, swidget->id, tw->name, + strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 + ? tw->sname : "none"); + + /* handle any special case widgets */ + switch (w->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + dai = kzalloc(sizeof(*dai), GFP_KERNEL); + if (!dai) { + kfree(swidget); + return -ENOMEM; + } + + ret = sof_widget_load_dai(scomp, index, swidget, tw, &reply, + dai); + if (ret == 0) { + sof_connect_dai_widget(scomp, w, tw, dai); + list_add(&dai->list, &sdev->dai_list); + swidget->private = dai; + } else { + kfree(dai); + } + break; + case snd_soc_dapm_mixer: + ret = sof_widget_load_mixer(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_pga: + ret = sof_widget_load_pga(scomp, index, swidget, tw, &reply); + /* Find scontrol for this pga and set readback offset*/ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + if (scontrol->comp_id == swidget->comp_id) { + scontrol->readback_offset = reply.offset; + break; + } + } + break; + case snd_soc_dapm_buffer: + ret = sof_widget_load_buffer(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_scheduler: + ret = sof_widget_load_pipeline(scomp, index, swidget, tw, + &reply); + break; + case snd_soc_dapm_aif_out: + ret = sof_widget_load_pcm(scomp, index, swidget, + SOF_IPC_STREAM_CAPTURE, tw, &reply); + break; + case snd_soc_dapm_aif_in: + ret = sof_widget_load_pcm(scomp, index, swidget, + SOF_IPC_STREAM_PLAYBACK, tw, &reply); + break; + case snd_soc_dapm_src: + ret = sof_widget_load_src(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_siggen: + ret = sof_widget_load_siggen(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_effect: + ret = sof_widget_load_process(scomp, index, swidget, tw, + &reply); + break; + case snd_soc_dapm_mux: + case snd_soc_dapm_demux: + ret = sof_widget_load_mux(scomp, index, swidget, tw, &reply); + break; + case snd_soc_dapm_switch: + case snd_soc_dapm_dai_link: + case snd_soc_dapm_kcontrol: + default: + dev_warn(sdev->dev, "warning: widget type %d name %s not handled\n", + swidget->id, tw->name); + break; + } + + /* check IPC reply */ + if (ret < 0 || reply.rhdr.error < 0) { + dev_err(sdev->dev, + "error: DSP failed to add widget id %d type %d name : %s stream %s reply %d\n", + tw->shift, swidget->id, tw->name, + strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 + ? tw->sname : "none", reply.rhdr.error); + kfree(swidget); + return ret; + } + + /* bind widget to external event */ + if (tw->event_type) { + ret = sof_widget_bind_event(sdev, swidget, + le16_to_cpu(tw->event_type)); + if (ret) { + dev_err(sdev->dev, "error: widget event binding failed\n"); + kfree(swidget->private); + kfree(swidget); + return ret; + } + } + + w->dobj.private = swidget; + list_add(&swidget->list, &sdev->widget_list); + return ret; +} + +static int sof_route_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_route *sroute; + + sroute = dobj->private; + if (!sroute) + return 0; + + /* free sroute and its private data */ + kfree(sroute->private); + list_del(&sroute->list); + kfree(sroute); + + return 0; +} + +static int sof_widget_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct snd_kcontrol_new *kc; + struct snd_soc_dapm_widget *widget; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_control *scontrol; + struct snd_sof_widget *swidget; + struct soc_mixer_control *sm; + struct soc_bytes_ext *sbe; + struct snd_sof_dai *dai; + struct soc_enum *se; + int ret = 0; + int i; + + swidget = dobj->private; + if (!swidget) + return 0; + + widget = swidget->widget; + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + dai = swidget->private; + + if (dai) { + /* free dai config */ + kfree(dai->dai_config); + list_del(&dai->list); + } + break; + case snd_soc_dapm_scheduler: + + /* power down the pipeline schedule core */ + pipeline = swidget->private; + ret = snd_sof_dsp_core_power_down(sdev, 1 << pipeline->core); + if (ret < 0) + dev_err(sdev->dev, "error: powering down pipeline schedule core %d\n", + pipeline->core); + + /* update enabled cores mask */ + sdev->enabled_cores_mask &= ~(1 << pipeline->core); + + break; + default: + break; + } + for (i = 0; i < widget->num_kcontrols; i++) { + kc = &widget->kcontrol_news[i]; + switch (dobj->widget.kcontrol_type) { + case SND_SOC_TPLG_TYPE_MIXER: + sm = (struct soc_mixer_control *)kc->private_value; + scontrol = sm->dobj.private; + if (sm->max > 1) + kfree(scontrol->volume_table); + break; + case SND_SOC_TPLG_TYPE_ENUM: + se = (struct soc_enum *)kc->private_value; + scontrol = se->dobj.private; + break; + case SND_SOC_TPLG_TYPE_BYTES: + sbe = (struct soc_bytes_ext *)kc->private_value; + scontrol = sbe->dobj.private; + break; + default: + dev_warn(sdev->dev, "unsupported kcontrol_type\n"); + goto out; + } + kfree(scontrol->control_data); + list_del(&scontrol->list); + kfree(scontrol); + } + +out: + /* free private value */ + kfree(swidget->private); + + /* remove and free swidget object */ + list_del(&swidget->list); + kfree(swidget); + + return ret; +} + +/* + * DAI HW configuration. + */ + +/* FE DAI - used for any driver specific init */ +static int sof_dai_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_driver *dai_drv, + struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_stream_caps *caps; + struct snd_sof_pcm *spcm; + int stream = SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0; + + /* nothing to do for BEs atm */ + if (!pcm) + return 0; + + spcm = kzalloc(sizeof(*spcm), GFP_KERNEL); + if (!spcm) + return -ENOMEM; + + spcm->sdev = sdev; + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id = COMP_ID_UNASSIGNED; + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = COMP_ID_UNASSIGNED; + + if (pcm) { + spcm->pcm = *pcm; + dev_dbg(sdev->dev, "tplg: load pcm %s\n", pcm->dai_name); + } + dai_drv->dobj.private = spcm; + list_add(&spcm->list, &sdev->pcm_list); + + /* do we need to allocate playback PCM DMA pages */ + if (!spcm->pcm.playback) + goto capture; + + caps = &spcm->pcm.caps[stream]; + + /* allocate playback page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, + PAGE_SIZE, &spcm->stream[stream].page_table); + if (ret < 0) { + dev_err(sdev->dev, "error: can't alloc page table for %s %d\n", + caps->name, ret); + + return ret; + } + + /* bind pcm to host comp */ + ret = spcm_bind(sdev, spcm, stream); + if (ret) { + dev_err(sdev->dev, + "error: can't bind pcm to host\n"); + goto free_playback_tables; + } + +capture: + stream = SNDRV_PCM_STREAM_CAPTURE; + + /* do we need to allocate capture PCM DMA pages */ + if (!spcm->pcm.capture) + return ret; + + caps = &spcm->pcm.caps[stream]; + + /* allocate capture page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, + PAGE_SIZE, &spcm->stream[stream].page_table); + if (ret < 0) { + dev_err(sdev->dev, "error: can't alloc page table for %s %d\n", + caps->name, ret); + goto free_playback_tables; + } + + /* bind pcm to host comp */ + ret = spcm_bind(sdev, spcm, stream); + if (ret) { + dev_err(sdev->dev, + "error: can't bind pcm to host\n"); + snd_dma_free_pages(&spcm->stream[stream].page_table); + goto free_playback_tables; + } + + return ret; + +free_playback_tables: + if (spcm->pcm.playback) + snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].page_table); + + return ret; +} + +static int sof_dai_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_pcm *spcm = dobj->private; + + /* free PCM DMA pages */ + if (spcm->pcm.playback) + snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].page_table); + + if (spcm->pcm.capture) + snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_CAPTURE].page_table); + + /* remove from list and free spcm */ + list_del(&spcm->list); + kfree(spcm); + + return 0; +} + +static void sof_dai_set_format(struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + /* clock directions wrt codec */ + if (hw_config->bclk_master == SND_SOC_TPLG_BCLK_CM) { + /* codec is bclk master */ + if (hw_config->fsync_master == SND_SOC_TPLG_FSYNC_CM) + config->format |= SOF_DAI_FMT_CBM_CFM; + else + config->format |= SOF_DAI_FMT_CBM_CFS; + } else { + /* codec is bclk slave */ + if (hw_config->fsync_master == SND_SOC_TPLG_FSYNC_CM) + config->format |= SOF_DAI_FMT_CBS_CFM; + else + config->format |= SOF_DAI_FMT_CBS_CFS; + } + + /* inverted clocks ? */ + if (hw_config->invert_bclk) { + if (hw_config->invert_fsync) + config->format |= SOF_DAI_FMT_IB_IF; + else + config->format |= SOF_DAI_FMT_IB_NF; + } else { + if (hw_config->invert_fsync) + config->format |= SOF_DAI_FMT_NB_IF; + else + config->format |= SOF_DAI_FMT_NB_NF; + } +} + +/* set config for all DAI's with name matching the link name */ +static int sof_set_dai_config(struct snd_sof_dev *sdev, u32 size, + struct snd_soc_dai_link *link, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dai *dai; + int found = 0; + + list_for_each_entry(dai, &sdev->dai_list, list) { + if (!dai->name) + continue; + + if (strcmp(link->name, dai->name) == 0) { + dai->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!dai->dai_config) + return -ENOMEM; + + found = 1; + } + } + + /* + * machine driver may define a dai link with playback and capture + * dai enabled, but the dai link in topology would support both, one + * or none of them. Here print a warning message to notify user + */ + if (!found) { + dev_warn(sdev->dev, "warning: failed to find dai for dai link %s", + link->name); + } + + return 0; +} + +static int sof_link_ssp_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg, + struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->ssp, 0, sizeof(struct sof_ipc_dai_ssp_params)); + config->hdr.size = size; + + ret = sof_parse_tokens(scomp, &config->ssp, ssp_tokens, + ARRAY_SIZE(ssp_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse ssp tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + config->ssp.mclk_rate = le32_to_cpu(hw_config->mclk_rate); + config->ssp.bclk_rate = le32_to_cpu(hw_config->bclk_rate); + config->ssp.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->ssp.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + config->ssp.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); + config->ssp.mclk_direction = hw_config->mclk_direction; + config->ssp.rx_slots = le32_to_cpu(hw_config->rx_slots); + config->ssp.tx_slots = le32_to_cpu(hw_config->tx_slots); + + dev_dbg(sdev->dev, "tplg: config SSP%d fmt 0x%x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d\n", + config->dai_index, config->format, + config->ssp.mclk_rate, config->ssp.bclk_rate, + config->ssp.fsync_rate, config->ssp.sample_valid_bits, + config->ssp.tdm_slot_width, config->ssp.tdm_slots, + config->ssp.mclk_id, config->ssp.quirks); + + /* validate SSP fsync rate and channel count */ + if (config->ssp.fsync_rate < 8000 || config->ssp.fsync_rate > 192000) { + dev_err(sdev->dev, "error: invalid fsync rate for SSP%d\n", + config->dai_index); + return -EINVAL; + } + + if (config->ssp.tdm_slots < 1 || config->ssp.tdm_slots > 8) { + dev_err(sdev->dev, "error: invalid channel count for SSP%d\n", + config->dai_index); + return -EINVAL; + } + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for SSP%d\n", + config->dai_index); + return ret; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) + dev_err(sdev->dev, "error: failed to save DAI config for SSP%d\n", + config->dai_index); + + return ret; +} + +static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg, + struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_dai_config *ipc_config; + struct sof_ipc_reply reply; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + u32 size; + int ret, j; + + /* + * config is only used for the common params in dmic_params structure + * that does not include the PDM controller config array + * Set the common params to 0. + */ + memset(&config->dmic, 0, sizeof(struct sof_ipc_dai_dmic_params)); + + /* get DMIC tokens */ + ret = sof_parse_tokens(scomp, &config->dmic, dmic_tokens, + ARRAY_SIZE(dmic_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse dmic tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* + * allocate memory for dmic dai config accounting for the + * variable number of active pdm controllers + * This will be the ipc payload for setting dai config + */ + size = sizeof(*config) + sizeof(struct sof_ipc_dai_dmic_pdm_ctrl) * + config->dmic.num_pdm_active; + + ipc_config = kzalloc(size, GFP_KERNEL); + if (!ipc_config) + return -ENOMEM; + + /* copy the common dai config and dmic params */ + memcpy(ipc_config, config, sizeof(*config)); + + /* + * alloc memory for private member + * Used to track the pdm config array index currently being parsed + */ + sdev->private = kzalloc(sizeof(u32), GFP_KERNEL); + if (!sdev->private) { + kfree(ipc_config); + return -ENOMEM; + } + + /* get DMIC PDM tokens */ + ret = sof_parse_tokens(scomp, &ipc_config->dmic.pdm[0], dmic_pdm_tokens, + ARRAY_SIZE(dmic_pdm_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse dmic pdm tokens failed %d\n", + le32_to_cpu(private->size)); + goto err; + } + + /* set IPC header size */ + ipc_config->hdr.size = size; + + /* debug messages */ + dev_dbg(sdev->dev, "tplg: config DMIC%d driver version %d\n", + ipc_config->dai_index, ipc_config->dmic.driver_ipc_version); + dev_dbg(sdev->dev, "pdmclk_min %d pdm_clkmax %d duty_min %hd\n", + ipc_config->dmic.pdmclk_min, ipc_config->dmic.pdmclk_max, + ipc_config->dmic.duty_min); + dev_dbg(sdev->dev, "duty_max %hd fifo_fs %d num_pdms active %d\n", + ipc_config->dmic.duty_max, ipc_config->dmic.fifo_fs, + ipc_config->dmic.num_pdm_active); + dev_dbg(sdev->dev, "fifo word length %hd\n", + ipc_config->dmic.fifo_bits); + + for (j = 0; j < ipc_config->dmic.num_pdm_active; j++) { + dev_dbg(sdev->dev, "pdm %hd mic a %hd mic b %hd\n", + ipc_config->dmic.pdm[j].id, + ipc_config->dmic.pdm[j].enable_mic_a, + ipc_config->dmic.pdm[j].enable_mic_b); + dev_dbg(sdev->dev, "pdm %hd polarity a %hd polarity b %hd\n", + ipc_config->dmic.pdm[j].id, + ipc_config->dmic.pdm[j].polarity_mic_a, + ipc_config->dmic.pdm[j].polarity_mic_b); + dev_dbg(sdev->dev, "pdm %hd clk_edge %hd skew %hd\n", + ipc_config->dmic.pdm[j].id, + ipc_config->dmic.pdm[j].clk_edge, + ipc_config->dmic.pdm[j].skew); + } + + if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1)) { + /* this takes care of backwards compatible handling of fifo_bits_b */ + ipc_config->dmic.reserved_2 = ipc_config->dmic.fifo_bits; + } + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + ipc_config->hdr.cmd, ipc_config, size, &reply, + sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to set DAI config for DMIC%d\n", + config->dai_index); + goto err; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, ipc_config); + if (ret < 0) + dev_err(sdev->dev, "error: failed to save DAI config for DMIC%d\n", + config->dai_index); + +err: + kfree(sdev->private); + kfree(ipc_config); + + return ret; +} + +/* + * for hda link, playback and capture are supported by different dai + * in FW. Here get the dai_index, set dma channel of each dai + * and send config to FW. In FW, each dai sets config by dai_index + */ +static int sof_link_hda_process(struct snd_sof_dev *sdev, + struct snd_soc_dai_link *link, + struct sof_ipc_dai_config *config, + int tx_slot, + int rx_slot) +{ + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + struct snd_sof_dai *sof_dai; + int found = 0; + int ret; + + list_for_each_entry(sof_dai, &sdev->dai_list, list) { + if (!sof_dai->name) + continue; + + if (strcmp(link->name, sof_dai->name) == 0) { + if (sof_dai->comp_dai.direction == + SNDRV_PCM_STREAM_PLAYBACK) { + if (!link->dpcm_playback) + return -EINVAL; + + config->hda.link_dma_ch = tx_slot; + } else { + if (!link->dpcm_capture) + return -EINVAL; + + config->hda.link_dma_ch = rx_slot; + } + + config->dai_index = sof_dai->comp_dai.dai_index; + found = 1; + + /* save config in dai component */ + sof_dai->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!sof_dai->dai_config) + return -ENOMEM; + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, + &reply, sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for direction:%d of HDA dai %d\n", + sof_dai->comp_dai.direction, + config->dai_index); + + return ret; + } + } + } + + /* + * machine driver may define a dai link with playback and capture + * dai enabled, but the dai link in topology would support both, one + * or none of them. Here print a warning message to notify user + */ + if (!found) { + dev_warn(sdev->dev, "warning: failed to find dai for dai link %s", + link->name); + } + + return 0; +} + +static int sof_link_hda_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg, + struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dai_link_component dai_component; + struct snd_soc_tplg_private *private = &cfg->priv; + struct snd_soc_dai *dai; + u32 size = sizeof(*config); + u32 tx_num = 0; + u32 tx_slot = 0; + u32 rx_num = 0; + u32 rx_slot = 0; + int ret; + + /* init IPC */ + memset(&dai_component, 0, sizeof(dai_component)); + memset(&config->hda, 0, sizeof(struct sof_ipc_dai_hda_params)); + config->hdr.size = size; + + /* get any bespoke DAI tokens */ + ret = sof_parse_tokens(scomp, config, hda_tokens, + ARRAY_SIZE(hda_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse hda tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + dai_component.dai_name = link->cpu_dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(sdev->dev, "error: failed to find dai %s in %s", + dai_component.dai_name, __func__); + return -EINVAL; + } + + if (link->dpcm_playback) + tx_num = 1; + + if (link->dpcm_capture) + rx_num = 1; + + ret = snd_soc_dai_get_channel_map(dai, &tx_num, &tx_slot, + &rx_num, &rx_slot); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to get dma channel for HDA%d\n", + config->dai_index); + + return ret; + } + + ret = sof_link_hda_process(sdev, link, config, tx_slot, rx_slot); + if (ret < 0) + dev_err(sdev->dev, "error: failed to process hda dai link %s", + link->name); + + return ret; +} + +/* DAI link - used for any driver specific init */ +static int sof_link_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_dai_config config; + struct snd_soc_tplg_hw_config *hw_config; + int num_hw_configs; + int ret; + int i = 0; + + link->platform_name = dev_name(sdev->dev); + + /* + * Set nonatomic property for FE dai links as their trigger action + * involves IPC's. + */ + if (!link->no_pcm) { + link->nonatomic = true; + + /* nothing more to do for FE dai links */ + return 0; + } + + /* check we have some tokens - we need at least DAI type */ + if (le32_to_cpu(private->size) == 0) { + dev_err(sdev->dev, "error: expected tokens for DAI, none found\n"); + return -EINVAL; + } + + /* Send BE DAI link configurations to DSP */ + memset(&config, 0, sizeof(config)); + + /* get any common DAI tokens */ + ret = sof_parse_tokens(scomp, &config, dai_link_tokens, + ARRAY_SIZE(dai_link_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse link tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + /* + * DAI links are expected to have at least 1 hw_config. + * But some older topologies might have no hw_config for HDA dai links. + */ + num_hw_configs = le32_to_cpu(cfg->num_hw_configs); + if (!num_hw_configs) { + if (config.type != SOF_DAI_INTEL_HDA) { + dev_err(sdev->dev, "error: unexpected DAI config count %d!\n", + le32_to_cpu(cfg->num_hw_configs)); + return -EINVAL; + } + } else { + dev_dbg(sdev->dev, "tplg: %d hw_configs found, default id: %d!\n", + cfg->num_hw_configs, le32_to_cpu(cfg->default_hw_config_id)); + + for (i = 0; i < num_hw_configs; i++) { + if (cfg->hw_config[i].id == cfg->default_hw_config_id) + break; + } + + if (i == num_hw_configs) { + dev_err(sdev->dev, "error: default hw_config id: %d not found!\n", + le32_to_cpu(cfg->default_hw_config_id)); + return -EINVAL; + } + } + + /* configure dai IPC message */ + hw_config = &cfg->hw_config[i]; + + config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; + config.format = le32_to_cpu(hw_config->fmt); + + /* now load DAI specific data and send IPC - type comes from token */ + switch (config.type) { + case SOF_DAI_INTEL_SSP: + ret = sof_link_ssp_load(scomp, index, link, cfg, hw_config, + &config); + break; + case SOF_DAI_INTEL_DMIC: + ret = sof_link_dmic_load(scomp, index, link, cfg, hw_config, + &config); + break; + case SOF_DAI_INTEL_HDA: + ret = sof_link_hda_load(scomp, index, link, cfg, hw_config, + &config); + break; + default: + dev_err(sdev->dev, "error: invalid DAI type %d\n", config.type); + ret = -EINVAL; + break; + } + if (ret < 0) + return ret; + + return 0; +} + +static int sof_link_hda_unload(struct snd_sof_dev *sdev, + struct snd_soc_dai_link *link) +{ + struct snd_soc_dai_link_component dai_component; + struct snd_soc_dai *dai; + int ret = 0; + + memset(&dai_component, 0, sizeof(dai_component)); + dai_component.dai_name = link->cpu_dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(sdev->dev, "error: failed to find dai %s in %s", + dai_component.dai_name, __func__); + return -EINVAL; + } + + /* + * FIXME: this call to hw_free is mainly to release the link DMA ID. + * This is abusing the API and handling SOC internals is not + * recommended. This part will be reworked. + */ + if (dai->driver->ops->hw_free) + ret = dai->driver->ops->hw_free(NULL, dai); + if (ret < 0) + dev_err(sdev->dev, "error: failed to free hda resource for %s\n", + link->name); + + return ret; +} + +static int sof_link_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dai_link *link = + container_of(dobj, struct snd_soc_dai_link, dobj); + + struct snd_sof_dai *sof_dai; + int ret = 0; + + /* only BE link is loaded by sof */ + if (!link->no_pcm) + return 0; + + list_for_each_entry(sof_dai, &sdev->dai_list, list) { + if (!sof_dai->name) + continue; + + if (strcmp(link->name, sof_dai->name) == 0) + goto found; + } + + dev_err(sdev->dev, "error: failed to find dai %s in %s", + link->name, __func__); + return -EINVAL; +found: + + switch (sof_dai->dai_config->type) { + case SOF_DAI_INTEL_SSP: + case SOF_DAI_INTEL_DMIC: + /* no resource needs to be released for SSP and DMIC */ + break; + case SOF_DAI_INTEL_HDA: + ret = sof_link_hda_unload(sdev, link); + break; + default: + dev_err(sdev->dev, "error: invalid DAI type %d\n", + sof_dai->dai_config->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/* DAI link - used for any driver specific init */ +static int sof_route_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dapm_route *route) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_pipe_comp_connect *connect; + struct snd_sof_widget *source_swidget, *sink_swidget; + struct snd_soc_dobj *dobj = &route->dobj; + struct snd_sof_route *sroute; + struct sof_ipc_reply reply; + int ret = 0; + + /* allocate memory for sroute and connect */ + sroute = kzalloc(sizeof(*sroute), GFP_KERNEL); + if (!sroute) + return -ENOMEM; + + sroute->sdev = sdev; + + connect = kzalloc(sizeof(*connect), GFP_KERNEL); + if (!connect) { + kfree(sroute); + return -ENOMEM; + } + + connect->hdr.size = sizeof(*connect); + connect->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT; + + dev_dbg(sdev->dev, "sink %s control %s source %s\n", + route->sink, route->control ? route->control : "none", + route->source); + + /* source component */ + source_swidget = snd_sof_find_swidget(sdev, (char *)route->source); + if (!source_swidget) { + dev_err(sdev->dev, "error: source %s not found\n", + route->source); + ret = -EINVAL; + goto err; + } + + /* + * Virtual widgets of type output/out_drv may be added in topology + * for compatibility. These are not handled by the FW. + * So, don't send routes whose source/sink widget is of such types + * to the DSP. + */ + if (source_swidget->id == snd_soc_dapm_out_drv || + source_swidget->id == snd_soc_dapm_output) + goto err; + + connect->source_id = source_swidget->comp_id; + + /* sink component */ + sink_swidget = snd_sof_find_swidget(sdev, (char *)route->sink); + if (!sink_swidget) { + dev_err(sdev->dev, "error: sink %s not found\n", + route->sink); + ret = -EINVAL; + goto err; + } + + /* + * Don't send routes whose sink widget is of type + * output or out_drv to the DSP + */ + if (sink_swidget->id == snd_soc_dapm_out_drv || + sink_swidget->id == snd_soc_dapm_output) + goto err; + + connect->sink_id = sink_swidget->comp_id; + + /* + * For virtual routes, both sink and source are not + * buffer. Since only buffer linked to component is supported by + * FW, others are reported as error, add check in route function, + * do not send it to FW when both source and sink are not buffer + */ + if (source_swidget->id != snd_soc_dapm_buffer && + sink_swidget->id != snd_soc_dapm_buffer) { + dev_dbg(sdev->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n", + route->source, route->sink); + ret = 0; + goto err; + } else { + ret = sof_ipc_tx_message(sdev->ipc, + connect->hdr.cmd, + connect, sizeof(*connect), + &reply, sizeof(reply)); + + /* check IPC return value */ + if (ret < 0) { + dev_err(sdev->dev, "error: failed to add route sink %s control %s source %s\n", + route->sink, + route->control ? route->control : "none", + route->source); + goto err; + } + + /* check IPC reply */ + if (reply.error < 0) { + dev_err(sdev->dev, "error: DSP failed to add route sink %s control %s source %s result %d\n", + route->sink, + route->control ? route->control : "none", + route->source, reply.error); + ret = reply.error; + goto err; + } + + sroute->route = route; + dobj->private = sroute; + sroute->private = connect; + + /* add route to route list */ + list_add(&sroute->list, &sdev->route_list); + + return ret; + } + +err: + kfree(connect); + kfree(sroute); + return ret; +} + +int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget) +{ + struct sof_ipc_pipe_ready ready; + struct sof_ipc_reply reply; + int ret; + + dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n", + swidget->widget->name, swidget->comp_id); + + memset(&ready, 0, sizeof(ready)); + ready.hdr.size = sizeof(ready); + ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE; + ready.comp_id = swidget->comp_id; + + ret = sof_ipc_tx_message(sdev->ipc, + ready.hdr.cmd, &ready, sizeof(ready), &reply, + sizeof(reply)); + if (ret < 0) + return ret; + return 1; +} + +/* completion - called at completion of firmware loading */ +static void sof_complete(struct snd_soc_component *scomp) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *swidget; + + /* some widget types require completion notificattion */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->complete) + continue; + + switch (swidget->id) { + case snd_soc_dapm_scheduler: + swidget->complete = + snd_sof_complete_pipeline(sdev, swidget); + break; + default: + break; + } + } +} + +/* manifest - optional to inform component of manifest */ +static int sof_manifest(struct snd_soc_component *scomp, int index, + struct snd_soc_tplg_manifest *man) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + u32 size; + u32 abi_version; + + size = le32_to_cpu(man->priv.size); + + /* backward compatible with tplg without ABI info */ + if (!size) { + dev_dbg(sdev->dev, "No topology ABI info\n"); + return 0; + } + + if (size != SOF_TPLG_ABI_SIZE) { + dev_err(sdev->dev, "error: invalid topology ABI size\n"); + return -EINVAL; + } + + dev_info(sdev->dev, + "Topology: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", + man->priv.data[0], man->priv.data[1], + man->priv.data[2], SOF_ABI_MAJOR, SOF_ABI_MINOR, + SOF_ABI_PATCH); + + abi_version = SOF_ABI_VER(man->priv.data[0], + man->priv.data[1], + man->priv.data[2]); + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) { + dev_err(sdev->dev, "error: incompatible topology ABI version\n"); + return -EINVAL; + } + + if (abi_version > SOF_ABI_VERSION) { + if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) { + dev_warn(sdev->dev, "warn: topology ABI is more recent than kernel\n"); + } else { + dev_err(sdev->dev, "error: topology ABI is more recent than kernel\n"); + return -EINVAL; + } + } + + return 0; +} + +/* vendor specific kcontrol handlers available for binding */ +static const struct snd_soc_tplg_kcontrol_ops sof_io_ops[] = { + {SOF_TPLG_KCTL_VOL_ID, snd_sof_volume_get, snd_sof_volume_put}, + {SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_get, snd_sof_bytes_put}, + {SOF_TPLG_KCTL_ENUM_ID, snd_sof_enum_get, snd_sof_enum_put}, + {SOF_TPLG_KCTL_SWITCH_ID, snd_sof_switch_get, snd_sof_switch_put}, +}; + +/* vendor specific bytes ext handlers available for binding */ +static const struct snd_soc_tplg_bytes_ext_ops sof_bytes_ext_ops[] = { + {SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_ext_get, snd_sof_bytes_ext_put}, +}; + +static struct snd_soc_tplg_ops sof_tplg_ops = { + /* external kcontrol init - used for any driver specific init */ + .control_load = sof_control_load, + .control_unload = sof_control_unload, + + /* external kcontrol init - used for any driver specific init */ + .dapm_route_load = sof_route_load, + .dapm_route_unload = sof_route_unload, + + /* external widget init - used for any driver specific init */ + /* .widget_load is not currently used */ + .widget_ready = sof_widget_ready, + .widget_unload = sof_widget_unload, + + /* FE DAI - used for any driver specific init */ + .dai_load = sof_dai_load, + .dai_unload = sof_dai_unload, + + /* DAI link - used for any driver specific init */ + .link_load = sof_link_load, + .link_unload = sof_link_unload, + + /* completion - called at completion of firmware loading */ + .complete = sof_complete, + + /* manifest - optional to inform component of manifest */ + .manifest = sof_manifest, + + /* vendor specific kcontrol handlers available for binding */ + .io_ops = sof_io_ops, + .io_ops_count = ARRAY_SIZE(sof_io_ops), + + /* vendor specific bytes ext handlers available for binding */ + .bytes_ext_ops = sof_bytes_ext_ops, + .bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops), +}; + +int snd_sof_init_topology(struct snd_sof_dev *sdev, + struct snd_soc_tplg_ops *ops) +{ + /* TODO: support linked list of topologies */ + sdev->tplg_ops = ops; + return 0; +} +EXPORT_SYMBOL(snd_sof_init_topology); + +int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file) +{ + const struct firmware *fw; + int ret; + + dev_dbg(sdev->dev, "loading topology:%s\n", file); + + ret = request_firmware(&fw, file, sdev->dev); + if (ret < 0) { + dev_err(sdev->dev, "error: tplg request firmware %s failed err: %d\n", + file, ret); + return ret; + } + + ret = snd_soc_tplg_component_load(sdev->component, + &sof_tplg_ops, fw, + SND_SOC_TPLG_INDEX_ALL); + if (ret < 0) { + dev_err(sdev->dev, "error: tplg component load failed %d\n", + ret); + ret = -EINVAL; + } + + release_firmware(fw); + return ret; +} +EXPORT_SYMBOL(snd_sof_load_topology); diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c new file mode 100644 index 000000000000..d588e4b70fad --- /dev/null +++ b/sound/soc/sof/trace.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// + +#include <linux/debugfs.h> +#include <linux/sched/signal.h> +#include "sof-priv.h" +#include "ops.h" + +static size_t sof_wait_trace_avail(struct snd_sof_dev *sdev, + loff_t pos, size_t buffer_size) +{ + wait_queue_entry_t wait; + loff_t host_offset = READ_ONCE(sdev->host_offset); + + /* + * If host offset is less than local pos, it means write pointer of + * host DMA buffer has been wrapped. We should output the trace data + * at the end of host DMA buffer at first. + */ + if (host_offset < pos) + return buffer_size - pos; + + /* If there is available trace data now, it is unnecessary to wait. */ + if (host_offset > pos) + return host_offset - pos; + + /* wait for available trace data from FW */ + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&sdev->trace_sleep, &wait); + + if (!signal_pending(current)) { + /* set timeout to max value, no error code */ + schedule_timeout(MAX_SCHEDULE_TIMEOUT); + } + remove_wait_queue(&sdev->trace_sleep, &wait); + + /* return bytes available for copy */ + host_offset = READ_ONCE(sdev->host_offset); + if (host_offset < pos) + return buffer_size - pos; + + return host_offset - pos; +} + +static ssize_t sof_dfsentry_trace_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + unsigned long rem; + loff_t lpos = *ppos; + size_t avail, buffer_size = dfse->size; + u64 lpos_64; + + /* make sure we know about any failures on the DSP side */ + sdev->dtrace_error = false; + + /* check pos and count */ + if (lpos < 0) + return -EINVAL; + if (!count) + return 0; + + /* check for buffer wrap and count overflow */ + lpos_64 = lpos; + lpos = do_div(lpos_64, buffer_size); + + if (count > buffer_size - lpos) /* min() not used to avoid sparse warnings */ + count = buffer_size - lpos; + + /* get available count based on current host offset */ + avail = sof_wait_trace_avail(sdev, lpos, buffer_size); + if (sdev->dtrace_error) { + dev_err(sdev->dev, "error: trace IO error\n"); + return -EIO; + } + + /* make sure count is <= avail */ + count = avail > count ? count : avail; + + /* copy available trace data to debugfs */ + rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count); + if (rem) + return -EFAULT; + + *ppos += count; + + /* move debugfs reading position */ + return count; +} + +static const struct file_operations sof_dfs_trace_fops = { + .open = simple_open, + .read = sof_dfsentry_trace_read, + .llseek = default_llseek, +}; + +static int trace_debugfs_create(struct snd_sof_dev *sdev) +{ + struct snd_sof_dfsentry *dfse; + + if (!sdev) + return -EINVAL; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->buf = sdev->dmatb.area; + dfse->size = sdev->dmatb.bytes; + dfse->sdev = sdev; + + dfse->dfsentry = debugfs_create_file("trace", 0444, sdev->debugfs_root, + dfse, &sof_dfs_trace_fops); + if (!dfse->dfsentry) { + /* can't rely on debugfs, only log error and keep going */ + dev_err(sdev->dev, + "error: cannot create debugfs entry for trace\n"); + } + + return 0; +} + +int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev) +{ + struct sof_ipc_dma_trace_params params; + struct sof_ipc_reply ipc_reply; + int ret; + + if (sdev->dtrace_is_enabled || !sdev->dma_trace_pages) + return -EINVAL; + + /* set IPC parameters */ + params.hdr.size = sizeof(params); + params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_PARAMS; + params.buffer.phy_addr = sdev->dmatp.addr; + params.buffer.size = sdev->dmatb.bytes; + params.buffer.pages = sdev->dma_trace_pages; + params.stream_tag = 0; + + sdev->host_offset = 0; + + ret = snd_sof_dma_trace_init(sdev, ¶ms.stream_tag); + if (ret < 0) { + dev_err(sdev->dev, + "error: fail in snd_sof_dma_trace_init %d\n", ret); + return ret; + } + dev_dbg(sdev->dev, "stream_tag: %d\n", params.stream_tag); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + params.hdr.cmd, ¶ms, sizeof(params), + &ipc_reply, sizeof(ipc_reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: can't set params for DMA for trace %d\n", ret); + goto trace_release; + } + + ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(sdev->dev, + "error: snd_sof_dma_trace_trigger: start: %d\n", ret); + goto trace_release; + } + + sdev->dtrace_is_enabled = true; + + return 0; + +trace_release: + snd_sof_dma_trace_release(sdev); + return ret; +} + +int snd_sof_init_trace(struct snd_sof_dev *sdev) +{ + int ret; + + /* set false before start initialization */ + sdev->dtrace_is_enabled = false; + + /* allocate trace page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, + PAGE_SIZE, &sdev->dmatp); + if (ret < 0) { + dev_err(sdev->dev, + "error: can't alloc page table for trace %d\n", ret); + return ret; + } + + /* allocate trace data buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + DMA_BUF_SIZE_FOR_TRACE, &sdev->dmatb); + if (ret < 0) { + dev_err(sdev->dev, + "error: can't alloc buffer for trace %d\n", ret); + goto page_err; + } + + /* create compressed page table for audio firmware */ + ret = snd_sof_create_page_table(sdev, &sdev->dmatb, sdev->dmatp.area, + sdev->dmatb.bytes); + if (ret < 0) + goto table_err; + + sdev->dma_trace_pages = ret; + dev_dbg(sdev->dev, "dma_trace_pages: %d\n", sdev->dma_trace_pages); + + if (sdev->first_boot) { + ret = trace_debugfs_create(sdev); + if (ret < 0) + goto table_err; + } + + init_waitqueue_head(&sdev->trace_sleep); + + ret = snd_sof_init_trace_ipc(sdev); + if (ret < 0) + goto table_err; + + return 0; +table_err: + sdev->dma_trace_pages = 0; + snd_dma_free_pages(&sdev->dmatb); +page_err: + snd_dma_free_pages(&sdev->dmatp); + return ret; +} +EXPORT_SYMBOL(snd_sof_init_trace); + +int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn) +{ + if (sdev->dtrace_is_enabled && sdev->host_offset != posn->host_offset) { + sdev->host_offset = posn->host_offset; + wake_up(&sdev->trace_sleep); + } + + if (posn->overflow != 0) + dev_err(sdev->dev, + "error: DSP trace buffer overflow %u bytes. Total messages %d\n", + posn->overflow, posn->messages); + + return 0; +} + +/* an error has occurred within the DSP that prevents further trace */ +void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev) +{ + if (sdev->dtrace_is_enabled) { + dev_err(sdev->dev, "error: waking up any trace sleepers\n"); + sdev->dtrace_error = true; + wake_up(&sdev->trace_sleep); + } +} +EXPORT_SYMBOL(snd_sof_trace_notify_for_error); + +void snd_sof_release_trace(struct snd_sof_dev *sdev) +{ + int ret; + + if (!sdev->dtrace_is_enabled) + return; + + ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); + if (ret < 0) + dev_err(sdev->dev, + "error: snd_sof_dma_trace_trigger: stop: %d\n", ret); + + ret = snd_sof_dma_trace_release(sdev); + if (ret < 0) + dev_err(sdev->dev, + "error: fail in snd_sof_dma_trace_release %d\n", ret); + + sdev->dtrace_is_enabled = false; +} +EXPORT_SYMBOL(snd_sof_release_trace); + +void snd_sof_free_trace(struct snd_sof_dev *sdev) +{ + snd_sof_release_trace(sdev); + + snd_dma_free_pages(&sdev->dmatb); + snd_dma_free_pages(&sdev->dmatp); +} +EXPORT_SYMBOL(snd_sof_free_trace); diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/utils.c new file mode 100644 index 000000000000..2ac4c3da0320 --- /dev/null +++ b/sound/soc/sof/utils.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Keyon Jie <yang.jie@linux.intel.com> +// + +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/platform_device.h> +#include <sound/soc.h> +#include <sound/sof.h> +#include "sof-priv.h" + +/* + * Register IO + * + * The sof_io_xyz() wrappers are typically referenced in snd_sof_dsp_ops + * structures and cannot be inlined. + */ + +void sof_io_write(struct snd_sof_dev *sdev, void __iomem *addr, u32 value) +{ + writel(value, addr); +} +EXPORT_SYMBOL(sof_io_write); + +u32 sof_io_read(struct snd_sof_dev *sdev, void __iomem *addr) +{ + return readl(addr); +} +EXPORT_SYMBOL(sof_io_read); + +void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value) +{ + writeq(value, addr); +} +EXPORT_SYMBOL(sof_io_write64); + +u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr) +{ + return readq(addr); +} +EXPORT_SYMBOL(sof_io_read64); + +/* + * IPC Mailbox IO + */ + +void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes) +{ + void __iomem *dest = sdev->bar[sdev->mailbox_bar] + offset; + + memcpy_toio(dest, message, bytes); +} +EXPORT_SYMBOL(sof_mailbox_write); + +void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset, + void *message, size_t bytes) +{ + void __iomem *src = sdev->bar[sdev->mailbox_bar] + offset; + + memcpy_fromio(message, src, bytes); +} +EXPORT_SYMBOL(sof_mailbox_read); + +/* + * Memory copy. + */ + +void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src, + size_t size) +{ + void __iomem *dest = sdev->bar[bar] + offset; + const u8 *src_byte = src; + u32 affected_mask; + u32 tmp; + int m, n; + + m = size / 4; + n = size % 4; + + /* __iowrite32_copy use 32bit size values so divide by 4 */ + __iowrite32_copy(dest, src, m); + + if (n) { + affected_mask = (1 << (8 * n)) - 1; + + /* first read the 32bit data of dest, then change affected + * bytes, and write back to dest. For unaffected bytes, it + * should not be changed + */ + tmp = ioread32(dest + m * 4); + tmp &= ~affected_mask; + + tmp |= *(u32 *)(src_byte + m * 4) & affected_mask; + iowrite32(tmp, dest + m * 4); + } +} +EXPORT_SYMBOL(sof_block_write); + +void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest, + size_t size) +{ + void __iomem *src = sdev->bar[bar] + offset; + + memcpy_fromio(dest, src, size); +} +EXPORT_SYMBOL(sof_block_read); diff --git a/sound/soc/sof/xtensa/Kconfig b/sound/soc/sof/xtensa/Kconfig new file mode 100644 index 000000000000..8a9343b85146 --- /dev/null +++ b/sound/soc/sof/xtensa/Kconfig @@ -0,0 +1,2 @@ +config SND_SOC_SOF_XTENSA + tristate diff --git a/sound/soc/sof/xtensa/Makefile b/sound/soc/sof/xtensa/Makefile new file mode 100644 index 000000000000..cc89c7472a38 --- /dev/null +++ b/sound/soc/sof/xtensa/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) + +snd-sof-xtensa-dsp-objs := core.o + +obj-$(CONFIG_SND_SOC_SOF_XTENSA) += snd-sof-xtensa-dsp.o diff --git a/sound/soc/sof/xtensa/core.c b/sound/soc/sof/xtensa/core.c new file mode 100644 index 000000000000..c3ad23a85b99 --- /dev/null +++ b/sound/soc/sof/xtensa/core.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. +// +// Author: Pan Xiuli <xiuli.pan@linux.intel.com> +// + +#include <linux/module.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../sof-priv.h" + +struct xtensa_exception_cause { + u32 id; + const char *msg; + const char *description; +}; + +/* + * From 4.4.1.5 table 4-64 Exception Causes of Xtensa + * Instruction Set Architecture (ISA) Reference Manual + */ +static const struct xtensa_exception_cause xtensa_exception_causes[] = { + {0, "IllegalInstructionCause", "Illegal instruction"}, + {1, "SyscallCause", "SYSCALL instruction"}, + {2, "InstructionFetchErrorCause", + "Processor internal physical address or data error during instruction fetch"}, + {3, "LoadStoreErrorCause", + "Processor internal physical address or data error during load or store"}, + {4, "Level1InterruptCause", + "Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register"}, + {5, "AllocaCause", + "MOVSP instruction, if caller’s registers are not in the register file"}, + {6, "IntegerDivideByZeroCause", + "QUOS, QUOU, REMS, or REMU divisor operand is zero"}, + {8, "PrivilegedCause", + "Attempt to execute a privileged operation when CRING ? 0"}, + {9, "LoadStoreAlignmentCause", "Load or store to an unaligned address"}, + {12, "InstrPIFDataErrorCause", + "PIF data error during instruction fetch"}, + {13, "LoadStorePIFDataErrorCause", + "Synchronous PIF data error during LoadStore access"}, + {14, "InstrPIFAddrErrorCause", + "PIF address error during instruction fetch"}, + {15, "LoadStorePIFAddrErrorCause", + "Synchronous PIF address error during LoadStore access"}, + {16, "InstTLBMissCause", "Error during Instruction TLB refill"}, + {17, "InstTLBMultiHitCause", + "Multiple instruction TLB entries matched"}, + {18, "InstFetchPrivilegeCause", + "An instruction fetch referenced a virtual address at a ring level less than CRING"}, + {20, "InstFetchProhibitedCause", + "An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch"}, + {24, "LoadStoreTLBMissCause", + "Error during TLB refill for a load or store"}, + {25, "LoadStoreTLBMultiHitCause", + "Multiple TLB entries matched for a load or store"}, + {26, "LoadStorePrivilegeCause", + "A load or store referenced a virtual address at a ring level less than CRING"}, + {28, "LoadProhibitedCause", + "A load referenced a page mapped with an attribute that does not permit loads"}, + {32, "Coprocessor0Disabled", + "Coprocessor 0 instruction when cp0 disabled"}, + {33, "Coprocessor1Disabled", + "Coprocessor 1 instruction when cp1 disabled"}, + {34, "Coprocessor2Disabled", + "Coprocessor 2 instruction when cp2 disabled"}, + {35, "Coprocessor3Disabled", + "Coprocessor 3 instruction when cp3 disabled"}, + {36, "Coprocessor4Disabled", + "Coprocessor 4 instruction when cp4 disabled"}, + {37, "Coprocessor5Disabled", + "Coprocessor 5 instruction when cp5 disabled"}, + {38, "Coprocessor6Disabled", + "Coprocessor 6 instruction when cp6 disabled"}, + {39, "Coprocessor7Disabled", + "Coprocessor 7 instruction when cp7 disabled"}, +}; + +/* only need xtensa atm */ +static void xtensa_dsp_oops(struct snd_sof_dev *sdev, void *oops) +{ + struct sof_ipc_dsp_oops_xtensa *xoops = oops; + int i; + + dev_err(sdev->dev, "error: DSP Firmware Oops\n"); + for (i = 0; i < ARRAY_SIZE(xtensa_exception_causes); i++) { + if (xtensa_exception_causes[i].id == xoops->exccause) { + dev_err(sdev->dev, "error: Exception Cause: %s, %s\n", + xtensa_exception_causes[i].msg, + xtensa_exception_causes[i].description); + } + } + dev_err(sdev->dev, "EXCCAUSE 0x%8.8x EXCVADDR 0x%8.8x PS 0x%8.8x SAR 0x%8.8x\n", + xoops->exccause, xoops->excvaddr, xoops->ps, xoops->sar); + dev_err(sdev->dev, "EPC1 0x%8.8x EPC2 0x%8.8x EPC3 0x%8.8x EPC4 0x%8.8x", + xoops->epc1, xoops->epc2, xoops->epc3, xoops->epc4); + dev_err(sdev->dev, "EPC5 0x%8.8x EPC6 0x%8.8x EPC7 0x%8.8x DEPC 0x%8.8x", + xoops->epc5, xoops->epc6, xoops->epc7, xoops->depc); + dev_err(sdev->dev, "EPS2 0x%8.8x EPS3 0x%8.8x EPS4 0x%8.8x EPS5 0x%8.8x", + xoops->eps2, xoops->eps3, xoops->eps4, xoops->eps5); + dev_err(sdev->dev, "EPS6 0x%8.8x EPS7 0x%8.8x INTENABL 0x%8.8x INTERRU 0x%8.8x", + xoops->eps6, xoops->eps7, xoops->intenable, xoops->interrupt); +} + +static void xtensa_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, + u32 stack_words) +{ + struct sof_ipc_dsp_oops_xtensa *xoops = oops; + u32 stack_ptr = xoops->stack; + /* 4 * 8chars + 3 ws + 1 terminating NUL */ + unsigned char buf[4 * 8 + 3 + 1]; + int i; + + dev_err(sdev->dev, "stack dump from 0x%8.8x\n", stack_ptr); + + /* + * example output: + * 0x0049fbb0: 8000f2d0 0049fc00 6f6c6c61 00632e63 + */ + for (i = 0; i < stack_words; i += 4) { + hex_dump_to_buffer(stack + i * 4, 16, 16, 4, + buf, sizeof(buf), false); + dev_err(sdev->dev, "0x%08x: %s\n", stack_ptr + i, buf); + } +} + +const struct sof_arch_ops sof_xtensa_arch_ops = { + .dsp_oops = xtensa_dsp_oops, + .dsp_stack = xtensa_stack, +}; +EXPORT_SYMBOL(sof_xtensa_arch_ops); + +MODULE_DESCRIPTION("SOF Xtensa DSP support"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sprd/Kconfig b/sound/soc/sprd/Kconfig index 43ece7daf0e9..21f9cc34f8bd 100644 --- a/sound/soc/sprd/Kconfig +++ b/sound/soc/sprd/Kconfig @@ -1,6 +1,15 @@ config SND_SOC_SPRD tristate "SoC Audio for the Spreadtrum SoC chips" depends on ARCH_SPRD || COMPILE_TEST + select SND_SOC_COMPRESS help Say Y or M if you want to add support for codecs attached to the Spreadtrum SoCs' Audio interfaces. + +config SND_SOC_SPRD_MCDT + bool "Spreadtrum multi-channel data transfer support" + depends on SND_SOC_SPRD + help + Say y here to enable multi-channel data transfer support. It + is used for sound stream transmission between audio subsystem + and other AP/CP subsystem. diff --git a/sound/soc/sprd/Makefile b/sound/soc/sprd/Makefile index 47620e57a9f2..a95fa56cd000 100644 --- a/sound/soc/sprd/Makefile +++ b/sound/soc/sprd/Makefile @@ -1,4 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 # Spreadtrum Audio Support -obj-$(CONFIG_SND_SOC_SPRD) += sprd-pcm-dma.o +snd-soc-sprd-platform-objs := sprd-pcm-dma.o sprd-pcm-compress.o + +obj-$(CONFIG_SND_SOC_SPRD) += snd-soc-sprd-platform.o + +obj-$(CONFIG_SND_SOC_SPRD_MCDT) += sprd-mcdt.o diff --git a/sound/soc/sprd/sprd-mcdt.c b/sound/soc/sprd/sprd-mcdt.c new file mode 100644 index 000000000000..7448015a4935 --- /dev/null +++ b/sound/soc/sprd/sprd-mcdt.c @@ -0,0 +1,1011 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Spreadtrum Communications Inc. + +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#include "sprd-mcdt.h" + +/* MCDT registers definition */ +#define MCDT_CH0_TXD 0x0 +#define MCDT_CH0_RXD 0x28 +#define MCDT_DAC0_WTMK 0x60 +#define MCDT_ADC0_WTMK 0x88 +#define MCDT_DMA_EN 0xb0 + +#define MCDT_INT_EN0 0xb4 +#define MCDT_INT_EN1 0xb8 +#define MCDT_INT_EN2 0xbc + +#define MCDT_INT_CLR0 0xc0 +#define MCDT_INT_CLR1 0xc4 +#define MCDT_INT_CLR2 0xc8 + +#define MCDT_INT_RAW1 0xcc +#define MCDT_INT_RAW2 0xd0 +#define MCDT_INT_RAW3 0xd4 + +#define MCDT_INT_MSK1 0xd8 +#define MCDT_INT_MSK2 0xdc +#define MCDT_INT_MSK3 0xe0 + +#define MCDT_DAC0_FIFO_ADDR_ST 0xe4 +#define MCDT_ADC0_FIFO_ADDR_ST 0xe8 + +#define MCDT_CH_FIFO_ST0 0x134 +#define MCDT_CH_FIFO_ST1 0x138 +#define MCDT_CH_FIFO_ST2 0x13c + +#define MCDT_INT_MSK_CFG0 0x140 +#define MCDT_INT_MSK_CFG1 0x144 + +#define MCDT_DMA_CFG0 0x148 +#define MCDT_FIFO_CLR 0x14c +#define MCDT_DMA_CFG1 0x150 +#define MCDT_DMA_CFG2 0x154 +#define MCDT_DMA_CFG3 0x158 +#define MCDT_DMA_CFG4 0x15c +#define MCDT_DMA_CFG5 0x160 + +/* Channel water mark definition */ +#define MCDT_CH_FIFO_AE_SHIFT 16 +#define MCDT_CH_FIFO_AE_MASK GENMASK(24, 16) +#define MCDT_CH_FIFO_AF_MASK GENMASK(8, 0) + +/* DMA channel select definition */ +#define MCDT_DMA_CH0_SEL_MASK GENMASK(3, 0) +#define MCDT_DMA_CH0_SEL_SHIFT 0 +#define MCDT_DMA_CH1_SEL_MASK GENMASK(7, 4) +#define MCDT_DMA_CH1_SEL_SHIFT 4 +#define MCDT_DMA_CH2_SEL_MASK GENMASK(11, 8) +#define MCDT_DMA_CH2_SEL_SHIFT 8 +#define MCDT_DMA_CH3_SEL_MASK GENMASK(15, 12) +#define MCDT_DMA_CH3_SEL_SHIFT 12 +#define MCDT_DMA_CH4_SEL_MASK GENMASK(19, 16) +#define MCDT_DMA_CH4_SEL_SHIFT 16 +#define MCDT_DAC_DMA_SHIFT 16 + +/* DMA channel ACK select definition */ +#define MCDT_DMA_ACK_SEL_MASK GENMASK(3, 0) + +/* Channel FIFO definition */ +#define MCDT_CH_FIFO_ADDR_SHIFT 16 +#define MCDT_CH_FIFO_ADDR_MASK GENMASK(9, 0) +#define MCDT_ADC_FIFO_SHIFT 16 +#define MCDT_FIFO_LENGTH 512 + +#define MCDT_ADC_CHANNEL_NUM 10 +#define MCDT_DAC_CHANNEL_NUM 10 +#define MCDT_CHANNEL_NUM (MCDT_ADC_CHANNEL_NUM + MCDT_DAC_CHANNEL_NUM) + +enum sprd_mcdt_fifo_int { + MCDT_ADC_FIFO_AE_INT, + MCDT_ADC_FIFO_AF_INT, + MCDT_DAC_FIFO_AE_INT, + MCDT_DAC_FIFO_AF_INT, + MCDT_ADC_FIFO_OV_INT, + MCDT_DAC_FIFO_OV_INT +}; + +enum sprd_mcdt_fifo_sts { + MCDT_ADC_FIFO_REAL_FULL, + MCDT_ADC_FIFO_REAL_EMPTY, + MCDT_ADC_FIFO_AF, + MCDT_ADC_FIFO_AE, + MCDT_DAC_FIFO_REAL_FULL, + MCDT_DAC_FIFO_REAL_EMPTY, + MCDT_DAC_FIFO_AF, + MCDT_DAC_FIFO_AE +}; + +struct sprd_mcdt_dev { + struct device *dev; + void __iomem *base; + spinlock_t lock; + struct sprd_mcdt_chan chan[MCDT_CHANNEL_NUM]; +}; + +static LIST_HEAD(sprd_mcdt_chan_list); +static DEFINE_MUTEX(sprd_mcdt_list_mutex); + +static void sprd_mcdt_update(struct sprd_mcdt_dev *mcdt, u32 reg, u32 val, + u32 mask) +{ + u32 orig = readl_relaxed(mcdt->base + reg); + u32 tmp; + + tmp = (orig & ~mask) | val; + writel_relaxed(tmp, mcdt->base + reg); +} + +static void sprd_mcdt_dac_set_watermark(struct sprd_mcdt_dev *mcdt, u8 channel, + u32 full, u32 empty) +{ + u32 reg = MCDT_DAC0_WTMK + channel * 4; + u32 water_mark = + (empty << MCDT_CH_FIFO_AE_SHIFT) & MCDT_CH_FIFO_AE_MASK; + + water_mark |= full & MCDT_CH_FIFO_AF_MASK; + sprd_mcdt_update(mcdt, reg, water_mark, + MCDT_CH_FIFO_AE_MASK | MCDT_CH_FIFO_AF_MASK); +} + +static void sprd_mcdt_adc_set_watermark(struct sprd_mcdt_dev *mcdt, u8 channel, + u32 full, u32 empty) +{ + u32 reg = MCDT_ADC0_WTMK + channel * 4; + u32 water_mark = + (empty << MCDT_CH_FIFO_AE_SHIFT) & MCDT_CH_FIFO_AE_MASK; + + water_mark |= full & MCDT_CH_FIFO_AF_MASK; + sprd_mcdt_update(mcdt, reg, water_mark, + MCDT_CH_FIFO_AE_MASK | MCDT_CH_FIFO_AF_MASK); +} + +static void sprd_mcdt_dac_dma_enable(struct sprd_mcdt_dev *mcdt, u8 channel, + bool enable) +{ + u32 shift = MCDT_DAC_DMA_SHIFT + channel; + + if (enable) + sprd_mcdt_update(mcdt, MCDT_DMA_EN, BIT(shift), BIT(shift)); + else + sprd_mcdt_update(mcdt, MCDT_DMA_EN, 0, BIT(shift)); +} + +static void sprd_mcdt_adc_dma_enable(struct sprd_mcdt_dev *mcdt, u8 channel, + bool enable) +{ + if (enable) + sprd_mcdt_update(mcdt, MCDT_DMA_EN, BIT(channel), BIT(channel)); + else + sprd_mcdt_update(mcdt, MCDT_DMA_EN, 0, BIT(channel)); +} + +static void sprd_mcdt_ap_int_enable(struct sprd_mcdt_dev *mcdt, u8 channel, + bool enable) +{ + if (enable) + sprd_mcdt_update(mcdt, MCDT_INT_MSK_CFG0, BIT(channel), + BIT(channel)); + else + sprd_mcdt_update(mcdt, MCDT_INT_MSK_CFG0, 0, BIT(channel)); +} + +static void sprd_mcdt_dac_write_fifo(struct sprd_mcdt_dev *mcdt, u8 channel, + u32 val) +{ + u32 reg = MCDT_CH0_TXD + channel * 4; + + writel_relaxed(val, mcdt->base + reg); +} + +static void sprd_mcdt_adc_read_fifo(struct sprd_mcdt_dev *mcdt, u8 channel, + u32 *val) +{ + u32 reg = MCDT_CH0_RXD + channel * 4; + + *val = readl_relaxed(mcdt->base + reg); +} + +static void sprd_mcdt_dac_dma_chn_select(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_dma_chan dma_chan) +{ + switch (dma_chan) { + case SPRD_MCDT_DMA_CH0: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH0_SEL_SHIFT, + MCDT_DMA_CH0_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH1: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH1_SEL_SHIFT, + MCDT_DMA_CH1_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH2: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH2_SEL_SHIFT, + MCDT_DMA_CH2_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH3: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH3_SEL_SHIFT, + MCDT_DMA_CH3_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH4: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH4_SEL_SHIFT, + MCDT_DMA_CH4_SEL_MASK); + break; + } +} + +static void sprd_mcdt_adc_dma_chn_select(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_dma_chan dma_chan) +{ + switch (dma_chan) { + case SPRD_MCDT_DMA_CH0: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH0_SEL_SHIFT, + MCDT_DMA_CH0_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH1: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH1_SEL_SHIFT, + MCDT_DMA_CH1_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH2: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH2_SEL_SHIFT, + MCDT_DMA_CH2_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH3: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH3_SEL_SHIFT, + MCDT_DMA_CH3_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH4: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH4_SEL_SHIFT, + MCDT_DMA_CH4_SEL_MASK); + break; + } +} + +static u32 sprd_mcdt_dma_ack_shift(u8 channel) +{ + switch (channel) { + default: + case 0: + case 8: + return 0; + case 1: + case 9: + return 4; + case 2: + return 8; + case 3: + return 12; + case 4: + return 16; + case 5: + return 20; + case 6: + return 24; + case 7: + return 28; + } +} + +static void sprd_mcdt_dac_dma_ack_select(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_dma_chan dma_chan) +{ + u32 reg, shift = sprd_mcdt_dma_ack_shift(channel), ack = dma_chan; + + switch (channel) { + case 0 ... 7: + reg = MCDT_DMA_CFG2; + break; + + case 8 ... 9: + reg = MCDT_DMA_CFG3; + break; + + default: + return; + } + + sprd_mcdt_update(mcdt, reg, ack << shift, + MCDT_DMA_ACK_SEL_MASK << shift); +} + +static void sprd_mcdt_adc_dma_ack_select(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_dma_chan dma_chan) +{ + u32 reg, shift = sprd_mcdt_dma_ack_shift(channel), ack = dma_chan; + + switch (channel) { + case 0 ... 7: + reg = MCDT_DMA_CFG4; + break; + + case 8 ... 9: + reg = MCDT_DMA_CFG5; + break; + + default: + return; + } + + sprd_mcdt_update(mcdt, reg, ack << shift, + MCDT_DMA_ACK_SEL_MASK << shift); +} + +static bool sprd_mcdt_chan_fifo_sts(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_fifo_sts fifo_sts) +{ + u32 reg, shift; + + switch (channel) { + case 0 ... 3: + reg = MCDT_CH_FIFO_ST0; + break; + case 4 ... 7: + reg = MCDT_CH_FIFO_ST1; + break; + case 8 ... 9: + reg = MCDT_CH_FIFO_ST2; + break; + default: + return false; + } + + switch (channel) { + case 0: + case 4: + case 8: + shift = fifo_sts; + break; + + case 1: + case 5: + case 9: + shift = 8 + fifo_sts; + break; + + case 2: + case 6: + shift = 16 + fifo_sts; + break; + + case 3: + case 7: + shift = 24 + fifo_sts; + break; + + default: + return false; + } + + return !!(readl_relaxed(mcdt->base + reg) & BIT(shift)); +} + +static void sprd_mcdt_dac_fifo_clear(struct sprd_mcdt_dev *mcdt, u8 channel) +{ + sprd_mcdt_update(mcdt, MCDT_FIFO_CLR, BIT(channel), BIT(channel)); +} + +static void sprd_mcdt_adc_fifo_clear(struct sprd_mcdt_dev *mcdt, u8 channel) +{ + u32 shift = MCDT_ADC_FIFO_SHIFT + channel; + + sprd_mcdt_update(mcdt, MCDT_FIFO_CLR, BIT(shift), BIT(shift)); +} + +static u32 sprd_mcdt_dac_fifo_avail(struct sprd_mcdt_dev *mcdt, u8 channel) +{ + u32 reg = MCDT_DAC0_FIFO_ADDR_ST + channel * 8; + u32 r_addr = (readl_relaxed(mcdt->base + reg) >> + MCDT_CH_FIFO_ADDR_SHIFT) & MCDT_CH_FIFO_ADDR_MASK; + u32 w_addr = readl_relaxed(mcdt->base + reg) & MCDT_CH_FIFO_ADDR_MASK; + + if (w_addr >= r_addr) + return 4 * (MCDT_FIFO_LENGTH - w_addr + r_addr); + else + return 4 * (r_addr - w_addr); +} + +static u32 sprd_mcdt_adc_fifo_avail(struct sprd_mcdt_dev *mcdt, u8 channel) +{ + u32 reg = MCDT_ADC0_FIFO_ADDR_ST + channel * 8; + u32 r_addr = (readl_relaxed(mcdt->base + reg) >> + MCDT_CH_FIFO_ADDR_SHIFT) & MCDT_CH_FIFO_ADDR_MASK; + u32 w_addr = readl_relaxed(mcdt->base + reg) & MCDT_CH_FIFO_ADDR_MASK; + + if (w_addr >= r_addr) + return 4 * (w_addr - r_addr); + else + return 4 * (MCDT_FIFO_LENGTH - r_addr + w_addr); +} + +static u32 sprd_mcdt_int_type_shift(u8 channel, + enum sprd_mcdt_fifo_int int_type) +{ + switch (channel) { + case 0: + case 4: + case 8: + return int_type; + + case 1: + case 5: + case 9: + return 8 + int_type; + + case 2: + case 6: + return 16 + int_type; + + case 3: + case 7: + return 24 + int_type; + + default: + return 0; + } +} + +static void sprd_mcdt_chan_int_en(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_fifo_int int_type, bool enable) +{ + u32 reg, shift = sprd_mcdt_int_type_shift(channel, int_type); + + switch (channel) { + case 0 ... 3: + reg = MCDT_INT_EN0; + break; + case 4 ... 7: + reg = MCDT_INT_EN1; + break; + case 8 ... 9: + reg = MCDT_INT_EN2; + break; + default: + return; + } + + if (enable) + sprd_mcdt_update(mcdt, reg, BIT(shift), BIT(shift)); + else + sprd_mcdt_update(mcdt, reg, 0, BIT(shift)); +} + +static void sprd_mcdt_chan_int_clear(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_fifo_int int_type) +{ + u32 reg, shift = sprd_mcdt_int_type_shift(channel, int_type); + + switch (channel) { + case 0 ... 3: + reg = MCDT_INT_CLR0; + break; + case 4 ... 7: + reg = MCDT_INT_CLR1; + break; + case 8 ... 9: + reg = MCDT_INT_CLR2; + break; + default: + return; + } + + sprd_mcdt_update(mcdt, reg, BIT(shift), BIT(shift)); +} + +static bool sprd_mcdt_chan_int_sts(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_fifo_int int_type) +{ + u32 reg, shift = sprd_mcdt_int_type_shift(channel, int_type); + + switch (channel) { + case 0 ... 3: + reg = MCDT_INT_MSK1; + break; + case 4 ... 7: + reg = MCDT_INT_MSK2; + break; + case 8 ... 9: + reg = MCDT_INT_MSK3; + break; + default: + return false; + } + + return !!(readl_relaxed(mcdt->base + reg) & BIT(shift)); +} + +static irqreturn_t sprd_mcdt_irq_handler(int irq, void *dev_id) +{ + struct sprd_mcdt_dev *mcdt = (struct sprd_mcdt_dev *)dev_id; + int i; + + spin_lock(&mcdt->lock); + + for (i = 0; i < MCDT_ADC_CHANNEL_NUM; i++) { + if (sprd_mcdt_chan_int_sts(mcdt, i, MCDT_ADC_FIFO_AF_INT)) { + struct sprd_mcdt_chan *chan = &mcdt->chan[i]; + + sprd_mcdt_chan_int_clear(mcdt, i, MCDT_ADC_FIFO_AF_INT); + if (chan->cb) + chan->cb->notify(chan->cb->data); + } + } + + for (i = 0; i < MCDT_DAC_CHANNEL_NUM; i++) { + if (sprd_mcdt_chan_int_sts(mcdt, i, MCDT_DAC_FIFO_AE_INT)) { + struct sprd_mcdt_chan *chan = + &mcdt->chan[i + MCDT_ADC_CHANNEL_NUM]; + + sprd_mcdt_chan_int_clear(mcdt, i, MCDT_DAC_FIFO_AE_INT); + if (chan->cb) + chan->cb->notify(chan->cb->data); + } + } + + spin_unlock(&mcdt->lock); + + return IRQ_HANDLED; +} + +/** + * sprd_mcdt_chan_write - write data to the MCDT channel's fifo + * @chan: the MCDT channel + * @tx_buf: send buffer + * @size: data size + * + * Note: We can not write data to the channel fifo when enabling the DMA mode, + * otherwise the channel fifo data will be invalid. + * + * If there are not enough space of the channel fifo, it will return errors + * to users. + * + * Returns 0 on success, or an appropriate error code on failure. + */ +int sprd_mcdt_chan_write(struct sprd_mcdt_chan *chan, char *tx_buf, u32 size) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + int avail, i = 0, words = size / 4; + u32 *buf = (u32 *)tx_buf; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (chan->dma_enable) { + dev_err(mcdt->dev, + "Can not write data when DMA mode enabled\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EINVAL; + } + + if (sprd_mcdt_chan_fifo_sts(mcdt, chan->id, MCDT_DAC_FIFO_REAL_FULL)) { + dev_err(mcdt->dev, "Channel fifo is full now\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EBUSY; + } + + avail = sprd_mcdt_dac_fifo_avail(mcdt, chan->id); + if (size > avail) { + dev_err(mcdt->dev, + "Data size is larger than the available fifo size\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EBUSY; + } + + while (i++ < words) + sprd_mcdt_dac_write_fifo(mcdt, chan->id, *buf++); + + spin_unlock_irqrestore(&mcdt->lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_write); + +/** + * sprd_mcdt_chan_read - read data from the MCDT channel's fifo + * @chan: the MCDT channel + * @rx_buf: receive buffer + * @size: data size + * + * Note: We can not read data from the channel fifo when enabling the DMA mode, + * otherwise the reading data will be invalid. + * + * Usually user need start to read data once receiving the fifo full interrupt. + * + * Returns data size of reading successfully, or an error code on failure. + */ +int sprd_mcdt_chan_read(struct sprd_mcdt_chan *chan, char *rx_buf, u32 size) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + int i = 0, avail, words = size / 4; + u32 *buf = (u32 *)rx_buf; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (chan->dma_enable) { + dev_err(mcdt->dev, "Can not read data when DMA mode enabled\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EINVAL; + } + + if (sprd_mcdt_chan_fifo_sts(mcdt, chan->id, MCDT_ADC_FIFO_REAL_EMPTY)) { + dev_err(mcdt->dev, "Channel fifo is empty\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EBUSY; + } + + avail = sprd_mcdt_adc_fifo_avail(mcdt, chan->id); + if (size > avail) + words = avail / 4; + + while (i++ < words) + sprd_mcdt_adc_read_fifo(mcdt, chan->id, buf++); + + spin_unlock_irqrestore(&mcdt->lock, flags); + return words * 4; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_read); + +/** + * sprd_mcdt_chan_int_enable - enable the interrupt mode for the MCDT channel + * @chan: the MCDT channel + * @water_mark: water mark to trigger a interrupt + * @cb: callback when a interrupt happened + * + * Now it only can enable fifo almost full interrupt for ADC channel and fifo + * almost empty interrupt for DAC channel. Morevoer for interrupt mode, user + * should use sprd_mcdt_chan_read() or sprd_mcdt_chan_write() to read or write + * data manually. + * + * For ADC channel, user can start to read data once receiving one fifo full + * interrupt. For DAC channel, user can start to write data once receiving one + * fifo empty interrupt or just call sprd_mcdt_chan_write() to write data + * directly. + * + * Returns 0 on success, or an error code on failure. + */ +int sprd_mcdt_chan_int_enable(struct sprd_mcdt_chan *chan, u32 water_mark, + struct sprd_mcdt_chan_callback *cb) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (chan->dma_enable || chan->int_enable) { + dev_err(mcdt->dev, "Failed to set interrupt mode.\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EINVAL; + } + + switch (chan->type) { + case SPRD_MCDT_ADC_CHAN: + sprd_mcdt_adc_fifo_clear(mcdt, chan->id); + sprd_mcdt_adc_set_watermark(mcdt, chan->id, water_mark, + MCDT_FIFO_LENGTH - 1); + sprd_mcdt_chan_int_en(mcdt, chan->id, + MCDT_ADC_FIFO_AF_INT, true); + sprd_mcdt_ap_int_enable(mcdt, chan->id, true); + break; + + case SPRD_MCDT_DAC_CHAN: + sprd_mcdt_dac_fifo_clear(mcdt, chan->id); + sprd_mcdt_dac_set_watermark(mcdt, chan->id, + MCDT_FIFO_LENGTH - 1, water_mark); + sprd_mcdt_chan_int_en(mcdt, chan->id, + MCDT_DAC_FIFO_AE_INT, true); + sprd_mcdt_ap_int_enable(mcdt, chan->id, true); + break; + + default: + dev_err(mcdt->dev, "Unsupported channel type\n"); + ret = -EINVAL; + } + + if (!ret) { + chan->cb = cb; + chan->int_enable = true; + } + + spin_unlock_irqrestore(&mcdt->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_int_enable); + +/** + * sprd_mcdt_chan_int_disable - disable the interrupt mode for the MCDT channel + * @chan: the MCDT channel + */ +void sprd_mcdt_chan_int_disable(struct sprd_mcdt_chan *chan) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (!chan->int_enable) { + spin_unlock_irqrestore(&mcdt->lock, flags); + return; + } + + switch (chan->type) { + case SPRD_MCDT_ADC_CHAN: + sprd_mcdt_chan_int_en(mcdt, chan->id, + MCDT_ADC_FIFO_AF_INT, false); + sprd_mcdt_chan_int_clear(mcdt, chan->id, MCDT_ADC_FIFO_AF_INT); + sprd_mcdt_ap_int_enable(mcdt, chan->id, false); + break; + + case SPRD_MCDT_DAC_CHAN: + sprd_mcdt_chan_int_en(mcdt, chan->id, + MCDT_DAC_FIFO_AE_INT, false); + sprd_mcdt_chan_int_clear(mcdt, chan->id, MCDT_DAC_FIFO_AE_INT); + sprd_mcdt_ap_int_enable(mcdt, chan->id, false); + break; + + default: + break; + } + + chan->int_enable = false; + spin_unlock_irqrestore(&mcdt->lock, flags); +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_int_disable); + +/** + * sprd_mcdt_chan_dma_enable - enable the DMA mode for the MCDT channel + * @chan: the MCDT channel + * @dma_chan: specify which DMA channel will be used for this MCDT channel + * @water_mark: water mark to trigger a DMA request + * + * Enable the DMA mode for the MCDT channel, that means we can use DMA to + * transfer data to the channel fifo and do not need reading/writing data + * manually. + * + * Returns 0 on success, or an error code on failure. + */ +int sprd_mcdt_chan_dma_enable(struct sprd_mcdt_chan *chan, + enum sprd_mcdt_dma_chan dma_chan, + u32 water_mark) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (chan->dma_enable || chan->int_enable || + dma_chan > SPRD_MCDT_DMA_CH4) { + dev_err(mcdt->dev, "Failed to set DMA mode\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EINVAL; + } + + switch (chan->type) { + case SPRD_MCDT_ADC_CHAN: + sprd_mcdt_adc_fifo_clear(mcdt, chan->id); + sprd_mcdt_adc_set_watermark(mcdt, chan->id, + water_mark, MCDT_FIFO_LENGTH - 1); + sprd_mcdt_adc_dma_enable(mcdt, chan->id, true); + sprd_mcdt_adc_dma_chn_select(mcdt, chan->id, dma_chan); + sprd_mcdt_adc_dma_ack_select(mcdt, chan->id, dma_chan); + break; + + case SPRD_MCDT_DAC_CHAN: + sprd_mcdt_dac_fifo_clear(mcdt, chan->id); + sprd_mcdt_dac_set_watermark(mcdt, chan->id, + MCDT_FIFO_LENGTH - 1, water_mark); + sprd_mcdt_dac_dma_enable(mcdt, chan->id, true); + sprd_mcdt_dac_dma_chn_select(mcdt, chan->id, dma_chan); + sprd_mcdt_dac_dma_ack_select(mcdt, chan->id, dma_chan); + break; + + default: + dev_err(mcdt->dev, "Unsupported channel type\n"); + ret = -EINVAL; + } + + if (!ret) + chan->dma_enable = true; + + spin_unlock_irqrestore(&mcdt->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_dma_enable); + +/** + * sprd_mcdt_chan_dma_disable - disable the DMA mode for the MCDT channel + * @chan: the MCDT channel + */ +void sprd_mcdt_chan_dma_disable(struct sprd_mcdt_chan *chan) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (!chan->dma_enable) { + spin_unlock_irqrestore(&mcdt->lock, flags); + return; + } + + switch (chan->type) { + case SPRD_MCDT_ADC_CHAN: + sprd_mcdt_adc_dma_enable(mcdt, chan->id, false); + sprd_mcdt_adc_fifo_clear(mcdt, chan->id); + break; + + case SPRD_MCDT_DAC_CHAN: + sprd_mcdt_dac_dma_enable(mcdt, chan->id, false); + sprd_mcdt_dac_fifo_clear(mcdt, chan->id); + break; + + default: + break; + } + + chan->dma_enable = false; + spin_unlock_irqrestore(&mcdt->lock, flags); +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_dma_disable); + +/** + * sprd_mcdt_request_chan - request one MCDT channel + * @channel: channel id + * @type: channel type, it can be one ADC channel or DAC channel + * + * Rreturn NULL if no available channel. + */ +struct sprd_mcdt_chan *sprd_mcdt_request_chan(u8 channel, + enum sprd_mcdt_channel_type type) +{ + struct sprd_mcdt_chan *temp, *chan = NULL; + + mutex_lock(&sprd_mcdt_list_mutex); + + list_for_each_entry(temp, &sprd_mcdt_chan_list, list) { + if (temp->type == type && temp->id == channel) { + chan = temp; + break; + } + } + + if (chan) + list_del(&chan->list); + + mutex_unlock(&sprd_mcdt_list_mutex); + + return chan; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_request_chan); + +/** + * sprd_mcdt_free_chan - free one MCDT channel + * @chan: the channel to be freed + */ +void sprd_mcdt_free_chan(struct sprd_mcdt_chan *chan) +{ + struct sprd_mcdt_chan *temp; + + sprd_mcdt_chan_dma_disable(chan); + sprd_mcdt_chan_int_disable(chan); + + mutex_lock(&sprd_mcdt_list_mutex); + + list_for_each_entry(temp, &sprd_mcdt_chan_list, list) { + if (temp == chan) { + mutex_unlock(&sprd_mcdt_list_mutex); + return; + } + } + + list_add_tail(&chan->list, &sprd_mcdt_chan_list); + mutex_unlock(&sprd_mcdt_list_mutex); +} +EXPORT_SYMBOL_GPL(sprd_mcdt_free_chan); + +static void sprd_mcdt_init_chans(struct sprd_mcdt_dev *mcdt, + struct resource *res) +{ + int i; + + for (i = 0; i < MCDT_CHANNEL_NUM; i++) { + struct sprd_mcdt_chan *chan = &mcdt->chan[i]; + + if (i < MCDT_ADC_CHANNEL_NUM) { + chan->id = i; + chan->type = SPRD_MCDT_ADC_CHAN; + chan->fifo_phys = res->start + MCDT_CH0_RXD + i * 4; + } else { + chan->id = i - MCDT_ADC_CHANNEL_NUM; + chan->type = SPRD_MCDT_DAC_CHAN; + chan->fifo_phys = res->start + MCDT_CH0_TXD + + (i - MCDT_ADC_CHANNEL_NUM) * 4; + } + + chan->mcdt = mcdt; + INIT_LIST_HEAD(&chan->list); + + mutex_lock(&sprd_mcdt_list_mutex); + list_add_tail(&chan->list, &sprd_mcdt_chan_list); + mutex_unlock(&sprd_mcdt_list_mutex); + } +} + +static int sprd_mcdt_probe(struct platform_device *pdev) +{ + struct sprd_mcdt_dev *mcdt; + struct resource *res; + int ret, irq; + + mcdt = devm_kzalloc(&pdev->dev, sizeof(*mcdt), GFP_KERNEL); + if (!mcdt) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mcdt->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mcdt->base)) + return PTR_ERR(mcdt->base); + + mcdt->dev = &pdev->dev; + spin_lock_init(&mcdt->lock); + platform_set_drvdata(pdev, mcdt); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get MCDT interrupt\n"); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, sprd_mcdt_irq_handler, + 0, "sprd-mcdt", mcdt); + if (ret) { + dev_err(&pdev->dev, "Failed to request MCDT IRQ\n"); + return ret; + } + + sprd_mcdt_init_chans(mcdt, res); + + return 0; +} + +static int sprd_mcdt_remove(struct platform_device *pdev) +{ + struct sprd_mcdt_chan *chan, *temp; + + mutex_lock(&sprd_mcdt_list_mutex); + + list_for_each_entry_safe(chan, temp, &sprd_mcdt_chan_list, list) + list_del(&chan->list); + + mutex_unlock(&sprd_mcdt_list_mutex); + + return 0; +} + +static const struct of_device_id sprd_mcdt_of_match[] = { + { .compatible = "sprd,sc9860-mcdt", }, + { } +}; +MODULE_DEVICE_TABLE(of, sprd_mcdt_of_match); + +static struct platform_driver sprd_mcdt_driver = { + .probe = sprd_mcdt_probe, + .remove = sprd_mcdt_remove, + .driver = { + .name = "sprd-mcdt", + .of_match_table = sprd_mcdt_of_match, + }, +}; + +module_platform_driver(sprd_mcdt_driver); + +MODULE_DESCRIPTION("Spreadtrum Multi-Channel Data Transfer Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sprd/sprd-mcdt.h b/sound/soc/sprd/sprd-mcdt.h new file mode 100644 index 000000000000..9cc7e207ac76 --- /dev/null +++ b/sound/soc/sprd/sprd-mcdt.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __SPRD_MCDT_H +#define __SPRD_MCDT_H + +enum sprd_mcdt_channel_type { + SPRD_MCDT_DAC_CHAN, + SPRD_MCDT_ADC_CHAN, + SPRD_MCDT_UNKNOWN_CHAN, +}; + +enum sprd_mcdt_dma_chan { + SPRD_MCDT_DMA_CH0, + SPRD_MCDT_DMA_CH1, + SPRD_MCDT_DMA_CH2, + SPRD_MCDT_DMA_CH3, + SPRD_MCDT_DMA_CH4, +}; + +struct sprd_mcdt_chan_callback { + void (*notify)(void *data); + void *data; +}; + +/** + * struct sprd_mcdt_chan - this struct represents a single channel instance + * @mcdt: the mcdt controller + * @id: channel id + * @fifo_phys: channel fifo physical address which is used for DMA transfer + * @type: channel type + * @cb: channel fifo interrupt's callback interface to notify the fifo events + * @dma_enable: indicate if use DMA mode to transfer data + * @int_enable: indicate if use interrupt mode to notify users to read or + * write data manually + * @list: used to link into the global list + * + * Note: users should not modify any members of this structure. + */ +struct sprd_mcdt_chan { + struct sprd_mcdt_dev *mcdt; + u8 id; + unsigned long fifo_phys; + enum sprd_mcdt_channel_type type; + enum sprd_mcdt_dma_chan dma_chan; + struct sprd_mcdt_chan_callback *cb; + bool dma_enable; + bool int_enable; + struct list_head list; +}; + +#ifdef CONFIG_SND_SOC_SPRD_MCDT +struct sprd_mcdt_chan *sprd_mcdt_request_chan(u8 channel, + enum sprd_mcdt_channel_type type); +void sprd_mcdt_free_chan(struct sprd_mcdt_chan *chan); + +int sprd_mcdt_chan_write(struct sprd_mcdt_chan *chan, char *tx_buf, u32 size); +int sprd_mcdt_chan_read(struct sprd_mcdt_chan *chan, char *rx_buf, u32 size); +int sprd_mcdt_chan_int_enable(struct sprd_mcdt_chan *chan, u32 water_mark, + struct sprd_mcdt_chan_callback *cb); +void sprd_mcdt_chan_int_disable(struct sprd_mcdt_chan *chan); + +int sprd_mcdt_chan_dma_enable(struct sprd_mcdt_chan *chan, + enum sprd_mcdt_dma_chan dma_chan, u32 water_mark); +void sprd_mcdt_chan_dma_disable(struct sprd_mcdt_chan *chan); + +#else + +struct sprd_mcdt_chan *sprd_mcdt_request_chan(u8 channel, + enum sprd_mcdt_channel_type type) +{ + return NULL; +} + +void sprd_mcdt_free_chan(struct sprd_mcdt_chan *chan) +{ } + +int sprd_mcdt_chan_write(struct sprd_mcdt_chan *chan, char *tx_buf, u32 size) +{ + return -EINVAL; +} + +int sprd_mcdt_chan_read(struct sprd_mcdt_chan *chan, char *rx_buf, u32 size) +{ + return 0; +} + +int sprd_mcdt_chan_int_enable(struct sprd_mcdt_chan *chan, u32 water_mark, + struct sprd_mcdt_chan_callback *cb) +{ + return -EINVAL; +} + +void sprd_mcdt_chan_int_disable(struct sprd_mcdt_chan *chan) +{ } + +int sprd_mcdt_chan_dma_enable(struct sprd_mcdt_chan *chan, + enum sprd_mcdt_dma_chan dma_chan, u32 water_mark) +{ + return -EINVAL; +} + +void sprd_mcdt_chan_dma_disable(struct sprd_mcdt_chan *chan) +{ } + +#endif + +#endif /* __SPRD_MCDT_H */ diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c new file mode 100644 index 000000000000..6cddf551bc11 --- /dev/null +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -0,0 +1,674 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Spreadtrum Communications Inc. + +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/dma/sprd-dma.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> + +#include "sprd-pcm-dma.h" + +#define SPRD_COMPR_DMA_CHANS 2 + +/* Default values if userspace does not set */ +#define SPRD_COMPR_MIN_FRAGMENT_SIZE SZ_8K +#define SPRD_COMPR_MAX_FRAGMENT_SIZE SZ_128K +#define SPRD_COMPR_MIN_NUM_FRAGMENTS 4 +#define SPRD_COMPR_MAX_NUM_FRAGMENTS 64 + +/* DSP FIFO size */ +#define SPRD_COMPR_MCDT_EMPTY_WMK 0 +#define SPRD_COMPR_MCDT_FIFO_SIZE 512 + +/* Stage 0 IRAM buffer size definition */ +#define SPRD_COMPR_IRAM_BUF_SIZE SZ_32K +#define SPRD_COMPR_IRAM_INFO_SIZE (sizeof(struct sprd_compr_playinfo)) +#define SPRD_COMPR_IRAM_LINKLIST_SIZE (1024 - SPRD_COMPR_IRAM_INFO_SIZE) +#define SPRD_COMPR_IRAM_SIZE (SPRD_COMPR_IRAM_BUF_SIZE + \ + SPRD_COMPR_IRAM_INFO_SIZE + \ + SPRD_COMPR_IRAM_LINKLIST_SIZE) + +/* Stage 1 DDR buffer size definition */ +#define SPRD_COMPR_AREA_BUF_SIZE SZ_2M +#define SPRD_COMPR_AREA_LINKLIST_SIZE 1024 +#define SPRD_COMPR_AREA_SIZE (SPRD_COMPR_AREA_BUF_SIZE + \ + SPRD_COMPR_AREA_LINKLIST_SIZE) + +struct sprd_compr_dma { + struct dma_chan *chan; + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + dma_addr_t phys; + void *virt; + int trans_len; +}; + +/* + * The Spreadtrum Audio compress offload mode will use 2-stage DMA transfer to + * save power. That means we can request 2 dma channels, one for source channel, + * and another one for destination channel. Once the source channel's transaction + * is done, it will trigger the destination channel's transaction automatically + * by hardware signal. + * + * For 2-stage DMA transfer, we can allocate 2 buffers: IRAM buffer (always + * power-on) and DDR buffer. The source channel will transfer data from IRAM + * buffer to the DSP fifo to decoding/encoding, once IRAM buffer is empty by + * transferring done, the destination channel will start to transfer data from + * DDR buffer to IRAM buffer. + * + * Since the DSP fifo is only 512B, IRAM buffer is allocated by 32K, and DDR + * buffer is larger to 2M. That means only the IRAM 32k data is transferred + * done, we can wake up the AP system to transfer data from DDR to IRAM, and + * other time the AP system can be suspended to save power. + */ +struct sprd_compr_stream { + struct snd_compr_stream *cstream; + struct sprd_compr_ops *compr_ops; + struct sprd_compr_dma dma[SPRD_COMPR_DMA_CHANS]; + + /* DMA engine channel number */ + int num_channels; + + /* Stage 0 IRAM buffer */ + struct snd_dma_buffer iram_buffer; + /* Stage 1 DDR buffer */ + struct snd_dma_buffer compr_buffer; + + /* DSP play information IRAM buffer */ + dma_addr_t info_phys; + void *info_area; + int info_size; + + /* Data size copied to IRAM buffer */ + int copied_total; + /* Total received data size from userspace */ + int received_total; + /* Stage 0 IRAM buffer received data size */ + int received_stage0; + /* Stage 1 DDR buffer received data size */ + int received_stage1; + /* Stage 1 DDR buffer pointer */ + int stage1_pointer; +}; + +static int sprd_platform_compr_trigger(struct snd_compr_stream *cstream, + int cmd); + +static void sprd_platform_compr_drain_notify(void *arg) +{ + struct snd_compr_stream *cstream = arg; + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + + memset(stream->info_area, 0, sizeof(struct sprd_compr_playinfo)); + + snd_compr_drain_notify(cstream); +} + +static void sprd_platform_compr_dma_complete(void *data) +{ + struct snd_compr_stream *cstream = data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct sprd_compr_dma *dma = &stream->dma[1]; + + /* Update data size copied to IRAM buffer */ + stream->copied_total += dma->trans_len; + if (stream->copied_total > stream->received_total) + stream->copied_total = stream->received_total; + + snd_compr_fragment_elapsed(cstream); +} + +static int sprd_platform_compr_dma_config(struct snd_compr_stream *cstream, + struct snd_compr_params *params, + int channel) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct sprd_compr_data *data = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct sprd_pcm_dma_params *dma_params = data->dma_params; + struct sprd_compr_dma *dma = &stream->dma[channel]; + struct dma_slave_config config = { }; + struct sprd_dma_linklist link = { }; + enum dma_transfer_direction dir; + struct scatterlist *sg, *sgt; + enum dma_slave_buswidth bus_width; + int period, period_cnt, sg_num = 2; + dma_addr_t src_addr, dst_addr; + unsigned long flags; + int ret, j; + + if (!dma_params) { + dev_err(dev, "no dma parameters setting\n"); + return -EINVAL; + } + + dma->chan = dma_request_slave_channel(dev, + dma_params->chan_name[channel]); + if (!dma->chan) { + dev_err(dev, "failed to request dma channel\n"); + return -ENODEV; + } + + sgt = sg = devm_kcalloc(dev, sg_num, sizeof(*sg), GFP_KERNEL); + if (!sg) { + ret = -ENOMEM; + goto sg_err; + } + + switch (channel) { + case 0: + bus_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + period = (SPRD_COMPR_MCDT_FIFO_SIZE - SPRD_COMPR_MCDT_EMPTY_WMK) * 4; + period_cnt = params->buffer.fragment_size / period; + src_addr = stream->iram_buffer.addr; + dst_addr = dma_params->dev_phys[channel]; + flags = SPRD_DMA_FLAGS(SPRD_DMA_SRC_CHN1, + SPRD_DMA_TRANS_DONE_TRG, + SPRD_DMA_FRAG_REQ, + SPRD_DMA_TRANS_INT); + break; + + case 1: + bus_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + period = params->buffer.fragment_size; + period_cnt = params->buffer.fragments; + src_addr = stream->compr_buffer.addr; + dst_addr = stream->iram_buffer.addr; + flags = SPRD_DMA_FLAGS(SPRD_DMA_DST_CHN1, + SPRD_DMA_TRANS_DONE_TRG, + SPRD_DMA_FRAG_REQ, + SPRD_DMA_TRANS_INT); + break; + + default: + ret = -EINVAL; + goto config_err; + } + + dma->trans_len = period * period_cnt; + + config.src_maxburst = period; + config.src_addr_width = bus_width; + config.dst_addr_width = bus_width; + if (cstream->direction == SND_COMPRESS_PLAYBACK) { + config.src_addr = src_addr; + config.dst_addr = dst_addr; + dir = DMA_MEM_TO_DEV; + } else { + config.src_addr = dst_addr; + config.dst_addr = src_addr; + dir = DMA_DEV_TO_MEM; + } + + sg_init_table(sgt, sg_num); + for (j = 0; j < sg_num; j++, sgt++) { + sg_dma_len(sgt) = dma->trans_len; + sg_dma_address(sgt) = dst_addr; + } + + /* + * Configure the link-list address for the DMA engine link-list + * mode. + */ + link.virt_addr = (unsigned long)dma->virt; + link.phy_addr = dma->phys; + + ret = dmaengine_slave_config(dma->chan, &config); + if (ret) { + dev_err(dev, + "failed to set slave configuration: %d\n", ret); + goto config_err; + } + + /* + * We configure the DMA request mode, interrupt mode, channel + * mode and channel trigger mode by the flags. + */ + dma->desc = dma->chan->device->device_prep_slave_sg(dma->chan, sg, + sg_num, dir, + flags, &link); + if (!dma->desc) { + dev_err(dev, "failed to prepare slave sg\n"); + ret = -ENOMEM; + goto config_err; + } + + /* Only channel 1 transfer can wake up the AP system. */ + if (!params->no_wake_mode && channel == 1) { + dma->desc->callback = sprd_platform_compr_dma_complete; + dma->desc->callback_param = cstream; + } + + devm_kfree(dev, sg); + + return 0; + +config_err: + devm_kfree(dev, sg); +sg_err: + dma_release_channel(dma->chan); + return ret; +} + +static int sprd_platform_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct sprd_compr_params compr_params = { }; + int ret; + + /* + * Configure the DMA engine 2-stage transfer mode. Channel 1 set as the + * destination channel, and channel 0 set as the source channel, that + * means once the source channel's transaction is done, it will trigger + * the destination channel's transaction automatically. + */ + ret = sprd_platform_compr_dma_config(cstream, params, 1); + if (ret) { + dev_err(dev, "failed to config stage 1 DMA: %d\n", ret); + return ret; + } + + ret = sprd_platform_compr_dma_config(cstream, params, 0); + if (ret) { + dev_err(dev, "failed to config stage 0 DMA: %d\n", ret); + goto config_err; + } + + compr_params.direction = cstream->direction; + compr_params.sample_rate = params->codec.sample_rate; + compr_params.channels = stream->num_channels; + compr_params.info_phys = stream->info_phys; + compr_params.info_size = stream->info_size; + compr_params.rate = params->codec.bit_rate; + compr_params.format = params->codec.id; + + ret = stream->compr_ops->set_params(cstream->direction, &compr_params); + if (ret) { + dev_err(dev, "failed to set parameters: %d\n", ret); + goto params_err; + } + + return 0; + +params_err: + dma_release_channel(stream->dma[0].chan); +config_err: + dma_release_channel(stream->dma[1].chan); + return ret; +} + +static int sprd_platform_compr_open(struct snd_compr_stream *cstream) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct sprd_compr_data *data = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct sprd_compr_stream *stream; + struct sprd_compr_callback cb; + int stream_id = cstream->direction, ret; + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + stream = devm_kzalloc(dev, sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->cstream = cstream; + stream->num_channels = 2; + stream->compr_ops = data->ops; + + /* + * Allocate the stage 0 IRAM buffer size, including the DMA 0 + * link-list size and play information of DSP address size. + */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_IRAM, dev, + SPRD_COMPR_IRAM_SIZE, &stream->iram_buffer); + if (ret < 0) + goto err_iram; + + /* Use to save link-list configuration for DMA 0. */ + stream->dma[0].virt = stream->iram_buffer.area + SPRD_COMPR_IRAM_SIZE; + stream->dma[0].phys = stream->iram_buffer.addr + SPRD_COMPR_IRAM_SIZE; + + /* Use to update the current data offset of DSP. */ + stream->info_phys = stream->iram_buffer.addr + SPRD_COMPR_IRAM_SIZE + + SPRD_COMPR_IRAM_LINKLIST_SIZE; + stream->info_area = stream->iram_buffer.area + SPRD_COMPR_IRAM_SIZE + + SPRD_COMPR_IRAM_LINKLIST_SIZE; + stream->info_size = SPRD_COMPR_IRAM_INFO_SIZE; + + /* + * Allocate the stage 1 DDR buffer size, including the DMA 1 link-list + * size. + */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, + SPRD_COMPR_AREA_SIZE, &stream->compr_buffer); + if (ret < 0) + goto err_compr; + + /* Use to save link-list configuration for DMA 1. */ + stream->dma[1].virt = stream->compr_buffer.area + SPRD_COMPR_AREA_SIZE; + stream->dma[1].phys = stream->compr_buffer.addr + SPRD_COMPR_AREA_SIZE; + + cb.drain_notify = sprd_platform_compr_drain_notify; + cb.drain_data = cstream; + ret = stream->compr_ops->open(stream_id, &cb); + if (ret) { + dev_err(dev, "failed to open compress platform: %d\n", ret); + goto err_open; + } + + runtime->private_data = stream; + return 0; + +err_open: + snd_dma_free_pages(&stream->compr_buffer); +err_compr: + snd_dma_free_pages(&stream->iram_buffer); +err_iram: + devm_kfree(dev, stream); + + return ret; +} + +static int sprd_platform_compr_free(struct snd_compr_stream *cstream) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + int stream_id = cstream->direction, i; + + for (i = 0; i < stream->num_channels; i++) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) { + dma_release_channel(dma->chan); + dma->chan = NULL; + } + } + + snd_dma_free_pages(&stream->compr_buffer); + snd_dma_free_pages(&stream->iram_buffer); + + stream->compr_ops->close(stream_id); + + devm_kfree(dev, stream); + return 0; +} + +static int sprd_platform_compr_trigger(struct snd_compr_stream *cstream, + int cmd) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + int channels = stream->num_channels, ret = 0, i; + int stream_id = cstream->direction; + + if (cstream->direction != SND_COMPRESS_PLAYBACK) { + dev_err(dev, "unsupported compress direction\n"); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (!dma->desc) + continue; + + dma->cookie = dmaengine_submit(dma->desc); + ret = dma_submit_error(dma->cookie); + if (ret) { + dev_err(dev, "failed to submit request: %d\n", + ret); + return ret; + } + } + + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dma_async_issue_pending(dma->chan); + } + + ret = stream->compr_ops->start(stream_id); + break; + + case SNDRV_PCM_TRIGGER_STOP: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dmaengine_terminate_async(dma->chan); + } + + stream->copied_total = 0; + stream->stage1_pointer = 0; + stream->received_total = 0; + stream->received_stage0 = 0; + stream->received_stage1 = 0; + + ret = stream->compr_ops->stop(stream_id); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dmaengine_pause(dma->chan); + } + + ret = stream->compr_ops->pause(stream_id); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dmaengine_resume(dma->chan); + } + + ret = stream->compr_ops->pause_release(stream_id); + break; + + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + case SND_COMPR_TRIGGER_DRAIN: + ret = stream->compr_ops->drain(stream->received_total); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int sprd_platform_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct sprd_compr_playinfo *info = + (struct sprd_compr_playinfo *)stream->info_area; + + tstamp->copied_total = stream->copied_total; + tstamp->pcm_io_frames = info->current_data_offset; + + return 0; +} + +static int sprd_platform_compr_copy(struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + int avail_bytes, data_count = count; + void *dst; + + /* + * We usually set fragment size as 32K, and the stage 0 IRAM buffer + * size is 32K too. So if now the received data size of the stage 0 + * IRAM buffer is less than 32K, that means we have some available + * spaces for the stage 0 IRAM buffer. + */ + if (stream->received_stage0 < runtime->fragment_size) { + avail_bytes = runtime->fragment_size - stream->received_stage0; + dst = stream->iram_buffer.area + stream->received_stage0; + + if (avail_bytes >= data_count) { + /* + * Copy data to the stage 0 IRAM buffer directly if + * spaces are enough. + */ + if (copy_from_user(dst, buf, data_count)) + return -EFAULT; + + stream->received_stage0 += data_count; + stream->copied_total += data_count; + goto copy_done; + } else { + /* + * If the data count is larger than the available spaces + * of the the stage 0 IRAM buffer, we should copy one + * partial data to the stage 0 IRAM buffer, and copy + * the left to the stage 1 DDR buffer. + */ + if (copy_from_user(dst, buf, avail_bytes)) + return -EFAULT; + + data_count -= avail_bytes; + stream->received_stage0 += avail_bytes; + stream->copied_total += avail_bytes; + buf += avail_bytes; + } + } + + /* + * Copy data to the stage 1 DDR buffer if no spaces for the stage 0 IRAM + * buffer. + */ + dst = stream->compr_buffer.area + stream->stage1_pointer; + if (data_count < stream->compr_buffer.bytes - stream->stage1_pointer) { + if (copy_from_user(dst, buf, data_count)) + return -EFAULT; + + stream->stage1_pointer += data_count; + } else { + avail_bytes = stream->compr_buffer.bytes - stream->stage1_pointer; + + if (copy_from_user(dst, buf, avail_bytes)) + return -EFAULT; + + if (copy_from_user(stream->compr_buffer.area, buf + avail_bytes, + data_count - avail_bytes)) + return -EFAULT; + + stream->stage1_pointer = data_count - avail_bytes; + } + + stream->received_stage1 += data_count; + +copy_done: + /* Update the copied data size. */ + stream->received_total += count; + return count; +} + +static int sprd_platform_compr_get_caps(struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + caps->direction = cstream->direction; + caps->min_fragment_size = SPRD_COMPR_MIN_FRAGMENT_SIZE; + caps->max_fragment_size = SPRD_COMPR_MAX_FRAGMENT_SIZE; + caps->min_fragments = SPRD_COMPR_MIN_NUM_FRAGMENTS; + caps->max_fragments = SPRD_COMPR_MAX_NUM_FRAGMENTS; + caps->num_codecs = 2; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + + return 0; +} + +static int +sprd_platform_compr_get_codec_caps(struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) +{ + switch (codec->codec) { + case SND_AUDIOCODEC_MP3: + codec->num_descriptors = 2; + codec->descriptor[0].max_ch = 2; + codec->descriptor[0].bit_rate[0] = 320; + codec->descriptor[0].bit_rate[1] = 128; + codec->descriptor[0].num_bitrates = 2; + codec->descriptor[0].profiles = 0; + codec->descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO; + codec->descriptor[0].formats = 0; + break; + + case SND_AUDIOCODEC_AAC: + codec->num_descriptors = 2; + codec->descriptor[1].max_ch = 2; + codec->descriptor[1].bit_rate[0] = 320; + codec->descriptor[1].bit_rate[1] = 128; + codec->descriptor[1].num_bitrates = 2; + codec->descriptor[1].profiles = 0; + codec->descriptor[1].modes = 0; + codec->descriptor[1].formats = 0; + break; + + default: + return -EINVAL; + } + + return 0; +} + +const struct snd_compr_ops sprd_platform_compr_ops = { + .open = sprd_platform_compr_open, + .free = sprd_platform_compr_free, + .set_params = sprd_platform_compr_set_params, + .trigger = sprd_platform_compr_trigger, + .pointer = sprd_platform_compr_pointer, + .copy = sprd_platform_compr_copy, + .get_caps = sprd_platform_compr_get_caps, + .get_codec_caps = sprd_platform_compr_get_codec_caps, +}; + +MODULE_DESCRIPTION("Spreadtrum ASoC Compress Platform Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:compress-platform"); diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index cbb27c4abeba..d38ebbbbf169 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -6,6 +6,7 @@ #include <linux/dma/sprd-dma.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of_reserved_mem.h> #include <linux/platform_device.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -13,7 +14,6 @@ #include "sprd-pcm-dma.h" -#define DRV_NAME "sprd_pcm_dma" #define SPRD_PCM_DMA_LINKLIST_SIZE 64 #define SPRD_PCM_DMA_BRUST_LEN 640 @@ -524,14 +524,21 @@ static void sprd_pcm_free(struct snd_pcm *pcm) static const struct snd_soc_component_driver sprd_soc_component = { .name = DRV_NAME, .ops = &sprd_pcm_ops, + .compr_ops = &sprd_platform_compr_ops, .pcm_new = sprd_pcm_new, .pcm_free = sprd_pcm_free, }; static int sprd_soc_platform_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; int ret; + ret = of_reserved_mem_device_init_by_idx(&pdev->dev, np, 0); + if (ret) + dev_warn(&pdev->dev, + "no reserved DMA memory for audio platform device\n"); + ret = devm_snd_soc_register_component(&pdev->dev, &sprd_soc_component, NULL, 0); if (ret) diff --git a/sound/soc/sprd/sprd-pcm-dma.h b/sound/soc/sprd/sprd-pcm-dma.h index d85a34f1461d..08e9fdba82f1 100644 --- a/sound/soc/sprd/sprd-pcm-dma.h +++ b/sound/soc/sprd/sprd-pcm-dma.h @@ -3,8 +3,11 @@ #ifndef __SPRD_PCM_DMA_H #define __SPRD_PCM_DMA_H +#define DRV_NAME "sprd_pcm_dma" #define SPRD_PCM_CHANNEL_MAX 2 +extern const struct snd_compr_ops sprd_platform_compr_ops; + struct sprd_pcm_dma_params { dma_addr_t dev_phys[SPRD_PCM_CHANNEL_MAX]; u32 datawidth[SPRD_PCM_CHANNEL_MAX]; @@ -12,4 +15,44 @@ struct sprd_pcm_dma_params { const char *chan_name[SPRD_PCM_CHANNEL_MAX]; }; +struct sprd_compr_playinfo { + int total_time; + int current_time; + int total_data_length; + int current_data_offset; +}; + +struct sprd_compr_params { + u32 direction; + u32 rate; + u32 sample_rate; + u32 channels; + u32 format; + u32 period; + u32 periods; + u32 info_phys; + u32 info_size; +}; + +struct sprd_compr_callback { + void (*drain_notify)(void *data); + void *drain_data; +}; + +struct sprd_compr_ops { + int (*open)(int str_id, struct sprd_compr_callback *cb); + int (*close)(int str_id); + int (*start)(int str_id); + int (*stop)(int str_id); + int (*pause)(int str_id); + int (*pause_release)(int str_id); + int (*drain)(int received_total); + int (*set_params)(int str_id, struct sprd_compr_params *params); +}; + +struct sprd_compr_data { + struct sprd_compr_ops *ops; + struct sprd_pcm_dma_params *dma_params; +}; + #endif /* __SPRD_PCM_DMA_H */ diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c index 78bed9734713..cc517e007039 100644 --- a/sound/soc/stm/stm32_adfsdm.c +++ b/sound/soc/stm/stm32_adfsdm.c @@ -44,7 +44,7 @@ struct stm32_adfsdm_priv { static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_PAUSE, + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_PAUSE, .formats = SNDRV_PCM_FMTBIT_S32_LE, .rate_min = 8000, diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c index 8968458eec62..8ee697ff1f86 100644 --- a/sound/soc/stm/stm32_i2s.c +++ b/sound/soc/stm/stm32_i2s.c @@ -179,7 +179,6 @@ enum i2s_datlen { I2S_I2SMOD_DATLEN_32, }; -#define STM32_I2S_DAI_NAME_SIZE 20 #define STM32_I2S_FIFO_SIZE 16 #define STM32_I2S_IS_MASTER(x) ((x)->ms_flg == I2S_MS_MASTER) @@ -202,7 +201,6 @@ enum i2s_datlen { * @phys_addr: I2S registers physical base address * @lock_fd: lock to manage race conditions in full duplex mode * @irq_lock: prevent race condition with IRQ - * @dais_name: DAI name * @mclk_rate: master clock frequency (Hz) * @fmt: DAI protocol * @refcount: keep count of opened streams on I2S @@ -224,7 +222,6 @@ struct stm32_i2s_data { dma_addr_t phys_addr; spinlock_t lock_fd; /* Manage race conditions for full duplex */ spinlock_t irq_lock; /* used to prevent race condition with IRQ */ - char dais_name[STM32_I2S_DAI_NAME_SIZE]; unsigned int mclk_rate; unsigned int fmt; int refcount; @@ -495,12 +492,6 @@ static int stm32_i2s_configure(struct snd_soc_dai *cpu_dai, unsigned int fthlv; int ret; - if ((params_channels(params) == 1) && - ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_DSP_A)) { - dev_err(cpu_dai->dev, "Mono mode supported only by DSP_A\n"); - return -EINVAL; - } - switch (format) { case 16: cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_16); @@ -550,6 +541,10 @@ static int stm32_i2s_startup(struct snd_pcm_substream *substream, i2s->substream = substream; spin_unlock_irqrestore(&i2s->irq_lock, flags); + if ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_DSP_A) + snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + ret = clk_prepare_enable(i2s->i2sclk); if (ret < 0) { dev_err(cpu_dai->dev, "Failed to enable clock: %d\n", ret); @@ -592,7 +587,8 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* Enable i2s */ - dev_dbg(cpu_dai->dev, "start I2S\n"); + dev_dbg(cpu_dai->dev, "start I2S %s\n", + playback_flg ? "playback" : "capture"); cfg1_mask = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN; regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, @@ -637,6 +633,9 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev_dbg(cpu_dai->dev, "stop I2S %s\n", + playback_flg ? "playback" : "capture"); + if (playback_flg) regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG, I2S_IER_UDRIE, @@ -653,8 +652,6 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd, break; } - dev_dbg(cpu_dai->dev, "stop I2S\n"); - ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG, I2S_CR1_SPE, 0); if (ret < 0) { @@ -734,7 +731,8 @@ static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = { static const struct snd_pcm_hardware stm32_i2s_pcm_hw = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, .buffer_bytes_max = 8 * PAGE_SIZE, - .period_bytes_max = 2048, + .period_bytes_min = 1024, + .period_bytes_max = 4 * PAGE_SIZE, .periods_min = 2, .periods_max = 8, }; @@ -770,12 +768,8 @@ static int stm32_i2s_dais_init(struct platform_device *pdev, if (!dai_ptr) return -ENOMEM; - snprintf(i2s->dais_name, STM32_I2S_DAI_NAME_SIZE, - "%s", dev_name(&pdev->dev)); - dai_ptr->probe = stm32_i2s_dai_probe; dai_ptr->ops = &stm32_i2s_pcm_dai_ops; - dai_ptr->name = i2s->dais_name; dai_ptr->id = 1; stm32_i2s_dai_init(&dai_ptr->playback, "playback"); stm32_i2s_dai_init(&dai_ptr->capture, "capture"); @@ -845,8 +839,9 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, /* Get irqs */ irq = platform_get_irq(pdev, 0); if (irq < 0) { - dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); - return -ENOENT; + if (irq != -EPROBE_DEFER) + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; } ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, IRQF_ONESHOT, diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c index d68d62f12df5..7550d5f08be3 100644 --- a/sound/soc/stm/stm32_sai.c +++ b/sound/soc/stm/stm32_sai.c @@ -21,6 +21,7 @@ #include <linux/delay.h> #include <linux/module.h> #include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> #include <linux/reset.h> #include <sound/dmaengine_pcm.h> @@ -44,20 +45,41 @@ static const struct of_device_id stm32_sai_ids[] = { {} }; -static int stm32_sai_sync_conf_client(struct stm32_sai_data *sai, int synci) +static int stm32_sai_pclk_disable(struct device *dev) +{ + struct stm32_sai_data *sai = dev_get_drvdata(dev); + + clk_disable_unprepare(sai->pclk); + + return 0; +} + +static int stm32_sai_pclk_enable(struct device *dev) { + struct stm32_sai_data *sai = dev_get_drvdata(dev); int ret; - /* Enable peripheral clock to allow GCR register access */ ret = clk_prepare_enable(sai->pclk); if (ret) { dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret); return ret; } + return 0; +} + +static int stm32_sai_sync_conf_client(struct stm32_sai_data *sai, int synci) +{ + int ret; + + /* Enable peripheral clock to allow GCR register access */ + ret = stm32_sai_pclk_enable(&sai->pdev->dev); + if (ret) + return ret; + writel_relaxed(FIELD_PREP(SAI_GCR_SYNCIN_MASK, (synci - 1)), sai->base); - clk_disable_unprepare(sai->pclk); + stm32_sai_pclk_disable(&sai->pdev->dev); return 0; } @@ -68,11 +90,9 @@ static int stm32_sai_sync_conf_provider(struct stm32_sai_data *sai, int synco) int ret; /* Enable peripheral clock to allow GCR register access */ - ret = clk_prepare_enable(sai->pclk); - if (ret) { - dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret); + ret = stm32_sai_pclk_enable(&sai->pdev->dev); + if (ret) return ret; - } dev_dbg(&sai->pdev->dev, "Set %pOFn%s as synchro provider\n", sai->pdev->dev.of_node, @@ -83,13 +103,13 @@ static int stm32_sai_sync_conf_provider(struct stm32_sai_data *sai, int synco) dev_err(&sai->pdev->dev, "%pOFn%s already set as sync provider\n", sai->pdev->dev.of_node, prev_synco == STM_SAI_SYNC_OUT_A ? "A" : "B"); - clk_disable_unprepare(sai->pclk); + stm32_sai_pclk_disable(&sai->pdev->dev); return -EINVAL; } writel_relaxed(FIELD_PREP(SAI_GCR_SYNCOUT_MASK, synco), sai->base); - clk_disable_unprepare(sai->pclk); + stm32_sai_pclk_disable(&sai->pdev->dev); return 0; } @@ -195,12 +215,54 @@ static int stm32_sai_probe(struct platform_device *pdev) return devm_of_platform_populate(&pdev->dev); } +#ifdef CONFIG_PM_SLEEP +/* + * When pins are shared by two sai sub instances, pins have to be defined + * in sai parent node. In this case, pins state is not managed by alsa fw. + * These pins are managed in suspend/resume callbacks. + */ +static int stm32_sai_suspend(struct device *dev) +{ + struct stm32_sai_data *sai = dev_get_drvdata(dev); + int ret; + + ret = stm32_sai_pclk_enable(dev); + if (ret) + return ret; + + sai->gcr = readl_relaxed(sai->base); + stm32_sai_pclk_disable(dev); + + return pinctrl_pm_select_sleep_state(dev); +} + +static int stm32_sai_resume(struct device *dev) +{ + struct stm32_sai_data *sai = dev_get_drvdata(dev); + int ret; + + ret = stm32_sai_pclk_enable(dev); + if (ret) + return ret; + + writel_relaxed(sai->gcr, sai->base); + stm32_sai_pclk_disable(dev); + + return pinctrl_pm_select_default_state(dev); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops stm32_sai_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(stm32_sai_suspend, stm32_sai_resume) +}; + MODULE_DEVICE_TABLE(of, stm32_sai_ids); static struct platform_driver stm32_sai_driver = { .driver = { .name = "st,stm32-sai", .of_match_table = stm32_sai_ids, + .pm = &stm32_sai_pm_ops, }, .probe = stm32_sai_probe, }; diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h index 08de899c766b..9c36a393ab7b 100644 --- a/sound/soc/stm/stm32_sai.h +++ b/sound/soc/stm/stm32_sai.h @@ -268,6 +268,7 @@ struct stm32_sai_conf { * @version: SOC version * @irq: SAI interrupt line * @set_sync: pointer to synchro mode configuration callback + * @gcr: SAI Global Configuration Register */ struct stm32_sai_data { struct platform_device *pdev; @@ -279,4 +280,5 @@ struct stm32_sai_data { int irq; int (*set_sync)(struct stm32_sai_data *sai, struct device_node *np_provider, int synco, int synci); + u32 gcr; }; diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index d7045aa520de..2a74ce7c9440 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -110,7 +110,7 @@ struct stm32_sai_sub_data { struct regmap *regmap; const struct regmap_config *regmap_config; struct snd_dmaengine_dai_dma_data dma_params; - struct snd_soc_dai_driver *cpu_dai_drv; + struct snd_soc_dai_driver cpu_dai_drv; struct snd_soc_dai *cpu_dai; struct snd_pcm_substream *substream; struct stm32_sai_data *pdata; @@ -169,6 +169,7 @@ static bool stm32_sai_sub_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case STM_SAI_DR_REGX: + case STM_SAI_SR_REGX: return true; default: return false; @@ -183,7 +184,6 @@ static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg) case STM_SAI_FRCR_REGX: case STM_SAI_SLOTR_REGX: case STM_SAI_IMR_REGX: - case STM_SAI_SR_REGX: case STM_SAI_CLRFR_REGX: case STM_SAI_DR_REGX: case STM_SAI_PDMCR_REGX: @@ -203,6 +203,7 @@ static const struct regmap_config stm32_sai_sub_regmap_config_f4 = { .volatile_reg = stm32_sai_sub_volatile_reg, .writeable_reg = stm32_sai_sub_writeable_reg, .fast_io = true, + .cache_type = REGCACHE_FLAT, }; static const struct regmap_config stm32_sai_sub_regmap_config_h7 = { @@ -214,6 +215,7 @@ static const struct regmap_config stm32_sai_sub_regmap_config_h7 = { .volatile_reg = stm32_sai_sub_volatile_reg, .writeable_reg = stm32_sai_sub_writeable_reg, .fast_io = true, + .cache_type = REGCACHE_FLAT, }; static int snd_pcm_iec958_info(struct snd_kcontrol *kcontrol, @@ -461,8 +463,8 @@ static irqreturn_t stm32_sai_isr(int irq, void *devid) if (!flags) return IRQ_NONE; - regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK, - SAI_XCLRFR_MASK); + regmap_write_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK, + SAI_XCLRFR_MASK); if (!sai->substream) { dev_err(&pdev->dev, "Device stopped. Spurious IRQ 0x%x\n", sr); @@ -728,9 +730,8 @@ static int stm32_sai_startup(struct snd_pcm_substream *substream, } /* Enable ITs */ - - regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, - SAI_XCLRFR_MASK, SAI_XCLRFR_MASK); + regmap_write_bits(sai->regmap, STM_SAI_CLRFR_REGX, + SAI_XCLRFR_MASK, SAI_XCLRFR_MASK); imr = SAI_XIMR_OVRUDRIE; if (STM_SAI_IS_CAPTURE(sai)) { @@ -762,10 +763,10 @@ static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai, * SAI fifo threshold is set to half fifo, to keep enough space * for DMA incoming bursts. */ - regmap_update_bits(sai->regmap, STM_SAI_CR2_REGX, - SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK, - SAI_XCR2_FFLUSH | - SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF)); + regmap_write_bits(sai->regmap, STM_SAI_CR2_REGX, + SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK, + SAI_XCR2_FFLUSH | + SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF)); /* DS bits in CR1 not set for SPDIF (size forced to 24 bits).*/ if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { @@ -1233,8 +1234,7 @@ static const struct snd_pcm_hardware stm32_sai_pcm_hw = { .periods_max = 8, }; -static struct snd_soc_dai_driver stm32_sai_playback_dai[] = { -{ +static struct snd_soc_dai_driver stm32_sai_playback_dai = { .probe = stm32_sai_dai_probe, .pcm_new = stm32_sai_pcm_new, .id = 1, /* avoid call to fmt_single_name() */ @@ -1251,11 +1251,9 @@ static struct snd_soc_dai_driver stm32_sai_playback_dai[] = { SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &stm32_sai_pcm_dai_ops, - } }; -static struct snd_soc_dai_driver stm32_sai_capture_dai[] = { -{ +static struct snd_soc_dai_driver stm32_sai_capture_dai = { .probe = stm32_sai_dai_probe, .id = 1, /* avoid call to fmt_single_name() */ .capture = { @@ -1271,7 +1269,6 @@ static struct snd_soc_dai_driver stm32_sai_capture_dai[] = { SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &stm32_sai_pcm_dai_ops, - } }; static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = { @@ -1440,29 +1437,6 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, return 0; } -static int stm32_sai_sub_dais_init(struct platform_device *pdev, - struct stm32_sai_sub_data *sai) -{ - sai->cpu_dai_drv = devm_kzalloc(&pdev->dev, - sizeof(struct snd_soc_dai_driver), - GFP_KERNEL); - if (!sai->cpu_dai_drv) - return -ENOMEM; - - if (STM_SAI_IS_PLAYBACK(sai)) { - memcpy(sai->cpu_dai_drv, &stm32_sai_playback_dai, - sizeof(stm32_sai_playback_dai)); - sai->cpu_dai_drv->playback.stream_name = sai->cpu_dai_drv->name; - } else { - memcpy(sai->cpu_dai_drv, &stm32_sai_capture_dai, - sizeof(stm32_sai_capture_dai)); - sai->cpu_dai_drv->capture.stream_name = sai->cpu_dai_drv->name; - } - sai->cpu_dai_drv->name = dev_name(&pdev->dev); - - return 0; -} - static int stm32_sai_sub_probe(struct platform_device *pdev) { struct stm32_sai_sub_data *sai; @@ -1494,9 +1468,11 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) if (ret) return ret; - ret = stm32_sai_sub_dais_init(pdev, sai); - if (ret) - return ret; + if (STM_SAI_IS_PLAYBACK(sai)) + sai->cpu_dai_drv = stm32_sai_playback_dai; + else + sai->cpu_dai_drv = stm32_sai_capture_dai; + sai->cpu_dai_drv.name = dev_name(&pdev->dev); ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, IRQF_SHARED, dev_name(&pdev->dev), sai); @@ -1506,7 +1482,7 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) } ret = devm_snd_soc_register_component(&pdev->dev, &stm32_component, - sai->cpu_dai_drv, 1); + &sai->cpu_dai_drv, 1); if (ret) return ret; @@ -1522,10 +1498,34 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int stm32_sai_sub_suspend(struct device *dev) +{ + struct stm32_sai_sub_data *sai = dev_get_drvdata(dev); + + regcache_cache_only(sai->regmap, true); + regcache_mark_dirty(sai->regmap); + return 0; +} + +static int stm32_sai_sub_resume(struct device *dev) +{ + struct stm32_sai_sub_data *sai = dev_get_drvdata(dev); + + regcache_cache_only(sai->regmap, false); + return regcache_sync(sai->regmap); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops stm32_sai_sub_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(stm32_sai_sub_suspend, stm32_sai_sub_resume) +}; + static struct platform_driver stm32_sai_sub_driver = { .driver = { .name = "st,stm32-sai-sub", .of_match_table = stm32_sai_sub_ids, + .pm = &stm32_sai_sub_pm_ops, }, .probe = stm32_sai_sub_probe, }; diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index 373df4f24be1..3d64200edbb5 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -21,6 +21,7 @@ #include <linux/delay.h> #include <linux/module.h> #include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> #include <linux/regmap.h> #include <linux/reset.h> @@ -471,6 +472,8 @@ static int stm32_spdifrx_get_ctrl_data(struct stm32_spdifrx_data *spdifrx) memset(spdifrx->cs, 0, SPDIFRX_CS_BYTES_NB); memset(spdifrx->ub, 0, SPDIFRX_UB_BYTES_NB); + pinctrl_pm_select_default_state(&spdifrx->pdev->dev); + ret = stm32_spdifrx_dma_ctrl_start(spdifrx); if (ret < 0) return ret; @@ -493,7 +496,7 @@ static int stm32_spdifrx_get_ctrl_data(struct stm32_spdifrx_data *spdifrx) if (wait_for_completion_interruptible_timeout(&spdifrx->cs_completion, msecs_to_jiffies(100)) <= 0) { - dev_err(&spdifrx->pdev->dev, "Failed to get control data\n"); + dev_dbg(&spdifrx->pdev->dev, "Failed to get control data\n"); ret = -EAGAIN; } @@ -502,6 +505,7 @@ static int stm32_spdifrx_get_ctrl_data(struct stm32_spdifrx_data *spdifrx) end: clk_disable_unprepare(spdifrx->kclk); + pinctrl_pm_select_sleep_state(&spdifrx->pdev->dev); return ret; } @@ -611,10 +615,15 @@ static bool stm32_spdifrx_readable_reg(struct device *dev, unsigned int reg) static bool stm32_spdifrx_volatile_reg(struct device *dev, unsigned int reg) { - if (reg == STM32_SPDIFRX_DR) + switch (reg) { + case STM32_SPDIFRX_DR: + case STM32_SPDIFRX_CSR: + case STM32_SPDIFRX_SR: + case STM32_SPDIFRX_DIR: return true; - - return false; + default: + return false; + } } static bool stm32_spdifrx_writeable_reg(struct device *dev, unsigned int reg) @@ -638,6 +647,7 @@ static const struct regmap_config stm32_h7_spdifrx_regmap_conf = { .volatile_reg = stm32_spdifrx_volatile_reg, .writeable_reg = stm32_spdifrx_writeable_reg, .fast_io = true, + .cache_type = REGCACHE_FLAT, }; static irqreturn_t stm32_spdifrx_isr(int irq, void *devid) @@ -835,7 +845,8 @@ static struct snd_soc_dai_driver stm32_spdifrx_dai[] = { static const struct snd_pcm_hardware stm32_spdifrx_pcm_hw = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, .buffer_bytes_max = 8 * PAGE_SIZE, - .period_bytes_max = 2048, /* MDMA constraint */ + .period_bytes_min = 1024, + .period_bytes_max = 4 * PAGE_SIZE, .periods_min = 2, .periods_max = 8, }; @@ -983,10 +994,36 @@ static int stm32_spdifrx_remove(struct platform_device *pdev) MODULE_DEVICE_TABLE(of, stm32_spdifrx_ids); +#ifdef CONFIG_PM_SLEEP +static int stm32_spdifrx_suspend(struct device *dev) +{ + struct stm32_spdifrx_data *spdifrx = dev_get_drvdata(dev); + + regcache_cache_only(spdifrx->regmap, true); + regcache_mark_dirty(spdifrx->regmap); + + return 0; +} + +static int stm32_spdifrx_resume(struct device *dev) +{ + struct stm32_spdifrx_data *spdifrx = dev_get_drvdata(dev); + + regcache_cache_only(spdifrx->regmap, false); + + return regcache_sync(spdifrx->regmap); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops stm32_spdifrx_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(stm32_spdifrx_suspend, stm32_spdifrx_resume) +}; + static struct platform_driver stm32_spdifrx_driver = { .driver = { .name = "st,stm32-spdifrx", .of_match_table = stm32_spdifrx_ids, + .pm = &stm32_spdifrx_pm_ops, }, .probe = stm32_spdifrx_probe, .remove = stm32_spdifrx_remove, diff --git a/sound/soc/ti/Kconfig b/sound/soc/ti/Kconfig index 4bf3c15d4e51..ee7c202c69b7 100644 --- a/sound/soc/ti/Kconfig +++ b/sound/soc/ti/Kconfig @@ -21,8 +21,8 @@ config SND_SOC_DAVINCI_ASP config SND_SOC_DAVINCI_MCASP tristate "Multichannel Audio Serial Port (McASP) support" - select SND_SOC_TI_EDMA_PCM if TI_EDMA - select SND_SOC_TI_SDMA_PCM if DMA_OMAP + select SND_SOC_TI_EDMA_PCM + select SND_SOC_TI_SDMA_PCM help Say Y or M here if you want to have support for McASP IP found in various Texas Instruments SoCs like: diff --git a/sound/soc/ti/ams-delta.c b/sound/soc/ti/ams-delta.c index 4dce494dfbd3..b9611db14c86 100644 --- a/sound/soc/ti/ams-delta.c +++ b/sound/soc/ti/ams-delta.c @@ -200,7 +200,7 @@ static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol, return 0; } -static const SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum, +static SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum, ams_delta_audio_mode); static const struct snd_kcontrol_new ams_delta_audio_controls[] = { diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index a3a67a8f0f54..9fbc759fdefe 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -45,6 +45,7 @@ #define MCASP_MAX_AFIFO_DEPTH 64 +#ifdef CONFIG_PM static u32 context_regs[] = { DAVINCI_MCASP_TXFMCTL_REG, DAVINCI_MCASP_RXFMCTL_REG, @@ -68,6 +69,7 @@ struct davinci_mcasp_context { u32 *xrsr_regs; /* for serializer configuration */ bool pm_state; }; +#endif struct davinci_mcasp_ruledata { struct davinci_mcasp *mcasp; diff --git a/sound/soc/ti/edma-pcm.c b/sound/soc/ti/edma-pcm.c index 59e588abe54b..fdffb801b185 100644 --- a/sound/soc/ti/edma-pcm.c +++ b/sound/soc/ti/edma-pcm.c @@ -23,7 +23,6 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/dmaengine_pcm.h> -#include <linux/edma.h> #include "edma-pcm.h" @@ -43,14 +42,12 @@ static const struct snd_pcm_hardware edma_pcm_hardware = { static const struct snd_dmaengine_pcm_config edma_dmaengine_pcm_config = { .pcm_hardware = &edma_pcm_hardware, .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, - .compat_filter_fn = edma_filter_fn, .prealloc_buffer_size = 128 * 1024, }; int edma_pcm_platform_register(struct device *dev) { - return devm_snd_dmaengine_pcm_register(dev, &edma_dmaengine_pcm_config, - SND_DMAENGINE_PCM_FLAG_COMPAT); + return devm_snd_dmaengine_pcm_register(dev, &edma_dmaengine_pcm_config, 0); } EXPORT_SYMBOL_GPL(edma_pcm_platform_register); diff --git a/sound/soc/ti/sdma-pcm.c b/sound/soc/ti/sdma-pcm.c index 21a9c2499d48..a236350beb10 100644 --- a/sound/soc/ti/sdma-pcm.c +++ b/sound/soc/ti/sdma-pcm.c @@ -11,7 +11,6 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/dmaengine_pcm.h> -#include <linux/omap-dmaengine.h> #include "sdma-pcm.h" @@ -31,7 +30,6 @@ static const struct snd_pcm_hardware sdma_pcm_hardware = { static const struct snd_dmaengine_pcm_config sdma_dmaengine_pcm_config = { .pcm_hardware = &sdma_pcm_hardware, .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, - .compat_filter_fn = omap_dma_filter_fn, .prealloc_buffer_size = 128 * 1024, }; @@ -39,13 +37,12 @@ int sdma_pcm_platform_register(struct device *dev, char *txdmachan, char *rxdmachan) { struct snd_dmaengine_pcm_config *config; - unsigned int flags = SND_DMAENGINE_PCM_FLAG_COMPAT; + unsigned int flags = 0; /* Standard names for the directions: 'tx' and 'rx' */ if (!txdmachan && !rxdmachan) return devm_snd_dmaengine_pcm_register(dev, - &sdma_dmaengine_pcm_config, - flags); + &sdma_dmaengine_pcm_config, 0); config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL); if (!config) @@ -65,7 +62,7 @@ int sdma_pcm_platform_register(struct device *dev, config->chan_names[0] = txdmachan; config->chan_names[1] = rxdmachan; - return devm_snd_dmaengine_pcm_register(dev, config, flags); + return devm_snd_dmaengine_pcm_register(dev, config, 0); } EXPORT_SYMBOL_GPL(sdma_pcm_platform_register); |