diff options
Diffstat (limited to 'sound/soc/loongson')
-rw-r--r-- | sound/soc/loongson/Kconfig | 42 | ||||
-rw-r--r-- | sound/soc/loongson/Makefile | 13 | ||||
-rw-r--r-- | sound/soc/loongson/loongson1_ac97.c | 398 | ||||
-rw-r--r-- | sound/soc/loongson/loongson_card.c | 128 | ||||
-rw-r--r-- | sound/soc/loongson/loongson_dma.c | 27 | ||||
-rw-r--r-- | sound/soc/loongson/loongson_i2s.c | 121 | ||||
-rw-r--r-- | sound/soc/loongson/loongson_i2s.h | 24 | ||||
-rw-r--r-- | sound/soc/loongson/loongson_i2s_pci.c | 61 | ||||
-rw-r--r-- | sound/soc/loongson/loongson_i2s_plat.c | 185 |
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"); |