summaryrefslogtreecommitdiff
path: root/sound/soc/loongson
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/loongson')
-rw-r--r--sound/soc/loongson/Kconfig42
-rw-r--r--sound/soc/loongson/Makefile13
-rw-r--r--sound/soc/loongson/loongson1_ac97.c398
-rw-r--r--sound/soc/loongson/loongson_card.c128
-rw-r--r--sound/soc/loongson/loongson_dma.c27
-rw-r--r--sound/soc/loongson/loongson_i2s.c121
-rw-r--r--sound/soc/loongson/loongson_i2s.h24
-rw-r--r--sound/soc/loongson/loongson_i2s_pci.c61
-rw-r--r--sound/soc/loongson/loongson_i2s_plat.c185
9 files changed, 803 insertions, 196 deletions
diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig
index b8d7e2bade24..1a3c28816e7a 100644
--- a/sound/soc/loongson/Kconfig
+++ b/sound/soc/loongson/Kconfig
@@ -1,11 +1,23 @@
# SPDX-License-Identifier: GPL-2.0
menu "SoC Audio for Loongson CPUs"
+
+config SND_SOC_LOONGSON_CARD
+ tristate "Loongson Sound Card Driver"
depends on LOONGARCH || COMPILE_TEST
+ select SND_SOC_LOONGSON_I2S_PCI if PCI
+ select SND_SOC_LOONGSON_I2S_PLATFORM if OF
+ help
+ Say Y or M if you want to add support for SoC audio using
+ loongson I2S controller.
+
+ The driver add support for ALSA SoC Audio support using
+ loongson I2S controller.
config SND_SOC_LOONGSON_I2S_PCI
tristate "Loongson I2S-PCI Device Driver"
- select REGMAP_MMIO
+ depends on LOONGARCH || COMPILE_TEST
depends on PCI
+ select REGMAP_MMIO
help
Say Y or M if you want to add support for I2S driver for
Loongson I2S controller.
@@ -13,15 +25,25 @@ config SND_SOC_LOONGSON_I2S_PCI
The controller is found in loongson bridge chips or SoCs,
and work as a PCI device.
-config SND_SOC_LOONGSON_CARD
- tristate "Loongson Sound Card Driver"
- select SND_SOC_LOONGSON_I2S_PCI
- depends on PCI
+config SND_SOC_LOONGSON_I2S_PLATFORM
+ tristate "Loongson I2S-PLAT Device Driver"
+ depends on LOONGARCH || COMPILE_TEST
+ select REGMAP_MMIO
+ select SND_SOC_GENERIC_DMAENGINE_PCM
help
- Say Y or M if you want to add support for SoC audio using
- loongson I2S controller.
-
- The driver add support for ALSA SoC Audio support using
- loongson I2S controller.
+ Say Y or M if you want to add support for I2S driver for
+ Loongson I2S controller.
+ The controller work as a platform device, we can found it in
+ Loongson-2K1000 SoCs.
endmenu
+
+config SND_LOONGSON1_AC97
+ tristate "Loongson1 AC97 Support"
+ depends on LOONGSON1_APB_DMA
+ select SND_SOC_AC97_CODEC
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select REGMAP_MMIO
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Loongson1 AC97 controller.
diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile
index 601a905a4860..4c6d3130bcee 100644
--- a/sound/soc/loongson/Makefile
+++ b/sound/soc/loongson/Makefile
@@ -1,8 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
#Platform Support
-snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o loongson_dma.o
-obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o
+snd-soc-loongson-i2s-pci-y := loongson_i2s_pci.o loongson_dma.o
+obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o snd-soc-loongson-i2s.o
+
+snd-soc-loongson-i2s-plat-y := loongson_i2s_plat.o
+obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PLATFORM) += snd-soc-loongson-i2s-plat.o snd-soc-loongson-i2s.o
+
+snd-soc-loongson-i2s-y := loongson_i2s.o
+
+obj-$(CONFIG_SND_LOONGSON1_AC97) += loongson1_ac97.o
#Machine Support
-snd-soc-loongson-card-objs := loongson_card.o
+snd-soc-loongson-card-y := loongson_card.o
obj-$(CONFIG_SND_SOC_LOONGSON_CARD) += snd-soc-loongson-card.o
diff --git a/sound/soc/loongson/loongson1_ac97.c b/sound/soc/loongson/loongson1_ac97.c
new file mode 100644
index 000000000000..84901900ad43
--- /dev/null
+++ b/sound/soc/loongson/loongson1_ac97.c
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AC97 Controller Driver for Loongson-1 SoC
+ *
+ * Copyright (C) 2025 Keguang Zhang <keguang.zhang@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+/* Loongson-1 AC97 Controller Registers */
+#define AC97_CSR 0x0
+#define AC97_OCC0 0x4
+#define AC97_ICC 0x10
+#define AC97_CRAC 0x18
+#define AC97_INTRAW 0x54
+#define AC97_INTM 0x58
+#define AC97_INT_CW_CLR 0x68
+#define AC97_INT_CR_CLR 0x6c
+
+/* Control Status Register Bits (CSR) */
+#define CSR_RESUME BIT(1)
+#define CSR_RST_FORCE BIT(0)
+
+/* MIC Channel Configuration Bits */
+#define M_DMA_EN BIT(22)
+#define M_FIFO_THRES GENMASK(21, 20)
+#define M_FIFO_THRES_FULL FIELD_PREP(M_FIFO_THRES, 3)
+#define M_FIFO_THRES_HALF FIELD_PREP(M_FIFO_THRES, 1)
+#define M_FIFO_THRES_QUARTER FIELD_PREP(M_FIFO_THRES, 0)
+#define M_SW GENMASK(19, 18)
+#define M_SW_16_BITS FIELD_PREP(M_SW, 2)
+#define M_SW_8_BITS FIELD_PREP(M_SW, 0)
+#define M_VSR BIT(17)
+#define M_CH_EN BIT(16)
+/* Right Channel Configuration Bits */
+#define R_DMA_EN BIT(14)
+#define R_FIFO_THRES GENMASK(13, 12)
+#define R_FIFO_THRES_EMPTY FIELD_PREP(R_FIFO_THRES, 3)
+#define R_FIFO_THRES_HALF FIELD_PREP(R_FIFO_THRES, 1)
+#define R_FIFO_THRES_QUARTER FIELD_PREP(R_FIFO_THRES, 0)
+#define R_SW GENMASK(11, 10)
+#define R_SW_16_BITS FIELD_PREP(R_SW, 2)
+#define R_SW_8_BITS FIELD_PREP(R_SW, 0)
+#define R_VSR BIT(9)
+#define R_CH_EN BIT(8)
+/* Left Channel Configuration Bits */
+#define L_DMA_EN BIT(6)
+#define L_FIFO_THRES GENMASK(5, 4)
+#define L_FIFO_THRES_EMPTY FIELD_PREP(L_FIFO_THRES, 3)
+#define L_FIFO_THRES_HALF FIELD_PREP(L_FIFO_THRES, 1)
+#define L_FIFO_THRES_QUARTER FIELD_PREP(L_FIFO_THRES, 0)
+#define L_SW GENMASK(3, 2)
+#define L_SW_16_BITS FIELD_PREP(L_SW, 2)
+#define L_SW_8_BITS FIELD_PREP(L_SW, 0)
+#define L_VSR BIT(1)
+#define L_CH_EN BIT(0)
+
+/* Codec Register Access Command Bits (CRAC) */
+#define CODEC_WR BIT(31)
+#define CODEC_ADR GENMASK(22, 16)
+#define CODEC_DAT GENMASK(15, 0)
+
+/* Interrupt Register (INTRAW) */
+#define CW_DONE BIT(1)
+#define CR_DONE BIT(0)
+
+#define LS1X_AC97_DMA_TX_EN BIT(31)
+#define LS1X_AC97_DMA_STEREO BIT(30)
+#define LS1X_AC97_DMA_TX_BYTES GENMASK(29, 28)
+#define LS1X_AC97_DMA_TX_4_BYTES FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 2)
+#define LS1X_AC97_DMA_TX_2_BYTES FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 1)
+#define LS1X_AC97_DMA_TX_1_BYTE FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 0)
+#define LS1X_AC97_DMA_DADDR_MASK GENMASK(27, 0)
+
+#define LS1X_AC97_DMA_FIFO_SIZE 128
+
+#define LS1X_AC97_TIMEOUT 3000
+
+struct ls1x_ac97 {
+ void __iomem *reg_base;
+ struct regmap *regmap;
+ dma_addr_t tx_dma_base;
+ dma_addr_t rx_dma_base;
+ struct snd_dmaengine_dai_dma_data capture_dma_data;
+ struct snd_dmaengine_dai_dma_data playback_dma_data;
+};
+
+static struct ls1x_ac97 *ls1x_ac97;
+
+static const struct regmap_config ls1x_ac97_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static void ls1x_ac97_reset(struct snd_ac97 *ac97)
+{
+ int val;
+
+ regmap_write(ls1x_ac97->regmap, AC97_CSR, CSR_RST_FORCE);
+ regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val,
+ !(val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT);
+}
+
+static void ls1x_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val)
+{
+ int tmp, ret;
+
+ tmp = FIELD_PREP(CODEC_ADR, reg) | FIELD_PREP(CODEC_DAT, val);
+ regmap_write(ls1x_ac97->regmap, AC97_CRAC, tmp);
+ ret = regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_INTRAW, tmp,
+ (tmp & CW_DONE), 0, LS1X_AC97_TIMEOUT);
+ if (ret)
+ pr_err("timeout on AC97 write! %d\n", ret);
+
+ regmap_read(ls1x_ac97->regmap, AC97_INT_CW_CLR, &ret);
+}
+
+static unsigned short ls1x_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+ int val, ret;
+
+ val = CODEC_WR | FIELD_PREP(CODEC_ADR, reg);
+ regmap_write(ls1x_ac97->regmap, AC97_CRAC, val);
+ ret = regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_INTRAW, val,
+ (val & CR_DONE), 0, LS1X_AC97_TIMEOUT);
+ if (ret) {
+ pr_err("timeout on AC97 read! %d\n", ret);
+ return ret;
+ }
+
+ regmap_read(ls1x_ac97->regmap, AC97_INT_CR_CLR, &ret);
+ regmap_read(ls1x_ac97->regmap, AC97_CRAC, &ret);
+
+ return (ret & CODEC_DAT);
+}
+
+static void ls1x_ac97_init(struct snd_ac97 *ac97)
+{
+ writel(0, ls1x_ac97->reg_base + AC97_INTRAW);
+ writel(0, ls1x_ac97->reg_base + AC97_INTM);
+
+ /* Config output channels */
+ regmap_update_bits(ls1x_ac97->regmap, AC97_OCC0,
+ R_DMA_EN | R_FIFO_THRES | R_CH_EN |
+ L_DMA_EN | L_FIFO_THRES | L_CH_EN,
+ R_DMA_EN | R_FIFO_THRES_EMPTY | R_CH_EN |
+ L_DMA_EN | L_FIFO_THRES_EMPTY | L_CH_EN);
+
+ /* Config inputs channel */
+ regmap_update_bits(ls1x_ac97->regmap, AC97_ICC,
+ M_DMA_EN | M_FIFO_THRES | M_CH_EN |
+ R_DMA_EN | R_FIFO_THRES | R_CH_EN |
+ L_DMA_EN | L_FIFO_THRES | L_CH_EN,
+ M_DMA_EN | M_FIFO_THRES_FULL | M_CH_EN |
+ R_DMA_EN | R_FIFO_THRES_EMPTY | R_CH_EN |
+ L_DMA_EN | L_FIFO_THRES_EMPTY | L_CH_EN);
+
+ if (ac97->ext_id & AC97_EI_VRA) {
+ regmap_update_bits(ls1x_ac97->regmap, AC97_OCC0, R_VSR | L_VSR, R_VSR | L_VSR);
+ regmap_update_bits(ls1x_ac97->regmap, AC97_ICC, M_VSR, M_VSR);
+ }
+}
+
+static struct snd_ac97_bus_ops ls1x_ac97_ops = {
+ .reset = ls1x_ac97_reset,
+ .write = ls1x_ac97_write,
+ .read = ls1x_ac97_read,
+ .init = ls1x_ac97_init,
+};
+
+static int ls1x_ac97_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct ls1x_ac97 *ac97 = dev_get_drvdata(cpu_dai->dev);
+ struct snd_dmaengine_dai_dma_data *dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ switch (params_channels(params)) {
+ case 1:
+ dma_data->addr &= ~LS1X_AC97_DMA_STEREO;
+ break;
+ case 2:
+ dma_data->addr |= LS1X_AC97_DMA_STEREO;
+ break;
+ default:
+ dev_err(cpu_dai->dev, "unsupported channels! %d\n", params_channels(params));
+ return -EINVAL;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ case SNDRV_PCM_FORMAT_U8:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ regmap_update_bits(ac97->regmap, AC97_OCC0,
+ R_SW | L_SW,
+ R_SW_8_BITS | L_SW_8_BITS);
+ else
+ regmap_update_bits(ac97->regmap, AC97_ICC,
+ M_SW | R_SW | L_SW,
+ M_SW_8_BITS | R_SW_8_BITS | L_SW_8_BITS);
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ case SNDRV_PCM_FORMAT_U16_LE:
+ case SNDRV_PCM_FORMAT_S16_BE:
+ case SNDRV_PCM_FORMAT_U16_BE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ regmap_update_bits(ac97->regmap, AC97_OCC0,
+ R_SW | L_SW,
+ R_SW_16_BITS | L_SW_16_BITS);
+ else
+ regmap_update_bits(ac97->regmap, AC97_ICC,
+ M_SW | R_SW | L_SW,
+ M_SW_16_BITS | R_SW_16_BITS | L_SW_16_BITS);
+ break;
+ default:
+ dev_err(cpu_dai->dev, "unsupported format! %d\n", params_format(params));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1x_ac97_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+ struct ls1x_ac97 *ac97 = dev_get_drvdata(cpu_dai->dev);
+
+ ac97->capture_dma_data.addr = ac97->rx_dma_base & LS1X_AC97_DMA_DADDR_MASK;
+ ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ ac97->capture_dma_data.fifo_size = LS1X_AC97_DMA_FIFO_SIZE;
+
+ ac97->playback_dma_data.addr = ac97->tx_dma_base & LS1X_AC97_DMA_DADDR_MASK;
+ ac97->playback_dma_data.addr |= LS1X_AC97_DMA_TX_4_BYTES;
+ ac97->playback_dma_data.addr |= LS1X_AC97_DMA_TX_EN;
+ ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ ac97->playback_dma_data.fifo_size = LS1X_AC97_DMA_FIFO_SIZE;
+
+ snd_soc_dai_init_dma_data(cpu_dai, &ac97->playback_dma_data, &ac97->capture_dma_data);
+ snd_soc_dai_set_drvdata(cpu_dai, ac97);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops ls1x_ac97_dai_ops = {
+ .probe = ls1x_ac97_dai_probe,
+ .hw_params = ls1x_ac97_hw_params,
+};
+
+#define LS1X_AC97_FMTS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |\
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\
+ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE)
+
+static struct snd_soc_dai_driver ls1x_ac97_dai[] = {
+ {
+ .name = "ls1x-ac97",
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = LS1X_AC97_FMTS,
+ },
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = LS1X_AC97_FMTS,
+ },
+ .ops = &ls1x_ac97_dai_ops,
+ },
+};
+
+static const struct snd_soc_component_driver ls1x_ac97_component = {
+ .name = KBUILD_MODNAME,
+ .legacy_dai_naming = 1,
+};
+
+static int ls1x_ac97_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ls1x_ac97 *ac97;
+ struct resource *res;
+ int ret;
+
+ ac97 = devm_kzalloc(dev, sizeof(struct ls1x_ac97), GFP_KERNEL);
+ if (!ac97)
+ return -ENOMEM;
+ ls1x_ac97 = ac97;
+ platform_set_drvdata(pdev, ac97);
+
+ ac97->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ac97->reg_base))
+ return PTR_ERR(ac97->reg_base);
+
+ ac97->regmap = devm_regmap_init_mmio(dev, ac97->reg_base, &ls1x_ac97_regmap_config);
+ if (IS_ERR(ac97->regmap))
+ return dev_err_probe(dev, PTR_ERR(ac97->regmap), "devm_regmap_init_mmio failed\n");
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-tx");
+ if (!res)
+ return dev_err_probe(dev, -EINVAL, "Missing 'audio-tx' in reg-names property\n");
+
+ ac97->tx_dma_base = dma_map_resource(dev, res->start, resource_size(res),
+ DMA_TO_DEVICE, 0);
+ if (dma_mapping_error(dev, ac97->tx_dma_base))
+ return -ENXIO;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-rx");
+ if (!res)
+ return dev_err_probe(dev, -EINVAL, "Missing 'audio-rx' in reg-names property\n");
+
+ ac97->rx_dma_base = dma_map_resource(dev, res->start, resource_size(res),
+ DMA_FROM_DEVICE, 0);
+ if (dma_mapping_error(dev, ac97->rx_dma_base))
+ return -ENXIO;
+
+ ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0);
+ if (ret)
+ dev_err_probe(dev, ret, "failed to register PCM\n");
+
+ ret = devm_snd_soc_register_component(dev, &ls1x_ac97_component,
+ ls1x_ac97_dai, ARRAY_SIZE(ls1x_ac97_dai));
+ if (ret)
+ dev_err_probe(dev, ret, "failed to register DAI\n");
+
+ return snd_soc_set_ac97_ops(&ls1x_ac97_ops);
+}
+
+static void ls1x_ac97_remove(struct platform_device *pdev)
+{
+ ls1x_ac97 = NULL;
+ snd_soc_set_ac97_ops(NULL);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ls1x_ac97_suspend(struct device *dev)
+{
+ int val;
+
+ regmap_clear_bits(ls1x_ac97->regmap, AC97_OCC0, R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
+ regmap_clear_bits(ls1x_ac97->regmap, AC97_ICC,
+ M_DMA_EN | M_CH_EN | R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
+ regmap_set_bits(ls1x_ac97->regmap, AC97_CSR, CSR_RESUME);
+
+ return regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val,
+ (val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT);
+}
+
+static int ls1x_ac97_resume(struct device *dev)
+{
+ int val;
+
+ regmap_set_bits(ls1x_ac97->regmap, AC97_OCC0, R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
+ regmap_set_bits(ls1x_ac97->regmap, AC97_ICC,
+ M_DMA_EN | M_CH_EN | R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
+ regmap_set_bits(ls1x_ac97->regmap, AC97_CSR, CSR_RESUME);
+
+ return regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val,
+ !(val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT);
+}
+#endif
+
+static const struct dev_pm_ops ls1x_ac97_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ls1x_ac97_suspend, ls1x_ac97_resume)
+};
+
+static const struct of_device_id ls1x_ac97_match[] = {
+ { .compatible = "loongson,ls1b-ac97" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ls1x_ac97_match);
+
+static struct platform_driver ls1x_ac97_driver = {
+ .probe = ls1x_ac97_probe,
+ .remove = ls1x_ac97_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = ls1x_ac97_match,
+ .pm = &ls1x_ac97_pm_ops,
+ },
+};
+
+module_platform_driver(ls1x_ac97_driver);
+
+MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
+MODULE_DESCRIPTION("Loongson-1 AC97 Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c
index e8432d466f60..7910d5d9ac4f 100644
--- a/sound/soc/loongson/loongson_card.c
+++ b/sound/soc/loongson/loongson_card.c
@@ -23,28 +23,28 @@ struct loongson_card_data {
static int loongson_card_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
- struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct loongson_card_data *ls_card = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
int ret, mclk;
- if (ls_card->mclk_fs) {
- mclk = ls_card->mclk_fs * params_rate(params);
- ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk,
- SND_SOC_CLOCK_OUT);
- if (ret < 0) {
- dev_err(codec_dai->dev, "cpu_dai clock not set\n");
- return ret;
- }
+ if (!ls_card->mclk_fs)
+ return 0;
- ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
- SND_SOC_CLOCK_IN);
- if (ret < 0) {
- dev_err(codec_dai->dev, "codec_dai clock not set\n");
- return ret;
- }
+ mclk = ls_card->mclk_fs * params_rate(params);
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "cpu_dai clock not set\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "codec_dai clock not set\n");
+ return ret;
}
+
return 0;
}
@@ -68,46 +68,53 @@ static struct snd_soc_dai_link loongson_dai_links[] = {
},
};
-static int loongson_card_parse_acpi(struct loongson_card_data *data)
+static struct acpi_device *loongson_card_acpi_find_device(struct snd_soc_card *card,
+ const char *name)
{
- struct snd_soc_card *card = &data->snd_card;
struct fwnode_handle *fwnode = card->dev->fwnode;
struct fwnode_reference_args args;
+ int status;
+
+ memset(&args, 0, sizeof(args));
+ status = acpi_node_get_property_reference(fwnode, name, 0, &args);
+ if (status || !is_acpi_device_node(args.fwnode)) {
+ dev_err(card->dev, "No matching phy in ACPI table\n");
+ return NULL;
+ }
+
+ return to_acpi_device_node(args.fwnode);
+}
+
+static int loongson_card_parse_acpi(struct loongson_card_data *data)
+{
+ struct snd_soc_card *card = &data->snd_card;
const char *codec_dai_name;
struct acpi_device *adev;
struct device *phy_dev;
- int ret, i;
+ int i;
/* fixup platform name based on reference node */
- memset(&args, 0, sizeof(args));
- ret = acpi_node_get_property_reference(fwnode, "cpu", 0, &args);
- if (ret || !is_acpi_device_node(args.fwnode)) {
- dev_err(card->dev, "No matching phy in ACPI table\n");
- return ret ?: -ENOENT;
- }
- adev = to_acpi_device_node(args.fwnode);
+ adev = loongson_card_acpi_find_device(card, "cpu");
+ if (!adev)
+ return -ENOENT;
+
phy_dev = acpi_get_first_physical_node(adev);
if (!phy_dev)
return -EPROBE_DEFER;
- for (i = 0; i < card->num_links; i++)
- loongson_dai_links[i].platforms->name = dev_name(phy_dev);
/* fixup codec name based on reference node */
- memset(&args, 0, sizeof(args));
- ret = acpi_node_get_property_reference(fwnode, "codec", 0, &args);
- if (ret || !is_acpi_device_node(args.fwnode)) {
- dev_err(card->dev, "No matching phy in ACPI table\n");
- return ret ?: -ENOENT;
- }
- adev = to_acpi_device_node(args.fwnode);
+ adev = loongson_card_acpi_find_device(card, "codec");
+ if (!adev)
+ return -ENOENT;
snprintf(codec_name, sizeof(codec_name), "i2c-%s", acpi_dev_name(adev));
- for (i = 0; i < card->num_links; i++)
- loongson_dai_links[i].codecs->name = codec_name;
- device_property_read_string(card->dev, "codec-dai-name",
- &codec_dai_name);
- for (i = 0; i < card->num_links; i++)
+ device_property_read_string(card->dev, "codec-dai-name", &codec_dai_name);
+
+ for (i = 0; i < card->num_links; i++) {
+ loongson_dai_links[i].platforms->name = dev_name(phy_dev);
+ loongson_dai_links[i].codecs->name = codec_name;
loongson_dai_links[i].codecs->dai_name = codec_dai_name;
+ }
return 0;
}
@@ -127,8 +134,8 @@ static int loongson_card_parse_of(struct loongson_card_data *data)
codec = of_get_child_by_name(dev->of_node, "codec");
if (!codec) {
dev_err(dev, "audio-codec property missing or invalid\n");
- ret = -EINVAL;
- goto err;
+ of_node_put(cpu);
+ return -EINVAL;
}
for (i = 0; i < card->num_links; i++) {
@@ -137,6 +144,7 @@ static int loongson_card_parse_of(struct loongson_card_data *data)
dev_err(dev, "getting cpu dlc error (%d)\n", ret);
goto err;
}
+ loongson_dai_links[i].platforms->of_node = loongson_dai_links[i].cpus->of_node;
ret = snd_soc_of_get_dlc(codec, NULL, loongson_dai_links[i].codecs, 0);
if (ret < 0) {
@@ -159,42 +167,36 @@ err:
static int loongson_asoc_card_probe(struct platform_device *pdev)
{
struct loongson_card_data *ls_priv;
+ struct device *dev = &pdev->dev;
struct snd_soc_card *card;
int ret;
- ls_priv = devm_kzalloc(&pdev->dev, sizeof(*ls_priv), GFP_KERNEL);
+ ls_priv = devm_kzalloc(dev, sizeof(*ls_priv), GFP_KERNEL);
if (!ls_priv)
return -ENOMEM;
card = &ls_priv->snd_card;
- card->dev = &pdev->dev;
+ card->dev = dev;
card->owner = THIS_MODULE;
card->dai_link = loongson_dai_links;
card->num_links = ARRAY_SIZE(loongson_dai_links);
snd_soc_card_set_drvdata(card, ls_priv);
- ret = device_property_read_string(&pdev->dev, "model", &card->name);
- if (ret) {
- dev_err(&pdev->dev, "Error parsing card name: %d\n", ret);
- return ret;
- }
- ret = device_property_read_u32(&pdev->dev, "mclk-fs", &ls_priv->mclk_fs);
- if (ret) {
- dev_err(&pdev->dev, "Error parsing mclk-fs: %d\n", ret);
- return ret;
- }
+ ret = device_property_read_string(dev, "model", &card->name);
+ if (ret)
+ return dev_err_probe(dev, ret, "Error parsing card name\n");
- if (has_acpi_companion(&pdev->dev))
- ret = loongson_card_parse_acpi(ls_priv);
- else
- ret = loongson_card_parse_of(ls_priv);
- if (ret < 0)
- return ret;
+ ret = device_property_read_u32(dev, "mclk-fs", &ls_priv->mclk_fs);
+ if (ret)
+ return dev_err_probe(dev, ret, "Error parsing mclk-fs\n");
- ret = devm_snd_soc_register_card(&pdev->dev, card);
+ ret = has_acpi_companion(dev) ? loongson_card_parse_acpi(ls_priv)
+ : loongson_card_parse_of(ls_priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "Error parsing acpi/of properties\n");
- return ret;
+ return devm_snd_soc_register_card(dev, card);
}
static const struct of_device_id loongson_asoc_dt_ids[] = {
diff --git a/sound/soc/loongson/loongson_dma.c b/sound/soc/loongson/loongson_dma.c
index 8090662e8ff2..20e4a0641340 100644
--- a/sound/soc/loongson/loongson_dma.c
+++ b/sound/soc/loongson/loongson_dma.c
@@ -17,11 +17,11 @@
#include "loongson_i2s.h"
/* DMA dma_order Register */
-#define DMA_ORDER_STOP (1 << 4) /* DMA stop */
-#define DMA_ORDER_START (1 << 3) /* DMA start */
-#define DMA_ORDER_ASK_VALID (1 << 2) /* DMA ask valid flag */
-#define DMA_ORDER_AXI_UNCO (1 << 1) /* Uncache access */
-#define DMA_ORDER_ADDR_64 (1 << 0) /* 64bits address support */
+#define DMA_ORDER_STOP BIT(4) /* DMA stop */
+#define DMA_ORDER_START BIT(3) /* DMA start */
+#define DMA_ORDER_ASK_VALID BIT(2) /* DMA ask valid flag */
+#define DMA_ORDER_AXI_UNCO BIT(1) /* Uncache access */
+#define DMA_ORDER_ADDR_64 BIT(0) /* 64bits address support */
#define DMA_ORDER_ASK_MASK (~0x1fUL) /* Ask addr mask */
#define DMA_ORDER_CTRL_MASK (0x0fUL) /* Control mask */
@@ -95,7 +95,6 @@ static int loongson_pcm_trigger(struct snd_soc_component *component,
struct device *dev = substream->pcm->card->dev;
void __iomem *order_reg = prtd->dma_data->order_addr;
u64 val;
- int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
@@ -129,7 +128,7 @@ static int loongson_pcm_trigger(struct snd_soc_component *component,
return -EINVAL;
}
- return ret;
+ return 0;
}
static int loongson_pcm_hw_params(struct snd_soc_component *component,
@@ -226,11 +225,10 @@ static int loongson_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_card *card = substream->pcm->card;
struct loongson_runtime_data *prtd;
struct loongson_dma_data *dma_data;
- int ret;
/*
* For mysterious reasons (and despite what the manual says)
@@ -252,20 +250,17 @@ static int loongson_pcm_open(struct snd_soc_component *component,
prtd->dma_desc_arr = dma_alloc_coherent(card->dev, PAGE_SIZE,
&prtd->dma_desc_arr_phy,
GFP_KERNEL);
- if (!prtd->dma_desc_arr) {
- ret = -ENOMEM;
+ if (!prtd->dma_desc_arr)
goto desc_err;
- }
+
prtd->dma_desc_arr_size = PAGE_SIZE / sizeof(*prtd->dma_desc_arr);
prtd->dma_pos_desc = dma_alloc_coherent(card->dev,
sizeof(*prtd->dma_pos_desc),
&prtd->dma_pos_desc_phy,
GFP_KERNEL);
- if (!prtd->dma_pos_desc) {
- ret = -ENOMEM;
+ if (!prtd->dma_pos_desc)
goto pos_err;
- }
dma_data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
prtd->dma_data = dma_data;
@@ -279,7 +274,7 @@ pos_err:
desc_err:
kfree(prtd);
- return ret;
+ return -ENOMEM;
}
static int loongson_pcm_close(struct snd_soc_component *component,
diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c
index d45228a3a558..e336656e13eb 100644
--- a/sound/soc/loongson/loongson_i2s.c
+++ b/sound/soc/loongson/loongson_i2s.c
@@ -9,6 +9,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
+#include <linux/export.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
#include <sound/soc.h>
@@ -21,34 +22,33 @@
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE)
+#define LOONGSON_I2S_TX_ENABLE (I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN)
+#define LOONGSON_I2S_RX_ENABLE (I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN)
+
+#define LOONGSON_I2S_DEF_DELAY 10
+#define LOONGSON_I2S_DEF_TIMEOUT 500000
+
static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ unsigned int mask;
int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
- I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN,
- I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN);
- else
- regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
- I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN,
- I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN);
+ mask = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ LOONGSON_I2S_TX_ENABLE : LOONGSON_I2S_RX_ENABLE;
+ regmap_update_bits(i2s->regmap, LS_I2S_CTRL, mask, mask);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
- I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 0);
- else
- regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
- I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 0);
+ mask = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ LOONGSON_I2S_TX_ENABLE : LOONGSON_I2S_RX_ENABLE;
+ regmap_update_bits(i2s->regmap, LS_I2S_CTRL, mask, 0);
break;
default:
ret = -EINVAL;
@@ -123,10 +123,40 @@ static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
return 0;
}
+static int loongson_i2s_enable_mclk(struct loongson_i2s *i2s)
+{
+ u32 val;
+
+ if (i2s->rev_id == 0)
+ return 0;
+
+ regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+ I2S_CTRL_MCLK_EN, I2S_CTRL_MCLK_EN);
+
+ return regmap_read_poll_timeout_atomic(i2s->regmap,
+ LS_I2S_CTRL, val,
+ val & I2S_CTRL_MCLK_READY,
+ LOONGSON_I2S_DEF_DELAY,
+ LOONGSON_I2S_DEF_TIMEOUT);
+}
+
+static int loongson_i2s_enable_bclk(struct loongson_i2s *i2s)
+{
+ u32 val;
+
+ if (i2s->rev_id == 0)
+ return 0;
+
+ return regmap_read_poll_timeout_atomic(i2s->regmap,
+ LS_I2S_CTRL, val,
+ val & I2S_CTRL_CLK_READY,
+ LOONGSON_I2S_DEF_DELAY,
+ LOONGSON_I2S_DEF_TIMEOUT);
+}
+
static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
- u32 val;
int ret;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
@@ -148,54 +178,29 @@ static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
/* Enable master mode */
regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
I2S_CTRL_MASTER);
- if (i2s->rev_id == 1) {
- ret = regmap_read_poll_timeout_atomic(i2s->regmap,
- LS_I2S_CTRL, val,
- val & I2S_CTRL_CLK_READY,
- 10, 500000);
- if (ret < 0)
- dev_warn(dai->dev, "wait BCLK ready timeout\n");
- }
+ ret = loongson_i2s_enable_bclk(i2s);
+ if (ret < 0)
+ dev_warn(dai->dev, "wait BCLK ready timeout\n");
break;
case SND_SOC_DAIFMT_BC_FP:
/* Enable MCLK */
- if (i2s->rev_id == 1) {
- regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
- I2S_CTRL_MCLK_EN,
- I2S_CTRL_MCLK_EN);
- ret = regmap_read_poll_timeout_atomic(i2s->regmap,
- LS_I2S_CTRL, val,
- val & I2S_CTRL_MCLK_READY,
- 10, 500000);
- if (ret < 0)
- dev_warn(dai->dev, "wait MCLK ready timeout\n");
- }
+ ret = loongson_i2s_enable_mclk(i2s);
+ if (ret < 0)
+ dev_warn(dai->dev, "wait MCLK ready timeout\n");
break;
case SND_SOC_DAIFMT_BP_FP:
/* Enable MCLK */
- if (i2s->rev_id == 1) {
- regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
- I2S_CTRL_MCLK_EN,
- I2S_CTRL_MCLK_EN);
- ret = regmap_read_poll_timeout_atomic(i2s->regmap,
- LS_I2S_CTRL, val,
- val & I2S_CTRL_MCLK_READY,
- 10, 500000);
- if (ret < 0)
- dev_warn(dai->dev, "wait MCLK ready timeout\n");
- }
+ ret = loongson_i2s_enable_mclk(i2s);
+ if (ret < 0)
+ dev_warn(dai->dev, "wait MCLK ready timeout\n");
/* Enable master mode */
regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
I2S_CTRL_MASTER);
- if (i2s->rev_id == 1) {
- ret = regmap_read_poll_timeout_atomic(i2s->regmap,
- LS_I2S_CTRL, val,
- val & I2S_CTRL_CLK_READY,
- 10, 500000);
- if (ret < 0)
- dev_warn(dai->dev, "wait BCLK ready timeout\n");
- }
+
+ ret = loongson_i2s_enable_bclk(i2s);
+ if (ret < 0)
+ dev_warn(dai->dev, "wait BCLK ready timeout\n");
break;
default:
return -EINVAL;
@@ -242,6 +247,7 @@ struct snd_soc_dai_driver loongson_i2s_dai = {
.ops = &loongson_i2s_dai_ops,
.symmetric_rate = 1,
};
+EXPORT_SYMBOL_GPL(loongson_i2s_dai);
static int i2s_suspend(struct device *dev)
{
@@ -255,15 +261,16 @@ static int i2s_suspend(struct device *dev)
static int i2s_resume(struct device *dev)
{
struct loongson_i2s *i2s = dev_get_drvdata(dev);
- int ret;
regcache_cache_only(i2s->regmap, false);
regcache_mark_dirty(i2s->regmap);
- ret = regcache_sync(i2s->regmap);
-
- return ret;
+ return regcache_sync(i2s->regmap);
}
const struct dev_pm_ops loongson_i2s_pm = {
SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)
};
+EXPORT_SYMBOL_GPL(loongson_i2s_pm);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Common functions for loongson I2S controller driver");
diff --git a/sound/soc/loongson/loongson_i2s.h b/sound/soc/loongson/loongson_i2s.h
index 89492eebf834..c8052a762c1b 100644
--- a/sound/soc/loongson/loongson_i2s.h
+++ b/sound/soc/loongson/loongson_i2s.h
@@ -27,18 +27,18 @@
#define LS_I2S_RX_ORDER 0x110 /* RX DMA Order */
/* Loongson I2S Control Register */
-#define I2S_CTRL_MCLK_READY (1 << 16) /* MCLK ready */
-#define I2S_CTRL_MASTER (1 << 15) /* Master mode */
-#define I2S_CTRL_MSB (1 << 14) /* MSB bit order */
-#define I2S_CTRL_RX_EN (1 << 13) /* RX enable */
-#define I2S_CTRL_TX_EN (1 << 12) /* TX enable */
-#define I2S_CTRL_RX_DMA_EN (1 << 11) /* DMA RX enable */
-#define I2S_CTRL_CLK_READY (1 << 8) /* BCLK ready */
-#define I2S_CTRL_TX_DMA_EN (1 << 7) /* DMA TX enable */
-#define I2S_CTRL_RESET (1 << 4) /* Controller soft reset */
-#define I2S_CTRL_MCLK_EN (1 << 3) /* Enable MCLK */
-#define I2S_CTRL_RX_INT_EN (1 << 1) /* RX interrupt enable */
-#define I2S_CTRL_TX_INT_EN (1 << 0) /* TX interrupt enable */
+#define I2S_CTRL_MCLK_READY BIT(16) /* MCLK ready */
+#define I2S_CTRL_MASTER BIT(15) /* Master mode */
+#define I2S_CTRL_MSB BIT(14) /* MSB bit order */
+#define I2S_CTRL_RX_EN BIT(13) /* RX enable */
+#define I2S_CTRL_TX_EN BIT(12) /* TX enable */
+#define I2S_CTRL_RX_DMA_EN BIT(11) /* DMA RX enable */
+#define I2S_CTRL_CLK_READY BIT(8) /* BCLK ready */
+#define I2S_CTRL_TX_DMA_EN BIT(7) /* DMA TX enable */
+#define I2S_CTRL_RESET BIT(4) /* Controller soft reset */
+#define I2S_CTRL_MCLK_EN BIT(3) /* Enable MCLK */
+#define I2S_CTRL_RX_INT_EN BIT(1) /* RX interrupt enable */
+#define I2S_CTRL_TX_INT_EN BIT(0) /* TX interrupt enable */
#define LS_I2S_DRVNAME "loongson-i2s"
diff --git a/sound/soc/loongson/loongson_i2s_pci.c b/sound/soc/loongson/loongson_i2s_pci.c
index fa90361865c6..1ea5501a97f8 100644
--- a/sound/soc/loongson/loongson_i2s_pci.c
+++ b/sound/soc/loongson/loongson_i2s_pci.c
@@ -16,6 +16,8 @@
#include "loongson_i2s.h"
#include "loongson_dma.h"
+#define DRIVER_NAME "loongson-i2s-pci"
+
static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
@@ -75,34 +77,33 @@ static int loongson_i2s_pci_probe(struct pci_dev *pdev,
{
const struct fwnode_handle *fwnode = pdev->dev.fwnode;
struct loongson_dma_data *tx_data, *rx_data;
+ struct device *dev = &pdev->dev;
struct loongson_i2s *i2s;
int ret;
if (pcim_enable_device(pdev)) {
- dev_err(&pdev->dev, "pci_enable_device failed\n");
+ dev_err(dev, "pci_enable_device failed\n");
return -ENODEV;
}
- i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
+ i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL);
if (!i2s)
return -ENOMEM;
i2s->rev_id = pdev->revision;
- i2s->dev = &pdev->dev;
+ i2s->dev = dev;
pci_set_drvdata(pdev, i2s);
- ret = pcim_iomap_regions(pdev, 1 << 0, dev_name(&pdev->dev));
- if (ret < 0) {
- dev_err(&pdev->dev, "iomap_regions failed\n");
- return ret;
+ i2s->reg_base = pcim_iomap_region(pdev, 0, DRIVER_NAME);
+ if (IS_ERR(i2s->reg_base)) {
+ dev_err(dev, "iomap_region failed\n");
+ return PTR_ERR(i2s->reg_base);
}
- i2s->reg_base = pcim_iomap_table(pdev)[0];
- i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base,
+
+ i2s->regmap = devm_regmap_init_mmio(dev, i2s->reg_base,
&loongson_i2s_regmap_config);
- if (IS_ERR(i2s->regmap)) {
- dev_err(&pdev->dev, "regmap_init_mmio failed\n");
- return PTR_ERR(i2s->regmap);
- }
+ if (IS_ERR(i2s->regmap))
+ return dev_err_probe(dev, PTR_ERR(i2s->regmap), "regmap_init_mmio failed\n");
tx_data = &i2s->tx_dma_data;
rx_data = &i2s->rx_dma_data;
@@ -114,37 +115,28 @@ static int loongson_i2s_pci_probe(struct pci_dev *pdev,
rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER;
tx_data->irq = fwnode_irq_get_byname(fwnode, "tx");
- if (tx_data->irq < 0) {
- dev_err(&pdev->dev, "dma tx irq invalid\n");
- return tx_data->irq;
- }
+ if (tx_data->irq < 0)
+ return dev_err_probe(dev, tx_data->irq, "dma tx irq invalid\n");
rx_data->irq = fwnode_irq_get_byname(fwnode, "rx");
- if (rx_data->irq < 0) {
- dev_err(&pdev->dev, "dma rx irq invalid\n");
- return rx_data->irq;
- }
+ if (rx_data->irq < 0)
+ return dev_err_probe(dev, rx_data->irq, "dma rx irq invalid\n");
- device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate);
- if (!i2s->clk_rate) {
- dev_err(&pdev->dev, "clock-frequency property invalid\n");
- return -EINVAL;
- }
+ ret = device_property_read_u32(dev, "clock-frequency", &i2s->clk_rate);
+ if (ret)
+ return dev_err_probe(dev, ret, "clock-frequency property invalid\n");
- dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (i2s->rev_id == 1) {
regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET);
udelay(200);
}
- ret = devm_snd_soc_register_component(&pdev->dev,
- &loongson_i2s_component,
+ ret = devm_snd_soc_register_component(dev, &loongson_i2s_component,
&loongson_i2s_dai, 1);
- if (ret) {
- dev_err(&pdev->dev, "register DAI failed %d\n", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "register DAI failed\n");
return 0;
}
@@ -156,11 +148,10 @@ static const struct pci_device_id loongson_i2s_ids[] = {
MODULE_DEVICE_TABLE(pci, loongson_i2s_ids);
static struct pci_driver loongson_i2s_driver = {
- .name = "loongson-i2s-pci",
+ .name = DRIVER_NAME,
.id_table = loongson_i2s_ids,
.probe = loongson_i2s_pci_probe,
.driver = {
- .owner = THIS_MODULE,
.pm = pm_sleep_ptr(&loongson_i2s_pm),
},
};
diff --git a/sound/soc/loongson/loongson_i2s_plat.c b/sound/soc/loongson/loongson_i2s_plat.c
new file mode 100644
index 000000000000..fa2e450ff618
--- /dev/null
+++ b/sound/soc/loongson/loongson_i2s_plat.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Loongson I2S controller master mode dirver(platform device)
+//
+// Copyright (C) 2023-2024 Loongson Technology Corporation Limited
+//
+// Author: Yingkun Meng <mengyingkun@loongson.cn>
+// Binbin Zhou <zhoubinbin@loongson.cn>
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/of_dma.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "loongson_i2s.h"
+
+#define LOONGSON_I2S_RX_DMA_OFFSET 21
+#define LOONGSON_I2S_TX_DMA_OFFSET 18
+
+#define LOONGSON_DMA0_CONF 0x0
+#define LOONGSON_DMA1_CONF 0x1
+#define LOONGSON_DMA2_CONF 0x2
+#define LOONGSON_DMA3_CONF 0x3
+#define LOONGSON_DMA4_CONF 0x4
+
+/* periods_max = PAGE_SIZE / sizeof(struct ls_dma_chan_reg) */
+static const struct snd_pcm_hardware loongson_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_PAUSE,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_3LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .period_bytes_min = 128,
+ .period_bytes_max = 128 * 1024,
+ .periods_min = 1,
+ .periods_max = 64,
+ .buffer_bytes_max = 1024 * 1024,
+};
+
+static const struct snd_dmaengine_pcm_config loongson_dmaengine_pcm_config = {
+ .pcm_hardware = &loongson_pcm_hardware,
+ .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+ .prealloc_buffer_size = 128 * 1024,
+};
+
+static int loongson_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if (substream->pcm->device & 1) {
+ runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
+ runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
+ }
+
+ if (substream->pcm->device & 2)
+ runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID);
+ /*
+ * For mysterious reasons (and despite what the manual says)
+ * playback samples are lost if the DMA count is not a multiple
+ * of the DMA burst size. Let's add a rule to enforce that.
+ */
+ snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
+ snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
+ snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver loongson_i2s_component_driver = {
+ .name = LS_I2S_DRVNAME,
+ .open = loongson_pcm_open,
+};
+
+static const struct regmap_config loongson_i2s_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x14,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int loongson_i2s_apbdma_config(struct platform_device *pdev)
+{
+ int val;
+ void __iomem *regs;
+
+ regs = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ val = readl(regs);
+ val |= LOONGSON_DMA2_CONF << LOONGSON_I2S_TX_DMA_OFFSET;
+ val |= LOONGSON_DMA3_CONF << LOONGSON_I2S_RX_DMA_OFFSET;
+ writel(val, regs);
+
+ return 0;
+}
+
+static int loongson_i2s_plat_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct loongson_i2s *i2s;
+ struct resource *res;
+ struct clk *i2s_clk;
+ int ret;
+
+ i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL);
+ if (!i2s)
+ return -ENOMEM;
+
+ ret = loongson_i2s_apbdma_config(pdev);
+ if (ret)
+ return ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2s->reg_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(i2s->reg_base))
+ return dev_err_probe(dev, PTR_ERR(i2s->reg_base),
+ "devm_ioremap_resource failed\n");
+
+ i2s->regmap = devm_regmap_init_mmio(dev, i2s->reg_base,
+ &loongson_i2s_regmap_config);
+ if (IS_ERR(i2s->regmap))
+ return dev_err_probe(dev, PTR_ERR(i2s->regmap),
+ "devm_regmap_init_mmio failed\n");
+
+ i2s->playback_dma_data.addr = res->start + LS_I2S_TX_DATA;
+ i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ i2s->playback_dma_data.maxburst = 4;
+
+ i2s->capture_dma_data.addr = res->start + LS_I2S_RX_DATA;
+ i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ i2s->capture_dma_data.maxburst = 4;
+
+ i2s_clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(i2s_clk))
+ return dev_err_probe(dev, PTR_ERR(i2s_clk), "clock property invalid\n");
+ i2s->clk_rate = clk_get_rate(i2s_clk);
+
+ dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+ dev_set_name(dev, LS_I2S_DRVNAME);
+ dev_set_drvdata(dev, i2s);
+
+ ret = devm_snd_soc_register_component(dev, &loongson_i2s_component_driver,
+ &loongson_i2s_dai, 1);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register DAI\n");
+
+ return devm_snd_dmaengine_pcm_register(dev, &loongson_dmaengine_pcm_config,
+ SND_DMAENGINE_PCM_FLAG_COMPAT);
+}
+
+static const struct of_device_id loongson_i2s_ids[] = {
+ { .compatible = "loongson,ls2k1000-i2s" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, loongson_i2s_ids);
+
+static struct platform_driver loongson_i2s_driver = {
+ .probe = loongson_i2s_plat_probe,
+ .driver = {
+ .name = "loongson-i2s-plat",
+ .pm = pm_sleep_ptr(&loongson_i2s_pm),
+ .of_match_table = loongson_i2s_ids,
+ },
+};
+module_platform_driver(loongson_i2s_driver);
+
+MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");